From c9c46ed60bd43484d67a98a63496ad0277c791a2 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Thu, 7 Jan 2021 17:22:12 -0800 Subject: [PATCH] refactor(ffi): Add Reason-Phrase API This adds an internal ability to copy the HTTP/1 reason-phrase and place it in the `http::Extensions` of a response, if it doesn't match the canonical reason. This could be exposed in the Rust API later, but for now it is only used by the C API. --- capi/examples/client.c | 4 +++- capi/include/hyper.h | 20 ++++++++++++++++++++ src/ffi/http_types.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/ffi/mod.rs | 2 +- src/proto/h1/role.rs | 27 +++++++++++++++++++++++++-- 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/capi/examples/client.c b/capi/examples/client.c index f8f1805f..0a1f7124 100644 --- a/capi/examples/client.c +++ b/capi/examples/client.c @@ -254,8 +254,10 @@ int main(int argc, char *argv[]) { hyper_task_free(task); uint16_t http_status = hyper_response_status(resp); + const uint8_t *rp = hyper_response_reason_phrase(resp); + size_t rp_len = hyper_response_reason_phrase_len(resp); - printf("\nResponse Status: %d\n", http_status); + printf("\nResponse Status: %d %.*s\n", http_status, (int) rp_len, rp); hyper_headers *headers = hyper_response_headers(resp); hyper_headers_foreach(headers, print_each_header, NULL); diff --git a/capi/include/hyper.h b/capi/include/hyper.h index f2a6f8db..78934710 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -349,6 +349,26 @@ void hyper_response_free(hyper_response *resp); */ uint16_t hyper_response_status(const hyper_response *resp); +/* + Get a pointer to the reason-phrase of this response. + + This buffer is not null-terminated. + + This buffer is owned by the response, and should not be used after + the response has been freed. + + Use `hyper_response_reason_phrase_len()` to get the length of this + buffer. + */ +const uint8_t *hyper_response_reason_phrase(const hyper_response *resp); + +/* + Get the length of the reason-phrase of this response. + + Use `hyper_response_reason_phrase()` to get the buffer pointer. + */ +size_t hyper_response_reason_phrase_len(const hyper_response *resp); + /* Get the HTTP version used by this response. diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index fdf645ca..6dba5a49 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -23,6 +23,9 @@ pub struct hyper_headers { #[derive(Debug, Default)] pub(crate) struct HeaderCaseMap(HeaderMap); +#[derive(Debug)] +pub(crate) struct ReasonPhrase(pub(crate) Bytes); + // ===== impl hyper_request ===== ffi_fn! { @@ -150,6 +153,30 @@ ffi_fn! { } } +ffi_fn! { + /// Get a pointer to the reason-phrase of this response. + /// + /// This buffer is not null-terminated. + /// + /// This buffer is owned by the response, and should not be used after + /// the response has been freed. + /// + /// Use `hyper_response_reason_phrase_len()` to get the length of this + /// buffer. + fn hyper_response_reason_phrase(resp: *const hyper_response) -> *const u8 { + unsafe { &*resp }.reason_phrase().as_ptr() + } +} + +ffi_fn! { + /// Get the length of the reason-phrase of this response. + /// + /// Use `hyper_response_reason_phrase()` to get the buffer pointer. + fn hyper_response_reason_phrase_len(resp: *const hyper_response) -> size_t { + unsafe { &*resp }.reason_phrase().len() + } +} + ffi_fn! { /// Get the HTTP version used by this response. /// @@ -205,6 +232,18 @@ impl hyper_response { hyper_response(resp) } + + fn reason_phrase(&self) -> &[u8] { + if let Some(reason) = self.0.extensions().get::() { + return &reason.0; + } + + if let Some(reason) = self.0.status().canonical_reason() { + return reason.as_bytes(); + } + + &[] + } } unsafe impl AsTaskType for hyper_response { diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index ffa9d6b1..423a0776 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -28,7 +28,7 @@ mod io; mod task; pub(crate) use self::body::UserBody; -pub(crate) use self::http_types::HeaderCaseMap; +pub(crate) use self::http_types::{HeaderCaseMap, ReasonPhrase}; pub const HYPER_ITER_CONTINUE: libc::c_int = 0; #[allow(unused)] diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 95015bff..0c7eb1ee 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -5,6 +5,8 @@ use std::fmt::{self, Write}; use std::mem; +#[cfg(feature = "ffi")] +use bytes::Bytes; use bytes::BytesMut; use http::header::{self, Entry, HeaderName, HeaderValue}; use http::{HeaderMap, Method, StatusCode, Version}; @@ -660,7 +662,7 @@ impl Http1Transaction for Client { loop { // Unsafe: see comment in Server Http1Transaction, above. let mut headers_indices: [HeaderIndices; MAX_HEADERS] = unsafe { mem::uninitialized() }; - let (len, status, version, headers_len) = { + let (len, status, reason, version, headers_len) = { let mut headers: [httparse::Header<'_>; MAX_HEADERS] = unsafe { mem::uninitialized() }; trace!( @@ -674,6 +676,20 @@ impl Http1Transaction for Client { httparse::Status::Complete(len) => { trace!("Response.parse Complete({})", len); let status = StatusCode::from_u16(res.code.unwrap())?; + + #[cfg(not(feature = "ffi"))] + let reason = (); + #[cfg(feature = "ffi")] + let reason = { + let reason = res.reason.unwrap(); + // Only save the reason phrase if it isnt the canonical reason + if Some(reason) != status.canonical_reason() { + Some(Bytes::copy_from_slice(reason.as_bytes())) + } else { + None + } + }; + let version = if res.version.unwrap() == 1 { Version::HTTP_11 } else { @@ -681,7 +697,7 @@ impl Http1Transaction for Client { }; record_header_indices(bytes, &res.headers, &mut headers_indices)?; let headers_len = res.headers.len(); - (len, status, version, headers_len) + (len, status, reason, version, headers_len) } httparse::Status::Partial => return Ok(None), } @@ -728,6 +744,13 @@ impl Http1Transaction for Client { extensions.insert(header_case_map); } + #[cfg(feature = "ffi")] + if let Some(reason) = reason { + extensions.insert(crate::ffi::ReasonPhrase(reason)); + } + #[cfg(not(feature = "ffi"))] + drop(reason); + let head = MessageHead { version, subject: status,