Reading full client body in NGX_HTTP_CONTENT_PHASE #222
-
|
We need to read the full client bodies to implement a transparent tunneling protocol on top of TLS and HTTP. I could not find any example in rust on how to do that. pub fn handler(request: &mut Request) -> Status {
let config = Module::location_conf(request).expect("location config is none");
match config.asl {
true => {
let rc = unsafe {
ngx_http_read_client_request_body(request.as_mut(), Some(client_request_body_cb))
};
if rc >= NGX_HTTP_SPECIAL_RESPONSE.try_into().unwrap() {
Status(rc)
} else {
Status::NGX_DONE
}
}
false => Status::NGX_DECLINED,
}
}
extern "C" fn client_request_body_cb(request: *mut ngx_http_request_t) {
let body = unsafe { *(*request).request_body };
let mut chain = unsafe { *body.bufs };
let mut bufs: Vec<MemoryBuffer> = vec![];
let first = MemoryBuffer::from_ngx_buf(body.buf);
if first.len() > 0 {
bufs.push(first);
}
loop {
if !chain.buf.is_null() {
let t = MemoryBuffer::from_ngx_buf(chain.buf);
if t.len() > 0 { // NOTE: There might be a slight bug/confusing behaviour when creating a MemoryBuffer from a len 0 ngx_buf, it panics at runtime
bufs.push(t);
}
}
if chain.next.is_null() {
break;
}
chain = unsafe { *chain.next };
}
}I'm not 100% sure if that is the right way to handle a ngx_chain. Do I also need for account for last_buf and last_in_chain on the buffers themselves? Also: do I need to evaluate buf.memory and decide if I need a MemoryBuffer vs. TemporaryBuffer? However, when nginx decides to spill the client body to temp, how do I handle this? My callback seems to be called with request.request_body having:
The issue is that I've never seen that file in my file system. I also can't make sense of the temp_file.file.info.st_size, which is 0, and temp_file.offset, which is ~1MiB, which seems to correspond to the body I sent ( let mut buffer: *mut u8 = unsafe {
*Request::from_ngx_http_request(request)
.pool()
.alloc(size)
.cast()
};
let x = unsafe {
ngx_read_file(
&mut temp_file.file,
buffer,
size,
size.try_into().unwrap(),
)
};
let v = unsafe {
Vec::from_raw_parts(buffer, size.try_into().unwrap(), size.try_into().unwrap())
};… but it always reads 0 bytes. Has anyone done this in Rust already and can give me some pointer? Thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
|
You don't need to copy from the chain, you can use the data in-place or convert it to a
Not knowing what you want to do with the data, I can only suggest to check the corresponding section of nginx development guide, and examples of chain manipulation in Also in your code snippet, let v = unsafe {
Vec::from_raw_parts(buffer, size.try_into().unwrap(), size.try_into().unwrap())
};seems to be a guaranteed double free, because both I'd try to write this and similar fragments as let v: Vec<u8, _> = Vec::new_in(request.pool());
v.try_reserve_exact(size)?;
unsafe {
let n = ngx_read_file(&mut temp_file.file, v.as_mut_ptr().cast(), core::cmp::min(v.capacity(), size), 0);
if n < 0 {
return Err(...);
}
v.set_len(n as usize);
} |
Beta Was this translation helpful? Give feedback.
You don't need to copy from the chain, you can use the data in-place or convert it to a
Vecof&[u8]slice references (seengx_output_chain_to_iovecfor a rough example). The chain's data is guaranteed to live until you free it, pass to a chain writer (important for phase handlers other than content handler), or terminate the request.The reallocation and copying is only necessary if you want a single contiguous buffer, and even then you'd want to avoid
TemporaryBuffer/MemoryBufferand use something likeVecbacked byngx_pool_tinstead.temp_file.file.infois a zero-initialized reserved storage forngx_fd_info(fstat(2)) results.ngx_http_read_client_request_bodydoes not usengx_fd_info,…