Implementing HTTP
Jonathan Reem
@jreem
github.com/reem
What does it look like?
GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1
Host: net.tutsplus.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120
Pragma: no-cache
Cache-Control: no-cache
Major Parts
- Methods
- Headers
- Status
- Body
Methods
- Get, Post, etc.
Use Strings
pub type Method = String;
pub static Get: Method = "GET";
pub static Post: Method = "POST";
// etc.
request.method = Get;
Use Numbers
pub type Method = u16;
pub static Get: Method = 1u16;
pub static Post: Method = 2u16;
// etc.
request.method = Get;
Use a Struct
pub struct Method {
name: String,
idempotent: bool,
safe: bool
}
pub static Get: Method = Method {
name: "GET",
idempotent: true,
safe: true
};
pub static Post: Method = Method {
name: "POST",
idempotent: false,
safe: false
};
// etc.
request.method = Get;
Use an `enum`
pub enum Method {
Get,
Post, // etc.
}
request.method = Get;
Headers
- Content-Length, Transfer-Encoding, Content-Type, etc.
Use Strings
pub struct Headers {
data: HashMap<String, String>
}
pub static TransferEncoding: String = "transfer-encoding";
pub static ContentLength: String = "content-length";
// etc.
request.headers.set(TransferEncoding, "Gzip");
Use Traits!
pub struct Headers {
data: HashMap<String, HeaderItem>
}
pub enum HeaderItem {
Raw(String),
Typed(Box<Header>)
}
pub trait Header {
fn name() -> String;
fn parse(String) -> Self;
fn display(self) -> String;
}
pub enum TransferEncoding { Gzip, Compress, Deflate, Chunked }
impl Header for TransferEncoding {
fn name() -> String { "transfer-encoding" }
fn parse(raw: String) -> TransferEncoding {
match raw {
"gzip" => Gzip, // etc.
}
}
fn display(self) -> String { self.to_string() }
}
request.headers.set(Gzip);
Status
- 200 OK, 404 NOT FOUND, 500 INTERNAL SERVER ERROR, etc.
Use an enum
pub enum Status {
Continue = 100,
SwitchingProtocols = 101,
Processing = 102,
Code103 = 103,
// etc.
}
response.status = Continue;
Body
- To stream or not to stream
- Track statically vs. track dynamically.
Stream, dynamically
pub struct Response {
body: TcpStream,
headers: Headers,
headers_written: bool
}
impl Writer for Response {
fn write(&self, msg: Bytes) {
if !self.headers_written {
self.write_headers();
self.headers_written = true;
}
self.body.write(msg);
}
}
response.headers.set(Gzip);
response.write(b"Hello World!");
// Woops
response.headers.set(Compress);
Stream, statically!
pub struct Fresh; pub struct Streaming;
pub struct Response<WriteStatus> { body: TcpStream, headers: Headers }
impl<W> Response<W> {
fn headers(&self) -> &Headers { &self.headers }
}
impl Response<Fresh> {
fn headers_mut(&mut self) -> &mut Headers { &mut self.headers }
fn start(self) -> Response<Streaming> {
self.write_headers();
Response { body: self.body, headers: self.headers }
}
}
impl Writer for Response<Streaming> {
fn write(&self, msg: Bytes) {
self.body.write(msg);
}
}
response.headers_mut().set(Gzip);
let response = response.start();
response.write(b"Hello World!");
// Compile time error.
response.headers_mut().set(Compress);
Congrats!
Further Reading
- Http Parsing
- Buffered reads/writes
- Tcp
- Header Interoperation
- Header Invalidation
- Errors
- Handlers
- Status Code Classes
- Decoding
Implementing HTTP
By Jonathan Reem
Implementing HTTP
- 1,685