├── .gitignore ├── .travis.yml ├── .gitmodules ├── CONTRIBUTORS ├── src ├── struct_adapter.c ├── ffi.rs └── lib.rs ├── Cargo.toml ├── CHANGELOG.md ├── appveyor.yml ├── LICENSE ├── benches └── bench.rs ├── examples └── parser.rs ├── README.md └── appveyor_rust_install.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | *.*~ 2 | target/ 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: stable 3 | os: 4 | - linux 5 | - osx 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "http-parser"] 2 | path = http-parser 3 | url = https://github.com/nodejs/http-parser 4 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | List of code contributors in the alphabetical order: 2 | 3 | Evgeny Safronov 4 | Nikita Baksalyar 5 | sybblow 6 | -------------------------------------------------------------------------------- /src/struct_adapter.c: -------------------------------------------------------------------------------- 1 | 2 | #include "../http-parser/http_parser.h" 3 | 4 | /* 5 | Realigns a bit field struct in a predictable way. 6 | */ 7 | uint32_t http_get_struct_flags(const http_parser *state) { 8 | return state->status_code | 9 | (state->method << 16) | 10 | (state->http_errno << 24) | 11 | (state->upgrade << 31); 12 | } 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-muncher" 3 | version = "0.3.2" 4 | authors = ["Nikita Baksalyar "] 5 | build = "build.rs" 6 | description = "Rust Streaming HTTP parser based on nodejs/http-parser" 7 | documentation = "http://nbaksalyar.github.io/rust-streaming-http-parser/" 8 | repository = "https://github.com/nbaksalyar/rust-streaming-http-parser" 9 | keywords = ["http", "web"] 10 | license = "MIT" 11 | 12 | [dependencies] 13 | libc = "0.2" 14 | 15 | [build-dependencies] 16 | cc = "1.0" 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [0.3.2] - 2018-01-15 8 | ### Changed 9 | - Replace the deprecated `gcc` dependency with `cc` in the build script 10 | 11 | ## [0.3.1] - 2016-12-23 12 | ### Added 13 | - `pause` and `unpause` methods calling underlying `http_parser_pause` (thanks to @3Hren) 14 | 15 | ## [0.3.0] - 2016-10-11 16 | ### Changed 17 | - Breaking API change: now all callbacks receive a mutable reference to parser 18 | - Node.js HTTP parser updated to 2.7.1 19 | - Example moved from readme to a separate file 20 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Appveyor configuration template for Rust 2 | # https://github.com/starkat99/appveyor-rust 3 | 4 | # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. 5 | os: Visual Studio 2015 6 | 7 | environment: 8 | matrix: 9 | - channel: stable 10 | target: x86_64-pc-windows-msvc 11 | - channel: stable 12 | target: i686-pc-windows-gnu 13 | 14 | ## Install Script ## 15 | 16 | install: 17 | - git submodule update --init --recursive 18 | - ps: .\appveyor_rust_install.ps1 19 | 20 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 21 | # the "directory does not contain a project or solution file" error. 22 | build: false 23 | 24 | # Uses 'cargo test' to run tests. Alternatively, the project may call compiled programs directly or 25 | # perform other testing commands. Rust will automatically be placed in the PATH environment 26 | # variable. 27 | test_script: 28 | - cmd: cargo test --verbose 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nikita Baksalyar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | extern crate http_muncher; 5 | 6 | use test::Bencher; 7 | 8 | use http_muncher::{ParserHandler, Parser}; 9 | 10 | #[bench] 11 | fn bench_request_parser(b: &mut Bencher) { 12 | struct TestRequestParser; 13 | 14 | impl ParserHandler for TestRequestParser { 15 | fn on_url(&mut self, _: &mut Parser, url: &[u8]) -> bool { 16 | assert_eq!(b"/say_hello", url); 17 | true 18 | } 19 | 20 | fn on_header_field(&mut self, _: &mut Parser, hdr: &[u8]) -> bool { 21 | assert!(hdr == b"Host" || hdr == b"Content-Length"); 22 | true 23 | } 24 | 25 | fn on_header_value(&mut self, _: &mut Parser, val: &[u8]) -> bool { 26 | assert!(val == b"localhost.localdomain" || val == b"11"); 27 | true 28 | } 29 | 30 | fn on_body(&mut self, _: &mut Parser, body: &[u8]) -> bool { 31 | assert_eq!(body, b"Hello world"); 32 | true 33 | } 34 | } 35 | 36 | let req = b"POST /say_hello HTTP/1.1\r\nContent-Length: 11\r\nHost: localhost.localdomain\r\n\r\nHello world"; 37 | 38 | let mut handler = TestRequestParser; 39 | 40 | b.iter(move || { 41 | let mut parser = Parser::request(); 42 | let parsed = parser.parse(&mut handler, req); 43 | 44 | assert!(parsed > 0); 45 | assert!(!parser.has_error()); 46 | assert_eq!((1, 1), parser.http_version()); 47 | assert_eq!("POST", parser.http_method()); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /examples/parser.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | extern crate http_muncher; 4 | use std::str; 5 | 6 | // Include the 2 main public interfaces of the crate 7 | use http_muncher::{Parser, ParserHandler}; 8 | 9 | // Now let's define a new listener for parser events: 10 | struct MyHandler; 11 | 12 | impl ParserHandler for MyHandler { 13 | 14 | // Now we can define our callbacks here. 15 | // 16 | // Let's try to handle headers: the following callback function will be 17 | // called when parser founds a header chunk in the HTTP stream. 18 | fn on_header_field(&mut self, parser: &mut Parser, header: &[u8]) -> bool { 19 | // Print the received header key part 20 | println!("{}: ", str::from_utf8(header).unwrap()); 21 | 22 | // We have nothing to say to parser, and we'd like 23 | // it to continue its work - so let's return "true". 24 | true 25 | } 26 | 27 | // And let's print the header value chunks in a similar vein: 28 | fn on_header_value(&mut self, parser: &mut Parser, value: &[u8]) -> bool { 29 | println!("\t{}", str::from_utf8(value).unwrap()); 30 | true 31 | } 32 | 33 | 34 | 35 | } 36 | 37 | fn main() { 38 | // Now we can create a parser instance with our callbacks handler: 39 | let mut callbacks_handler = MyHandler {}; 40 | let mut parser = Parser::request(); 41 | 42 | // Let's define a mock HTTP request: 43 | let http_request = "GET / HTTP/1.1\r\n\ 44 | Content-Type: text/plain\r\n\ 45 | Content-Length: 2\r\n\ 46 | Hello: World\r\n\r\n\ 47 | Hi"; 48 | 49 | // And now we're ready to go! 50 | parser.parse(&mut callbacks_handler, http_request.as_bytes()); 51 | 52 | // Now that callbacks have been called, we can introspect 53 | // the parsing results. For instance, print the HTTP version: 54 | let (http_major, http_minor) = parser.http_version(); 55 | println!("\nHTTP v.{}.{}", http_major, http_minor); 56 | } 57 | 58 | // Now execute "cargo run", and as a result you should see this output: 59 | 60 | // Content-Type: 61 | // text/plain 62 | // Content-Length: 63 | // 0 64 | // Hello: 65 | // World 66 | // 67 | // HTTP v1.1 68 | 69 | // ... and the rest is almost the same - have fun experimenting! 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HttpMuncher: Rust Streaming HTTP parser 2 | 3 | ![Build Status](https://travis-ci.org/nbaksalyar/rust-streaming-http-parser.svg?branch=master) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/2ihcgjco68t08uge?svg=true)](https://ci.appveyor.com/project/nbaksalyar/rust-streaming-http-parser) 5 | [![](http://meritbadge.herokuapp.com/http-muncher)](https://crates.io/crates/http-muncher) 6 | [![Documentation](https://docs.rs/http-muncher/badge.svg)](https://docs.rs/http-muncher) 7 | 8 | Rust wrapper for NodeJS [http-parser](https://github.com/nodejs/http-parser) library. 9 | 10 | It's intended to be used as an HTTP/1.x protocol handler in Rust-based web servers. 11 | 12 | ## Motivation 13 | 14 | Why not write a brand new HTTP parser from scratch in Rust or just use an existing crate such as **[httparse](https://github.com/seanmonstar/httparse)**? 15 | 16 | Here's why: 17 | 18 | * NodeJS HTTP parser library is based on a full-featured and robust [nginx](http://nginx.org)'s HTTP parser, and it's safe, fast, and lightweight by design; 19 | * It's compatible with HTTP/1.1, including upgrade connections and chunked responses; 20 | * I haven't found a decent HTTP parser that is capable of streamed parsing, i.e. the one that can eagerly use partial data that comes from a TCP socket; 21 | * Rust's FFI has little to no overhead; 22 | * In most cases, it's silly to reinvent the wheel; 23 | * It was a lot of fun trying to learn Rust along the way. :) 24 | 25 | ## Usage 26 | 27 | Add the library to your `Cargo.toml` dependencies section: 28 | 29 | [dependencies] 30 | http-muncher = "0.3" 31 | 32 | Or, for the edge version: 33 | 34 | [dependencies] 35 | http-muncher = {git = "https://github.com/nbaksalyar/rust-streaming-http-parser"} 36 | 37 | You can find usage example in [examples/parser.rs](examples/parser.rs) (run it by executing `cargo run --example parser`) and in the library tests. 38 | 39 | ## API documentation 40 | 41 | You can find [API docs here](https://docs.rs/http-muncher/). 42 | 43 | ## Alternative libraries 44 | 45 | * [http-parser-rs](https://github.com/magic003/http-parser-rs) - Rust port of NodeJS HTTP parser (without FFI usage). 46 | * [httparse](https://github.com/seanmonstar/httparse) - pure Rust HTTP parser implementation. 47 | 48 | ## License 49 | 50 | The MIT License (MIT) 51 | 52 | Copyright (c) 2015 Nikita Baksalyar <> 53 | -------------------------------------------------------------------------------- /appveyor_rust_install.ps1: -------------------------------------------------------------------------------- 1 | ##### Appveyor Rust Install Script ##### 2 | 3 | # https://github.com/starkat99/appveyor-rust 4 | 5 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 6 | # specified by the "channel" and "target" environment variables from the build matrix. By default, 7 | # Rust will be installed to C:\Rust for easy usage, but this path can be overridden by setting the 8 | # RUST_INSTALL_DIR environment variable. The URL to download rust distributions defaults to 9 | # https://static.rust-lang.org/dist/ but can overridden by setting the RUST_DOWNLOAD_URL environment 10 | # variable. 11 | # 12 | # For simple configurations, instead of using the build matrix, you can override the channel and 13 | # target environment variables with the --channel and --target script arguments. 14 | # 15 | # If no channel or target arguments or environment variables are specified, will default to stable 16 | # channel and x86_64-pc-windows-msvc target. 17 | 18 | param([string]$channel=${env:channel}, [string]$target=${env:target}) 19 | 20 | # Initialize our parameters from arguments and environment variables, falling back to defaults 21 | if (!$channel) { 22 | $channel = "stable" 23 | } 24 | if (!$target) { 25 | $target = "x86_64-pc-windows-msvc" 26 | } 27 | 28 | $downloadUrl = "https://static.rust-lang.org/dist/" 29 | if ($env:RUST_DOWNLOAD_URL) { 30 | $downloadUrl = $env:RUST_DOWNLOAD_URL 31 | } 32 | 33 | $installDir = "C:\Rust" 34 | if ($env:RUST_INSTALL_DIR) { 35 | $installUrl = $env:RUST_INSTALL_DIR 36 | } 37 | 38 | if ($channel -eq "stable") { 39 | # Download manifest so we can find actual filename of installer to download. Needed for stable. 40 | echo "Downloading $channel channel manifest" 41 | $manifest = "${env:Temp}\channel-rust-${channel}" 42 | Start-FileDownload "${downloadUrl}channel-rust-${channel}" -FileName "$manifest" 43 | 44 | # Search the manifest lines for the correct filename based on target 45 | $match = Get-Content "$manifest" | Select-String -pattern "${target}.exe" -simplematch 46 | 47 | if (!$match -or !$match.line) { 48 | throw "Could not find $target in $channel channel manifest" 49 | } 50 | 51 | $installer = $match.line 52 | } else { 53 | # Otherwise download the file specified by channel directly. 54 | $installer = "rust-${channel}-${target}.exe" 55 | } 56 | 57 | # Download installer 58 | echo "Downloading ${downloadUrl}$installer" 59 | Start-FileDownload "${downloadUrl}$installer" -FileName "${env:Temp}\$installer" 60 | 61 | # Execute installer and wait for it to finish 62 | echo "Installing $installer to $installDir" 63 | &"${env:Temp}\$installer" /VERYSILENT /NORESTART /DIR="$installDir" | Write-Output 64 | 65 | # Add Rust to the path. 66 | $env:Path += ";${installDir}\bin;C:\MinGW\bin" 67 | 68 | echo "Installation of $channel Rust $target completed" 69 | 70 | # Test and display installed version information for rustc and cargo 71 | rustc -V 72 | cargo -V -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | //! This module provides an interface to the NodeJS http-parser library. 2 | 3 | use libc; 4 | use std::mem; 5 | 6 | pub type HttpCallback = extern fn(*mut HttpParser) -> libc::c_int; 7 | pub type HttpDataCallback = extern fn(*mut HttpParser, *const u32, libc::size_t) -> libc::c_int; 8 | 9 | #[repr(C)] 10 | #[derive(Clone, Copy, PartialEq)] 11 | pub enum ParserType { 12 | HttpRequest, 13 | HttpResponse, 14 | HttpBoth 15 | } 16 | 17 | #[repr(C)] 18 | pub struct HttpParser { 19 | // Private Interface 20 | _internal_state: libc::uint32_t, 21 | _nread: libc::uint32_t, 22 | _content_length: libc::uint64_t, 23 | 24 | // Read-Only 25 | pub http_major: libc::c_ushort, 26 | pub http_minor: libc::c_ushort, 27 | pub _extended_status: libc::uint32_t, 28 | 29 | // Public Interface 30 | pub data: *mut libc::c_void 31 | } 32 | 33 | unsafe impl Send for HttpParser { } 34 | 35 | impl HttpParser { 36 | pub fn new(parser_type: ParserType) -> HttpParser { 37 | let mut p: HttpParser = unsafe { mem::uninitialized() }; 38 | unsafe { http_parser_init(&mut p as *mut _, parser_type); } 39 | return p; 40 | } 41 | 42 | pub fn http_body_is_final(&self) -> libc::c_int { 43 | unsafe { return http_body_is_final(self); } 44 | } 45 | 46 | pub fn http_should_keep_alive(&self) -> libc::c_int { 47 | unsafe { http_should_keep_alive(self) } 48 | } 49 | 50 | pub fn http_parser_pause(&self, paused: libc::c_int) { 51 | unsafe { http_parser_pause(self, paused) } 52 | } 53 | } 54 | 55 | #[repr(C)] 56 | pub struct HttpParserSettings { 57 | pub on_message_begin: HttpCallback, 58 | pub on_url: HttpDataCallback, 59 | pub on_status: HttpDataCallback, 60 | pub on_header_field: HttpDataCallback, 61 | pub on_header_value: HttpDataCallback, 62 | pub on_headers_complete: HttpCallback, 63 | pub on_body: HttpDataCallback, 64 | pub on_message_complete: HttpCallback, 65 | pub on_chunk_header: HttpCallback, 66 | pub on_chunk_complete: HttpCallback 67 | } 68 | 69 | #[allow(dead_code)] 70 | extern "C" { 71 | pub fn http_parser_version() -> u32; 72 | pub fn http_parser_init(parser: *mut HttpParser, parser_type: ParserType); 73 | pub fn http_parser_settings_init(settings: *mut HttpParserSettings); 74 | pub fn http_parser_execute(parser: *mut HttpParser, settings: *const HttpParserSettings, data: *const u8, len: libc::size_t) -> libc::size_t; 75 | pub fn http_method_str(method_code: u8) -> *const libc::c_char; 76 | pub fn http_errno_name(http_errno: u8) -> *const libc::c_char; 77 | pub fn http_errno_description(http_errno: u8) -> *const libc::c_char; 78 | pub fn http_body_is_final(parser: *const HttpParser) -> libc::c_int; 79 | 80 | // Helper function to predictably use aligned bit-field struct 81 | pub fn http_get_struct_flags(parser: *const HttpParser) -> u32; 82 | 83 | pub fn http_should_keep_alive(parser: *const HttpParser) -> libc::c_int; 84 | pub fn http_parser_pause(parser: *const HttpParser, paused: libc::c_int); 85 | } 86 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | mod ffi; 4 | 5 | use std::marker::Send; 6 | 7 | use ffi::*; 8 | 9 | 10 | struct ParserContext<'a, H: ParserHandler + 'a> { 11 | parser: &'a mut Parser, 12 | handler: &'a mut H, 13 | } 14 | 15 | #[inline] 16 | unsafe fn unwrap_context<'a, H: ParserHandler>(http: *mut HttpParser) -> &'a mut ParserContext<'a, H> { 17 | &mut *((*http).data as *mut ParserContext) 18 | } 19 | 20 | macro_rules! notify_fn_wrapper { 21 | ( $callback:ident ) => ({ 22 | extern "C" fn $callback(http: *mut HttpParser) -> libc::c_int { 23 | let context = unsafe { unwrap_context::(http) }; 24 | if context.handler.$callback(context.parser) { 0 } else { 1 } 25 | }; 26 | 27 | $callback:: 28 | }); 29 | } 30 | 31 | macro_rules! data_fn_wrapper { 32 | ( $callback:ident ) => ({ 33 | extern "C" fn $callback(http: *mut HttpParser, data: *const u32, size: libc::size_t) -> libc::c_int { 34 | let slice = unsafe { std::slice::from_raw_parts(data as *const u8, size as usize) }; 35 | let context = unsafe { unwrap_context::(http) }; 36 | if context.handler.$callback(context.parser, slice) { 0 } else { 1 } 37 | }; 38 | 39 | $callback:: 40 | }); 41 | } 42 | 43 | impl HttpParserSettings { 44 | fn new() -> HttpParserSettings { 45 | HttpParserSettings { 46 | on_url: data_fn_wrapper!(on_url), 47 | on_message_begin: notify_fn_wrapper!(on_message_begin), 48 | on_status: data_fn_wrapper!(on_status), 49 | on_header_field: data_fn_wrapper!(on_header_field), 50 | on_header_value: data_fn_wrapper!(on_header_value), 51 | on_headers_complete: notify_fn_wrapper!(on_headers_complete), 52 | on_body: data_fn_wrapper!(on_body), 53 | on_message_complete: notify_fn_wrapper!(on_message_complete), 54 | on_chunk_header: notify_fn_wrapper!(on_chunk_header), 55 | on_chunk_complete: notify_fn_wrapper!(on_chunk_complete), 56 | } 57 | } 58 | } 59 | 60 | // High level Rust interface 61 | 62 | /// Used to define a set of callbacks in your code. 63 | /// They would be called by the parser whenever new data is available. 64 | /// You should bear in mind that the data might get in your callbacks in a partial form. 65 | /// 66 | /// Return `bool` as a result of each function call - either 67 | /// `true` for the "OK, go on" status, or `false` when you want to stop 68 | /// the parser after the function call has ended. 69 | /// 70 | /// All callbacks provide a default no-op implementation (i.e. they just return `true`). 71 | /// 72 | #[allow(unused_variables)] 73 | pub trait ParserHandler: Sized { 74 | /// Called when the URL part of a request becomes available. 75 | /// E.g. for `GET /forty-two HTTP/1.1` it will be called with `"/forty_two"` argument. 76 | /// 77 | /// It's not called in the response mode. 78 | fn on_url(&mut self, &mut Parser, &[u8]) -> bool { 79 | true 80 | } 81 | 82 | /// Called when a response status becomes available. 83 | /// 84 | /// It's not called in the request mode. 85 | fn on_status(&mut self, &mut Parser, &[u8]) -> bool { 86 | true 87 | } 88 | 89 | /// Called for each HTTP header key part. 90 | fn on_header_field(&mut self, &mut Parser, &[u8]) -> bool { 91 | true 92 | } 93 | 94 | /// Called for each HTTP header value part. 95 | fn on_header_value(&mut self, &mut Parser, &[u8]) -> bool { 96 | true 97 | } 98 | 99 | /// Called with body text as an argument when the new part becomes available. 100 | fn on_body(&mut self, &mut Parser, &[u8]) -> bool { 101 | true 102 | } 103 | 104 | /// Notified when all available headers have been processed. 105 | fn on_headers_complete(&mut self, &mut Parser) -> bool { 106 | true 107 | } 108 | 109 | /// Notified when the parser receives first bytes to parse. 110 | fn on_message_begin(&mut self, &mut Parser) -> bool { 111 | true 112 | } 113 | 114 | /// Notified when the parser has finished its job. 115 | fn on_message_complete(&mut self, &mut Parser) -> bool { 116 | true 117 | } 118 | 119 | fn on_chunk_header(&mut self, &mut Parser) -> bool { 120 | true 121 | } 122 | fn on_chunk_complete(&mut self, &mut Parser) -> bool { 123 | true 124 | } 125 | } 126 | 127 | fn http_method_name(method_code: u8) -> &'static str { 128 | unsafe { 129 | let method_str = http_method_str(method_code); 130 | let buf = std::ffi::CStr::from_ptr(method_str); 131 | return std::str::from_utf8(buf.to_bytes()).unwrap(); 132 | } 133 | } 134 | 135 | fn _http_errno_name(errno: u8) -> &'static str { 136 | unsafe { 137 | let err_str = http_errno_name(errno); 138 | let buf = std::ffi::CStr::from_ptr(err_str); 139 | return std::str::from_utf8(buf.to_bytes()).unwrap(); 140 | } 141 | } 142 | 143 | fn _http_errno_description(errno: u8) -> &'static str { 144 | unsafe { 145 | let err_str = http_errno_description(errno); 146 | let buf = std::ffi::CStr::from_ptr(err_str); 147 | return std::str::from_utf8(buf.to_bytes()).unwrap(); 148 | } 149 | } 150 | 151 | /// The main parser interface. 152 | /// 153 | /// # Example 154 | /// ``` 155 | /// use http_muncher::{Parser, ParserHandler}; 156 | /// use std::str; 157 | /// 158 | /// struct MyHandler; 159 | /// impl ParserHandler for MyHandler { 160 | /// fn on_header_field(&mut self, parser: &mut Parser, header: &[u8]) -> bool { 161 | /// println!("{}: ", str::from_utf8(header).unwrap()); 162 | /// true 163 | /// } 164 | /// fn on_header_value(&mut self, parser: &mut Parser, value: &[u8]) -> bool { 165 | /// println!("\t {:?}", str::from_utf8(value).unwrap()); 166 | /// true 167 | /// } 168 | /// } 169 | /// 170 | /// let http_request = b"GET / HTTP/1.0\r\n\ 171 | /// Content-Length: 0\r\n\r\n"; 172 | /// 173 | /// let mut parser = Parser::request(); 174 | /// parser.parse(&mut MyHandler {}, http_request); 175 | /// ``` 176 | #[allow(dead_code)] 177 | pub struct Parser { 178 | state: HttpParser, 179 | parser_type: ParserType, 180 | flags: u32, 181 | } 182 | 183 | unsafe impl Send for Parser {} 184 | 185 | impl Parser { 186 | /// Creates a new parser instance for an HTTP response. 187 | pub fn response() -> Parser { 188 | Parser { 189 | parser_type: ParserType::HttpResponse, 190 | state: HttpParser::new(ParserType::HttpResponse), 191 | flags: 0, 192 | } 193 | } 194 | 195 | /// Creates a new parser instance for an HTTP request. 196 | pub fn request() -> Parser { 197 | Parser { 198 | parser_type: ParserType::HttpRequest, 199 | state: HttpParser::new(ParserType::HttpRequest), 200 | flags: 0, 201 | } 202 | } 203 | 204 | /// Creates a new parser instance to handle both HTTP requests and responses. 205 | pub fn request_and_response() -> Parser { 206 | Parser { 207 | parser_type: ParserType::HttpBoth, 208 | state: HttpParser::new(ParserType::HttpBoth), 209 | flags: 0, 210 | } 211 | } 212 | 213 | /// Parses the provided `data` and returns a number of bytes read. 214 | pub fn parse(&mut self, handler: &mut H, data: &[u8]) -> usize { 215 | unsafe { 216 | let mut context = ParserContext { 217 | parser: self, 218 | handler: handler, 219 | }; 220 | 221 | context.parser.state.data = &mut context as *mut _ as *mut libc::c_void; 222 | 223 | let size = 224 | http_parser_execute(&mut context.parser.state as *mut _, 225 | &HttpParserSettings::new::() as *const _, 226 | data.as_ptr(), 227 | data.len() as libc::size_t) as usize; 228 | 229 | context.parser.flags = http_get_struct_flags(&context.parser.state as *const _); 230 | 231 | size 232 | } 233 | } 234 | 235 | /// Returns an HTTP request or response version. 236 | pub fn http_version(&self) -> (u16, u16) { 237 | (self.state.http_major, self.state.http_minor) 238 | } 239 | 240 | /// Returns an HTTP response status code (think *404*). 241 | pub fn status_code(&self) -> u16 { 242 | return (self.flags & 0xFFFF) as u16; 243 | } 244 | 245 | /// Returns an HTTP method static string (`GET`, `POST`, and so on). 246 | pub fn http_method(&self) -> &'static str { 247 | let method_code = ((self.flags >> 16) & 0xFF) as u8; 248 | return http_method_name(method_code); 249 | } 250 | 251 | fn http_errnum(&self) -> u8 { 252 | return ((self.flags >> 24) & 0x7F) as u8; 253 | } 254 | 255 | /// Checks if the last `parse` call was finished successfully. 256 | /// Returns `true` if it wasn't. 257 | pub fn has_error(&self) -> bool { 258 | self.http_errnum() != 0x00 259 | } 260 | 261 | /// In case of a parsing error returns its mnemonic name. 262 | pub fn error(&self) -> &'static str { 263 | _http_errno_name(self.http_errnum()) 264 | } 265 | 266 | /// In case of a parsing error returns its description. 267 | pub fn error_description(&self) -> &'static str { 268 | _http_errno_description(self.http_errnum()) 269 | } 270 | 271 | /// Checks if an upgrade protocol (e.g. WebSocket) was requested. 272 | pub fn is_upgrade(&self) -> bool { 273 | return ((self.flags >> 31) & 0x01) == 1; 274 | } 275 | 276 | /// Checks if it was the final body chunk. 277 | pub fn is_final_chunk(&self) -> bool { 278 | return self.state.http_body_is_final() == 1; 279 | } 280 | 281 | /// If `should_keep_alive()` in the `on_headers_complete` or `on_message_complete` callback 282 | /// returns 0, then this should be the last message on the connection. 283 | /// If you are the server, respond with the "Connection: close" header. 284 | /// If you are the client, close the connection. 285 | pub fn should_keep_alive(&self) -> bool { 286 | self.state.http_should_keep_alive() == 1 287 | } 288 | 289 | pub fn pause(&mut self) { 290 | self.state.http_parser_pause(1); 291 | } 292 | 293 | pub fn unpause(&mut self) { 294 | self.state.http_parser_pause(0); 295 | } 296 | } 297 | 298 | impl std::fmt::Debug for Parser { 299 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 300 | let (version_major, version_minor) = self.http_version(); 301 | return write!(fmt, 302 | "status_code: {}\nmethod: {}\nerror: {}, {}\nupgrade: {}\nhttp_version: \ 303 | {}.{}", 304 | self.status_code(), 305 | self.http_method(), 306 | self.error(), 307 | self.error_description(), 308 | self.is_upgrade(), 309 | version_major, 310 | version_minor); 311 | } 312 | } 313 | 314 | /// Returns a version of the underlying `http-parser` library. 315 | pub fn version() -> (u32, u32, u32) { 316 | let version = unsafe { http_parser_version() }; 317 | 318 | let major = (version >> 16) & 255; 319 | let minor = (version >> 8) & 255; 320 | let patch = version & 255; 321 | 322 | (major, minor, patch) 323 | } 324 | 325 | #[cfg(test)] 326 | mod tests { 327 | use super::{version, ParserHandler, Parser}; 328 | 329 | #[test] 330 | fn test_version() { 331 | assert_eq!((2, 7, 1), version()); 332 | } 333 | 334 | #[test] 335 | fn test_request_parser() { 336 | struct TestRequestParser; 337 | 338 | impl ParserHandler for TestRequestParser { 339 | fn on_url(&mut self, _: &mut Parser, url: &[u8]) -> bool { 340 | assert_eq!(b"/say_hello", url); 341 | true 342 | } 343 | 344 | fn on_header_field(&mut self, _: &mut Parser, hdr: &[u8]) -> bool { 345 | assert!(hdr == b"Host" || hdr == b"Content-Length"); 346 | true 347 | } 348 | 349 | fn on_header_value(&mut self, _: &mut Parser, val: &[u8]) -> bool { 350 | assert!(val == b"localhost.localdomain" || val == b"11"); 351 | true 352 | } 353 | 354 | fn on_body(&mut self, parser: &mut Parser, body: &[u8]) -> bool { 355 | assert_eq!((1, 1), parser.http_version()); 356 | assert_eq!(body, b"Hello world"); 357 | true 358 | } 359 | } 360 | 361 | let req = b"POST /say_hello HTTP/1.1\r\nContent-Length: 11\r\nHost: localhost.localdomain\r\n\r\nHello world"; 362 | 363 | let mut handler = TestRequestParser; 364 | 365 | let mut parser = Parser::request(); 366 | let parsed = parser.parse(&mut handler, req); 367 | 368 | assert!(parsed > 0); 369 | assert!(!parser.has_error()); 370 | assert_eq!("POST", parser.http_method()); 371 | } 372 | 373 | #[test] 374 | fn test_response_parser() { 375 | struct TestResponseParser; 376 | 377 | impl ParserHandler for TestResponseParser { 378 | fn on_status(&mut self, _: &mut Parser, status: &[u8]) -> bool { 379 | assert_eq!(b"OK", status); 380 | true 381 | } 382 | 383 | fn on_header_field(&mut self, _: &mut Parser, hdr: &[u8]) -> bool { 384 | assert_eq!(b"Host", hdr); 385 | true 386 | } 387 | 388 | fn on_header_value(&mut self, _: &mut Parser, val: &[u8]) -> bool { 389 | assert_eq!(b"localhost.localdomain", val); 390 | true 391 | } 392 | } 393 | 394 | let req = b"HTTP/1.1 200 OK\r\nHost: localhost.localdomain\r\n\r\n"; 395 | 396 | let mut handler = TestResponseParser; 397 | 398 | let mut parser = Parser::response(); 399 | let parsed = parser.parse(&mut handler, req); 400 | 401 | assert!(parsed > 0); 402 | assert!(!parser.has_error()); 403 | assert_eq!((1, 1), parser.http_version()); 404 | assert_eq!(200, parser.status_code()); 405 | } 406 | 407 | #[test] 408 | fn test_ws_upgrade() { 409 | struct DummyHandler; 410 | 411 | impl ParserHandler for DummyHandler {}; 412 | 413 | let req = b"GET / HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"; 414 | 415 | let mut handler = DummyHandler; 416 | 417 | let mut parser = Parser::request(); 418 | parser.parse(&mut handler, req); 419 | 420 | assert_eq!(parser.is_upgrade(), true); 421 | } 422 | 423 | #[test] 424 | fn test_error_status() { 425 | struct DummyHandler { 426 | url_parsed: bool, 427 | } 428 | 429 | impl ParserHandler for DummyHandler { 430 | fn on_url(&mut self, _: &mut Parser, _: &[u8]) -> bool { 431 | self.url_parsed = true; 432 | false 433 | } 434 | 435 | fn on_header_field(&mut self, _: &mut Parser, _: &[u8]) -> bool { 436 | panic!("This callback shouldn't be executed!"); 437 | } 438 | } 439 | 440 | let req = b"GET / HTTP/1.1\r\nHeader: hello\r\n\r\n"; 441 | 442 | let mut handler = DummyHandler { url_parsed: false }; 443 | 444 | let mut parser = Parser::request(); 445 | parser.parse(&mut handler, req); 446 | 447 | assert!(handler.url_parsed); 448 | } 449 | 450 | #[test] 451 | fn test_streaming() { 452 | struct DummyHandler; 453 | 454 | impl ParserHandler for DummyHandler {}; 455 | 456 | let req = b"GET / HTTP/1.1\r\nHeader: hello\r\n\r\n"; 457 | 458 | let mut handler = DummyHandler; 459 | let mut parser = Parser::request(); 460 | 461 | parser.parse(&mut handler, &req[0..10]); 462 | 463 | assert_eq!(parser.http_version(), (0, 0)); 464 | assert!(!parser.has_error()); 465 | 466 | parser.parse(&mut handler, &req[10..]); 467 | 468 | assert_eq!(parser.http_version(), (1, 1)); 469 | } 470 | 471 | #[test] 472 | fn test_catch_error() { 473 | struct DummyHandler; 474 | 475 | impl ParserHandler for DummyHandler {}; 476 | 477 | let req = b"UNKNOWN_METHOD / HTTP/3.0\r\nAnswer: 42\r\n\r\n"; 478 | 479 | let mut handler = DummyHandler; 480 | let mut parser = Parser::request(); 481 | 482 | parser.parse(&mut handler, req); 483 | 484 | assert!(parser.has_error()); 485 | assert_eq!(parser.error(), "HPE_INVALID_METHOD"); 486 | } 487 | } 488 | --------------------------------------------------------------------------------