Skip to content

Commit 315d8e3

Browse files
committed
feat: async subrequest
1 parent a9f98c8 commit 315d8e3

File tree

5 files changed

+454
-24
lines changed

5 files changed

+454
-24
lines changed

.github/workflows/nginx.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ env:
5050
NGX_TEST_FILES: examples/t
5151
NGX_TEST_GLOBALS_DYNAMIC: >-
5252
load_module ${{ github.workspace }}/nginx/objs/ngx_http_async_module.so;
53+
load_module ${{ github.workspace }}/nginx/objs/ngx_http_async_request_module.so;
5354
load_module ${{ github.workspace }}/nginx/objs/ngx_http_awssigv4_module.so;
5455
load_module ${{ github.workspace }}/nginx/objs/ngx_http_curl_module.so;
5556
load_module ${{ github.workspace }}/nginx/objs/ngx_http_shared_dict_module.so;

examples/async_request.rs

Lines changed: 182 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,224 @@
1+
use std::ffi::{c_char, c_void};
2+
13
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,
36
};
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};
58

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+
};
714

815
struct SampleAsyncHandler;
916

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+
1047
impl AsyncHandler for SampleAsyncHandler {
1148
const PHASE: HttpPhase = HttpPhase::Access;
1249
type Module = Module;
13-
type ReturnType = ngx_int_t;
50+
type ReturnType = Result<ngx_int_t, SampleAsyncHandlerError>;
1451

1552
async fn worker(request: &mut Request) -> Self::ReturnType {
1653
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+
1788
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+
18119
ngx_log_error!(
19120
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
22124
);
23-
nginx_sys::NGX_OK as _
125+
126+
Ok(rc)
24127
}
25128
}
26129

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+
};
38140

39141
#[cfg(feature = "export-modules")]
40142
ngx::ngx_modules!(ngx_http_async_request_module);
41143

42144
#[used]
43145
#[allow(non_upper_case_globals)]
44146
#[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 {
46148
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 _ },
47150
type_: nginx_sys::NGX_HTTP_MODULE as _,
48-
..nginx_sys::ngx_module_t::default()
151+
..ngx_module_t::default()
49152
};
50153

51154
struct Module;
52155

53156
impl HttpModule for Module {
54-
fn module() -> &'static nginx_sys::ngx_module_t {
157+
fn module() -> &'static ngx_module_t {
55158
unsafe { &*::core::ptr::addr_of!(ngx_http_async_request_module) }
56159
}
57160

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 {
59162
// SAFETY: this function is called with non-NULL cf always
60163
let cf = unsafe { &mut *cf };
61164
add_phase_handler::<SampleAsyncHandler>(cf)
62165
.map_or(nginx_sys::NGX_ERROR as _, |_| nginx_sys::NGX_OK as _)
63166
}
64167
}
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+
}

examples/config

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ if [ $HTTP = YES ]; then
2323
ngx_rust_module
2424
fi
2525

26+
if :; then
27+
ngx_module_name=ngx_http_async_request_module
28+
ngx_module_libs="-lm"
29+
ngx_rust_target_name=async_request
30+
ngx_rust_target_features=async
31+
32+
ngx_rust_module
33+
34+
ngx_rust_target_features=
35+
fi
36+
2637
if :; then
2738
ngx_module_name=ngx_http_awssigv4_module
2839
ngx_module_libs="-lm"

examples/t/async_request.t

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/perl
2+
3+
# (C) Nginx, Inc
4+
5+
# Tests for ngx-rust example modules.
6+
7+
###############################################################################
8+
9+
use warnings;
10+
use strict;
11+
12+
use Test::More;
13+
14+
BEGIN { use FindBin; chdir($FindBin::Bin); }
15+
16+
use lib 'lib';
17+
use Test::Nginx;
18+
19+
###############################################################################
20+
21+
select STDERR; $| = 1;
22+
select STDOUT; $| = 1;
23+
24+
my $t = Test::Nginx->new()->has(qw/http/)->plan(1)
25+
->write_file_expand('nginx.conf', <<"EOF");
26+
27+
%%TEST_GLOBALS%%
28+
29+
daemon off;
30+
31+
events {
32+
}
33+
34+
http {
35+
%%TEST_GLOBALS_HTTP%%
36+
37+
server {
38+
listen 127.0.0.1:8080;
39+
server_name localhost;
40+
41+
location / {
42+
async_request on;
43+
}
44+
45+
location /proxy {
46+
internal;
47+
proxy_pass http://127.0.0.1:8081;
48+
}
49+
}
50+
51+
server {
52+
listen 127.0.0.1:8081;
53+
server_name localhost;
54+
55+
location / {
56+
return 200 'Hello from backend';
57+
}
58+
}
59+
}
60+
61+
EOF
62+
63+
$t->write_file('index.html', '');
64+
$t->run();
65+
66+
like(http_get('/'), qr/200 OK.*Hello from backend/s, 'async subrequest works');

0 commit comments

Comments
 (0)