unit_rs/
request.rs

1use std::io::Read;
2use std::marker::PhantomData;
3
4use libc::c_void;
5
6use crate::error::{IntoUnitResult, UnitResult};
7use crate::nxt_unit::{self, nxt_unit_request_info_t, nxt_unit_sptr_get, nxt_unit_sptr_t};
8use crate::response::Response;
9use crate::{BodyWriter, UnitError};
10
11/// A request received by the NGINX Unit server.
12///
13/// This object can be used to inspect the properties and headers of the
14/// request, and send a response back to the client.
15pub struct Request<'a> {
16    pub(crate) nxt_request: *mut nxt_unit_request_info_t,
17    pub(crate) _lifetime: PhantomData<&'a mut ()>,
18}
19
20impl<'a> Request<'a> {
21    /// Allocate a buffer for the initial response, capable of containing at
22    /// most `max_fields_count` fields (headers), and at most
23    /// `max_response_size` bytes for the field names, field values, and
24    /// response content combined.
25    ///
26    /// The buffer can be filled and sent using the methods on the returned
27    /// [`Response`].
28    ///
29    /// The buffer can be resized with [`Response::realloc`].
30    pub fn create_response(
31        &'a self,
32        status_code: u16,
33        max_fields_count: usize,
34        max_response_size: usize,
35    ) -> UnitResult<Response<'a>> {
36        // SAFETY: Unit's C API will return an error if the response was already
37        // sent.
38        // This structure is neither Send nor Sync, so parallel responses may
39        // exist safely (but may result in error results).
40        unsafe {
41            nxt_unit::nxt_unit_response_init(
42                self.nxt_request,
43                status_code,
44                max_fields_count as u32,
45                max_response_size as u32,
46            )
47            .into_unit_result()?;
48        }
49
50        Ok(Response { request: self })
51    }
52
53    /// Send an initial response to the client.
54    ///
55    /// This is a convenience method that calls the [`Request::create_response`],
56    /// [`Response::add_field`], [`Response::add_content`], and
57    /// [`Response::send`] methods.
58    ///
59    /// # Panic
60    /// This method will panic if the header name is longer than `u8::MAX`
61    /// bytes, or if the header value is longer than `u32::MAX`.
62    pub fn send_response(
63        &self,
64        status_code: u16,
65        headers: &[(impl AsRef<[u8]>, impl AsRef<[u8]>)],
66        body: impl AsRef<[u8]>,
67    ) -> UnitResult<()> {
68        let headers_size: usize = headers
69            .iter()
70            .map(|(name, value)| name.as_ref().len() + value.as_ref().len())
71            .sum();
72        let response_size = headers_size + body.as_ref().len();
73
74        for (name, value) in headers {
75            assert!(name.as_ref().len() <= u8::MAX as usize);
76            assert!(value.as_ref().len() <= u32::MAX as usize);
77        }
78
79        let response = self.create_response(status_code, headers.len(), response_size)?;
80        for (name, value) in headers {
81            response.add_field(name, value)?;
82        }
83        response.add_content(body)?;
84        response.send()
85    }
86
87    /// Allocate and send additional response chunks to the client, using a
88    /// writer to copy data into the chunks.
89    ///
90    /// A chunk will be immediately sent to the client once the writer's memory
91    /// buffer reaches `chunk_size`, or [`flush()`](std::io::Write::flush) is
92    /// called on the writer.
93    ///
94    /// The writer will also flush when dropped, but may panic in case of errors.
95    ///
96    /// # Panic
97    /// Panics if flushing was not successful when flushing during a drop. It is
98    /// recommended to manually call [`flush()`](std::io::Write::flush), or use
99    /// the [`Request::send_chunks_with_writer()`] method.
100    pub fn write_chunks(&'a self, chunk_size: usize) -> std::io::Result<BodyWriter<'a>> {
101        BodyWriter::new(self, chunk_size)
102    }
103
104    /// Allocate and send additional response chunks to the client.
105    ///
106    /// This is similar to [`write_chunks()`](Request::write_chunks), but will
107    /// also flush the writer before returning, convert the result into a
108    /// [`UnitError`], and log the error.
109    ///
110    /// This is useful to prevent errors from being silently ignored if the
111    /// writer needs to flush while being dropped.
112    pub fn send_chunks_with_writer<T>(
113        &'a self,
114        chunk_size: usize,
115        f: impl FnOnce(&mut BodyWriter<'a>) -> std::io::Result<T>,
116    ) -> UnitResult<T> {
117        let write = || -> std::io::Result<T> {
118            let mut writer = self.write_chunks(chunk_size)?;
119            let result = f(&mut writer)?;
120            std::io::Write::flush(&mut writer)?;
121            Ok(result)
122        };
123
124        write().map_err(|err| {
125            self.log(
126                LogLevel::Error,
127                &format!("Error writing to response: {}", err),
128            );
129            UnitError::error()
130        })
131    }
132
133    /// Send another chunk of bytes for this request's response. The bytes will
134    /// be immediately sent to the client.
135    ///
136    /// This method allocates a buffer in Unit's shared memory region, and calls
137    /// a user function to fill it.
138    ///
139    /// The user function receives a `&mut &mut [u8]` slice, and the `write!`
140    /// macro can be used to advance the start position of the slice. Only the
141    /// bytes between the original start and the new start positions will be
142    /// sent, and the rest will be discarded.
143    pub fn send_chunk_with_buffer<T>(
144        &self,
145        size: usize,
146        f: impl FnOnce(&mut &mut [u8]) -> UnitResult<T>,
147    ) -> UnitResult<T> {
148        let req = self.nxt_request;
149
150        assert!(size <= u32::MAX as usize);
151
152        unsafe {
153            let buf = nxt_unit::nxt_unit_response_buf_alloc(req, size as u32);
154
155            if buf.is_null() {
156                return Err(UnitError(nxt_unit::NXT_UNIT_ERROR as i32));
157            }
158
159            libc::memset((*buf).start as *mut c_void, 0, size);
160
161            let mut buf_contents = std::slice::from_raw_parts_mut((*buf).start as *mut u8, size);
162            let result = f(&mut buf_contents)?;
163
164            (*buf).free = (*buf).free.add(size - buf_contents.len());
165
166            nxt_unit::nxt_unit_buf_send(buf).into_unit_result()?;
167
168            Ok(result)
169        }
170    }
171
172    /// Copy bytes from the request body into the target buffer and return the
173    /// number of bytes written.
174    ///
175    /// If the buffer is smaller than the contents of the body, the contents
176    /// will be truncated to the size of the buffer.
177    pub fn read_body(&self, target: &mut [u8]) -> usize {
178        unsafe {
179            let bytes = nxt_unit::nxt_unit_request_read(
180                self.nxt_request,
181                target.as_mut_ptr() as *mut c_void,
182                target.len() as u64,
183            );
184            bytes as usize
185        }
186    }
187
188    /// Create a reader that implements the [`Read`](std::io::Read) trait,
189    /// which will read from the request body in a blocking manner.
190    pub fn body(&self) -> BodyReader<'a> {
191        BodyReader {
192            _lifetime: Default::default(),
193            nxt_request: self.nxt_request,
194        }
195    }
196
197    /// Create an interator over all header (name, value) tuples.
198    pub fn fields(&self) -> impl Iterator<Item = (&str, &str)> {
199        unsafe {
200            let r = &(*(*self.nxt_request).request);
201
202            (0..r.fields_count as isize).into_iter().map(|i| {
203                let field = &*r.fields.as_ptr().offset(i);
204                let name = sptr_to_slice(&field.name, field.name_length.into());
205                let value = sptr_to_slice(&field.value, field.value_length.into());
206                (name, value)
207            })
208        }
209    }
210
211    /// Return whether or not the request was encrypted.
212    pub fn tls(&self) -> bool {
213        unsafe { (*(*self.nxt_request).request).tls != 0 }
214    }
215
216    /// Return the method of the request (e.g. "GET").
217    pub fn method(&self) -> &str {
218        unsafe {
219            let r = &(*(*self.nxt_request).request);
220            sptr_to_slice(&r.method, r.method_length.into())
221        }
222    }
223
224    /// Return the protocol version of the request (e.g. "HTTP/1.1").
225    pub fn version(&self) -> &str {
226        unsafe {
227            let r = &(*(*self.nxt_request).request);
228            sptr_to_slice(&r.version, r.version_length.into())
229        }
230    }
231
232    /// Return the remote IP address of the client.
233    pub fn remote(&self) -> &str {
234        unsafe {
235            let r = &(*(*self.nxt_request).request);
236            sptr_to_slice(&r.remote, r.remote_length.into())
237        }
238    }
239
240    /// Return the local IP address of the server.
241    pub fn local(&self) -> &str {
242        unsafe {
243            let r = &(*(*self.nxt_request).request);
244            sptr_to_slice(&r.local, r.local_length.into())
245        }
246    }
247
248    /// Return the host name of the server.
249    pub fn server_name(&self) -> &str {
250        unsafe {
251            let r = &(*(*self.nxt_request).request);
252            sptr_to_slice(&r.server_name, r.server_name_length)
253        }
254    }
255
256    /// Return the combined URI path and query string.
257    pub fn target(&self) -> &str {
258        unsafe {
259            let r = &(*(*self.nxt_request).request);
260            sptr_to_slice(&r.target, r.target_length)
261        }
262    }
263
264    /// Return the URI path.
265    pub fn path(&self) -> &str {
266        unsafe {
267            let r = &(*(*self.nxt_request).request);
268            sptr_to_slice(&r.path, r.path_length)
269        }
270    }
271
272    /// Return the URI query string.
273    pub fn query(&self) -> &str {
274        unsafe {
275            let r = &(*(*self.nxt_request).request);
276            sptr_to_slice(&r.query, r.query_length)
277        }
278    }
279
280    /// Log an error message.
281    pub fn log<S: AsRef<str>>(&self, level: LogLevel, message: S) {
282        unsafe {
283            nxt_unit::nxt_unit_req_log(
284                self.nxt_request,
285                level as i32,
286                "%s\0".as_ptr() as *const i8,
287                message.as_ref(),
288            )
289        }
290    }
291}
292
293unsafe fn sptr_to_slice(sptr: &nxt_unit_sptr_t, length: u32) -> &str {
294    let ptr = nxt_unit_sptr_get(sptr) as *mut u8;
295    let slice = std::slice::from_raw_parts(ptr, length as usize);
296    // FIXME: temporary, NGINX Unit doesn't guarantee this
297    std::str::from_utf8(slice).unwrap()
298}
299
300#[repr(u32)]
301pub enum LogLevel {
302    Alert = nxt_unit::NXT_UNIT_LOG_ALERT,
303    Error = nxt_unit::NXT_UNIT_LOG_ERR,
304    Warning = nxt_unit::NXT_UNIT_LOG_WARN,
305    Notice = nxt_unit::NXT_UNIT_LOG_NOTICE,
306    Info = nxt_unit::NXT_UNIT_LOG_INFO,
307    Debug = nxt_unit::NXT_UNIT_LOG_DEBUG,
308}
309
310/// A reader that reads from the request body.
311///
312/// This reader is non-blocking, as Unit will buffer the whole request body
313/// before running the request handler.
314pub struct BodyReader<'a> {
315    _lifetime: std::marker::PhantomData<&'a ()>,
316    nxt_request: *mut nxt_unit_request_info_t,
317}
318
319impl BodyReader<'_> {
320    /// Convenience function that allocates and copies the request body data
321    /// into a [`Vec<u8>`].
322    pub fn read_to_vec(&mut self) -> std::io::Result<Vec<u8>> {
323        let mut vec = Vec::new();
324        self.read_to_end(&mut vec)?;
325        Ok(vec)
326    }
327}
328
329impl std::panic::UnwindSafe for BodyReader<'_> {}
330
331impl std::io::Read for BodyReader<'_> {
332    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
333        // SAFETY: The target is user-provided and initialized.
334        // The BodyReader and Request are not Sync nor Send, so this is
335        // thread-safe.
336        // This function does not seem to have any sort of error reporting.
337        let bytes = unsafe {
338            nxt_unit::nxt_unit_request_read(
339                self.nxt_request,
340                buf.as_mut_ptr() as *mut c_void,
341                buf.len() as u64,
342            )
343        };
344        Ok(bytes as usize)
345    }
346}