Skip to content

Commit db33cf9

Browse files
committed
feat: async subrequest
Add asynchronous subrequest functionality: - Implement async subrequest handling - Add support for non-blocking subrequest operations - Enable concurrent subrequest processing - Integrate with async phase handler framework
1 parent 5daf27a commit db33cf9

8 files changed

Lines changed: 549 additions & 32 deletions

File tree

.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/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ autobins = false
1313
build = "../build.rs"
1414

1515
[dependencies]
16+
# allocator-api2 = { version = "0.4.0", default-features = false, features = ["fresh-rust"] }
17+
# async-task = { version = "4.7.1" }
18+
# futures = "0.3"
1619
nginx-sys = { path = "../nginx-sys/", default-features = false }
1720
ngx = { path = "../", default-features = false, features = ["std"] }
1821

@@ -51,6 +54,12 @@ name = "async"
5154
path = "async.rs"
5255
crate-type = ["cdylib"]
5356

57+
[[example]]
58+
name = "async_request"
59+
path = "async_request.rs"
60+
crate-type = ["cdylib"]
61+
required-features = ["async"]
62+
5463
[[example]]
5564
name = "shared_dict"
5665
path = "shared_dict.rs"
@@ -63,6 +72,7 @@ default = ["export-modules", "ngx/vendored"]
6372
# outside of the NGINX buildsystem. However, cargo currently does not detect
6473
# this configuration automatically.
6574
# See https://github.com/rust-lang/rust/issues/20267
75+
async = ["ngx/async"]
6676
export-modules = []
6777
linux = []
6878

