feat(ffi): Initial C API for hyper

This commit is contained in:
Sean McArthur
2021-01-07 17:22:12 -08:00
parent 8861f9a786
commit 3ae1581a53
22 changed files with 2910 additions and 14 deletions

17
capi/README.md Normal file
View File

@@ -0,0 +1,17 @@
# C API for hyper
This provides auxiliary pieces for a C API to use the hyper library.
## Unstable
The C API of hyper is currently **unstable**, which means it's not part of the semver contract as the rest of the Rust API is.
Because of that, it's only accessible if `--cfg hyper_unstable_ffi` is passed to `rustc` when compiling. The easiest way to do that is setting the `RUSTFLAGS` environment variable.
## Building
The C API is part of the Rust library, but isn't compiled by default. Using `cargo`, it can be compiled with the following command:
```
RUSTFLAGS="--cfg hyper_unstable_ffi" cargo build --features client,http1,http2,ffi
```

14
capi/cbindgen.toml Normal file
View File

@@ -0,0 +1,14 @@
language = "C"
include_guard = "_HYPER_H"
no_includes = true
sys_includes = ["stdint.h", "stddef.h"]
cpp_compat = true
documentation_style = "c"
[parse.expand]
crates = ["hyper-capi"]
[export.rename]
"Exec" = "hyper_executor"
"Io" = "hyper_io"
"Task" = "hyper_task"

22
capi/examples/Makefile Normal file
View File

@@ -0,0 +1,22 @@
#
# Build the example client
#
TARGET = client
OBJS = client.o
RPATH=$(PWD)/../../target/debug
CFLAGS = -I../include
LDFLAGS = -L$(RPATH) -Wl,-rpath,$(RPATH)
LIBS = -lhyper
$(TARGET): $(OBJS)
$(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LIBS)
upload: upload.o
$(CC) -o upload upload.o $(LDFLAGS) $(LIBS)
clean:
rm -f $(OBJS) $(TARGET)
rm -f upload upload.o

343
capi/examples/client.c Normal file
View File

