Skip to content

Commit 0eb092f

Browse files
committed
feat(examples): add example using ngx::async_
1 parent 4043c1f commit 0eb092f

File tree

6 files changed

+346
-0
lines changed

6 files changed

+346
-0
lines changed

.github/workflows/nginx.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ env:
4949
5050
NGX_TEST_FILES: examples/t
5151
NGX_TEST_GLOBALS_DYNAMIC: >-
52+
load_module ${{ github.workspace }}/nginx/objs/ngx_http_async_module.so;
5253
load_module ${{ github.workspace }}/nginx/objs/ngx_http_tokio_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;

examples/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ idna_adapter = "=1.1.0"
2525
libc = "0.2.140"
2626
tokio = { version = "1.33.0", features = ["full"] }
2727

28+
29+
[[example]]
30+
name = "async"
31+
path = "async.rs"
32+
crate-type = ["cdylib"]
33+
required-features = [ "async" ]
34+
2835
[[example]]
2936
name = "curl"
3037
path = "curl.rs"
@@ -65,3 +72,4 @@ default = ["export-modules", "ngx/vendored"]
6572
# See https://github.com/rust-lang/rust/issues/20267
6673
export-modules = []
6774
linux = []
75+
async = [ "ngx/async" ]

examples/async.conf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
daemon off;
2+
master_process off;
3+
# worker_processes 1;
4+
5+
load_module modules/libasync.so;
6+
error_log error.log debug;
7+
8+
events { }
9+
10+
http {
11+
server {
12+
listen *:8000;
13+
server_name localhost;
14+
location / {
15+
root html;
16+
index index.html index.htm;
17+
async on;
18+
}
19+
error_page 500 502 503 504 /50x.html;
20+
location = /50x.html {
21+
root html;
22+
}
23+
}
24+
}

