Skip to content

Commit

Permalink
Add more documentation for the FFI API
Browse files Browse the repository at this point in the history
  • Loading branch information
jsha committed Nov 17, 2023
1 parent 2954280 commit c30f41c
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 27 deletions.
62 changes: 53 additions & 9 deletions src/ffi/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,39 @@ use super::{UserDataPointer, HYPER_ITER_CONTINUE};
use crate::body::{Bytes, Frame, Incoming as IncomingBody};

/// A streaming HTTP body.
///
/// This is used both for sending requests (with `hyper_request_set_body`) and
/// for receiving responses (with `hyper_response_body`).
///
/// For outgoing request bodies, call `hyper_body_set_data_func` to provide the
/// data.
///
/// For incoming response bodies, call `hyper_body_data` to get a task that will
/// yield a chunk of data each time it is polled. That task must be then be
/// added to the executor with `hyper_executor_push`.
///
/// Methods:
///
/// - hyper_body_new: Create a new “empty” body.
/// - hyper_body_set_userdata: Set userdata on this body, which will be passed to callback functions.
/// - hyper_body_set_data_func: Set the data callback for this body.
/// - hyper_body_data: Return a task that will poll the body for the next buffer of data.
/// - hyper_body_foreach: Return a task that will poll the body and execute the callback with each body chunk that is received.
/// - hyper_body_free: Free a body.
pub struct hyper_body(pub(super) IncomingBody);

/// A buffer of bytes that is sent or received on a `hyper_body`.
///
/// Obtain one of these in the callback of `hyper_body_foreach` or by receiving
/// a task of type `HYPER_TASK_BUF` from `hyper_executor_poll` (after calling
/// `hyper_body_data` and pushing the resulting task).
///
/// Methods:
///
/// - hyper_buf_bytes: Get a pointer to the bytes in this buffer.
/// - hyper_buf_copy: Create a new hyper_buf * by copying the provided bytes.
/// - hyper_buf_free: Free this buffer.
/// - hyper_buf_len: Get the length of the bytes this buffer contains.
pub struct hyper_buf(pub(crate) Bytes);