@@ -0,0 +1,343 @@
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/select.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include "hyper.h"
struct conn_data {
int fd;
hyper_waker *read_waker;
hyper_waker *write_waker;
};
static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) {
struct conn_data *conn = (struct conn_data *)userdata;
ssize_t ret = read(conn->fd, buf, buf_len);
if (ret < 0) {
int err = errno;
if (err == EAGAIN) {
// would block, register interest
if (conn->read_waker != NULL) {
hyper_waker_free(conn->read_waker);
}
conn->read_waker = hyper_context_waker(ctx);
return HYPER_IO_PENDING;
} else {
// kaboom
return HYPER_IO_ERROR;
}
} else {
return ret;
}
}
static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) {
struct conn_data *conn = (struct conn_data *)userdata;
ssize_t ret = write(conn->fd, buf, buf_len);
if (ret < 0) {
int err = errno;
if (err == EAGAIN) {
// would block, register interest
if (conn->write_waker != NULL) {
hyper_waker_free(conn->write_waker);
}
conn->write_waker = hyper_context_waker(ctx);
return HYPER_IO_PENDING;
} else {
// kaboom
return HYPER_IO_ERROR;
}
} else {
return ret;
}
}
static void free_conn_data(struct conn_data *conn) {
if (conn->read_waker) {
hyper_waker_free(conn->read_waker);
conn->read_waker = NULL;
}
if (conn->write_waker) {
hyper_waker_free(conn->write_waker);
conn->write_waker = NULL;
}
free(conn);
}
static int connect_to(const char *host, const char *port) {
struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo *result, *rp;
if (getaddrinfo(host, port, &hints, &result) != 0) {
printf("dns failed for %s\n", host);
return -1;
}
int sfd;
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1) {
continue;
}
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
break;
} else {
close(sfd);
}
}
freeaddrinfo(result);
// no address succeeded
if (rp == NULL) {
printf("connect failed for %s\n", host);
return -1;
}
return sfd;
}
static int print_each_header(void *userdata,
const uint8_t *name,
size_t name_len,
const uint8_t *value,
size_t value_len) {
printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value);
return HYPER_ITER_CONTINUE;
}
static int print_each_chunk(void *userdata, const hyper_buf *chunk) {
const uint8_t *buf = hyper_buf_bytes(chunk);
size_t len = hyper_buf_len(chunk);
write(1, buf, len);
return HYPER_ITER_CONTINUE;
}
typedef enum {
EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set
EXAMPLE_HANDSHAKE,
EXAMPLE_SEND,
EXAMPLE_RESP_BODY
} example_id;
#define STR_ARG(XX) (uint8_t *)XX, strlen(XX)
int main(int argc, char *argv[]) {
const char *host = argc > 1 ? argv[1] : "httpbin.org";
const char *port = argc > 2 ? argv[2] : "80";
const char *path = argc > 3 ? argv[3] : "/";
printf("connecting to port %s on %s...\n", port, host);
int fd = connect_to(host, port);
if (fd < 0) {
return 1;
}
printf("connected to %s, now get %s\n", host, path);
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
printf("failed to set socket to non-blocking\n");
return 1;
}
fd_set fds_read;
fd_set fds_write;
fd_set fds_excep;
struct conn_data *conn = malloc(sizeof(struct conn_data));
conn->fd = fd;
conn->read_waker = NULL;
conn->write_waker = NULL;
// Hookup the IO
hyper_io *io = hyper_io_new();
hyper_io_set_userdata(io, (void *)conn);
hyper_io_set_read(io, read_cb);
hyper_io_set_write(io, write_cb);
printf("http handshake ...\n");
// We need an executor generally to poll futures
const hyper_executor *exec = hyper_executor_new();
// Prepare client options
hyper_clientconn_options *opts = hyper_clientconn_options_new();
hyper_clientconn_options_exec(opts, exec);
hyper_task *handshake = hyper_clientconn_handshake(io, opts);
hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE);
// Let's wait for the handshake to finish...
hyper_executor_push(exec, handshake);
// In case a task errors...
hyper_error *err;
// The polling state machine!
while (1) {
// Poll all ready tasks and act on them...
while (1) {
hyper_task *task = hyper_executor_poll(exec);
if (!task) {
break;
}
switch ((example_id) hyper_task_userdata(task)) {
case EXAMPLE_HANDSHAKE:
;
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
printf("handshake error!\n");
err = hyper_task_value(task);
goto fail;
}
assert(hyper_task_type(task) == HYPER_TASK_CLIENTCONN);
printf("preparing http request ...\n");
hyper_clientconn *client = hyper_task_value(task);
hyper_task_free(task);
// Prepare the request
hyper_request *req = hyper_request_new();
if (hyper_request_set_method(req, STR_ARG("GET"))) {
printf("error setting method\n");
return 1;
}
if (hyper_request_set_uri(req, STR_ARG(path))) {
printf("error setting uri\n");
return 1;
}
hyper_headers *req_headers = hyper_request_headers(req);
hyper_headers_set(req_headers, STR_ARG("host"), STR_ARG(host));
// Send it!
hyper_task *send = hyper_clientconn_send(client, req);
hyper_task_set_userdata(send, (void *)EXAMPLE_SEND);
printf("sending ...\n");
hyper_executor_push(exec, send);
// For this example, no longer need the client
hyper_clientconn_free(client);
break;
case EXAMPLE_SEND:
;
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
printf("send error!\n");
err = hyper_task_value(task);
goto fail;
}
assert(hyper_task_type(task) == HYPER_TASK_RESPONSE);
// Take the results
hyper_response *resp = hyper_task_value(task);
hyper_task_free(task);
uint16_t http_status = hyper_response_status(resp);
printf("\nResponse Status: %d\n", http_status);
hyper_headers *headers = hyper_response_headers(resp);
hyper_headers_foreach(headers, print_each_header, NULL);
printf("\n");
hyper_body *resp_body = hyper_response_body(resp);
hyper_task *foreach = hyper_body_foreach(resp_body, print_each_chunk, NULL);
hyper_task_set_userdata(foreach, (void *)EXAMPLE_RESP_BODY);
hyper_executor_push(exec, foreach);
// No longer need the response
hyper_response_free(resp);
break;
case EXAMPLE_RESP_BODY:
;
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
printf("body error!\n");
err = hyper_task_value(task);
goto fail;
}
assert(hyper_task_type(task) == HYPER_TASK_EMPTY);
printf("\n -- Done! -- \n");
// Cleaning up before exiting
hyper_task_free(task);
hyper_executor_free(exec);
free_conn_data(conn);
return 0;
case EXAMPLE_NOT_SET:
// A background task for hyper completed...
hyper_task_free(task);
break;
}
}
// All futures are pending on IO work, so select on the fds.
FD_ZERO(&fds_read);
FD_ZERO(&fds_write);
FD_ZERO(&fds_excep);
if (conn->read_waker) {
FD_SET(conn->fd, &fds_read);
}
if (conn->write_waker) {
FD_SET(conn->fd, &fds_write);
}
int sel_ret = select(conn->fd + 1, &fds_read, &fds_write, &fds_excep, NULL);
if (sel_ret < 0) {
printf("select() error\n");
return 1;
} else {
if (FD_ISSET(conn->fd, &fds_read)) {
hyper_waker_wake(conn->read_waker);
conn->read_waker = NULL;
}
if (FD_ISSET(conn->fd, &fds_write)) {
hyper_waker_wake(conn->write_waker);
conn->write_waker = NULL;
}
}
}
return 0;
fail:
if (err) {
printf("error code: %d\n", hyper_error_code(err));
// grab the error details
char errbuf [256];
size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf));
printf("details: %.*s\n", (int) errlen, errbuf);
// clean up the error
hyper_error_free(err);
}
return 1;
}