examples/async_request.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
use ngx::http::{
2+
AsyncHandler, AsyncSubRequestBuilder, AsyncSubRequestError, HTTPStatus, HttpModule,
3+
HttpModuleLocationConf, HttpPhase, Merge, MergeConfigError, Request, add_phase_handler,
4+
};
5+
use ngx::{async_ as ngx_async, ngx_log_debug_http, ngx_log_error};
6+
7+
use nginx_sys::{
8+
NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET, ngx_command_t, ngx_conf_t,
9+
ngx_flag_t, ngx_http_complex_value_t, ngx_http_module_t, ngx_http_request_t,
10+
ngx_http_send_response, ngx_int_t, ngx_module_t, ngx_str_t, ngx_uint_t,
11+
};
12+
13+
const NGX_CONF_UNSET_FLAG: ngx_flag_t = nginx_sys::NGX_CONF_UNSET as _;
14+
15+
struct SampleAsyncHandler;
16+
17+
enum SampleAsyncHandlerError {
18+
SubrequestCreationFailed(AsyncSubRequestError),
19+
SubrequestFailed(ngx_int_t),
20+
}
21+
22+
impl core::fmt::Display for SampleAsyncHandlerError {
23+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::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+
47+
impl AsyncHandler for SampleAsyncHandler {
48+
const PHASE: HttpPhase = HttpPhase::Access;
49+
type Module = Module;
50+
type Output = Result<ngx_int_t, SampleAsyncHandlerError>;
51+
52+
async fn worker(request: &mut Request) -> Self::Output {
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 != 1 {
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+
let uri: &str = if co.uri.is_empty() {
65+
"/proxy"
66+
} else {
67+
co.uri.to_str().unwrap_or("/proxy")
68+
};
69+
70+
let fut = AsyncSubRequestBuilder::new(uri)
71+
.args("arg1=val1&arg2=val2")
72+
.in_memory()
73+
.waited()
74+
.build(request)?;
75+
76+
let (subrc, sr) = fut.await.map_err(SampleAsyncHandlerError::from)?;
77+
78+
ngx_log_error!(nginx_sys::NGX_LOG_INFO, log, "Subrequest rc {}", subrc);
79+
80+
if subrc != nginx_sys::NGX_OK as _ {
81+
return HTTPStatus::try_from(subrc)
82+
.map(Into::into)
83+
.map_err(|_| SampleAsyncHandlerError::from(subrc));
84+
}
85+
86+
ngx_log_error!(
87+
nginx_sys::NGX_LOG_INFO,
88+
log,
89+
"Subrequest status: {:?}",
90+
sr.get_status()
91+
);
92+
93+
ngx_async::sleep(core::time::Duration::from_secs(2)).await;
94+
95+
let mut resp_len: usize = 0;
96+
97+
let mut rc = nginx_sys::NGX_OK as ngx_int_t;
98+
99+
if let Some(out) = sr.get_out() {
100+
if !out.buf.is_null() {
101+
let b = unsafe { &*out.buf };
102+
resp_len = unsafe { b.last.offset_from(b.pos) } as usize;
103+
104+
let sr_ptr: *const ngx_http_request_t = sr.as_ref();
105+
106+
let mut ct: ngx_str_t = (unsafe { *sr_ptr }).headers_out.content_type;
107+
108+
let mut cv: ngx_http_complex_value_t = unsafe { core::mem::zeroed() };
109+
cv.value = ngx_str_t {
110+
len: resp_len as _,
111+
data: b.pos as _,
112+
};
113+
114+
rc = unsafe {
115+
ngx_http_send_response(request_ptr, sr.get_status().0, &raw mut ct, &raw mut cv)
116+
};
117+
118+
if rc == nginx_sys::NGX_OK as _ {
119+
rc = nginx_sys::NGX_HTTP_OK as _;
120+
}
121+
}
122+
}
123+
124+
ngx_log_error!(
125+
nginx_sys::NGX_LOG_INFO,
126+
log,
127+
"Async handler after timeout; subrequest response length: {}",
128+
resp_len
129+
);
130+
131+
Ok(rc)
132+
}
133+
}
134+
135+
static NGX_HTTP_ASYNC_REQUEST_MODULE_CTX: ngx_http_module_t = ngx_http_module_t {
136+
preconfiguration: None,
137+
postconfiguration: Some(Module::postconfiguration),
138+
create_main_conf: None,
139+
init_main_conf: None,
140+
create_srv_conf: None,
141+
merge_srv_conf: None,
142+
create_loc_conf: Some(Module::create_loc_conf),
143+
merge_loc_conf: Some(Module::merge_loc_conf),
144+
};
145+
146+
#[cfg(feature = "export-modules")]
147+
ngx::ngx_modules!(ngx_http_async_request_module);
148+
149+
#[used]
150+
#[allow(non_upper_case_globals)]
151+
#[cfg_attr(not(feature = "export-modules"), unsafe(no_mangle))]
152+
pub static mut ngx_http_async_request_module: ngx_module_t = ngx_module_t {
153+
ctx: &raw const NGX_HTTP_ASYNC_REQUEST_MODULE_CTX as _,
154+
commands: unsafe { &raw mut NGX_HTTP_ASYNC_REQUEST_COMMANDS[0] },
155+
type_: nginx_sys::NGX_HTTP_MODULE as _,
156+
..ngx_module_t::default()
157+
};
158+
159+
struct Module;
160+
161+
impl HttpModule for Module {
162+
fn module() -> &'static ngx_module_t {
163+
unsafe { &*::core::ptr::addr_of!(ngx_http_async_request_module) }
164+
}
165+
166+
unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t {
167+
// SAFETY: this function is called with non-NULL cf always
168+
let cf = unsafe { &mut *cf };
169+
add_phase_handler::<SampleAsyncHandler>(cf)
170+
.map_or(nginx_sys::NGX_ERROR as _, |_| nginx_sys::NGX_OK as _)
171+
}
172+
}
173+
174+
#[derive(Debug)]
175+
struct ModuleConfig {
176+
enable: ngx_flag_t,
177+
uri: ngx_str_t,
178+
}
179+
180+
impl Default for ModuleConfig {
181+
fn default() -> Self {
182+
Self {
183+
enable: NGX_CONF_UNSET_FLAG,
184+
uri: ngx_str_t::empty(),
185+
}
186+
}
187+
}
188+
189+
impl Merge for ModuleConfig {
190+
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
191+
if self.enable == NGX_CONF_UNSET_FLAG {
192+
if prev.enable != NGX_CONF_UNSET_FLAG {
193+
self.enable = prev.enable;
194+
} else {
195+
self.enable = 0;
196+
}
197+
}
198+
if self.uri.len == 0 {
199+
self.uri = prev.uri;
200+
}
201+
Ok(())
202+
}
203+
}
204+
205+
unsafe impl HttpModuleLocationConf for Module {
206+
type LocationConf = ModuleConfig;
207+
}
208+
209+
static mut NGX_HTTP_ASYNC_REQUEST_COMMANDS: [ngx_command_t; 3] = [
210+
ngx_command_t {
211+
name: ngx::ngx_string!("async_request"),
212+
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
213+
set: Some(nginx_sys::ngx_conf_set_flag_slot),
214+
conf: NGX_HTTP_LOC_CONF_OFFSET,
215+
offset: core::mem::offset_of!(ModuleConfig, enable),
216+
post: core::ptr::null_mut(),
217+
},
218+
ngx_command_t {
219+
name: ngx::ngx_string!("async_uri"),
220+
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
221+
set: Some(nginx_sys::ngx_conf_set_str_slot),
222+
conf: NGX_HTTP_LOC_CONF_OFFSET,
223+
offset: core::mem::offset_of!(ModuleConfig, uri),
224+
post: core::ptr::null_mut(),
225+
},
226+
ngx_command_t::empty(),
227+
];

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)