|
| 1 | +use std::ffi::{c_char, c_void}; |
| 2 | + |
1 | 3 | use ngx::http::{ |
2 | | - add_phase_handler, AsyncHandler, HttpModule, HttpPhase, Request, |
| 4 | + add_phase_handler, AsyncHandler, AsyncSubRequestBuilder, AsyncSubRequestError, HttpModule, |
| 5 | + HttpModuleLocationConf, HttpPhase, Merge, MergeConfigError, Request, |
3 | 6 | }; |
4 | | -use ngx::{async_ as ngx_async, ngx_log_debug_http, ngx_log_error}; |
| 7 | +use ngx::{async_ as ngx_async, ngx_conf_log_error, ngx_log_debug_http, ngx_log_error}; |
5 | 8 |
|
6 | | -use nginx_sys::ngx_int_t; |
| 9 | +use nginx_sys::{ |
| 10 | + ngx_command_t, ngx_conf_t, ngx_http_complex_value_t, ngx_http_module_t, ngx_http_request_t, |
| 11 | + ngx_http_send_response, ngx_int_t, ngx_module_t, ngx_str_t, ngx_uint_t, NGX_CONF_TAKE1, |
| 12 | + NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET, |
| 13 | +}; |
7 | 14 |
|
8 | 15 | struct SampleAsyncHandler; |
9 | 16 |
|
| 17 | +enum SampleAsyncHandlerError { |
| 18 | + SubrequestCreationFailed(AsyncSubRequestError), |
| 19 | + SubrequestFailed(ngx_int_t), |
| 20 | +} |
| 21 | + |
| 22 | +impl std::fmt::Display for SampleAsyncHandlerError { |
| 23 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 24 | + match self { |
| 25 | + SampleAsyncHandlerError::SubrequestCreationFailed(e) => { |
| 26 | + write!(f, "Subrequest creation failed: {}", e) |
| 27 | + } |
| 28 | + SampleAsyncHandlerError::SubrequestFailed(rc) => { |
| 29 | + write!(f, "Subrequest failed with return code: {}", rc) |
| 30 | + } |
| 31 | + } |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +impl From<AsyncSubRequestError> for SampleAsyncHandlerError { |
| 36 | + fn from(err: AsyncSubRequestError) -> Self { |
| 37 | + SampleAsyncHandlerError::SubrequestCreationFailed(err) |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +impl From<ngx_int_t> for SampleAsyncHandlerError { |
| 42 | + fn from(rc: ngx_int_t) -> Self { |
| 43 | + SampleAsyncHandlerError::SubrequestFailed(rc) |
| 44 | + } |
| 45 | +} |
| 46 | + |
10 | 47 | impl AsyncHandler for SampleAsyncHandler { |
11 | 48 | const PHASE: HttpPhase = HttpPhase::Access; |
12 | 49 | type Module = Module; |
13 | | - type ReturnType = ngx_int_t; |
| 50 | + type ReturnType = Result<ngx_int_t, SampleAsyncHandlerError>; |
14 | 51 |
|
15 | 52 | async fn worker(request: &mut Request) -> Self::ReturnType { |
16 | 53 | ngx_log_debug_http!(request, "worker started"); |
| 54 | + |
| 55 | + let co = Module::location_conf(request).expect("module config is none"); |
| 56 | + ngx_log_debug_http!(request, "async_request module enabled: {}", co.enable); |
| 57 | + |
| 58 | + if !co.enable { |
| 59 | + return Ok(nginx_sys::NGX_DECLINED as _); |
| 60 | + } |
| 61 | + |
| 62 | + let log = request.log(); |
| 63 | + let request_ptr: *mut ngx_http_request_t = request.as_mut(); |
| 64 | + |
| 65 | + let fut = AsyncSubRequestBuilder::new("/proxy") |
| 66 | + //.args("arg1=val1&arg2=val2") |
| 67 | + .in_memory() |
| 68 | + .waited() |
| 69 | + .build(request)?; |
| 70 | + |
| 71 | + let subrc = fut.await; |
| 72 | + |
| 73 | + ngx_log_error!(nginx_sys::NGX_LOG_INFO, log, "Subrequest rc {}", subrc.0); |
| 74 | + |
| 75 | + if subrc.0 != nginx_sys::NGX_OK as _ || subrc.1.is_none() { |
| 76 | + return Err(SampleAsyncHandlerError::from(subrc.0)); |
| 77 | + } |
| 78 | + |
| 79 | + let sr = subrc.1.unwrap(); |
| 80 | + |
| 81 | + ngx_log_error!( |
| 82 | + nginx_sys::NGX_LOG_INFO, |
| 83 | + log, |
| 84 | + "Subrequest status: {:?}", |
| 85 | + sr.get_status() |
| 86 | + ); |
| 87 | + |
17 | 88 | ngx_async::sleep(core::time::Duration::from_secs(2)).await; |
| 89 | + |
| 90 | + let mut resp_len: usize = 0; |
| 91 | + |
| 92 | + let mut rc = nginx_sys::NGX_OK as ngx_int_t; |
| 93 | + |
| 94 | + if let Some(out) = sr.get_out() { |
| 95 | + if !out.buf.is_null() { |
| 96 | + let b = unsafe { &*out.buf }; |
| 97 | + resp_len = unsafe { b.last.offset_from(b.pos) } as usize; |
| 98 | + |
| 99 | + let sr_ptr: *const ngx_http_request_t = sr.as_ref(); |
| 100 | + |
| 101 | + let mut ct: ngx_str_t = (unsafe { *sr_ptr }).headers_out.content_type; |
| 102 | + |
| 103 | + let mut cv: ngx_http_complex_value_t = unsafe { core::mem::zeroed() }; |
| 104 | + cv.value = ngx_str_t { |
| 105 | + len: resp_len as _, |
| 106 | + data: b.pos as _, |
| 107 | + }; |
| 108 | + |
| 109 | + rc = unsafe { |
| 110 | + ngx_http_send_response(request_ptr, sr.get_status().0, &mut ct, &mut cv) |
| 111 | + }; |
| 112 | + |
| 113 | + if rc == nginx_sys::NGX_OK as _ { |
| 114 | + rc = nginx_sys::NGX_HTTP_OK as _; |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + |
18 | 119 | ngx_log_error!( |
19 | 120 | nginx_sys::NGX_LOG_INFO, |
20 | | - request.log(), |
21 | | - "Async handler after timeout", |
| 121 | + log, |
| 122 | + "Async handler after timeout; subrequest response length: {}", |
| 123 | + resp_len |
22 | 124 | ); |
23 | | - nginx_sys::NGX_OK as _ |
| 125 | + |
| 126 | + Ok(rc) |
24 | 127 | } |
25 | 128 | } |
26 | 129 |
|
27 | | -static NGX_HTTP_ASYNC_REQUEST_MODULE_CTX: nginx_sys::ngx_http_module_t = |
28 | | - nginx_sys::ngx_http_module_t { |
29 | | - preconfiguration: None, |
30 | | - postconfiguration: Some(Module::postconfiguration), |
31 | | - create_main_conf: None, |
32 | | - init_main_conf: None, |
33 | | - create_srv_conf: None, |
34 | | - merge_srv_conf: None, |
35 | | - create_loc_conf: None, |
36 | | - merge_loc_conf: None, |
37 | | - }; |
| 130 | +static NGX_HTTP_ASYNC_REQUEST_MODULE_CTX: ngx_http_module_t = ngx_http_module_t { |
| 131 | + preconfiguration: None, |
| 132 | + postconfiguration: Some(Module::postconfiguration), |
| 133 | + create_main_conf: None, |
| 134 | + init_main_conf: None, |
| 135 | + create_srv_conf: None, |
| 136 | + merge_srv_conf: None, |
| 137 | + create_loc_conf: Some(Module::create_loc_conf), |
| 138 | + merge_loc_conf: Some(Module::merge_loc_conf), |
| 139 | +}; |
38 | 140 |
|
39 | 141 | #[cfg(feature = "export-modules")] |
40 | 142 | ngx::ngx_modules!(ngx_http_async_request_module); |
41 | 143 |
|
42 | 144 | #[used] |
43 | 145 | #[allow(non_upper_case_globals)] |
44 | 146 | #[cfg_attr(not(feature = "export-modules"), no_mangle)] |
45 | | -pub static mut ngx_http_async_request_module: nginx_sys::ngx_module_t = nginx_sys::ngx_module_t { |
| 147 | +pub static mut ngx_http_async_request_module: ngx_module_t = ngx_module_t { |
46 | 148 | ctx: core::ptr::addr_of!(NGX_HTTP_ASYNC_REQUEST_MODULE_CTX) as _, |
| 149 | + commands: unsafe { &NGX_HTTP_ASYNC_REQUEST_COMMANDS[0] as *const _ as *mut _ }, |
47 | 150 | type_: nginx_sys::NGX_HTTP_MODULE as _, |
48 | | - ..nginx_sys::ngx_module_t::default() |
| 151 | + ..ngx_module_t::default() |
49 | 152 | }; |
50 | 153 |
|
51 | 154 | struct Module; |
52 | 155 |
|
53 | 156 | impl HttpModule for Module { |
54 | | - fn module() -> &'static nginx_sys::ngx_module_t { |
| 157 | + fn module() -> &'static ngx_module_t { |
55 | 158 | unsafe { &*::core::ptr::addr_of!(ngx_http_async_request_module) } |
56 | 159 | } |
57 | 160 |
|
58 | | - unsafe extern "C" fn postconfiguration(cf: *mut nginx_sys::ngx_conf_t) -> ngx_int_t { |
| 161 | + unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t { |
59 | 162 | // SAFETY: this function is called with non-NULL cf always |
60 | 163 | let cf = unsafe { &mut *cf }; |
61 | 164 | add_phase_handler::<SampleAsyncHandler>(cf) |
62 | 165 | .map_or(nginx_sys::NGX_ERROR as _, |_| nginx_sys::NGX_OK as _) |
63 | 166 | } |
64 | 167 | } |
| 168 | + |
| 169 | +#[derive(Debug, Default)] |
| 170 | +struct ModuleConfig { |
| 171 | + enable: bool, |
| 172 | +} |
| 173 | + |
| 174 | +unsafe impl HttpModuleLocationConf for Module { |
| 175 | + type LocationConf = ModuleConfig; |
| 176 | +} |
| 177 | + |
| 178 | +impl Merge for ModuleConfig { |
| 179 | + fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> { |
| 180 | + if prev.enable { |
| 181 | + self.enable = true; |
| 182 | + }; |
| 183 | + Ok(()) |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +static mut NGX_HTTP_ASYNC_REQUEST_COMMANDS: [ngx_command_t; 2] = [ |
| 188 | + ngx_command_t { |
| 189 | + name: ngx::ngx_string!("async_request"), |
| 190 | + type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t, |
| 191 | + set: Some(ngx_http_async_request_commands_set_enable), |
| 192 | + conf: NGX_HTTP_LOC_CONF_OFFSET, |
| 193 | + offset: 0, |
| 194 | + post: std::ptr::null_mut(), |
| 195 | + }, |
| 196 | + ngx_command_t::empty(), |
| 197 | +]; |
| 198 | + |
| 199 | +extern "C" fn ngx_http_async_request_commands_set_enable( |
| 200 | + cf: *mut ngx_conf_t, |
| 201 | + _cmd: *mut ngx_command_t, |
| 202 | + conf: *mut c_void, |
| 203 | +) -> *mut c_char { |
| 204 | + unsafe { |
| 205 | + let conf = &mut *(conf as *mut ModuleConfig); |
| 206 | + let args: &[ngx_str_t] = (*(*cf).args).as_slice(); |
| 207 | + |
| 208 | + conf.enable = match args[1].to_str() { |
| 209 | + Err(_) => false, |
| 210 | + Ok(s) if s.len() == 2 && s.eq_ignore_ascii_case("on") => true, |
| 211 | + Ok(s) if s.len() == 3 && s.eq_ignore_ascii_case("off") => false, |
| 212 | + _ => { |
| 213 | + ngx_conf_log_error!( |
| 214 | + nginx_sys::NGX_LOG_EMERG, |
| 215 | + cf, |
| 216 | + "`async_request` argument must be 'on' or 'off'" |
| 217 | + ); |
| 218 | + return ngx::core::NGX_CONF_ERROR; |
| 219 | + } |
| 220 | + }; |
| 221 | + } |
| 222 | + |
| 223 | + ngx::core::NGX_CONF_OK |
| 224 | +} |
0 commit comments