examples/async.rs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
use std::ffi::{c_char, c_void};
2+
use std::time::Instant;
3+
4+
use ngx::async_::{sleep, spawn, Task};
5+
use ngx::core;
6+
use ngx::ffi::{
7+
ngx_array_push, ngx_buf_t, ngx_chain_t, ngx_command_t, ngx_conf_t, ngx_http_finalize_request,
8+
ngx_http_handler_pt, ngx_http_module_t, ngx_http_phases_NGX_HTTP_ACCESS_PHASE,
9+
ngx_http_read_client_request_body, ngx_http_request_t, ngx_int_t, ngx_module_t, ngx_str_t,
10+
ngx_uint_t, NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET, NGX_HTTP_MODULE,
11+
NGX_HTTP_SPECIAL_RESPONSE, NGX_LOG_EMERG,
12+
};
13+
use ngx::http::{self, HttpModule, MergeConfigError};
14+
use ngx::http::{HttpModuleLocationConf, HttpModuleMainConf, NgxHttpCoreModule};
15+
use ngx::{http_request_handler, ngx_conf_log_error, ngx_log_debug_http, ngx_string};
16+
17+
struct Module;
18+
19+
impl http::HttpModule for Module {
20+
fn module() -> &'static ngx_module_t {
21+
unsafe { &*std::ptr::addr_of!(ngx_http_async_module) }
22+
}
23+
24+
unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t {
25+
// SAFETY: this function is called with non-NULL cf always
26+
let cf = &mut *cf;
27+
let cmcf = NgxHttpCoreModule::main_conf_mut(cf).expect("http core main conf");
28+
29+
let h = ngx_array_push(
30+
&mut cmcf.phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers,
31+
) as *mut ngx_http_handler_pt;
32+
if h.is_null() {
33+
return core::Status::NGX_ERROR.into();
34+
}
35+
// set an Access phase handler
36+
*h = Some(async_access_handler);
37+
core::Status::NGX_OK.into()
38+
}
39+
}
40+
41+
#[derive(Debug, Default)]
42+
struct ModuleConfig {
43+
enable: bool,
44+
}
45+
46+
unsafe impl HttpModuleLocationConf for Module {
47+
type LocationConf = ModuleConfig;
48+
}
49+
50+
static mut NGX_HTTP_ASYNC_COMMANDS: [ngx_command_t; 2] = [
51+
ngx_command_t {
52+
name: ngx_string!("async"),
53+
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
54+
set: Some(ngx_http_async_commands_set_enable),
55+
conf: NGX_HTTP_LOC_CONF_OFFSET,
56+
offset: 0,
57+
post: std::ptr::null_mut(),
58+
},
59+
ngx_command_t::empty(),
60+
];
61+
62+
static NGX_HTTP_ASYNC_MODULE_CTX: ngx_http_module_t = ngx_http_module_t {
63+
preconfiguration: Some(Module::preconfiguration),
64+
postconfiguration: Some(Module::postconfiguration),
65+
create_main_conf: None,
66+
init_main_conf: None,
67+
create_srv_conf: None,
68+
merge_srv_conf: None,
69+
create_loc_conf: Some(Module::create_loc_conf),
70+
merge_loc_conf: Some(Module::merge_loc_conf),
71+
};
72+
73+
// Generate the `ngx_modules` table with exported modules.
74+
// This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem.
75+
#[cfg(feature = "export-modules")]
76+
ngx::ngx_modules!(ngx_http_async_module);
77+
78+
#[used]
79+
#[allow(non_upper_case_globals)]
80+
#[cfg_attr(not(feature = "export-modules"), no_mangle)]
81+
pub static mut ngx_http_async_module: ngx_module_t = ngx_module_t {
82+
ctx: std::ptr::addr_of!(NGX_HTTP_ASYNC_MODULE_CTX) as _,
83+
commands: unsafe { &NGX_HTTP_ASYNC_COMMANDS[0] as *const _ as *mut _ },
84+
type_: NGX_HTTP_MODULE as _,
85+
..ngx_module_t::default()
86+
};
87+
88+
impl http::Merge for ModuleConfig {
89+
fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
90+
if prev.enable {
91+
self.enable = true;
92+
};
93+
Ok(())
94+
}
95+
}
96+
97+
extern "C" fn ngx_http_async_commands_set_enable(
98+
cf: *mut ngx_conf_t,
99+
_cmd: *mut ngx_command_t,
100+
conf: *mut c_void,
101+
) -> *mut c_char {
102+
unsafe {
103+
let conf = &mut *(conf as *mut ModuleConfig);
104+
let args: &[ngx_str_t] = (*(*cf).args).as_slice();
105+
let val = match args[1].to_str() {
106+
Ok(s) => s,
107+
Err(_) => {
108+
ngx_conf_log_error!(NGX_LOG_EMERG, cf, "`async` argument is not utf-8 encoded");
109+
return ngx::core::NGX_CONF_ERROR;
110+
}
111+
};
112+
113+
// set default value optionally
114+
conf.enable = false;
115+
116+
if val.eq_ignore_ascii_case("on") {
117+
conf.enable = true;
118+
} else if val.eq_ignore_ascii_case("off") {
119+
conf.enable = false;
120+
}
121+
};
122+
123+
ngx::core::NGX_CONF_OK
124+
}
125+
126+
http_request_handler!(async_access_handler, |request: &mut http::Request| {
127+
let co = Module::location_conf(request).expect("module config is none");
128+
129+
ngx_log_debug_http!(request, "async module enabled: {}", co.enable);
130+
131+
if !co.enable {
132+
return core::Status::NGX_DECLINED;
133+
}
134+
135+
if request
136+
.get_module_ctx::<Task<()>>(unsafe { &*std::ptr::addr_of!(ngx_http_async_module) })
137+
.is_some()
138+
{
139+
return core::Status::NGX_DONE;
140+
}
141+
142+
let rc =
143+
unsafe { ngx_http_read_client_request_body(request.into(), Some(content_event_handler)) };
144+
if rc as u32 >= NGX_HTTP_SPECIAL_RESPONSE {
145+
return core::Status(rc);
146+
}
147+
148+
core::Status::NGX_DONE
149+
});
150+
151+
extern "C" fn content_event_handler(request: *mut ngx_http_request_t) {
152+
let task = spawn(async move {
153+
let start = Instant::now();
154+
sleep(std::time::Duration::from_secs(2)).await;
155+
156+
let req = unsafe { http::Request::from_ngx_http_request(request) };
157+
req.add_header_out(
158+
"X-Async-Time",
159+
start.elapsed().as_millis().to_string().as_str(),
160+
);
161+
req.set_status(http::HTTPStatus::OK);
162+
req.send_header();
163+
let buf = req.pool().calloc(std::mem::size_of::<ngx_buf_t>()) as *mut ngx_buf_t;
164+
unsafe {
165+
(*buf).set_last_buf(if req.is_main() { 1 } else { 0 });
166+
(*buf).set_last_in_chain(1);
167+
}
168+
req.output_filter(&mut ngx_chain_t {
169+
buf,
170+
next: std::ptr::null_mut(),
171+
});
172+
173+
unsafe {
174+
ngx::ffi::ngx_post_event(
175+
(*(*request).connection).write,
176+
std::ptr::addr_of_mut!(ngx::ffi::ngx_posted_events),
177+
);
178+
}
179+
});
180+
181+
let req = unsafe { http::Request::from_ngx_http_request(request) };
182+
183+
let ctx = req.pool().allocate::<Task<()>>(task);
184+
if ctx.is_null() {
185+
unsafe { ngx_http_finalize_request(request, core::Status::NGX_ERROR.into()) };
186+
return;
187+
}
188+
req.set_module_ctx(ctx.cast(), unsafe {
189+
&*std::ptr::addr_of!(ngx_http_async_module)
190+
});
191+
unsafe { (*request).write_event_handler = Some(write_event_handler) };
192+
}
193+
194+
extern "C" fn write_event_handler(request: *mut ngx_http_request_t) {
195+
let req = unsafe { http::Request::from_ngx_http_request(request) };
196+
if let Some(task) =
197+
req.get_module_ctx::<Task<()>>(unsafe { &*std::ptr::addr_of!(ngx_http_async_module) })
198+
{
199+
if task.is_finished() {
200+
unsafe { ngx_http_finalize_request(request, core::Status::NGX_OK.into()) };
201+
return;
202+
}
203+
}
204+
205+
let write_event =
206+
unsafe { (*(*request).connection).write.as_ref() }.expect("write event is not null");
207+
if write_event.timedout() != 0 {
208+
unsafe {
209+
ngx::ffi::ngx_connection_error(
210+
(*request).connection,
211+
ngx::ffi::NGX_ETIMEDOUT as i32,
212+
c"client timed out".as_ptr() as *mut _,
213+
)
214+
};
215+
return;
216+
}
217+
218+
if unsafe { ngx::ffi::ngx_http_output_filter(request, std::ptr::null_mut()) }
219+
== ngx::ffi::NGX_ERROR as isize
220+
{
221+
// Client error
222+
return;
223+
}
224+
let clcf =
225+
NgxHttpCoreModule::location_conf(unsafe { request.as_ref().expect("request not null") })
226+
.expect("http core server conf");
227+
228+
if unsafe {
229+
ngx::ffi::ngx_handle_write_event(std::ptr::from_ref(write_event) as *mut _, clcf.send_lowat)
230+
} != ngx::ffi::NGX_OK as isize
231+
{
232+
// Client error
233+
return;
234+
}
235+
236+
if write_event.delayed() == 0 {
237+
if (write_event.active() != 0) && (write_event.ready() == 0) {
238+
unsafe {
239+
ngx::ffi::ngx_add_timer(
240+
std::ptr::from_ref(write_event) as *mut _,
241+
clcf.send_timeout,
242+
)
243+
}
244+
} else if write_event.timer_set() != 0 {
245+
unsafe { ngx::ffi::ngx_del_timer(std::ptr::from_ref(write_event) as *mut _) }
246+
}
247+
}
248+
}

examples/config

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ if [ $HTTP = YES ]; then
1515
ngx_rust_target_type=EXAMPLE
1616
ngx_rust_target_features=
1717

18+
if :; then
19+
ngx_module_name=ngx_http_async_module
20+
ngx_module_libs="-lm"
21+
ngx_rust_target_name=async
22+
ngx_rust_target_features=async
23+
24+
ngx_rust_module
25+
fi
26+
1827
if :; then
1928
ngx_module_name=ngx_http_tokio_module
2029
ngx_module_libs="-lm"

examples/t/async.t

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 on;
43+
}
44+
}
45+
}
46+
47+
EOF
48+
49+
$t->write_file('index.html', '');
50+
$t->run();
51+
52+
###############################################################################
53+
54+
like(http_get('/index.html'), qr/X-Async-Time:/, 'async handler');
55+
56+
###############################################################################

0 commit comments

Comments
 (0)