Now we can read a raw HTTP response we need to turn it into something useful.
1.0 HTTP Request: The RFC 2616 Definition
1.1 Request
An HTTP Request is defined like this
Request = Request-Line ; Section 5.1
*(( general-header ; Section 4.5
| request-header ; Section 5.3
| entity-header ) CRLF) ; Section 7.1
CRLF
[ message-body ] ; Section 4.3
1.2 Request-Line
Request-Line
is defined like this
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
1.2.1 Method
Method
is defined like this
Method = "OPTIONS" ; Section 9.2
| "GET" ; Section 9.3
| "HEAD" ; Section 9.4
| "POST" ; Section 9.5
| "PUT" ; Section 9.6
| "DELETE" ; Section 9.7
| "TRACE" ; Section 9.8
| "CONNECT" ; Section 9.9
| extension-method
1.2.2 Request-URI
Request-URI
is defined like this
Request-URI = "*" | absoluteURI | abs_path | authority
where absoluteURI
, abs_path
and authority
are as defined in RFC 2396.
1.3 Headers
general-header
, request-header
and entity-header
are all forms of message-header
which is defined like this
message-header = field-name ":" [ field-value ]
1.4 The Message Body
message-body
is defined like this.
message-body = entity-body
| <entity-body encoded as per Transfer-Encoding>
entity-body = *OCTET
2.0 Representing A Request
2.1 The Components
2.1.1 The Request Method
We can represent the Method using an enum like so
enum Method
{
CONNECT,
DELETE,
GET,
HEAD,
OPTIONS,
POST,
PUT,
TRACE
}
For the moment we are not going to support extension methods
2.1.2 The Request URI
We can represent the Request URI in terms of its four possible variants by using an enum like so
enum RequestURI
{
AbsolutePath(~str),
AbsoluteURI(url::Url),
Authority(~str),
Wildcard
}
The AbsoluteURI variant uses the type Url
from the std
library.
2.1.3 Headers
We are going to represent the set of headers using a new type Headers
which is defined like this
pub struct Headers
{
priv headers: ~[Header]
}
and Header
is defined like this
struct Header
{
priv key: ~str,
priv value: ~str
}
2.2 The Request
A Request
simply holds the method, the URI and the headers.
pub struct Request
{
priv method: Method,
priv uri: RequestURI,
priv headers: Headers
}
For the moment we are going to ignore the message-body.
3.0 The Request Methods
3.1 Reading A Request
We define a static method read
on the Request
type which takes a RequestBuffer
and returns a Request
pub fn read(buffer: &mut RequestBuffer) -> Request
{
let (method, uri) = readRequestLine(buffer);
let headers = Headers::read(buffer);
Request { method: method, uri: uri, headers: headers}
}
The function readRequestLine
reads the request line and splits it into its constituent parts and returns them as a tuple.
fn readRequestLine(buffer: &mut RequestBuffer) -> (Method, RequestURI)
{
let line = buffer.readLine();
let mut parts: ~[&str] = ~[];
str::each_word(line, |part| { parts.push(part); true });
if (vec::len(parts) != 3)
{
fail!(fmt!("Invalid status line: %s", line));
}
let method = Method::fromString(parts[0]);
let uri = RequestURI::fromString(method, parts[1]);
(method, uri)
}
3.2 Accessors
3.2.1 getMethod
This method is very simple. It just returns the Method
held by the Request
.
pub fn getMethod(&self) -> Method
{
self.method
}
3.2.2 getURI
This method is a little less straightforward than getMethod
.
3.2.1.1 Take One
We cannot simply return a shallow copy of the RequestURI
like this
pub fn getURI(&self) -> RequestURI
{
self.uri
}
because to quote the compiler
request.rs:41:8: 41:16 error: moving out of immutable field
request.rs:41 self.uri
^~~~~~~~
error: aborting due to previous error
As we know in this case the RequestURI
value is returned via a shallow copy.
A RequestURI
value may contain an owned pointer so this would be moved to the copy being returned which would make the original in the Request
invalid.
3.2.1.2 Take Two
We could return a deep copy with a pointer to a copy of the original string in the owned heap as well, but this seems a tad extravagant.
An alternative would be to return a borrowed pointer like so
pub fn getURI(&self) -> &RequestURI
{
&self.uri
}
but the compiler is now very unhappy indeed
request.rs:41:8: 41:17 error: cannot infer an appropriate lifetime due to conflicting requirements
request.rs:41 &self.uri
^~~~~~~~~
request.rs:40:4: 42:5 note: first, the lifetime cannot outlive the lifetime &'self as defined on the block at 40:4...
request.rs:40 {
request.rs:41 &self.uri
request.rs:42 }
request.rs:41:8: 41:17 note: ...due to the following expression
request.rs:41 &self.uri
^~~~~~~~~
request.rs:40:4: 42:5 note: but, the lifetime must be valid for the anonymous lifetime #1 defined on the block at 40:4...
request.rs:40 {
request.rs:41 &self.uri
request.rs:42 }
request.rs:41:8: 41:17 note: ...due to the following expression
request.rs:41 &self.uri
^~~~~~~~~
error: aborting due to previous error
but it does have a point.
We have not so much attempted to borrow a pointer, as tried to walk off with it without any indication of when we might be planning to give it back, if ever.
3.2.1.3 Take Three
To placate the compiler and assure it of our good intentions we need to explicitly specify the intended lifetime of the borrowed pointer like this
pub fn getURI(&self) -> &'self RequestURI
{
&self.uri
}
The named lifetime
'self
specifies that the lifetime of the borrowed pointer is the same as the thing from which it was borrowed which in this case is the Request
.
This makes sense because as long as the Request
exists the RequestURI
exists, so as long as the Request
exists the borrowed pointer to the RequestURI
is valid.
3.2.3 getHeader
This method delegates to the Headers
get
method and returns its result.
pub fn getHeader(&self, key: &str) -> Option<&'self str>
{
self.headers.get(key)
}
4.0 The Headers Methods
4.1 Reading The Headers
The static method read
reads the successive header lines until an empty line is read.
Each header lined is convered to a Header
value.
The method returns a Headers
value constructed using the collected Header
values.
pub fn read(buffer: &mut RequestBuffer) -> Headers
{
let mut headers: ~[Header] = ~[];
loop
{
let line = buffer.readLine();
if (line.len() == 0)
{
break;
}
headers.push(toHeader(line));
}
Headers { headers: headers }
}
The function toHeader
splits the header line into a key and a value and returns the result as a Header
value.
fn toHeader(line: &str) -> Header
{
let mut parts: ~[&str] = ~[];
str::each_splitn_char(line, ':', 1, |s| { parts.push(s); true });
if (vec::len(parts) != 2)
{
fail!(fmt!("Bad header: %s", line));
}
Header { key: parts[0].to_str(), value: parts[1].trim().to_str() }
}
4.2 Getting A Header Value
The get
method returns an Option
as it is possible that the specified header was not present in the original HTTP Request.
If the header is present a borrowed pointer to the value is returned via the Option
Ok
variant.
The lifetime of the borrowed pointer is explicitly specified to be self
. (See the Request
getURI
method.)
pub fn get(&self, key: &str) -> Option<&'self str>
{
let lcKey = str::to_lower(key);
match self.headers.position(|h| h.key.to_lower() == lcKey)
{
Some(index) =>
{
let value: &str = self.headers[index].value;
Some(value)
},
None => None
}
}
5.0 Method and RequestURI Methods
5.1 Method.fromString
The Method
fromString
static method is very simple. If the argument matches one of the defined methods return the corresponding enum variant, otherwise fail.
impl Method
{
pub fn fromString(s: &str) -> Method
{
match s
{
"CONNECT" => CONNECT,
"DELETE" => DELETE,
"GET" => GET,
"HEAD" => HEAD,
"OPTIONS" => OPTIONS,
"POST" => POST,
"PUT" => PUT,
"TRACE" => TRACE,
_ =>
{
fail!(fmt!("Unrecognized method %s", s));
}
}
}
}
5.2 RequestURI.fromString
The RequestURI
fromString
static method converts the string specifying the URI contained in the HTTP Request into one of the variants of the enum type RequestURI
.
It takes the Method
of the Request as an argument so that it can identify the CONNECT
case where the specified Request-URI
is actually is an authority
which may otherwise be indistinguishable from an absoluteURI
in certain circumstances.
impl RequestURI
{
pub fn fromString(method: Method, s: &str) -> RequestURI
{
match method
{
CONNECT =>
{
Authority(s.to_str())
},
_ =>
{
let length = s.len();
if (length == 1)
{
match s.char_at(0)
{
'*' =>
{
Wildcard
},
'/' =>
{
AbsolutePath(~"/")
},
_ =>
{
fail!(fmt!("Invalid URI: %s", s));
}
}
}
else
{
match s.char_at(0)
{
'/' =>
{
AbsolutePath(s.to_str())
}
_ =>
{
match url::from_str(s)
{
Ok(url) => AbsoluteURI(url),
Err(err) => fail!(err)
}
}
}
}
}
}
}
}
6.0 Source Files
6.1 headers.rs
// headers.rs
// part of httpd v0.5
use buffer::RequestBuffer;
pub struct Headers
{
priv headers: ~[Header]
}
struct Header
{
priv key: ~str,
priv value: ~str
}
impl Headers
{
pub fn read(buffer: &mut RequestBuffer) -> Headers
{
let mut headers: ~[Header] = ~[];
loop
{
let line = buffer.readLine();
if (line.len() == 0)
{
break;
}
headers.push(toHeader(line));
}
Headers { headers: headers }
}
pub fn get(&self, key: &str) -> Option<&'self str>
{
let lcKey = str::to_lower(key);
match self.headers.position(|h| h.key.to_lower() == lcKey)
{
Some(index) =>
{
let value: &str = self.headers[index].value;
Some(value)
},
None => None
}
}
}
fn toHeader(line: &str) -> Header
{
let mut parts: ~[&str] = ~[];
str::each_splitn_char(line, ':', 1, |s| { parts.push(s); true });
if (vec::len(parts) != 2)
{
fail!(fmt!("Bad header: %s", line));
}
Header { key: parts[0].to_str(), value: parts[1].trim().to_str() }
}
6.2 request.rs
// request.rs
// part of httpd v0.5
use HTTP::Method;
use HTTP::RequestURI;
use buffer::RequestBuffer;
use headers::Headers;
//
pub struct Request
{
priv method: Method,
priv uri: RequestURI,
priv headers: Headers
}
impl Request
{
pub fn read(buffer: &mut RequestBuffer) -> Request
{
let (method, uri) = readRequestLine(buffer);
let headers = Headers::read(buffer);
Request { method: method, uri: uri, headers: headers}
}
//
pub fn getMethod(&self) -> Method
{
self.method
}
pub fn getURI(&self) -> &'self RequestURI
{
&self.uri
}
pub fn getHeader(&self, key: &str) -> Option<&'self str>
{
self.headers.get(key)
}
}
fn readRequestLine(buffer: &mut RequestBuffer) -> (Method, RequestURI)
{
let line = buffer.readLine();
let mut parts: ~[&str] = ~[];
str::each_word(line, |part| { parts.push(part); true });
if (vec::len(parts) != 3)
{
fail!(fmt!("Invalid status line: %s", line));
}
let method = Method::fromString(parts[0]);
let uri = RequestURI::fromString(method, parts[1]);
(method, uri)
}
Copyright (c) 2013 By Simon Lewis. All Rights Reserved.
Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.
Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.