|
| 1 | +use core::fmt::Display; |
| 2 | + |
| 3 | +use ngx::core::Status; |
| 4 | +use ngx::http::subrequest::{SubRequestBuilder, SubRequestError}; |
| 5 | +use ngx::http::{ |
| 6 | + HTTPStatus, HttpModule, HttpModuleLocationConf, HttpPhase, HttpRequestHandler, Merge, |
| 7 | + MergeConfigError, Request, RequestContext, add_phase_handler, |
| 8 | +}; |
| 9 | +use ngx::{ngx_log_debug_http, ngx_log_error}; |
| 10 | + |
| 11 | +use nginx_sys::{ |
| 12 | + NGX_CONF_TAKE1, NGX_ERROR, NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET, ngx_command_t, |
| 13 | + ngx_conf_t, ngx_flag_t, ngx_http_complex_value_t, ngx_http_module_t, ngx_http_request_t, |
| 14 | + ngx_http_send_response, ngx_int_t, ngx_module_t, ngx_str_t, ngx_uint_t, |
| 15 | +}; |
| 16 | + |
| 17 | +const NGX_CONF_UNSET_FLAG: ngx_flag_t = nginx_sys::NGX_CONF_UNSET as _; |
| 18 | + |
| 19 | +struct SampleHandler; |
| 20 | + |
| 21 | +enum SampleHandlerError { |
| 22 | + ContextAllocation, |
| 23 | + SubRequestCreation(SubRequestError), |
| 24 | + SubRequest(ngx_int_t), |
| 25 | + Response(ngx_int_t), |
| 26 | +} |
| 27 | + |
| 28 | +impl Display for SampleHandlerError { |
| 29 | + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 30 | + match self { |
| 31 | + SampleHandlerError::ContextAllocation => { |
| 32 | + write!(f, "context allocation failed") |
| 33 | + } |
| 34 | + SampleHandlerError::SubRequestCreation(e) => { |
| 35 | + write!(f, "subrequest creation failed: {}", e) |
| 36 | + } |
| 37 | + SampleHandlerError::SubRequest(rc) => { |
| 38 | + write!(f, "subrequest failed with return code: {}", rc) |
| 39 | + } |
| 40 | + SampleHandlerError::Response(rc) => { |
| 41 | + write!(f, "response creation failed with return code: {}", rc) |
| 42 | + } |
| 43 | + } |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +impl HttpRequestHandler for SampleHandler { |
| 48 | + const PHASE: HttpPhase = HttpPhase::Access; |
| 49 | + type Output = Result<Status, SampleHandlerError>; |
| 50 | + |
| 51 | + fn handler(request: &mut Request) -> Self::Output { |
| 52 | + let co = Module::location_conf(request).expect("module config is none"); |
| 53 | + ngx_log_debug_http!(request, "subrequest module enabled: {}", co.enable); |
| 54 | + |
| 55 | + if co.enable != 1 { |
| 56 | + return Ok(Status::NGX_DECLINED); |
| 57 | + } |
| 58 | + |
| 59 | + let rptr: *mut ngx_http_request_t = request.as_mut(); |
| 60 | + |
| 61 | + match SRCtx::get(request) { |
| 62 | + Some(ctx) => ctx.rc.map_or( |
| 63 | + // `ctx` has been created but not filled yet - subrequest is still in progress |
| 64 | + Ok(Status::NGX_AGAIN), |
| 65 | + // `ctx` has been created and filled - subrequest is completed |
| 66 | + |rc| { |
| 67 | + let status = ctx.status.0; |
| 68 | + let msg = format!("subrequest completed with HTTP status: {status}, rc: {rc}"); |
| 69 | + ngx_log_debug_http!(request, "{msg}"); |
| 70 | + |
| 71 | + if ctx.status.0 >= nginx_sys::NGX_HTTP_SPECIAL_RESPONSE as _ { |
| 72 | + Ok(Status::from(ctx.status)) |
| 73 | + } else if rc == nginx_sys::NGX_OK as _ { |
| 74 | + let mut cv: ngx_http_complex_value_t = unsafe { core::mem::zeroed() }; |
| 75 | + cv.value = ngx_str_t { |
| 76 | + len: msg.len() as _, |
| 77 | + data: msg.as_ptr() as _, |
| 78 | + }; |
| 79 | + let resp_rc = unsafe { |
| 80 | + ngx_http_send_response(rptr, status, core::ptr::null_mut(), &raw mut cv) |
| 81 | + }; |
| 82 | + if resp_rc == nginx_sys::NGX_OK as _ { |
| 83 | + Ok(Status::from(ctx.status)) |
| 84 | + } else { |
| 85 | + Err(SampleHandlerError::Response(resp_rc)) |
| 86 | + } |
| 87 | + } else { |
| 88 | + Err(SampleHandlerError::SubRequest(rc)) |
| 89 | + } |
| 90 | + }, |
| 91 | + ), |
| 92 | + None => { |
| 93 | + if SRCtx::create(request, SRCtx::default).is_some() { |
| 94 | + let uri: &str = if co.uri.is_empty() { |
| 95 | + "/proxy" |
| 96 | + } else { |
| 97 | + co.uri.to_str().unwrap_or("/proxy") |
| 98 | + }; |
| 99 | + |
| 100 | + SubRequestBuilder::new(uri) |
| 101 | + .args("arg1=val1&arg2=val2") |
| 102 | + .in_memory() |
| 103 | + .waited() |
| 104 | + .build(request, sr_handler) |
| 105 | + .map_err(SampleHandlerError::SubRequestCreation)?; |
| 106 | + |
| 107 | + Ok(Status::NGX_AGAIN) |
| 108 | + } else { |
| 109 | + Err(SampleHandlerError::ContextAllocation) |
| 110 | + } |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +struct SRCtx { |
| 117 | + rc: Option<ngx_int_t>, |
| 118 | + status: HTTPStatus, |
| 119 | +} |
| 120 | + |
| 121 | +impl Default for SRCtx { |
| 122 | + fn default() -> Self { |
| 123 | + Self { |
| 124 | + rc: None, |
| 125 | + status: HTTPStatus(NGX_ERROR as _), |
| 126 | + } |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +impl RequestContext<Module> for SRCtx {} |
| 131 | + |
| 132 | +fn sr_handler(r: &mut Request, mut rc: ngx_int_t) -> ngx_int_t { |
| 133 | + let status = r.get_status(); |
| 134 | + if let Some(ctx) = SRCtx::get_mut(r.get_main_mut()) { |
| 135 | + ctx.rc = Some(rc); |
| 136 | + ctx.status = status; |
| 137 | + } else { |
| 138 | + ngx_log_error!( |
| 139 | + nginx_sys::NGX_LOG_ERR, |
| 140 | + r.log(), |
| 141 | + "subrequest: context not found" |
| 142 | + ); |
| 143 | + rc = NGX_ERROR as _; |
| 144 | + } |
| 145 | + rc |
| 146 | +} |
| 147 | + |
| 148 | +static NGX_HTTP_SUBREQUEST_MODULE_CTX: ngx_http_module_t = ngx_http_module_t { |
| 149 | + preconfiguration: None, |
| 150 | + postconfiguration: Some(Module::postconfiguration), |
| 151 | + create_main_conf: None, |
| 152 | + init_main_conf: None, |
| 153 | + create_srv_conf: None, |
| 154 | + merge_srv_conf: None, |
| 155 | + create_loc_conf: Some(Module::create_loc_conf), |
| 156 | + merge_loc_conf: Some(Module::merge_loc_conf), |
| 157 | +}; |
| 158 | + |
| 159 | +#[cfg(feature = "export-modules")] |
| 160 | +ngx::ngx_modules!(ngx_http_subrequest_module); |
| 161 | + |
| 162 | +#[used] |
| 163 | +#[allow(non_upper_case_globals)] |
| 164 | +#[cfg_attr(not(feature = "export-modules"), unsafe(no_mangle))] |
| 165 | +pub static mut ngx_http_subrequest_module: ngx_module_t = ngx_module_t { |
| 166 | + ctx: &raw const NGX_HTTP_SUBREQUEST_MODULE_CTX as _, |
| 167 | + commands: unsafe { &raw mut NGX_HTTP_SUBREQUEST_COMMANDS[0] }, |
| 168 | + type_: nginx_sys::NGX_HTTP_MODULE as _, |
| 169 | + ..ngx_module_t::default() |
| 170 | +}; |
| 171 | + |
| 172 | +struct Module; |
| 173 | + |
| 174 | +impl HttpModule for Module { |
| 175 | + fn module() -> &'static ngx_module_t { |
| 176 | + unsafe { &*::core::ptr::addr_of!(ngx_http_subrequest_module) } |
| 177 | + } |
| 178 | + |
| 179 | + unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t { |
| 180 | + // SAFETY: this function is called with non-NULL cf always |
| 181 | + let cf = unsafe { &mut *cf }; |
| 182 | + add_phase_handler::<SampleHandler>(cf) |
| 183 | + .map_or(nginx_sys::NGX_ERROR as _, |_| nginx_sys::NGX_OK as _) |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +#[derive(Debug)] |
| 188 | +struct ModuleConfig { |
| 189 | + enable: ngx_flag_t, |
| 190 | + uri: ngx_str_t, |
| 191 | +} |
| 192 | + |
| 193 | +impl Default for ModuleConfig { |
| 194 | + fn default() -> Self { |
| 195 | + Self { |
| 196 | + enable: NGX_CONF_UNSET_FLAG, |
| 197 | + uri: ngx_str_t::empty(), |
| 198 | + } |
| 199 | + } |
| 200 | +} |
| 201 | + |
| 202 | +impl Merge for ModuleConfig { |
| 203 | + fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> { |
| 204 | + if self.enable == NGX_CONF_UNSET_FLAG { |
| 205 | + if prev.enable != NGX_CONF_UNSET_FLAG { |
| 206 | + self.enable = prev.enable; |
| 207 | + } else { |
| 208 | + self.enable = 0; |
| 209 | + } |
| 210 | + } |
| 211 | + if self.uri.len == 0 { |
| 212 | + self.uri = prev.uri; |
| 213 | + } |
| 214 | + Ok(()) |
| 215 | + } |
| 216 | +} |
| 217 | + |
| 218 | +unsafe impl HttpModuleLocationConf for Module { |
| 219 | + type LocationConf = ModuleConfig; |
| 220 | +} |
| 221 | + |
| 222 | +static mut NGX_HTTP_SUBREQUEST_COMMANDS: [ngx_command_t; 3] = [ |
| 223 | + ngx_command_t { |
| 224 | + name: ngx::ngx_string!("subrequest"), |
| 225 | + type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t, |
| 226 | + set: Some(nginx_sys::ngx_conf_set_flag_slot), |
| 227 | + conf: NGX_HTTP_LOC_CONF_OFFSET, |
| 228 | + offset: core::mem::offset_of!(ModuleConfig, enable), |
| 229 | + post: core::ptr::null_mut(), |
| 230 | + }, |
| 231 | + ngx_command_t { |
| 232 | + name: ngx::ngx_string!("subrequest_uri"), |
| 233 | + type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t, |
| 234 | + set: Some(nginx_sys::ngx_conf_set_str_slot), |
| 235 | + conf: NGX_HTTP_LOC_CONF_OFFSET, |
| 236 | + offset: core::mem::offset_of!(ModuleConfig, uri), |
| 237 | + post: core::ptr::null_mut(), |
| 238 | + }, |
| 239 | + ngx_command_t::empty(), |
| 240 | +]; |
0 commit comments