Skip to content

Latest commit

 

History

History
258 lines (203 loc) · 6.04 KB

http1_head_parsing.md

File metadata and controls

258 lines (203 loc) · 6.04 KB

Head Parsing

When parsing HTTP/1.x content, everything prior to the body is considered the head or headers. It starts off with the request or response line followed by optional headers, and is terminated with a \r\n\r\n sequence.

Here is a sample of request head content:

GET /resource HTTP/1.1\r\n
Host: github.com\r\n
Connection: keep-alive\r\n
Accept-Encoding: gzip, deflate, sdch, br\r\n
Accept-Language: en-US,en;q=0.8\r\n
\r\n

Response head content format is identical, with the exception of the initial line, which denotes the HTTP response details:

HTTP/1.1 200 OK\r\n

The headers are formatted the same for a request or response, however, the types of headers being used will vary.

Callbacks

http_box::http1::HttpHandler outlines the necessary callback functions we must implement in order to handle parsed head data:

Request:

  • HttpHandler::on_method()
  • HttpHandler::on_url()

Response:

  • HttpHandler::on_status()
  • HttpHandler::on_status_code()

Request + Response:

  • HttpHandler::on_header_name()
  • HttpHandler::on_header_value()
  • HttpHandler::on_headers_finished()
  • HttpHandler::on_initial_finished()
  • HttpHandler::on_version()

Example

extern crate http_box;

use http_box::http1::{ HttpHandler, Parser, State };
use std::collections::HashMap;

// container for storing the parsed data
pub struct Handler {
    // buffer for accumulating header name
    pub header_name: Vec<u8>,

    // buffer for accumulating header value
    pub header_value: Vec<u8>,

    // headers
    pub headers: HashMap<String,String>,

    // indicates that head parsing has finished
    pub headers_finished: bool,

    // indicates that entire request/response line has been parsed
    pub initial_finished: bool,

    // request method
    pub method: Vec<u8>,

    // current state
    pub state: State,

    // response status
    pub status: Vec<u8>,

    // response status code
    pub status_code: u16,

    // request URL
    pub url: Vec<u8>,

    // request + response major version
    pub version_major: u16,

    // request + response minor version
    pub version_minor: u16
}

// container implementation
impl Handler {
    pub fn new() -> Handler {
        Handler{
            header_name:      Vec::new(),
            header_value:     Vec::new(),
            headers:          HashMap::new(),
            headers_finished: false,
            initial_finished: false,
            method:           Vec::new(),
            state:            State::None,
            status:           Vec::new(),
            status_code:      0,
            url:              Vec::new(),
            version_major:    0,
            version_minor:    0
        }
    }

    fn flush_header(&mut self) {
        if self.header_name.len() > 0 && self.header_value.len() > 0 {
            self.headers.insert(
                String::from_utf8(self.header_name.clone()).unwrap(),
                String::from_utf8(self.header_value.clone()).unwrap()
            );
        }

        self.header_name.clear();
        self.header_value.clear();
    }

    pub fn is_request(&self) -> bool {
        self.method.len() > 0
    }

    pub fn is_headers_finished(&self) -> bool {
        self.headers_finished
    }

    pub fn is_initial_finished(&self) -> bool {
        self.initial_finished
    }
}

// our callback implementation
impl HttpHandler for Handler {
    // receive a slice of header name
    fn on_header_name(&mut self, data: &[u8]) -> bool {
        if self.state == State::HeaderValue {
            self.flush_header();

            self.state = State::HeaderName;
        }

        self.header_name.extend_from_slice(data);
        true
    }

    // receive a slice of header value
    fn on_header_value(&mut self, data: &[u8]) -> bool {
        self.state = State::HeaderValue;
        self.header_value.extend_from_slice(data);
        true
    }

    // executed when headers are finished
    fn on_headers_finished(&mut self) -> bool {
        self.headers_finished = true;
        self.flush_header();
        true
    }

    // executed when the initial request/response line is finished
    fn on_initial_finished(&mut self) -> bool {
        self.initial_finished = true;
        true
    }

    // receive a slice of request method
    fn on_method(&mut self, data: &[u8]) -> bool {
        self.method.extend_from_slice(data);
        true
    }

    // receive a slice of response status
    fn on_status(&mut self, data: &[u8]) -> bool {
        self.status.extend_from_slice(data);
        true
    }

    // receive response status code
    fn on_status_code(&mut self, code: u16) -> bool {
        self.status_code = code;
        true
    }

    // receive a slice of request url
    fn on_url(&mut self, data: &[u8]) -> bool {
        self.url.extend_from_slice(data);
        true
    }

    // receive request/response version
    fn on_version(&mut self, major: u16, minor: u16) -> bool {
        self.version_major = major;
        self.version_minor = minor;
        true
    }
}

fn main() {
    // init callback handler and parser
    let mut h = Handler::new();
    let mut p = Parser::new();

    // parse head content
    p.resume(
        &mut h,
        b"GET /resource?query HTTP/1.1\r\n\
          Host: github.com\r\n\
          Connection: close\r\n\
          Accept-Encoding: gzip, deflate, sdch, br\r\n\
          Accept-Language: en-US,en;q=0.8\r\n\
          \r\n"
    );

    assert!(h.is_headers_finished());
    assert!(h.is_initial_finished());
    assert!(h.is_request());

    assert_eq!(
        h.method,
        b"GET"
    );

    assert_eq!(
        h.url,
        b"/resource?query"
    );

    assert_eq!(
        h.headers.get("host").unwrap(),
        "github.com"
    );

    assert_eq!(
        h.headers.get("connection").unwrap(),
        "close"
    );

    assert_eq!(
        h.headers.get("accept-encoding").unwrap(),
        "gzip, deflate, sdch, br"
    );

    assert_eq!(
        h.headers.get("accept-language").unwrap(),
        "en-US,en;q=0.8"
    );
}