-
-
Notifications
You must be signed in to change notification settings - Fork 339
Initial implementation of Method #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3428825
b7d41a5
f4795fd
bdadf2b
9611ecb
1d18cdc
611f338
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
pub mod method; | ||
pub mod version; | ||
|
||
pub use method::Method; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
//! The HTTP request method | ||
|
||
use self::Inner::*; | ||
|
||
use std::{fmt, str}; | ||
use std::convert::AsRef; | ||
use std::error::Error; | ||
|
||
/// The Request Method (VERB) | ||
/// | ||
/// Currently includes 8 variants representing the 8 methods defined in | ||
/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH, | ||
/// and an Extension variant for all extensions. | ||
#[derive(Clone, PartialEq, Eq, Hash, Debug)] | ||
pub struct Method(Inner); | ||
|
||
#[derive(Debug)] | ||
pub struct FromBytesError { | ||
_priv: (), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I again wonder if |
||
} | ||
|
||
#[derive(Clone, PartialEq, Eq, Hash, Debug)] | ||
enum Inner { | ||
Options, | ||
Get, | ||
Post, | ||
Put, | ||
Delete, | ||
Head, | ||
Trace, | ||
Connect, | ||
Patch, | ||
// If the extension is short enough, store it inline | ||
ExtensionInline([u8; MAX_INLINE], u8), | ||
// Otherwise, allocate it | ||
ExtensionAllocated(Box<[u8]>), | ||
} | ||
|
||
const MAX_INLINE: usize = 15; | ||
|
||
// From the HTTP spec section 5.1.1, the HTTP method is case-sensitive and can | ||
// contain the following characters: | ||
// | ||
// ``` | ||
// method = token | ||
// token = 1*tchar | ||
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / | ||
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA | ||
// ``` | ||
// | ||
// https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Method | ||
// | ||
const METHOD_CHARS: [u8; 256] = [ | ||
// 0 1 2 3 4 5 6 7 8 9 | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 1x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 2x | ||
b'\0', b'\0', b'\0', b'!', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 3x | ||
b'\0', b'\0', b'*', b'+', b'\0', b'-', b'.', b'\0', b'0', b'1', // 4x | ||
b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'\0', b'\0', // 5x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'A', b'B', b'C', b'D', b'E', // 6x | ||
b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x | ||
b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x | ||
b'Z', b'\0', b'\0', b'\0', b'^', b'_', b'`', b'a', b'b', b'c', // 9x | ||
b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x | ||
b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x | ||
b'x', b'y', b'z', b'\0', b'|', b'\0', b'~', b'\0', b'\0', b'\0', // 12x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x | ||
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0' // 25x | ||
]; | ||
|
||
/// GET | ||
pub const GET: Method = Method(Get); | ||
|
||
/// POST | ||
pub const POST: Method = Method(Post); | ||
|
||
/// PUT | ||
pub const PUT: Method = Method(Put); | ||
|
||
/// DELETE | ||
pub const DELETE: Method = Method(Delete); | ||
|
||
/// HEAD | ||
pub const HEAD: Method = Method(Head); | ||
|
||
/// OPTIONS | ||
pub const OPTIONS: Method = Method(Options); | ||
|
||
/// CONNECT | ||
pub const CONNECT: Method = Method(Connect); | ||
|
||
/// PATCH | ||
pub const PATCH: Method = Method(Patch); | ||
|
||
/// TRACE | ||
pub const TRACE: Method = Method(Trace); | ||
|
||
impl Method { | ||
/// Converts a slice of bytes to an HTTP method. | ||
pub fn from_bytes(src: &[u8]) -> Result<Method, FromBytesError> { | ||
match src.len() { | ||
3 => { | ||
match src { | ||
b"GET" => Ok(Method(Get)), | ||
b"PUT" => Ok(Method(Put)), | ||
_ => Method::extension_inline(src), | ||
} | ||
} | ||
4 => { | ||
match src { | ||
b"POST" => Ok(Method(Post)), | ||
b"HEAD" => Ok(Method(Head)), | ||
_ => Method::extension_inline(src), | ||
} | ||
} | ||
5 => { | ||
match src { | ||
b"PATCH" => Ok(Method(Patch)), | ||
b"TRACE" => Ok(Method(Trace)), | ||
_ => Method::extension_inline(src), | ||
} | ||
} | ||
6 => { | ||
match src { | ||
b"DELETE" => Ok(Method(Delete)), | ||
_ => Method::extension_inline(src), | ||
} | ||
} | ||
7 => { | ||
match src { | ||
b"OPTIONS" => Ok(Method(Options)), | ||
b"CONNECT" => Ok(Method(Connect)), | ||
_ => Method::extension_inline(src), | ||
} | ||
} | ||
_ => { | ||
if src.len() < MAX_INLINE { | ||
Method::extension_inline(src) | ||
} else { | ||
let mut data: Vec<u8> = vec![0; src.len()]; | ||
|
||
try!(write_checked(src, &mut data)); | ||
|
||
Ok(Method(ExtensionAllocated(data.into_boxed_slice()))) | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn extension_inline(src: &[u8]) -> Result<Method, FromBytesError> { | ||
let mut data: [u8; MAX_INLINE] = Default::default(); | ||
|
||
try!(write_checked(src, &mut data)); | ||
|
||
Ok(Method(ExtensionInline(data, src.len() as u8))) | ||
} | ||
|
||
/// Whether a method is considered "safe", meaning the request is | ||
/// essentially read-only. | ||
/// | ||
/// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1) | ||
/// for more words. | ||
pub fn is_safe(&self) -> bool { | ||
match self.0 { | ||
Get | Head | Options | Trace => true, | ||
_ => false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this may be incorrect in the face of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. |
||
} | ||
} | ||
|
||
/// Whether a method is considered "idempotent", meaning the request has | ||
/// the same result if executed multiple times. | ||
/// | ||
/// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for | ||
/// more words. | ||
pub fn is_idempotent(&self) -> bool { | ||
if self.is_safe() { | ||
true | ||
} else { | ||
match self.0 { | ||
Put | Delete => true, | ||
_ => false | ||
} | ||
} | ||
} | ||
|
||
/// Return a &str representation of the HTTP method | ||
pub fn as_str(&self) -> &str { | ||
match self.0 { | ||
Options => "OPTIONS", | ||
Get => "GET", | ||
Post => "POST", | ||
Put => "PUT", | ||
Delete => "DELETE", | ||
Head => "HEAD", | ||
Trace => "TRACE", | ||
Connect => "CONNECT", | ||
Patch => "PATCH", | ||
ExtensionInline(ref data, len) => { | ||
unsafe { | ||
str::from_utf8_unchecked(&data[..len as usize]) | ||
} | ||
} | ||
ExtensionAllocated(ref data) => { | ||
unsafe { | ||
str::from_utf8_unchecked(data) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), FromBytesError> { | ||
for (i, &b) in src.iter().enumerate() { | ||
let b = METHOD_CHARS[b as usize]; | ||
|
||
if b == 0 { | ||
return Err(FromBytesError::new()); | ||
} | ||
|
||
dst[i] = b; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
impl AsRef<str> for Method { | ||
fn as_ref(&self) -> &str { | ||
self.as_str() | ||
} | ||
} | ||
|
||
impl PartialEq<str> for Method { | ||
fn eq(&self, other: &str) -> bool { | ||
self.as_ref() == other | ||
} | ||
} | ||
|
||
impl<'a> PartialEq<&'a str> for Method { | ||
fn eq(&self, other: &&'a str) -> bool { | ||
self.as_ref() == *other | ||
} | ||
} | ||
|
||
impl fmt::Display for Method { | ||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||
fmt.write_str(self.as_ref()) | ||
} | ||
} | ||
|
||
impl Default for Method { | ||
fn default() -> Method { | ||
GET | ||
} | ||
} | ||
|
||
impl FromBytesError { | ||
fn new() -> FromBytesError { | ||
FromBytesError { | ||
_priv: (), | ||
} | ||
} | ||
} | ||
|
||
impl fmt::Display for FromBytesError { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "{}", self.description()) | ||
} | ||
} | ||
|
||
impl Error for FromBytesError { | ||
fn description(&self) -> &str { | ||
"invalid HTTP method" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the derived
PartailEq
andHash
may also be incorrect in the presence offrom_bytes_unchecked
(similar to below) as it's possible to haveInner::Get
andInner::ExtensionInline(b"GET")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that is the point! We want to avoid string comparisons if we don't have to, and
from_bytes_unchecked
isunsafe
. So, if you do it wrong, that is on you :)