386
capi/examples/upload.c Normal file
View File

@@ -0,0 +1,386 @@
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/select.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include "hyper.h"
struct conn_data {
int fd;
hyper_waker *read_waker;
hyper_waker *write_waker;
};
static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) {
struct conn_data *conn = (struct conn_data *)userdata;
ssize_t ret = read(conn->fd, buf, buf_len);
if (ret < 0) {
int err = errno;
if (err == EAGAIN) {
// would block, register interest
if (conn->read_waker != NULL) {
hyper_waker_free(conn->read_waker);
}
conn->read_waker = hyper_context_waker(ctx);
return HYPER_IO_PENDING;
} else {
// kaboom
return HYPER_IO_ERROR;
}
} else {
return ret;
}
}
static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) {
struct conn_data *conn = (struct conn_data *)userdata;
ssize_t ret = write(conn->fd, buf, buf_len);
if (ret < 0) {
int err = errno;
if (err == EAGAIN) {
// would block, register interest
if (conn->write_waker != NULL) {
hyper_waker_free(conn->write_waker);
}
conn->write_waker = hyper_context_waker(ctx);
return HYPER_IO_PENDING;
} else {
// kaboom
return HYPER_IO_ERROR;
}
} else {
return ret;
}
}
static void free_conn_data(struct conn_data *conn) {
if (conn->read_waker) {
hyper_waker_free(conn->read_waker);
conn->read_waker = NULL;
}
if (conn->write_waker) {
hyper_waker_free(conn->write_waker);
conn->write_waker = NULL;
}
free(conn);
}
static int connect_to(const char *host, const char *port) {
struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo *result, *rp;
if (getaddrinfo(host, port, &hints, &result) != 0) {
printf("dns failed for %s\n", host);
return -1;
}
int sfd;
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1) {
continue;
}
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
break;
} else {
close(sfd);
}
}
freeaddrinfo(result);
// no address succeeded
if (rp == NULL) {
printf("connect failed for %s\n", host);
return -1;
}
return sfd;
}
struct upload_body {
int fd;
char *buf;
size_t len;
};
static int poll_req_upload(void *userdata,
hyper_context *ctx,
hyper_buf **chunk) {
struct upload_body* upload = userdata;
ssize_t res = read(upload->fd, upload->buf, upload->len);
if (res < 0) {
printf("error reading upload file: %d", errno);
return HYPER_POLL_ERROR;
} else if (res == 0) {
// All done!
*chunk = NULL;
return HYPER_POLL_READY;
} else {
*chunk = hyper_buf_copy(upload->buf, res);
return HYPER_POLL_READY;
}
}
static int print_each_header(void *userdata,
const uint8_t *name,
size_t name_len,
const uint8_t *value,
size_t value_len) {
printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value);
return HYPER_ITER_CONTINUE;
}
typedef enum {
EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set
EXAMPLE_HANDSHAKE,
EXAMPLE_SEND,
EXAMPLE_RESP_BODY
} example_id;
#define STR_ARG(XX) (uint8_t *)XX, strlen(XX)
int main(int argc, char *argv[]) {
const char *file = argc > 1 ? argv[1] : NULL;
const char *host = argc > 2 ? argv[2] : "httpbin.org";
const char *port = argc > 3 ? argv[3] : "80";
const char *path = argc > 4 ? argv[4] : "/post";
if (!file) {
printf("Pass a file path as the first argument.\n");
return 1;
}
struct upload_body upload;
upload.fd = open(file, O_RDONLY);
if (upload.fd < 0) {
printf("error opening file to upload: %d", errno);
return 1;
}
printf("connecting to port %s on %s...\n", port, host);
int fd = connect_to(host, port);
if (fd < 0) {
return 1;
}
printf("connected to %s, now upload to %s\n", host, path);
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
printf("failed to set socket to non-blocking\n");
return 1;
}
upload.len = 8192;
upload.buf = malloc(upload.len);
fd_set fds_read;
fd_set fds_write;
fd_set fds_excep;
struct conn_data *conn = malloc(sizeof(struct conn_data));
conn->fd = fd;
conn->read_waker = NULL;
conn->write_waker = NULL;
// Hookup the IO
hyper_io *io = hyper_io_new();
hyper_io_set_userdata(io, (void *)conn);
hyper_io_set_read(io, read_cb);
hyper_io_set_write(io, write_cb);
printf("http handshake ...\n");
// We need an executor generally to poll futures
const hyper_executor *exec = hyper_executor_new();
// Prepare client options
hyper_clientconn_options *opts = hyper_clientconn_options_new();
hyper_clientconn_options_exec(opts, exec);
hyper_task *handshake = hyper_clientconn_handshake(io, opts);
hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE);
// Let's wait for the handshake to finish...
hyper_executor_push(exec, handshake);
// This body will get filled in eventually...
hyper_body *resp_body = NULL;
// The polling state machine!
while (1) {
// Poll all ready tasks and act on them...
while (1) {
hyper_task *task = hyper_executor_poll(exec);
if (!task) {
break;
}
hyper_task_return_type task_type = hyper_task_type(task);
switch ((example_id) hyper_task_userdata(task)) {
case EXAMPLE_HANDSHAKE:
;
if (task_type == HYPER_TASK_ERROR) {
printf("handshake error!\n");
return 1;
}
assert(task_type == HYPER_TASK_CLIENTCONN);
printf("preparing http request ...\n");
hyper_clientconn *client = hyper_task_value(task);
hyper_task_free(task);
// Prepare the request
hyper_request *req = hyper_request_new();
if (hyper_request_set_method(req, STR_ARG("POST"))) {
printf("error setting method\n");
return 1;
}
if (hyper_request_set_uri(req, STR_ARG(path))) {
printf("error setting uri\n");
return 1;
}
hyper_headers *req_headers = hyper_request_headers(req);
hyper_headers_set(req_headers, STR_ARG("host"), STR_ARG(host));
// Prepare the req body
hyper_body *body = hyper_body_new();
hyper_body_set_userdata(body, &upload);
hyper_body_set_data_func(body, poll_req_upload);
hyper_request_set_body(req, body);
// Send it!
hyper_task *send = hyper_clientconn_send(client, req);
hyper_task_set_userdata(send, (void *)EXAMPLE_SEND);
printf("sending ...\n");
hyper_executor_push(exec, send);
// For this example, no longer need the client
hyper_clientconn_free(client);
break;
case EXAMPLE_SEND:
;
if (task_type == HYPER_TASK_ERROR) {
printf("send error!\n");
return 1;
}
assert(task_type == HYPER_TASK_RESPONSE);
// Take the results
hyper_response *resp = hyper_task_value(task);
hyper_task_free(task);
uint16_t http_status = hyper_response_status(resp);
printf("\nResponse Status: %d\n", http_status);
hyper_headers *headers = hyper_response_headers(resp);
hyper_headers_foreach(headers, print_each_header, NULL);
printf("\n");
resp_body = hyper_response_body(resp);
// Set us up to peel data from the body a chunk at a time
hyper_task *body_data = hyper_body_data(resp_body);
hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY);
hyper_executor_push(exec, body_data);
// No longer need the response
hyper_response_free(resp);
break;
case EXAMPLE_RESP_BODY:
;
if (task_type == HYPER_TASK_ERROR) {
printf("body error!\n");
return 1;
}
if (task_type == HYPER_TASK_BUF) {
hyper_buf *chunk = hyper_task_value(task);
write(1, hyper_buf_bytes(chunk), hyper_buf_len(chunk));
hyper_buf_free(chunk);
hyper_task_free(task);
hyper_task *body_data = hyper_body_data(resp_body);
hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY);
hyper_executor_push(exec, body_data);
break;
} else {
assert(task_type == HYPER_TASK_EMPTY);
hyper_task_free(task);
hyper_body_free(resp_body);
printf("\n -- Done! -- \n");
// Cleaning up before exiting
hyper_executor_free(exec);
free_conn_data(conn);
free(upload.buf);
return 0;
}
case EXAMPLE_NOT_SET:
// A background task for hyper completed...
hyper_task_free(task);
break;
}
}
// All futures are pending on IO work, so select on the fds.
FD_ZERO(&fds_read);
FD_ZERO(&fds_write);
FD_ZERO(&fds_excep);
if (conn->read_waker) {
FD_SET(conn->fd, &fds_read);
}
if (conn->write_waker) {
FD_SET(conn->fd, &fds_write);
}
int sel_ret = select(conn->fd + 1, &fds_read, &fds_write, &fds_excep, NULL);
if (sel_ret < 0) {
printf("select() error\n");
return 1;
} else {
if (FD_ISSET(conn->fd, &fds_read)) {
hyper_waker_wake(conn->read_waker);
conn->read_waker = NULL;
}
if (FD_ISSET(conn->fd, &fds_write)) {
hyper_waker_wake(conn->write_waker);
conn->write_waker = NULL;
}
}
}
return 0;
}

