Skip to content

Commit

Permalink
refactor(ffi): Add Reason-Phrase API
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seanmonstar committed Jan 8, 2021
1 parent 3df799e commit 2ce5952
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 4 deletions.
4 changes: 3 additions & 1 deletion capi/examples/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions capi/include/hyper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
39 changes: 39 additions & 0 deletions src/ffi/http_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub struct hyper_headers {
#[derive(Debug, Default)]
pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>);

#[derive(Debug)]
pub(crate) struct ReasonPhrase(pub(crate) Bytes);

// ===== impl hyper_request =====

ffi_fn! {
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -205,6 +232,18 @@ impl hyper_response {

hyper_response(resp)
}

fn reason_phrase(&self) -> &[u8] {
if let Some(reason) = self.0.extensions().get::<ReasonPhrase>() {
return &reason.0;
}

if let Some(reason) = self.0.status().canonical_reason() {
return reason.as_bytes();
}

&[]
}
}

unsafe impl AsTaskType for hyper_response {
Expand Down
2 changes: 1 addition & 1 deletion src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
27 changes: 25 additions & 2 deletions src/proto/h1/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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!(
Expand All @@ -674,14 +676,28 @@ 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 {
Version::HTTP_10
};
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),
}
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 2ce5952

Please sign in to comment.