1#![cfg_attr(rustfmt, rustfmt_skip)]
2
3#[global_allocator]
4static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
5
6use criterion::*;
7use nom::{IResult, bytes::complete::{tag, take_while1}, character::complete::{line_ending, char}, multi::many1};
8
9#[cfg_attr(rustfmt, rustfmt_skip)]
10#[derive(Debug)]
11struct Request<'a> {
12  method:  &'a [u8],
13  uri:     &'a [u8],
14  version: &'a [u8],
15}
16
17#[derive(Debug)]
18struct Header<'a> {
19  name: &'a [u8],
20  value: Vec<&'a [u8]>,
21}
22
23#[cfg_attr(rustfmt, rustfmt_skip)]
24#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
25fn is_token(c: u8) -> bool {
26  match c {
27    128..=255 => false,
28    0..=31    => false,
29    b'('      => false,
30    b')'      => false,
31    b'<'      => false,
32    b'>'      => false,
33    b'@'      => false,
34    b','      => false,
35    b';'      => false,
36    b':'      => false,
37    b'\\'     => false,
38    b'"'      => false,
39    b'/'      => false,
40    b'['      => false,
41    b']'      => false,
42    b'?'      => false,
43    b'='      => false,
44    b'{'      => false,
45    b'}'      => false,
46    b' '      => false,
47    _         => true,
48  }
49}
50
51fn not_line_ending(c: u8) -> bool {
52  c != b'\r' && c != b'\n'
53}
54
55fn is_space(c: u8) -> bool {
56  c == b' '
57}
58
59fn is_not_space(c: u8) -> bool {
60  c != b' '
61}
62fn is_horizontal_space(c: u8) -> bool {
63  c == b' ' || c == b'\t'
64}
65
66fn is_version(c: u8) -> bool {
67  c >= b'0' && c <= b'9' || c == b'.'
68}
69
70fn request_line(input: &[u8]) -> IResult<&[u8], Request<'_>> {
71  let (input, method) = take_while1(is_token)(input)?;
72  let (input, _) = take_while1(is_space)(input)?;
73  let (input, uri) = take_while1(is_not_space)(input)?;
74  let (input, _) = take_while1(is_space)(input)?;
75  let (input, version) = http_version(input)?;
76  let (input, _) = line_ending(input)?;
77
78  Ok((input, Request {method, uri, version}))
79}
80
81fn http_version(input: &[u8]) -> IResult<&[u8], &[u8]> {
82  let (input, _) = tag("HTTP/")(input)?;
83  let (input, version) = take_while1(is_version)(input)?;
84
85  Ok((input, version))
86}
87
88fn message_header_value(input: &[u8]) -> IResult<&[u8], &[u8]> {
89  let (input, _) = take_while1(is_horizontal_space)(input)?;
90  let (input, data) = take_while1(not_line_ending)(input)?;
91  let (input, _) = line_ending(input)?;
92
93  Ok((input, data))
94}
95
96fn message_header(input: &[u8]) -> IResult<&[u8], Header<'_>> {
97  let (input, name) = take_while1(is_token)(input)?;
98  let (input, _) = char(':')(input)?;
99  let (input, value) = many1(message_header_value)(input)?;
100
101  Ok((input, Header{ name, value }))
102}
103
104fn request(input: &[u8]) -> IResult<&[u8], (Request<'_>, Vec<Header<'_>>)> {
105  let (input, req) = request_line(input)?;
106  let (input, h) = many1(message_header)(input)?;
107  let (input, _) = line_ending(input)?;
108
109  Ok((input, (req, h)))
110}
111
112
113fn parse(data: &[u8]) -> Option<Vec<(Request<'_>, Vec<Header<'_>>)>> {
114  let mut buf = &data[..];
115  let mut v = Vec::new();
116  loop {
117    match request(buf) {
118      Ok((b, r)) => {
119        buf = b;
120        v.push(r);
121
122        if b.is_empty() {
123
124          //println!("{}", i);
125          break;
126        }
127      }
128      Err(e) => {
129        println!("error: {:?}", e);
130        return None;
131      },
132    }
133  }
134
135  Some(v)
136}
137
138/*
139#[bench]
140fn small_test(b: &mut Bencher) {
141  let data = include_bytes!("../../http-requests.txt");
142  b.iter(||{
143    parse(data)
144  });
145}
146
147#[bench]
148fn bigger_test(b: &mut Bencher) {
149  let data = include_bytes!("../../bigger.txt");
150  b.iter(||{
151    parse(data)
152  });
153}
154*/
155
156fn one_test(c: &mut Criterion) {
157  let data = &b"GET / HTTP/1.1
158Host: www.reddit.com
159User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:15.0) Gecko/20100101 Firefox/15.0.1
160Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
161Accept-Language: en-us,en;q=0.5
162Accept-Encoding: gzip, deflate
163Connection: keep-alive
164
165"[..];
166
167  let mut http_group = c.benchmark_group("http");
168  http_group.throughput(Throughput::Bytes(data.len() as u64));
169  http_group.bench_with_input(
170    BenchmarkId::new("parse", data.len()),
171     data,
172      |b, data| {
173    b.iter(|| parse(data).unwrap());
174  });
175
176  http_group.finish();
177}
178
179/*
180fn main() {
181  let mut contents: Vec<u8> = Vec::new();
182
183  {
184    use std::io::Read;
185
186    let mut file = File::open(env::args().nth(1).expect("File to read")).expect("Failed to open file");
187
188    let _ = file.read_to_end(&mut contents).unwrap();
189  }
190
191  let buf = &contents[..];
192  loop {
193    parse(buf);
194  }
195}
196*/
197
198criterion_group!(http, one_test);
199criterion_main!(http);
200