72
capi/gen_header.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
CAPI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
WORK_DIR=`mktemp -d`
# check if tmp dir was created
if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then
echo "Could not create temp dir"
exit 1
fi
header_file_backup="$CAPI_DIR/include/hyper.h.backup"
function cleanup {
#echo "$WORK_DIR"
rm -rf "$WORK_DIR"
rm "$header_file_backup"
}
trap cleanup EXIT
mkdir "$WORK_DIR/src"
# Fake a library
cat > "$WORK_DIR/src/lib.rs" << EOF
#[path = "$CAPI_DIR/../src/ffi/mod.rs"]
pub mod ffi;
EOF
# And its Cargo.toml
cat > "$WORK_DIR/Cargo.toml" << EOF
[package]
name = "hyper"
version = "0.0.0"
edition = "2018"
publish = false
[dependencies]
EOF
cp "$CAPI_DIR/include/hyper.h" "$header_file_backup"
#cargo metadata --no-default-features --features ffi --format-version 1 > "$WORK_DIR/metadata.json"
cd $WORK_DIR
# Expand just the ffi module
cargo rustc -- -Z unstable-options --pretty=expanded > expanded.rs 2>/dev/null
# Replace the previous copy with the single expanded file
rm -rf ./src
mkdir src
mv expanded.rs src/lib.rs
# Bindgen!
cbindgen\
-c "$CAPI_DIR/cbindgen.toml"\
--lockfile "$CAPI_DIR/../Cargo.lock"\
-o "$CAPI_DIR/include/hyper.h"\
$1
bindgen_exit_code=$?
if [[ "--verify" == "$1" && "$bindgen_exit_code" != 0 ]]; then
echo "diff generated (<) vs backup (>)"
diff "$CAPI_DIR/include/hyper.h" "$header_file_backup"
fi
exit $bindgen_exit_code