pub(crate) struct UserBody {
Expand Down Expand Up @@ -51,20 +81,31 @@ ffi_fn! {
}

ffi_fn! {
/// Return a task that will poll the body for the next buffer of data.
/// Create a task that will poll a response body for the next buffer of data.
///
/// The task value may have different types depending on the outcome:
/// The task may have different types depending on the outcome:
///
/// - `HYPER_TASK_BUF`: Success, and more data was received.
/// - `HYPER_TASK_ERROR`: An error retrieving the data.
/// - `HYPER_TASK_EMPTY`: The body has finished streaming data.
///
/// When the application receives the task from `hyper_executor_poll`,
/// if the task type is `HYPER_TASK_BUF`, it should cast the task to
/// `hyper_buf *` and consume all the bytes in the buffer. Then
/// the application should call `hyper_body_data` again for the same
/// `hyper_body *`, to create a task for the next buffer of data.
/// Repeat until the polled task type is `HYPER_TASK_ERROR` or
/// `HYPER_TASK_EMPTY`.
///
/// To avoid a memory leak, the task must eventually be consumed by
/// `hyper_task_free`, or taken ownership of by `hyper_executor_push`
/// without subsequently being given back by `hyper_executor_poll`.
///
/// This does not consume the `hyper_body *`, so it may be used to again.
/// However, it MUST NOT be used or freed until the related task completes.
/// This does not consume the `hyper_body *`, so it may be used again.
/// However, the `hyper_body *` MUST NOT be used or freed until the
/// related task is returned from `hyper_executor_poll`.
///
/// For a more convenient method, see also `hyper_body_foreach`.
fn hyper_body_data(body: *mut hyper_body) -> *mut hyper_task {
// This doesn't take ownership of the Body, so don't allow destructor
let mut body = ManuallyDrop::new(non_null!(Box::from_raw(body) ?= ptr::null_mut()));
Expand All @@ -88,18 +129,21 @@ ffi_fn! {
}

ffi_fn! {
/// Return a task that will poll the body and execute the callback with each
/// Create a task that will poll the body and execute the callback with each
/// body chunk that is received.
///
/// To avoid a memory leak, the task must eventually be consumed by
/// `hyper_task_free`, or taken ownership of by `hyper_executor_push`
/// without subsequently being given back by `hyper_executor_poll`.
///
/// The `hyper_buf` pointer is only a borrowed reference, it cannot live outside
/// the execution of the callback. You must make a copy to retain it.
/// The `hyper_buf` pointer is only a borrowed reference. It cannot live outside
/// the execution of the callback. You must make a copy of the bytes to retain them.
///
/// The callback should return `HYPER_ITER_CONTINUE` to continue iterating
/// chunks as they are received, or `HYPER_ITER_BREAK` to cancel.
/// chunks as they are received, or `HYPER_ITER_BREAK` to cancel. Each
/// invocation of the callback must consume all the bytes it is provided.
/// There is no mechanism to signal to Hyper that only a subset of bytes were
/// consumed.
///
/// This will consume the `hyper_body *`, you shouldn't use it anymore or free it.
fn hyper_body_foreach(body: *mut hyper_body, func: hyper_body_foreach_callback, userdata: *mut c_void) -> *mut hyper_task {
Expand Down Expand Up @@ -129,7 +173,7 @@ ffi_fn! {
}

ffi_fn! {
/// Set the data callback for this body.
/// Set the outgoing data callback for this body.
///
/// The callback is called each time hyper needs to send more data for the
/// body. It is passed the value from `hyper_body_set_userdata`.
Expand Down
36 changes: 33 additions & 3 deletions src/ffi/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,39 @@ pub struct hyper_clientconn_options {

/// An HTTP client connection handle.
///
/// These are used to send a request on a single connection. It's possible to
/// send multiple requests on a single connection, such as when HTTP/1
/// keep-alive or HTTP/2 is used.
/// These are used to send one or more requests on a single connection.
///
/// It's possible to send multiple requests on a single connection, such
/// as when HTTP/1 keep-alive or HTTP/2 is used.
///
/// To create a hyper_clientconn:
///
/// 1. Create a hyper_clientconn_options with hyper_clientconn_options_new.
/// 2. Create a hyper_io with hyper_io_new.
/// 3. Call hyper_clientconn_handshake with the hyper_io and hyper_clientconn_options.
/// This creates a hyper_task.
/// 4. Add the hyper_task to an executor with hyper_executor_push.
/// 5. Poll that executor until it yields a task of type HYPER_TASK_CLIENTCONN.
/// 6. Extract the hyper_clientconn from the task with hyper_task_value.
/// This will require a cast from void * to hyper_clientconn *.
///
/// This process results in a hyper_clientconn that permanently owns the
/// hyper_io. Because the hyper_io in turn owns a TCP or TLS connection, that means
/// the hyper_clientconn owns the connection for both the clientconn's lifetime
/// and the connection's lifetime.
///
/// In other words, each connection (hyper_io) must have exactly one hyper_clientconn
/// associated with it. That's because hyper_clientconn_handshake sends the
/// [HTTP/2 Connection Preface] (for HTTP/2 connections). Since that preface can't
/// be sent twice, handshake can't be called twice.
///
/// [HTTP/2 Connection Preface]: https://datatracker.ietf.org/doc/html/rfc9113#name-http-2-connection-preface
///
/// Methods:
///
/// - hyper_clientconn_handshake: Starts an HTTP client connection handshake using the provided IO transport and options.
/// - hyper_clientconn_send: Send a request on the client connection.
/// - hyper_clientconn_free: Free a hyper_clientconn *.
pub struct hyper_clientconn {
tx: Tx,
}
Expand Down
19 changes: 19 additions & 0 deletions src/ffi/http_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,30 @@ use crate::{HeaderMap, Method, Request, Response, Uri};
pub struct hyper_request(pub(super) Request<IncomingBody>);

/// An HTTP response.
///
/// This is obtained when `hyper_executor_poll` returns a `hyper_task`
/// of type `HYPER_TASK_RESPONSE`. To figure out which request this response
/// corresponds to, check the userdata of the task, which you should
/// previously have set to an application-specific identifier for the
/// request.
///
/// Methods:
///
/// - hyper_response_status: Get the HTTP-Status code of this response.
/// - hyper_response_version: Get the HTTP version used by this response.
/// - hyper_response_reason_phrase: Get a pointer to the reason-phrase of this response.
/// - hyper_response_reason_phrase_len: Get the length of the reason-phrase of this response.
/// - hyper_response_headers: Gets a reference to the HTTP headers of this response.
/// - hyper_response_body: Take ownership of the body of this response.
/// - hyper_response_free: Free an HTTP response.
pub struct hyper_response(pub(super) Response<IncomingBody>);

/// An HTTP header map.
///
/// These can be part of a request or response.
///
/// Obtain a pointer to read or modify these from `hyper_request_headers`
/// or `hyper_response_headers`.
#[derive(Clone)]
pub struct hyper_headers {
pub(super) headers: HeaderMap,
Expand Down
39 changes: 29 additions & 10 deletions src/ffi/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,20 @@ type hyper_io_read_callback =
type hyper_io_write_callback =
extern "C" fn(*mut c_void, *mut hyper_context<'_>, *const u8, size_t) -> size_t;

/// An IO object used to represent a socket or similar concept.
/// A read/write handle for a specific connection.
///
/// This owns a specific TCP or TLS connection for the lifetime of
/// that connection. It contains a read and write callback, as well as a
/// void *userdata. Typically the userdata will point to a struct
/// containing a file descriptor and a TLS context.
///
/// Methods:
///
/// - hyper_io_new: Create a new IO type used to represent a transport.
/// - hyper_io_set_read: Set the read function for this IO transport.
/// - hyper_io_set_userdata: Set the user data pointer for this IO to some value.
/// - hyper_io_set_write: Set the write function for this IO transport.
/// - hyper_io_free: Free an IO handle.
pub struct hyper_io {
read: hyper_io_read_callback,
write: hyper_io_write_callback,
Expand All @@ -32,6 +45,11 @@ ffi_fn! {
/// The read and write functions of this transport should be set with
/// `hyper_io_set_read` and `hyper_io_set_write`.
///
/// It is expected that the underlying transport is non-blocking. When
/// a read or write callback can't make progress because there is no
/// data available yet, it should use the `hyper_waker` mechanism to
/// arrange to be called again when data is available.
///
/// To avoid a memory leak, the IO handle must eventually be consumed by
/// `hyper_io_free` or `hyper_clientconn_handshake`.
fn hyper_io_new() -> *mut hyper_io {
Expand Down Expand Up @@ -72,10 +90,11 @@ ffi_fn! {
/// unless you have already written them yourself. It is also undefined behavior
/// to return that more bytes have been written than actually set on the `buf`.
///
/// If there is no data currently available, a waker should be claimed from
/// the `ctx` and registered with whatever polling mechanism is used to signal
/// when data is available later on. The return value should be
/// `HYPER_IO_PENDING`.
/// If there is no data currently available, the callback should create a
/// `hyper_waker` from its `hyper_context` argument and register the waker
/// with whatever polling mechanism is used to signal when data is available
/// later on. The return value should be `HYPER_IO_PENDING`. See the
/// documentation for `hyper_waker`.
///
/// If there is an irrecoverable error reading data, then `HYPER_IO_ERROR`
/// should be the return value.
Expand All @@ -90,11 +109,11 @@ ffi_fn! {
/// Data from the `buf` pointer should be written to the transport, up to
/// `buf_len` bytes. The number of bytes written should be the return value.
///
/// If no data can currently be written, the `waker` should be cloned and
/// registered with whatever polling mechanism is used to signal when data
/// is available later on. The return value should be `HYPER_IO_PENDING`.
///
/// Yeet.
/// If there is no data currently available, the callback should create a
/// `hyper_waker` from its `hyper_context` argument and register the waker
/// with whatever polling mechanism is used to signal when data is available
/// later on. The return value should be `HYPER_IO_PENDING`. See the documentation
/// for `hyper_waker`.
///
/// If there is an irrecoverable error reading data, then `HYPER_IO_ERROR`
/// should be the return value.
Expand Down

0 comments on commit c30f41c

Please sign in to comment.