├── .gitignore ├── LICENSE ├── README.md ├── examples └── hello_world │ ├── .gitignore │ ├── Cargo.toml │ ├── nginx.conf │ └── src │ └── lib.rs └── nginx-rs ├── .gitignore ├── Cargo.toml ├── build.rs ├── src ├── bindings.rs ├── core │ ├── buffer.rs │ ├── mod.rs │ ├── pool.rs │ ├── status.rs │ └── string.rs ├── http │ ├── conf.rs │ ├── mod.rs │ ├── module.rs │ ├── request.rs │ └── status.rs ├── lib.rs └── log.rs └── wrapper.h /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 David Coles 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nginx-rs 2 | 3 | [![crates.io](https://img.shields.io/crates/v/nginx-rs.svg)](https://crates.io/crates/nginx-rs) 4 | [![MIT License](https://img.shields.io/crates/l/nginx-rs.svg)](LICENSE) 5 | 6 | > **Note** 7 | > **[Nov 2023]** Nginx now provides official Rust support with the [`ngx`](https://crates.io/crates/ngx) crate. 8 | > 9 | > See Nginx's blog post [*Extending NGINX with Rust (an Alternative to C)*](https://www.nginx.com/blog/extending-nginx-with-rust-an-alternative-to-c/). 10 | > 11 | > **[Feb 2023]** I haven't had a chance to work on this recently, but you might be interested in 12 | > Cloudflare's blog post [*ROFL with a LOL: rewriting an NGINX module in Rust*](https://blog.cloudflare.com/rust-nginx-module/), which takes some inspiration from this module. 13 | 14 | A framework for writing Nginx modules in pure Rust. 15 | 16 | This module is in early stages. It lacks documentation and the API is still quite unstable. 17 | But it can be used to write simple request handlers for content or access control. 18 | 19 | ## Building Modules 20 | 21 | Building modules requires a checkout of the Nginx sources 22 | [configured for building dynamic modules](https://www.nginx.com/blog/compiling-dynamic-modules-nginx-plus/): 23 | 24 | ```bash 25 | export NGINX_DIR=/path/to/nginx 26 | cd "${NGINX_DIR}" 27 | auto/configure --with-compat 28 | ``` 29 | 30 | Once Nginx is configured, you can then build your module: 31 | 32 | ```bash 33 | cd /path/to/module 34 | cargo build --release 35 | ``` 36 | 37 | The resulting `.so` in `target/release` can then be loaded using the 38 | [`load_module` directive](https://nginx.org/en/docs/ngx_core_module.html#load_module). 39 | 40 | ## Examples 41 | 42 | - [hello_world](/examples/hello_world) — Demonstrations access control and content handlers 43 | 44 | ## Licence 45 | 46 | This project is licensed under the terms of the [MIT license](LICENSE). 47 | -------------------------------------------------------------------------------- /examples/hello_world/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/hello_world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-rs" 3 | version = "0.1.0" 4 | authors = ["David Coles "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | nginx-rs = { path = "../../nginx-rs" } 12 | 13 | [profile.release] 14 | debug = true 15 | -------------------------------------------------------------------------------- /examples/hello_world/nginx.conf: -------------------------------------------------------------------------------- 1 | daemon off; 2 | master_process off; 3 | 4 | load_module modules/libhello_rs.so; 5 | 6 | error_log logs/error.log debug; 7 | 8 | events { } 9 | 10 | http { 11 | server { 12 | listen 8000; 13 | location / { 14 | hello_world; 15 | hello_world_text "David"; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/hello_world/src/lib.rs: -------------------------------------------------------------------------------- 1 | use nginx_rs::bindings::*; 2 | use nginx_rs::core::*; 3 | use nginx_rs::http::*; 4 | 5 | use nginx_rs::{ngx_modules, ngx_string, http_request_handler, ngx_null_command, ngx_log_debug_http}; 6 | 7 | use std::borrow::Cow; 8 | use std::os::raw::{c_char, c_void}; 9 | use std::ptr; 10 | 11 | #[no_mangle] 12 | static mut ngx_http_hello_world_commands: [ngx_command_t; 3] = [ 13 | ngx_command_t { 14 | name: ngx_string!("hello_world"), 15 | type_: (NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS) as ngx_uint_t, 16 | set: Some(ngx_http_hello_world), 17 | conf: 0, 18 | offset: 0, 19 | post: ptr::null_mut(), 20 | }, 21 | ngx_command_t { 22 | name: ngx_string!("hello_world_text"), 23 | type_: (NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1) as ngx_uint_t, 24 | set: Some(ngx_http_hello_world_set_text), 25 | conf: NGX_RS_HTTP_LOC_CONF_OFFSET, 26 | offset: 0, 27 | post: ptr::null_mut(), 28 | }, 29 | ngx_null_command!(), 30 | ]; 31 | 32 | #[no_mangle] 33 | static ngx_http_hello_world_module_ctx: ngx_http_module_t = ngx_http_module_t { 34 | preconfiguration: Some(Module::preconfiguration), 35 | postconfiguration: Some(Module::postconfiguration), 36 | 37 | create_main_conf: Some(Module::create_main_conf), 38 | init_main_conf: Some(Module::init_main_conf), 39 | 40 | create_srv_conf: Some(Module::create_srv_conf), 41 | merge_srv_conf: Some(Module::merge_srv_conf), 42 | 43 | create_loc_conf: Some(Module::create_loc_conf), 44 | merge_loc_conf: Some(Module::merge_loc_conf), 45 | }; 46 | 47 | #[no_mangle] 48 | pub static mut ngx_http_hello_world_module: ngx_module_t = ngx_module_t { 49 | ctx_index: ngx_uint_t::max_value(), 50 | index: ngx_uint_t::max_value(), 51 | name: ptr::null_mut(), 52 | spare0: 0, 53 | spare1: 0, 54 | version: nginx_version as ngx_uint_t, 55 | signature: NGX_RS_MODULE_SIGNATURE.as_ptr() as *const c_char, 56 | 57 | ctx: &ngx_http_hello_world_module_ctx as *const _ as *mut _, 58 | commands: unsafe { &ngx_http_hello_world_commands[0] as *const _ as *mut _ }, 59 | type_: NGX_HTTP_MODULE as ngx_uint_t, 60 | 61 | init_master: None, 62 | init_module: None, 63 | init_process: None, 64 | init_thread: None, 65 | exit_thread: None, 66 | exit_process: None, 67 | exit_master: None, 68 | 69 | spare_hook0: 0, 70 | spare_hook1: 0, 71 | spare_hook2: 0, 72 | spare_hook3: 0, 73 | spare_hook4: 0, 74 | spare_hook5: 0, 75 | spare_hook6: 0, 76 | spare_hook7: 0, 77 | }; 78 | 79 | ngx_modules!(ngx_http_hello_world_module); 80 | 81 | struct Module; 82 | 83 | impl HTTPModule for Module { 84 | type MainConf = (); 85 | type SrvConf = (); 86 | type LocConf = LocConf; 87 | 88 | unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t { 89 | let cmcf = ngx_http_conf_get_module_main_conf(cf, &ngx_http_core_module) as *mut ngx_http_core_main_conf_t; 90 | 91 | let h = ngx_array_push(&mut (*cmcf).phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers) as *mut ngx_http_handler_pt; 92 | if h.is_null() { 93 | return ERROR.into(); 94 | } 95 | 96 | *h = Some(ngx_http_hello_world_access_handler); 97 | 98 | OK.into() 99 | } 100 | } 101 | 102 | #[derive(Default)] 103 | struct LocConf { 104 | text: String, 105 | } 106 | 107 | impl Merge for LocConf { 108 | fn merge(&mut self, prev: &LocConf) { 109 | if self.text.is_empty() { 110 | self.text = String::from(if !prev.text.is_empty() { &prev.text } else { "" }); 111 | } 112 | } 113 | } 114 | 115 | #[no_mangle] 116 | unsafe extern "C" fn ngx_http_hello_world(cf: *mut ngx_conf_t, _cmd: *mut ngx_command_t, conf: *mut c_void) -> *mut c_char { 117 | let conf = &mut *(conf as *mut LocConf); 118 | let clcf = ngx_http_conf_get_module_loc_conf(cf, &ngx_http_core_module) as *mut ngx_http_core_loc_conf_t; 119 | (*clcf).handler = Some(ngx_http_hello_world_handler); 120 | 121 | ptr::null_mut() 122 | } 123 | 124 | #[no_mangle] 125 | unsafe extern "C" fn ngx_http_hello_world_set_text(cf: *mut ngx_conf_t, _cmd: *mut ngx_command_t, conf: *mut c_void) -> *mut c_char { 126 | let conf = &mut *(conf as *mut LocConf); 127 | let args = (*(*cf).args).elts as *mut ngx_str_t; 128 | let value = NgxStr::from_ngx_str(*args.add(1)); 129 | conf.text = String::from(value.to_string_lossy()); 130 | 131 | ptr::null_mut() 132 | } 133 | 134 | 135 | http_request_handler!(ngx_http_hello_world_access_handler, |request: &mut Request| { 136 | if request.user_agent().as_bytes().starts_with(b"curl") { 137 | return HTTP_FORBIDDEN.into(); 138 | } 139 | 140 | OK 141 | }); 142 | 143 | http_request_handler!(ngx_http_hello_world_handler, |request: &mut Request| { 144 | ngx_log_debug_http!(request, "http hello_world handler"); 145 | 146 | // Ignore client request body if any 147 | if !request.discard_request_body().is_ok() { 148 | return HTTP_INTERNAL_SERVER_ERROR.into(); 149 | } 150 | 151 | let hlcf = unsafe { request.get_module_loc_conf(&ngx_http_hello_world_module) as *mut LocConf }; 152 | let text = unsafe { &(*hlcf).text }; 153 | 154 | // Create body 155 | let user_agent = request.user_agent(); 156 | let body = format!("Hello, {}!\n", if text.is_empty() { user_agent.to_string_lossy() } else { Cow::from(text) }); 157 | 158 | // Send header 159 | request.set_status(HTTP_OK); 160 | request.set_content_length_n(body.len()); 161 | let status = request.send_header(); 162 | if status == ERROR || status > OK || request.header_only() { 163 | return status; 164 | } 165 | 166 | // Send body 167 | let mut buf = match request.pool().create_buffer_from_str(&body) { 168 | Some(buf) => buf, 169 | None => return HTTP_INTERNAL_SERVER_ERROR.into(), 170 | }; 171 | assert!(&buf.as_bytes()[..7] == b"Hello, "); 172 | buf.set_last_buf(request.is_main()); 173 | buf.set_last_in_chain(true); 174 | 175 | let mut out = ngx_chain_t { buf: buf.as_ngx_buf_mut(), next: ptr::null_mut() }; 176 | request.output_filter(&mut out) 177 | }); 178 | -------------------------------------------------------------------------------- /nginx-rs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /nginx-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nginx-rs" 3 | version = "0.1.0" 4 | authors = ["David Coles "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Nginx modules in Rust" 8 | homepage = "https://github.com/dcoles/nginx-rs" 9 | repository = "https://github.com/dcoles/nginx-rs" 10 | readme = "../README.md" 11 | keywords = ["nginx", "modules"] 12 | categories = ["api-bindings", "web-programming::http-server"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | 18 | [build-dependencies] 19 | bindgen = "0.51" 20 | -------------------------------------------------------------------------------- /nginx-rs/build.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | // Tell cargo to tell rustc to link the system bzip2 8 | // shared library. 9 | println!("cargo:rustc-link-lib=bz2"); 10 | 11 | let nginx_dir = env::var("NGINX_DIR").unwrap_or(String::from("../../nginx")); 12 | 13 | // The bindgen::Builder is the main entry point 14 | // to bindgen, and lets you build up options for 15 | // the resulting bindings. 16 | let bindings = bindgen::Builder::default() 17 | // The input header we would like to generate 18 | // bindings for. 19 | .header("wrapper.h") 20 | .layout_tests(false) 21 | .whitelist_type("ngx_.*") 22 | .whitelist_function("ngx_.*") 23 | .whitelist_var("NGX_.*|ngx_.*|nginx_.*") 24 | .clang_arg(format!("-I{}/src/core", nginx_dir)) 25 | .clang_arg(format!("-I{}/src/event", nginx_dir)) 26 | .clang_arg(format!("-I{}/src/event/modules", nginx_dir)) 27 | .clang_arg(format!("-I{}/src/os/unix", nginx_dir)) 28 | .clang_arg(format!("-I{}/objs", nginx_dir)) 29 | .clang_arg(format!("-I{}/src/http", nginx_dir)) 30 | .clang_arg(format!("-I{}/src/http/modules", nginx_dir)) 31 | // Finish the builder and generate the bindings. 32 | .generate() 33 | // Unwrap the Result and panic on failure. 34 | .expect("Unable to generate bindings"); 35 | 36 | // Write the bindings to the $OUT_DIR/bindings.rs file. 37 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 38 | bindings 39 | .write_to_file(out_path.join("bindings.rs")) 40 | .expect("Couldn't write bindings!"); 41 | } 42 | -------------------------------------------------------------------------------- /nginx-rs/src/bindings.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(dead_code)] 5 | #![allow(clippy::all)] 6 | 7 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 8 | -------------------------------------------------------------------------------- /nginx-rs/src/core/buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::bindings::*; 2 | 3 | use std::slice; 4 | 5 | pub trait Buffer { 6 | fn as_ngx_buf(&self) -> *const ngx_buf_t; 7 | 8 | fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t; 9 | 10 | fn as_bytes(&self) -> &[u8] { 11 | let buf = self.as_ngx_buf(); 12 | unsafe { slice::from_raw_parts((*buf).pos, self.len()) } 13 | } 14 | 15 | fn len(&self) -> usize { 16 | let buf = self.as_ngx_buf(); 17 | unsafe { 18 | let pos = (*buf).pos; 19 | let last = (*buf).last; 20 | assert!(last >= pos); 21 | usize::wrapping_sub(last as _, pos as _) 22 | } 23 | } 24 | 25 | fn is_empty(&self) -> bool { 26 | self.len() == 0 27 | } 28 | 29 | fn set_last_buf(&mut self, last: bool) { 30 | let buf = self.as_ngx_buf_mut(); 31 | unsafe { 32 | (*buf).set_last_buf(if last { 1 } else { 0 }); 33 | } 34 | } 35 | 36 | fn set_last_in_chain(&mut self, last: bool) { 37 | let buf = self.as_ngx_buf_mut(); 38 | unsafe { 39 | (*buf).set_last_in_chain(if last { 1 } else { 0 }); 40 | } 41 | } 42 | } 43 | 44 | pub trait MutableBuffer: Buffer { 45 | fn as_bytes_mut(&mut self) -> &mut [u8] { 46 | let buf = self.as_ngx_buf_mut(); 47 | unsafe { slice::from_raw_parts_mut((*buf).pos, self.len()) } 48 | } 49 | } 50 | 51 | pub struct TemporaryBuffer(*mut ngx_buf_t); 52 | 53 | impl TemporaryBuffer { 54 | pub fn from_ngx_buf(buf: *mut ngx_buf_t) -> TemporaryBuffer { 55 | assert!(!buf.is_null()); 56 | TemporaryBuffer(buf) 57 | } 58 | } 59 | 60 | impl Buffer for TemporaryBuffer { 61 | fn as_ngx_buf(&self) -> *const ngx_buf_t { 62 | self.0 63 | } 64 | 65 | fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t { 66 | self.0 67 | } 68 | } 69 | 70 | impl MutableBuffer for TemporaryBuffer { 71 | fn as_bytes_mut(&mut self) -> &mut [u8] { 72 | unsafe { slice::from_raw_parts_mut((*self.0).pos, self.len()) } 73 | } 74 | } 75 | 76 | pub struct MemoryBuffer(*mut ngx_buf_t); 77 | 78 | impl MemoryBuffer { 79 | pub fn from_ngx_buf(buf: *mut ngx_buf_t) -> MemoryBuffer { 80 | assert!(!buf.is_null()); 81 | MemoryBuffer(buf) 82 | } 83 | } 84 | 85 | impl Buffer for MemoryBuffer { 86 | fn as_ngx_buf(&self) -> *const ngx_buf_t { 87 | self.0 88 | } 89 | 90 | fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t { 91 | self.0 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /nginx-rs/src/core/mod.rs: -------------------------------------------------------------------------------- 1 | mod buffer; 2 | mod pool; 3 | mod status; 4 | mod string; 5 | 6 | pub use buffer::*; 7 | pub use pool::*; 8 | pub use status::*; 9 | pub use string::*; 10 | 11 | /// Static empty configuration directive initializer for [`ngx_command_t`]. 12 | /// 13 | /// This is typically used to terminate an array of configuration directives. 14 | /// 15 | /// [`ngx_command_t`]: https://nginx.org/en/docs/dev/development_guide.html#config_directives 16 | #[macro_export] 17 | macro_rules! ngx_null_command { 18 | () => { 19 | ngx_command_t { 20 | name: $crate::ngx_null_string!(), 21 | type_: 0, 22 | set: None, 23 | conf: 0, 24 | offset: 0, 25 | post: ::std::ptr::null_mut(), 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /nginx-rs/src/core/pool.rs: -------------------------------------------------------------------------------- 1 | use crate::bindings::*; 2 | use crate::core::buffer::{TemporaryBuffer, MemoryBuffer, Buffer}; 3 | 4 | use std::{ptr, mem}; 5 | use std::os::raw::c_void; 6 | 7 | pub struct Pool(*mut ngx_pool_t); 8 | 9 | impl Pool { 10 | pub unsafe fn from_ngx_pool(pool: *mut ngx_pool_t) -> Pool { 11 | assert!(!pool.is_null()); 12 | Pool(pool) 13 | } 14 | 15 | pub fn create_buffer(&mut self, size: usize) -> Option { 16 | let buf = unsafe { ngx_create_temp_buf(self.0, size) }; 17 | if buf.is_null() { 18 | return None; 19 | } 20 | 21 | Some(TemporaryBuffer::from_ngx_buf(buf)) 22 | } 23 | 24 | pub fn create_buffer_from_str(&mut self, str: &str) -> Option 25 | { 26 | let mut buffer = self.create_buffer(str.len())?; 27 | unsafe { 28 | let mut buf = buffer.as_ngx_buf_mut(); 29 | ptr::copy_nonoverlapping(str.as_ptr(), (*buf).pos, str.len()); 30 | (*buf).last = (*buf).pos.add(str.len()); 31 | } 32 | Some(buffer) 33 | } 34 | 35 | pub fn create_buffer_from_static_str(&mut self, str: &'static str) -> Option { 36 | let buf = self.calloc_type::(); 37 | if buf.is_null() { 38 | return None; 39 | } 40 | 41 | // We cast away const, but buffers with the memory flag are read-only 42 | let start = str.as_ptr() as *mut u8; 43 | let end = unsafe { start.add(str.len()) }; 44 | 45 | unsafe { 46 | (*buf).start = start; 47 | (*buf).pos = start; 48 | (*buf).last = end; 49 | (*buf).end = end; 50 | (*buf).set_memory(1); 51 | } 52 | 53 | Some(MemoryBuffer::from_ngx_buf(buf)) 54 | } 55 | 56 | unsafe fn add_cleanup_for_value(&mut self, value: *mut T) -> Result<(), ()> { 57 | let cln = ngx_pool_cleanup_add(self.0, 0); 58 | if cln.is_null() { 59 | return Err(()); 60 | } 61 | (*cln).handler = Some(cleanup_type::); 62 | (*cln).data = value as *mut c_void; 63 | 64 | Ok(()) 65 | } 66 | 67 | pub fn alloc(&mut self, size: usize) -> *mut c_void { 68 | unsafe { ngx_palloc(self.0, size) } 69 | } 70 | 71 | pub fn alloc_type(&mut self) -> *mut T { 72 | self.alloc(mem::size_of::()) as *mut T 73 | } 74 | 75 | pub fn calloc(&mut self, size: usize) -> *mut c_void { 76 | unsafe { ngx_pcalloc(self.0, size) } 77 | } 78 | 79 | pub fn calloc_type(&mut self) -> *mut T { 80 | self.calloc(mem::size_of::()) as *mut T 81 | } 82 | 83 | pub fn allocate(&mut self, value: T) -> *mut T { 84 | unsafe { 85 | let p = self.alloc(mem::size_of::()) as *mut T; 86 | ptr::write(p, value); 87 | if self.add_cleanup_for_value(p).is_err() { 88 | ptr::drop_in_place(p); 89 | return ptr::null_mut(); 90 | }; 91 | p 92 | } 93 | } 94 | } 95 | 96 | unsafe extern "C" fn cleanup_type(data: *mut c_void) { 97 | ptr::drop_in_place(data as *mut T); 98 | } 99 | -------------------------------------------------------------------------------- /nginx-rs/src/core/status.rs: -------------------------------------------------------------------------------- 1 | use crate::bindings::*; 2 | 3 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 4 | pub struct Status(pub ngx_int_t); 5 | 6 | impl Status { 7 | pub fn is_ok(&self) -> bool { 8 | self == &OK 9 | } 10 | } 11 | 12 | impl Into for Status { 13 | fn into(self) -> ngx_int_t { 14 | self.0 15 | } 16 | } 17 | 18 | pub const OK: Status = Status(NGX_OK as ngx_int_t); 19 | pub const ERROR: Status = Status(NGX_ERROR as ngx_int_t); 20 | pub const AGAIN: Status = Status(NGX_AGAIN as ngx_int_t); 21 | -------------------------------------------------------------------------------- /nginx-rs/src/core/string.rs: -------------------------------------------------------------------------------- 1 | use crate::bindings::*; 2 | 3 | use std::slice; 4 | use std::str::{self, Utf8Error}; 5 | use std::borrow::Cow; 6 | 7 | /// Static string initializer for [`ngx_str_t`]. 8 | /// 9 | /// The resulting byte string is always nul-terminated (just like a C string). 10 | /// 11 | /// [`ngx_str_t`]: https://nginx.org/en/docs/dev/development_guide.html#string_overview 12 | #[macro_export] 13 | macro_rules! ngx_string { 14 | ($s:expr) => { 15 | { 16 | ngx_str_t { len: $s.len(), data: concat!($s, "\0").as_ptr() as *mut u8 } 17 | } 18 | }; 19 | } 20 | 21 | /// Static empty string initializer for [`ngx_str_t`]. 22 | /// 23 | /// [`ngx_str_t`]: https://nginx.org/en/docs/dev/development_guide.html#string_overview 24 | #[macro_export] 25 | macro_rules! ngx_null_string { 26 | () => { 27 | ngx_str_t { len: 0, data: ::std::ptr::null_mut() } 28 | }; 29 | } 30 | 31 | /// Representation of a borrowed [Nginx string]. 32 | /// 33 | /// [Nginx string]: https://nginx.org/en/docs/dev/development_guide.html#string_overview 34 | pub struct NgxStr([u_char]); 35 | 36 | impl NgxStr { 37 | /// Create an [`NgxStr`] from an [`ngx_str_t`]. 38 | /// 39 | /// [`ngx_str_t`]: https://nginx.org/en/docs/dev/development_guide.html#string_overview 40 | pub unsafe fn from_ngx_str<'a>(str: ngx_str_t) -> &'a NgxStr { 41 | // SAFETY: The caller has provided a valid `ngx_str_t` with a `data` pointer that points 42 | // to range of bytes of at least `len` bytes, whose content remains valid and doesn't 43 | // change for the lifetime of the returned `NgxStr`. 44 | slice::from_raw_parts(str.data, str.len).into() 45 | } 46 | 47 | /// Access the [`NgxStr`] as a byte slice. 48 | pub fn as_bytes(&self) -> &[u8] { 49 | &self.0 50 | } 51 | 52 | /// Yields a `&str` slice if the [`NgxStr`] contains valid UTF-8. 53 | pub fn to_str(&self) -> Result<&str, Utf8Error> { 54 | str::from_utf8(self.as_bytes()) 55 | } 56 | 57 | /// Converts an [`NgxStr`] into a [`Cow`], replacing invalid UTF-8 sequences. 58 | /// 59 | /// See [`String::from_utf8_lossy`]. 60 | pub fn to_string_lossy(&self) -> Cow { 61 | String::from_utf8_lossy(self.as_bytes()) 62 | } 63 | 64 | /// Returns `true` if the [`NgxStr`] is empty, otherwise `false`. 65 | pub fn is_empty(&self) -> bool { 66 | self.0.is_empty() 67 | } 68 | } 69 | 70 | impl From<&[u8]> for &NgxStr { 71 | fn from(bytes: &[u8]) -> Self { 72 | // SAFETY: An `NgxStr` is identical to a `[u8]` slice, given `u_char` is an alias for `u8`. 73 | unsafe { 74 | &*(bytes as *const [u8] as *const NgxStr) 75 | } 76 | } 77 | } 78 | 79 | impl From<&str> for &NgxStr { 80 | fn from(s: &str) -> Self { 81 | s.as_bytes().into() 82 | } 83 | } 84 | 85 | impl AsRef<[u8]> for NgxStr { 86 | fn as_ref(&self) -> &[u8] { 87 | self.as_bytes() 88 | } 89 | } 90 | 91 | impl Default for &NgxStr { 92 | fn default() -> Self { 93 | // SAFETY: The null `ngx_str_t` is always a valid Nginx string. 94 | unsafe { 95 | NgxStr::from_ngx_str(ngx_null_string!()) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /nginx-rs/src/http/conf.rs: -------------------------------------------------------------------------------- 1 | use crate::bindings::*; 2 | 3 | use std::os::raw::c_void; 4 | 5 | pub unsafe fn ngx_http_conf_get_module_main_conf(cf: *mut ngx_conf_t, module: &ngx_module_t) -> *mut c_void { 6 | let http_conf_ctx = (*cf).ctx as *mut ngx_http_conf_ctx_t; 7 | *(*http_conf_ctx).main_conf.add(module.ctx_index) 8 | } 9 | 10 | pub unsafe fn ngx_http_conf_get_module_srv_conf(cf: *mut ngx_conf_t, module: &ngx_module_t) -> *mut c_void { 11 | let http_conf_ctx = (*cf).ctx as *mut ngx_http_conf_ctx_t; 12 | *(*http_conf_ctx).srv_conf.add(module.ctx_index) 13 | } 14 | 15 | pub unsafe fn ngx_http_conf_get_module_loc_conf(cf: *mut ngx_conf_t, module: &ngx_module_t) -> *mut c_void { 16 | let http_conf_ctx = (*cf).ctx as *mut ngx_http_conf_ctx_t; 17 | *(*http_conf_ctx).loc_conf.add(module.ctx_index) 18 | } 19 | -------------------------------------------------------------------------------- /nginx-rs/src/http/mod.rs: -------------------------------------------------------------------------------- 1 | mod conf; 2 | mod status; 3 | mod module; 4 | mod request; 5 | 6 | pub use conf::*; 7 | pub use status::*; 8 | pub use module::*; 9 | pub use request::*; 10 | -------------------------------------------------------------------------------- /nginx-rs/src/http/module.rs: -------------------------------------------------------------------------------- 1 | use crate::bindings::*; 2 | use crate::core::*; 3 | 4 | use std::os::raw::{c_void, c_char}; 5 | use core::ptr; 6 | 7 | pub trait Merge { 8 | fn merge(&mut self, prev: &Self); 9 | } 10 | 11 | impl Merge for () { 12 | fn merge(&mut self, _prev: &Self) {} 13 | } 14 | 15 | pub trait HTTPModule { 16 | type MainConf: Merge + Default; 17 | type SrvConf: Merge + Default; 18 | type LocConf: Merge + Default; 19 | 20 | unsafe extern "C" fn preconfiguration(_cf: *mut ngx_conf_t) -> ngx_int_t { 21 | OK.into() 22 | } 23 | 24 | unsafe extern "C" fn postconfiguration(_cf: *mut ngx_conf_t) -> ngx_int_t { 25 | OK.into() 26 | } 27 | 28 | unsafe extern "C" fn create_main_conf(cf: *mut ngx_conf_t) -> *mut c_void { 29 | let mut pool = Pool::from_ngx_pool((*cf).pool); 30 | pool.allocate::(Default::default()) as *mut c_void 31 | } 32 | 33 | unsafe extern "C" fn init_main_conf(_cf: *mut ngx_conf_t, _conf: *mut c_void) -> *mut c_char { 34 | ptr::null_mut() 35 | } 36 | 37 | unsafe extern "C" fn create_srv_conf(cf: *mut ngx_conf_t) -> *mut c_void { 38 | let mut pool = Pool::from_ngx_pool((*cf).pool); 39 | pool.allocate::(Default::default()) as *mut c_void 40 | } 41 | 42 | unsafe extern "C" fn merge_srv_conf(_cf: *mut ngx_conf_t, prev: *mut c_void, conf: *mut c_void) -> *mut c_char { 43 | let prev = &mut *(prev as *mut Self::SrvConf); 44 | let conf = &mut *(conf as *mut Self::SrvConf); 45 | conf.merge(prev); 46 | ptr::null_mut() 47 | } 48 | 49 | unsafe extern "C" fn create_loc_conf(cf: *mut ngx_conf_t) -> *mut c_void { 50 | let mut pool = Pool::from_ngx_pool((*cf).pool); 51 | pool.allocate::(Default::default()) as *mut c_void 52 | } 53 | 54 | unsafe extern "C" fn merge_loc_conf(_cf: *mut ngx_conf_t, prev: *mut c_void, conf: *mut c_void) -> *mut c_char { 55 | let prev = &mut *(prev as *mut Self::LocConf); 56 | let conf = &mut *(conf as *mut Self::LocConf); 57 | conf.merge(prev); 58 | ptr::null_mut() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /nginx-rs/src/http/request.rs: -------------------------------------------------------------------------------- 1 | use crate::{bindings::*, ngx_null_string}; 2 | use crate::core::*; 3 | 4 | use crate::http::status::*; 5 | 6 | use std::os::raw::c_void; 7 | 8 | /// Define a static request handler. 9 | /// 10 | /// Handlers are expected to take a single [`Request`] argument and return a [`Status`]. 11 | #[macro_export] 12 | macro_rules! http_request_handler { 13 | ( $name: ident, $handler: expr ) => { 14 | #[no_mangle] 15 | extern "C" fn $name(r: *mut ngx_http_request_t) -> ngx_int_t { 16 | let status: Status = $handler(unsafe { &mut $crate::http::Request::from_ngx_http_request(r) }); 17 | status.0 18 | } 19 | }; 20 | } 21 | 22 | #[repr(transparent)] 23 | pub struct Request(ngx_http_request_t); 24 | 25 | impl Request { 26 | /// Create a [`Request`] from an [`ngx_http_request_t`]. 27 | /// 28 | /// [`ngx_http_request_t`]: https://nginx.org/en/docs/dev/development_guide.html#http_request 29 | pub unsafe fn from_ngx_http_request<'a>(r: *mut ngx_http_request_t) -> &'a mut Request { 30 | // SAFETY: The caller has provided a valid non-null pointer to a valid `ngx_http_request_t` 31 | // which shares the same representation as `Request`. 32 | &mut *r.cast::() 33 | } 34 | 35 | /// Is this the main request (as opposed to a subrequest)? 36 | pub fn is_main(&self) -> bool { 37 | let main = self.0.main.cast(); 38 | std::ptr::eq(self, main) 39 | } 40 | 41 | /// Request pool. 42 | pub fn pool(&self) -> Pool { 43 | // SAFETY: This request is allocated from `pool`, thus must be a valid pool. 44 | unsafe { 45 | Pool::from_ngx_pool(self.0.pool) 46 | } 47 | } 48 | 49 | /// Pointer to a [`ngx_connection_t`] client connection object. 50 | /// 51 | /// [`ngx_connection_t`]: https://nginx.org/en/docs/dev/development_guide.html#connection 52 | pub fn connection(&self) -> *mut ngx_connection_t { 53 | self.0.connection 54 | } 55 | 56 | /// Module location configuration. 57 | pub fn get_module_loc_conf(&self, module: &ngx_module_t) -> *mut c_void { 58 | unsafe { 59 | *self.0.loc_conf.add(module.ctx_index) 60 | } 61 | } 62 | 63 | /// Get the value of a [complex value]. 64 | /// 65 | /// [complex value]: https://nginx.org/en/docs/dev/development_guide.html#http_complex_values 66 | pub fn get_complex_value(&self, cv: &ngx_http_complex_value_t) -> Option<&NgxStr> { 67 | let r = (self as *const Request as *mut Request).cast(); 68 | let val = cv as *const ngx_http_complex_value_t as *mut ngx_http_complex_value_t; 69 | // SAFETY: `ngx_http_complex_value` does not mutate `r` or `val` and guarentees that 70 | // a valid Nginx string is stored in `value` if it successfully returns. 71 | unsafe { 72 | let mut value = ngx_null_string!(); 73 | if ngx_http_complex_value(r, val, &mut value) != NGX_OK as ngx_int_t { 74 | return None; 75 | } 76 | Some(NgxStr::from_ngx_str(value)) 77 | } 78 | } 79 | 80 | /// Discard (read and ignore) the [request body]. 81 | /// 82 | /// [request body]: https://nginx.org/en/docs/dev/development_guide.html#http_request_body 83 | pub fn discard_request_body(&mut self) -> Status 84 | { 85 | unsafe { 86 | Status(ngx_http_discard_request_body(&mut self.0)) 87 | } 88 | } 89 | 90 | /// Client HTTP [User-Agent]. 91 | /// 92 | /// [User-Agent]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent 93 | pub fn user_agent(&self) -> &NgxStr { 94 | unsafe { 95 | NgxStr::from_ngx_str((*self.0.headers_in.user_agent).value) 96 | } 97 | } 98 | 99 | /// Set HTTP status of response. 100 | pub fn set_status(&mut self, status: HTTPStatus) { 101 | self.0.headers_out.status = status.into(); 102 | } 103 | 104 | /// Set response body [Content-Length]. 105 | /// 106 | /// [Content-Length]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length 107 | pub fn set_content_length_n(&mut self, n: usize) { 108 | self.0.headers_out.content_length_n = n as off_t; 109 | } 110 | 111 | /// Send the output header. 112 | /// 113 | /// Do not call this function until all output headers are set. 114 | pub fn send_header(&mut self) -> Status { 115 | unsafe { 116 | Status(ngx_http_send_header(&mut self.0)) 117 | } 118 | } 119 | 120 | /// Flag indicating that the output does not require a body. 121 | /// 122 | /// For example, this flag is used by `HTTP HEAD` requests. 123 | pub fn header_only(&self) -> bool { 124 | self.0.header_only() != 0 125 | } 126 | 127 | /// Send the [response body]. 128 | /// 129 | /// This function can be called multiple times. 130 | /// Set the `last_buf` flag in the last body buffer. 131 | /// 132 | /// [response body]: https://nginx.org/en/docs/dev/development_guide.html#http_request_body 133 | pub fn output_filter(&mut self, body: &mut ngx_chain_t) -> Status { 134 | unsafe { 135 | Status(ngx_http_output_filter(&mut self.0, body)) 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /nginx-rs/src/http/status.rs: -------------------------------------------------------------------------------- 1 | use crate::bindings::*; 2 | use crate::core::Status; 3 | 4 | pub struct HTTPStatus(pub ngx_uint_t); 5 | 6 | impl Into for HTTPStatus { 7 | fn into(self) -> Status { 8 | Status(self.0 as ngx_int_t) 9 | } 10 | } 11 | 12 | impl Into for HTTPStatus { 13 | fn into(self) -> ngx_uint_t { 14 | self.0 15 | } 16 | } 17 | 18 | pub const HTTP_OK: HTTPStatus = HTTPStatus(NGX_HTTP_OK as ngx_uint_t); 19 | pub const HTTP_INTERNAL_SERVER_ERROR: HTTPStatus = HTTPStatus(NGX_HTTP_INTERNAL_SERVER_ERROR as ngx_uint_t); 20 | pub const HTTP_FORBIDDEN: HTTPStatus = HTTPStatus(NGX_HTTP_FORBIDDEN as ngx_uint_t); 21 | -------------------------------------------------------------------------------- /nginx-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod http; 2 | pub mod bindings; 3 | pub mod core; 4 | pub mod log; 5 | 6 | /// Define modules exported by this library. 7 | /// 8 | /// These are normally generated by the Nginx module system, but need to be 9 | /// defined when building modules outside of it. 10 | #[macro_export] 11 | macro_rules! ngx_modules { 12 | ($( $mod:ident ),+) => { 13 | #[no_mangle] 14 | pub static mut ngx_modules: [*const ngx_module_t; $crate::count!($( $mod, )+) + 1] = [ 15 | $( unsafe { &$mod } as *const ngx_module_t, )+ 16 | ptr::null() 17 | ]; 18 | 19 | #[no_mangle] 20 | pub static mut ngx_module_names: [*const c_char; $crate::count!($( $mod, )+) + 1] = [ 21 | $( concat!(stringify!($mod), "\0").as_ptr() as *const i8, )+ 22 | ptr::null() 23 | ]; 24 | 25 | #[no_mangle] 26 | pub static mut ngx_module_order: [*const c_char; 1] = [ 27 | ptr::null() 28 | ]; 29 | }; 30 | } 31 | 32 | /// Count number of arguments 33 | #[macro_export] 34 | macro_rules! count { 35 | () => { 0usize }; 36 | ($x:tt, $( $xs:tt ),*) => { 1usize + $crate::count!($( $xs, )*) }; 37 | } 38 | -------------------------------------------------------------------------------- /nginx-rs/src/log.rs: -------------------------------------------------------------------------------- 1 | /// Write to logger at a specified level. 2 | /// 3 | /// See [Logging](https://nginx.org/en/docs/dev/development_guide.html#logging) 4 | /// for available log levels. 5 | #[macro_export] 6 | macro_rules! ngx_log_debug { 7 | ( $level:expr, $log:expr, $($arg:tt)* ) => { 8 | let log_level = unsafe { (*$log).log_level }; 9 | if log_level & $level as usize != 0 { 10 | let level = $crate::bindings::NGX_LOG_DEBUG as $crate::bindings::ngx_uint_t; 11 | let fmt = ::std::ffi::CString::new("%s").unwrap(); 12 | let c_message = ::std::ffi::CString::new(format!($($arg)*)).unwrap(); 13 | unsafe { 14 | ngx_log_error_core(level, $log, 0, fmt.as_ptr(), c_message.as_ptr()); 15 | } 16 | } 17 | } 18 | } 19 | 20 | /// Log to request connection log at level [`NGX_LOG_DEBUG_HTTP`]. 21 | /// 22 | /// [`NGX_LOG_DEBUG_HTTP`]: https://nginx.org/en/docs/dev/development_guide.html#logging 23 | #[macro_export] 24 | macro_rules! ngx_log_debug_http { 25 | ( $request:expr, $($arg:tt)* ) => { 26 | let log = unsafe { (*$request.connection()).log }; 27 | $crate::ngx_log_debug!(NGX_LOG_DEBUG_HTTP, log, $($arg)*); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /nginx-rs/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Define as constants since bindgen can't parse these values 4 | const size_t NGX_RS_HTTP_LOC_CONF_OFFSET = NGX_HTTP_LOC_CONF_OFFSET; 5 | const char* NGX_RS_MODULE_SIGNATURE = NGX_MODULE_SIGNATURE; 6 | --------------------------------------------------------------------------------