554
capi/include/hyper.h Normal file
View File

@@ -0,0 +1,554 @@
#ifndef _HYPER_H
#define _HYPER_H
#include <stdint.h>
#include <stddef.h>
#define HYPER_ITER_CONTINUE 0
#define HYPER_ITER_BREAK 1
#define HYPER_HTTP_VERSION_NONE 0
#define HYPER_HTTP_VERSION_1_0 10
#define HYPER_HTTP_VERSION_1_1 11
#define HYPER_HTTP_VERSION_2 20
#define HYPER_IO_PENDING 4294967295
#define HYPER_IO_ERROR 4294967294
#define HYPER_POLL_READY 0
#define HYPER_POLL_PENDING 1
#define HYPER_POLL_ERROR 3
typedef enum {
/*
All is well.
*/
HYPERE_OK,
/*
General error, details in the `hyper_error *`.
*/
HYPERE_ERROR,
/*
A function argument was invalid.
*/
HYPERE_INVALID_ARG,
/*
The IO transport returned an EOF when one wasn't expected.
This typically means an HTTP request or response was expected, but the
connection closed cleanly without sending (all of) it.
*/
HYPERE_UNEXPECTED_EOF,
/*
Aborted by a user supplied callback.
*/
HYPERE_ABORTED_BY_CALLBACK,
/*
An optional hyper feature was not enabled.
*/
HYPERE_FEATURE_NOT_ENABLED,
} hyper_code;
typedef enum {
/*
The value of this task is null (does not imply an error).
*/
HYPER_TASK_EMPTY,
/*
The value of this task is `hyper_error *`.
*/
HYPER_TASK_ERROR,
/*
The value of this task is `hyper_clientconn *`.
*/
HYPER_TASK_CLIENTCONN,
/*
The value of this task is `hyper_response *`.
*/
HYPER_TASK_RESPONSE,
/*
The value of this task is `hyper_buf *`.
*/
HYPER_TASK_BUF,
} hyper_task_return_type;
typedef struct hyper_executor hyper_executor;
typedef struct hyper_io hyper_io;
typedef struct hyper_task hyper_task;
typedef struct hyper_body hyper_body;
typedef struct hyper_buf hyper_buf;
typedef struct hyper_clientconn hyper_clientconn;
typedef struct hyper_clientconn_options hyper_clientconn_options;
typedef struct hyper_context hyper_context;
typedef struct hyper_error hyper_error;
typedef struct hyper_headers hyper_headers;
typedef struct hyper_request hyper_request;
typedef struct hyper_response hyper_response;
typedef struct hyper_waker hyper_waker;
typedef int (*hyper_body_foreach_callback)(void*, const hyper_buf*);
typedef int (*hyper_body_data_callback)(void*, hyper_context*, hyper_buf**);
typedef int (*hyper_headers_foreach_callback)(void*, const uint8_t*, size_t, const uint8_t*, size_t);
typedef size_t (*hyper_io_read_callback)(void*, hyper_context*, uint8_t*, size_t);
typedef size_t (*hyper_io_write_callback)(void*, hyper_context*, const uint8_t*, size_t);
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/*
Returns a static ASCII (null terminated) string of the hyper version.
*/
const char *hyper_version(void);
/*
Create a new "empty" body.
If not configured, this body acts as an empty payload.
*/
hyper_body *hyper_body_new(void);
/*
Free a `hyper_body *`.
*/
void hyper_body_free(hyper_body *body);
/*
Return a task that will poll the body for the next buffer of data.
The task value 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.
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.
*/
hyper_task *hyper_body_data(hyper_body *body);
/*
Return a task that will poll the body and execute the callback with each
body chunk that is received.
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 callback should return `HYPER_ITER_CONTINUE` to continue iterating
chunks as they are received, or `HYPER_ITER_BREAK` to cancel.
This will consume the `hyper_body *`, you shouldn't use it anymore or free it.
*/
hyper_task *hyper_body_foreach(hyper_body *body, hyper_body_foreach_callback func, void *userdata);
/*
Set userdata on this body, which will be passed to callback functions.
*/
void hyper_body_set_userdata(hyper_body *body, void *userdata);
/*
Set the 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`.
If there is data available, the `hyper_buf **` argument should be set
to a `hyper_buf *` containing the data, and `HYPER_POLL_READY` should
be returned.
Returning `HYPER_POLL_READY` while the `hyper_buf **` argument points
to `NULL` will indicate the body has completed all data.
If there is more data to send, but it isn't yet available, a
`hyper_waker` should be saved from the `hyper_context *` argument, and
`HYPER_POLL_PENDING` should be returned. You must wake the saved waker
to signal the task when data is available.
If some error has occurred, you can return `HYPER_POLL_ERROR` to abort
the body.
*/
void hyper_body_set_data_func(hyper_body *body, hyper_body_data_callback func);
/*
Create a new `hyper_buf *` by copying the provided bytes.
This makes an owned copy of the bytes, so the `buf` argument can be
freed or changed afterwards.
*/
hyper_buf *hyper_buf_copy(const uint8_t *buf, size_t len);
/*
Get a pointer to the bytes in this buffer.
This should be used in conjunction with `hyper_buf_len` to get the length
of the bytes data.
This pointer is borrowed data, and not valid once the `hyper_buf` is
consumed/freed.
*/
const uint8_t *hyper_buf_bytes(const hyper_buf *buf);
/*
Get the length of the bytes this buffer contains.
*/
size_t hyper_buf_len(const hyper_buf *buf);
/*
Free this buffer.
*/
void hyper_buf_free(hyper_buf *buf);
/*
Starts an HTTP client connection handshake using the provided IO transport
and options.
Both the `io` and the `options` are consumed in this function call.
The returned `hyper_task *` must be polled with an executor until the
handshake completes, at which point the value can be taken.
*/
hyper_task *hyper_clientconn_handshake(hyper_io *io, hyper_clientconn_options *options);
/*
Send a request on the client connection.
Returns a task that needs to be polled until it is ready. When ready, the
task yields a `hyper_response *`.
*/
hyper_task *hyper_clientconn_send(hyper_clientconn *conn, hyper_request *req);
/*
Free a `hyper_clientconn *`.
*/
void hyper_clientconn_free(hyper_clientconn *conn);
/*
Creates a new set of HTTP clientconn options to be used in a handshake.
*/
hyper_clientconn_options *hyper_clientconn_options_new(void);
/*
Free a `hyper_clientconn_options *`.
*/
void hyper_clientconn_options_free(hyper_clientconn_options *opts);
/*
Set the client background task executor.
This does not consume the `options` or the `exec`.
*/
void hyper_clientconn_options_exec(hyper_clientconn_options *opts, const hyper_executor *exec);
/*
Set the whether to use HTTP2.
Pass `0` to disable, `1` to enable.
*/
hyper_code hyper_clientconn_options_http2(hyper_clientconn_options *opts, int enabled);
/*
Frees a `hyper_error`.
*/
void hyper_error_free(hyper_error *err);
/*
Get an equivalent `hyper_code` from this error.
*/
hyper_code hyper_error_code(const hyper_error *err);
/*
Print the details of this error to a buffer.
The `dst_len` value must be the maximum length that the buffer can
store.
The return value is number of bytes that were written to `dst`.
*/
size_t hyper_error_print(const hyper_error *err, uint8_t *dst, size_t dst_len);
/*
Construct a new HTTP request.
*/
hyper_request *hyper_request_new(void);
/*
Free an HTTP request if not going to send it on a client.
*/
void hyper_request_free(hyper_request *req);
/*
Set the HTTP Method of the request.
*/
hyper_code hyper_request_set_method(hyper_request *req, const uint8_t *method, size_t method_len);
/*
Set the URI of the request.
*/
hyper_code hyper_request_set_uri(hyper_request *req, const uint8_t *uri, size_t uri_len);
/*
Set the preferred HTTP version of the request.
The version value should be one of the `HYPER_HTTP_VERSION_` constants.
Note that this won't change the major HTTP version of the connection,
since that is determined at the handshake step.
*/
hyper_code hyper_request_set_version(hyper_request *req, int version);
/*
Gets a reference to the HTTP headers of this request
This is not an owned reference, so it should not be accessed after the
`hyper_request` has been consumed.
*/
hyper_headers *hyper_request_headers(hyper_request *req);
/*
Set the body of the request.
The default is an empty body.
This takes ownership of the `hyper_body *`, you must not use it or
free it after setting it on the request.
*/
hyper_code hyper_request_set_body(hyper_request *req, hyper_body *body);
/*
Free an HTTP response after using it.
*/
void hyper_response_free(hyper_response *resp);
/*
Get the HTTP-Status code of this response.
It will always be within the range of 100-599.
*/
uint16_t hyper_response_status(const hyper_response *resp);
/*
Get the HTTP version used by this response.
The returned value could be:
- `HYPER_HTTP_VERSION_1_0`
- `HYPER_HTTP_VERSION_1_1`
- `HYPER_HTTP_VERSION_2`
- `HYPER_HTTP_VERSION_NONE` if newer (or older).
*/
int hyper_response_version(const hyper_response *resp);
/*
Gets a reference to the HTTP headers of this response.
This is not an owned reference, so it should not be accessed after the
`hyper_response` has been freed.
*/
hyper_headers *hyper_response_headers(hyper_response *resp);
/*
Take ownership of the body of this response.
It is safe to free the response even after taking ownership of its body.
*/
hyper_body *hyper_response_body(hyper_response *resp);
/*
Iterates the headers passing each name and value pair to the callback.
The `userdata` pointer is also passed to the callback.
The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or
`HYPER_ITER_BREAK` to stop.
*/
void hyper_headers_foreach(const hyper_headers *headers,
hyper_headers_foreach_callback func,
void *userdata);
/*
Sets the header with the provided name to the provided value.
This overwrites any previous value set for the header.
*/
hyper_code hyper_headers_set(hyper_headers *headers,
const uint8_t *name,
size_t name_len,
const uint8_t *value,
size_t value_len);
/*
Adds the provided value to the list of the provided name.
If there were already existing values for the name, this will append the
new value to the internal list.
*/
hyper_code hyper_headers_add(hyper_headers *headers,
const uint8_t *name,
size_t name_len,
const uint8_t *value,
size_t value_len);
/*
Create a new IO type used to represent a transport.
The read and write functions of this transport should be set with
`hyper_io_set_read` and `hyper_io_set_write`.
*/
hyper_io *hyper_io_new(void);
/*
Free an unused `hyper_io *`.
This is typically only useful if you aren't going to pass ownership
of the IO handle to hyper, such as with `hyper_clientconn_handshake()`.
*/
void hyper_io_free(hyper_io *io);
/*
Set the user data pointer for this IO to some value.
This value is passed as an argument to the read and write callbacks.
*/
void hyper_io_set_userdata(hyper_io *io, void *data);
/*
Set the read function for this IO transport.
Data that is read from the transport should be put in the `buf` pointer,
up to `buf_len` bytes. The number of bytes read should be the return value.
It is undefined behavior to try to access the bytes in the `buf` pointer,
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 an irrecoverable error reading data, then `HYPER_IO_ERROR`
should be the return value.
*/
void hyper_io_set_read(hyper_io *io, hyper_io_read_callback func);
/*
Set the write function for this IO transport.
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 an irrecoverable error reading data, then `HYPER_IO_ERROR`
should be the return value.
*/
void hyper_io_set_write(hyper_io *io, hyper_io_write_callback func);
/*
Creates a new task executor.
*/
const hyper_executor *hyper_executor_new(void);
/*
Frees an executor and any incomplete tasks still part of it.
*/
void hyper_executor_free(const hyper_executor *exec);
/*
Push a task onto the executor.
The executor takes ownership of the task, it should not be accessed
again unless returned back to the user with `hyper_executor_poll`.
*/
hyper_code hyper_executor_push(const hyper_executor *exec, hyper_task *task);
/*
Polls the executor, trying to make progress on any tasks that have notified
that they are ready again.
If ready, returns a task from the executor that has completed.
If there are no ready tasks, this returns `NULL`.
*/
hyper_task *hyper_executor_poll(const hyper_executor *exec);
/*
Free a task.
*/
void hyper_task_free(hyper_task *task);
/*
Takes the output value of this task.
This must only be called once polling the task on an executor has finished
this task.
Use `hyper_task_type` to determine the type of the `void *` return value.
*/
void *hyper_task_value(hyper_task *task);
/*
Query the return type of this task.
*/
hyper_task_return_type hyper_task_type(hyper_task *task);
/*
Set a user data pointer to be associated with this task.
This value will be passed to task callbacks, and can be checked later
with `hyper_task_userdata`.
*/
void hyper_task_set_userdata(hyper_task *task, void *userdata);
/*
Retrieve the userdata that has been set via `hyper_task_set_userdata`.
*/
void *hyper_task_userdata(hyper_task *task);
/*
Copies a waker out of the task context.
*/
hyper_waker *hyper_context_waker(hyper_context *cx);
/*
Free a waker that hasn't been woken.
*/
void hyper_waker_free(hyper_waker *waker);
/*
Free a waker that hasn't been woken.
*/
void hyper_waker_wake(hyper_waker *waker);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* _HYPER_H */