Skip to content

Commit 3ae1581

Browse files
committedJan 8, 2021
feat(ffi): Initial C API for hyper
1 parent 8861f9a commit 3ae1581

22 files changed

+2910
-14
lines changed
 

‎.github/workflows/CI.yml

+48-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
- style
1717
- test
1818
- features
19+
- ffi
1920
- doc
2021
steps:
2122
- run: exit 0
@@ -111,7 +112,53 @@ jobs:
111112
run: cargo install cargo-hack
112113

113114
- name: check --each-feature
114-
run: cargo hack check --each-feature -Z avoid-dev-deps
115+
run: cargo hack check --each-feature --skip ffi -Z avoid-dev-deps
116+
117+
ffi:
118+
name: Test C API (FFI)
119+
needs: [style]
120+
121+
runs-on: ubuntu-latest
122+
123+
steps:
124+
- name: Checkout
125+
uses: actions/checkout@v1
126+
127+
- name: Install Rust
128+
uses: actions-rs/toolchain@v1
129+
with:
130+
profile: minimal
131+
toolchain: stable
132+
override: true
133+
134+
- name: Install cbindgen
135+
uses: actions-rs/cargo@v1
136+
with:
137+
command: install
138+
args: cbindgen
139+
140+
- name: Build FFI
141+
uses: actions-rs/cargo@v1
142+
env:
143+
RUSTFLAGS: --cfg hyper_unstable_ffi
144+
with:
145+
command: build
146+
args: --features client,http1,http2,ffi
147+
148+
# TODO: re-enable check once figuring out how to get it working in CI
149+
# - name: Verify cbindgen
150+
# run: ./capi/gen_header.sh --verify
151+
152+
- name: Make Examples
153+
run: cd capi/examples && make client
154+
155+
- name: Run FFI unit tests
156+
uses: actions-rs/cargo@v1
157+
env:
158+
RUSTFLAGS: --cfg hyper_unstable_ffi
159+
with:
160+
command: test
161+
args: --features full,ffi --lib
115162

116163
doc:
117164
name: Build docs
Has a conversation. Original line has a conversation.

‎.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
/target
2-
/Cargo.lock
1+
target
2+
Cargo.lock

‎Cargo.toml

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ include = [
1919
#"build.rs",
2020
]
2121

22+
[lib]
23+
crate-type = ["lib", "staticlib", "cdylib"]
24+
2225
[dependencies]
2326
bytes = "1"
2427
futures-core = { version = "0.3", default-features = false }
@@ -38,6 +41,7 @@ want = "0.3"
3841

3942
# Optional
4043

44+
libc = { version = "0.2", optional = true }
4145
socket2 = { version = "0.3.16", optional = true }
4246

4347
[dev-dependencies]
@@ -94,7 +98,6 @@ server = []
9498
stream = []
9599

96100
# Tokio support
97-
98101
runtime = [
99102
"tcp",
100103
"tokio/rt",
@@ -106,6 +109,9 @@ tcp = [
106109
"tokio/time",
107110
]
108111

112+
# C-API support (currently unstable (no semver))
113+
ffi = ["libc"]
114+
109115
# internal features used in CI
110116
nightly = []
111117
__internal_happy_eyeballs_tests = []

‎capi/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# C API for hyper
2+
3+
This provides auxiliary pieces for a C API to use the hyper library.
4+
5+
## Unstable
6+
7+
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.
8+
9+
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.
10+
11+
## Building
12+
13+
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:
14+
15+
```
16+
RUSTFLAGS="--cfg hyper_unstable_ffi" cargo build --features client,http1,http2,ffi
17+
```

‎capi/cbindgen.toml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
language = "C"
2+
include_guard = "_HYPER_H"
3+
no_includes = true
4+
sys_includes = ["stdint.h", "stddef.h"]
5+
cpp_compat = true
6+
documentation_style = "c"
7+
8+
[parse.expand]
9+
crates = ["hyper-capi"]
10+
11+
[export.rename]
12+
"Exec" = "hyper_executor"
13+
"Io" = "hyper_io"
14+
"Task" = "hyper_task"

‎capi/examples/Makefile

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#
2+
# Build the example client
3+
#
4+
5+
TARGET = client
6+
7+
OBJS = client.o
8+
9+
RPATH=$(PWD)/../../target/debug
10+
CFLAGS = -I../include
11+
LDFLAGS = -L$(RPATH) -Wl,-rpath,$(RPATH)
12+
LIBS = -lhyper
13+
14+
$(TARGET): $(OBJS)
15+
$(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LIBS)
16+
17+
upload: upload.o
18+
$(CC) -o upload upload.o $(LDFLAGS) $(LIBS)
19+
20+
clean:
21+
rm -f $(OBJS) $(TARGET)
22+
rm -f upload upload.o

‎capi/examples/client.c

+343
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
#include <stdlib.h>
2+
#include <stdio.h>
3+
#include <unistd.h>
4+
#include <fcntl.h>
5+
#include <errno.h>
6+
#include <sys/select.h>
7+
#include <assert.h>
8+
9+
#include <sys/types.h>
10+
#include <sys/socket.h>
11+
#include <netdb.h>
12+
#include <string.h>
13+
14+
#include "hyper.h"
15+
16+
17+
struct conn_data {
18+
int fd;
19+
hyper_waker *read_waker;
20+
hyper_waker *write_waker;
21+
};
22+
23+
static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) {
24+
struct conn_data *conn = (struct conn_data *)userdata;
25+
ssize_t ret = read(conn->fd, buf, buf_len);
26+
27+
if (ret < 0) {
28+
int err = errno;
29+
if (err == EAGAIN) {
30+
// would block, register interest
31+
if (conn->read_waker != NULL) {
32+
hyper_waker_free(conn->read_waker);
33+
}
34+
conn->read_waker = hyper_context_waker(ctx);
35+
return HYPER_IO_PENDING;
36+
} else {
37+
// kaboom
38+
return HYPER_IO_ERROR;
39+
}
40+
} else {
41+
return ret;
42+
}
43+
}
44+
45+
static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) {
46+
struct conn_data *conn = (struct conn_data *)userdata;
47+
ssize_t ret = write(conn->fd, buf, buf_len);
48+
49+
if (ret < 0) {
50+
int err = errno;
51+
if (err == EAGAIN) {
52+
// would block, register interest
53+
if (conn->write_waker != NULL) {
54+
hyper_waker_free(conn->write_waker);
55+
}
56+
conn->write_waker = hyper_context_waker(ctx);
57+
return HYPER_IO_PENDING;
58+
} else {
59+
// kaboom
60+
return HYPER_IO_ERROR;
61+
}
62+
} else {
63+
return ret;
64+
}
65+
}
66+
67+
static void free_conn_data(struct conn_data *conn) {
68+
if (conn->read_waker) {
69+
hyper_waker_free(conn->read_waker);
70+
conn->read_waker = NULL;
71+
}
72+
if (conn->write_waker) {
73+
hyper_waker_free(conn->write_waker);
74+
conn->write_waker = NULL;
75+
}
76+
77+
free(conn);
78+
}
79+
80+
static int connect_to(const char *host, const char *port) {
81+
struct addrinfo hints;
82+
memset(&hints, 0, sizeof(struct addrinfo));
83+
hints.ai_family = AF_UNSPEC;
84+
hints.ai_socktype = SOCK_STREAM;
85+
86+
struct addrinfo *result, *rp;
87+
if (getaddrinfo(host, port, &hints, &result) != 0) {
88+
printf("dns failed for %s\n", host);
89+
return -1;
90+
}
91+
92+
int sfd;
93+
for (rp = result; rp != NULL; rp = rp->ai_next) {
94+
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
95+
if (sfd == -1) {
96+
continue;
97+
}
98+
99+
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
100+
break;
101+
} else {
102+
close(sfd);
103+
}
104+
}
105+
106+
freeaddrinfo(result);
107+
108+
// no address succeeded
109+
if (rp == NULL) {
110+
printf("connect failed for %s\n", host);
111+
return -1;
112+
}
113+
114+
return sfd;
115+
}
116+
117+
static int print_each_header(void *userdata,
118+
const uint8_t *name,
119+
size_t name_len,
120+
const uint8_t *value,
121+
size_t value_len) {
122+
printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value);
123+
return HYPER_ITER_CONTINUE;
124+
}
125+
126+
static int print_each_chunk(void *userdata, const hyper_buf *chunk) {
127+
const uint8_t *buf = hyper_buf_bytes(chunk);
128+
size_t len = hyper_buf_len(chunk);
129+
130+
write(1, buf, len);
131+
132+
return HYPER_ITER_CONTINUE;
133+
}
134+
135+
typedef enum {
136+
EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set
137+
EXAMPLE_HANDSHAKE,
138+
EXAMPLE_SEND,
139+
EXAMPLE_RESP_BODY
140+
} example_id;
141+
142+
#define STR_ARG(XX) (uint8_t *)XX, strlen(XX)
143+
144+
int main(int argc, char *argv[]) {
145+
const char *host = argc > 1 ? argv[1] : "httpbin.org";
146+
const char *port = argc > 2 ? argv[2] : "80";
147+
const char *path = argc > 3 ? argv[3] : "/";
148+
printf("connecting to port %s on %s...\n", port, host);
149+
150+
int fd = connect_to(host, port);
151+
if (fd < 0) {
152+
return 1;
153+
}
154+
printf("connected to %s, now get %s\n", host, path);
155+
156+
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
157+
printf("failed to set socket to non-blocking\n");
158+
return 1;
159+
}
160+
161+
fd_set fds_read;
162+
fd_set fds_write;
163+
fd_set fds_excep;
164+
165+
struct conn_data *conn = malloc(sizeof(struct conn_data));
166+
167+
conn->fd = fd;
168+
conn->read_waker = NULL;
169+
conn->write_waker = NULL;
170+
171+
172+
// Hookup the IO
173+
hyper_io *io = hyper_io_new();
174+
hyper_io_set_userdata(io, (void *)conn);
175+
hyper_io_set_read(io, read_cb);
176+
hyper_io_set_write(io, write_cb);
177+
178+
printf("http handshake ...\n");
179+
180+
// We need an executor generally to poll futures
181+
const hyper_executor *exec = hyper_executor_new();
182+
183+
// Prepare client options
184+
hyper_clientconn_options *opts = hyper_clientconn_options_new();
185+
hyper_clientconn_options_exec(opts, exec);
186+
187+
hyper_task *handshake = hyper_clientconn_handshake(io, opts);
188+
hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE);
189+
190+
// Let's wait for the handshake to finish...
191+
hyper_executor_push(exec, handshake);
192+
193+
// In case a task errors...
194+
hyper_error *err;
195+
196+
// The polling state machine!
197+
while (1) {
198+
// Poll all ready tasks and act on them...
199+
while (1) {
200+
hyper_task *task = hyper_executor_poll(exec);
201+
if (!task) {
202+
break;
203+
}
204+
switch ((example_id) hyper_task_userdata(task)) {
205+
case EXAMPLE_HANDSHAKE:
206+
;
207+
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
208+
printf("handshake error!\n");
209+
err = hyper_task_value(task);
210+
goto fail;
211+
}
212+
assert(hyper_task_type(task) == HYPER_TASK_CLIENTCONN);
213+
214+
printf("preparing http request ...\n");
215+
216+
hyper_clientconn *client = hyper_task_value(task);
217+
hyper_task_free(task);
218+
219+
// Prepare the request
220+
hyper_request *req = hyper_request_new();
221+
if (hyper_request_set_method(req, STR_ARG("GET"))) {
222+
printf("error setting method\n");
223+
return 1;
224+
}
225+
if (hyper_request_set_uri(req, STR_ARG(path))) {
226+
printf("error setting uri\n");
227+
return 1;
228+
}
229+
230+
hyper_headers *req_headers = hyper_request_headers(req);
231+
hyper_headers_set(req_headers, STR_ARG("host"), STR_ARG(host));
232+
233+
// Send it!
234+
hyper_task *send = hyper_clientconn_send(client, req);
235+
hyper_task_set_userdata(send, (void *)EXAMPLE_SEND);
236+
printf("sending ...\n");
237+
hyper_executor_push(exec, send);
238+
239+
// For this example, no longer need the client
240+
hyper_clientconn_free(client);
241+
242+
break;
243+
case EXAMPLE_SEND:
244+
;
245+
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
246+
printf("send error!\n");
247+
err = hyper_task_value(task);
248+
goto fail;
249+
}
250+
assert(hyper_task_type(task) == HYPER_TASK_RESPONSE);
251+
252+
// Take the results
253+
hyper_response *resp = hyper_task_value(task);
254+
hyper_task_free(task);
255+
256+
uint16_t http_status = hyper_response_status(resp);
257+
258+
printf("\nResponse Status: %d\n", http_status);
259+
260+
hyper_headers *headers = hyper_response_headers(resp);
261+
hyper_headers_foreach(headers, print_each_header, NULL);
262+
printf("\n");
263+
264+
hyper_body *resp_body = hyper_response_body(resp);
265+
hyper_task *foreach = hyper_body_foreach(resp_body, print_each_chunk, NULL);
266+
hyper_task_set_userdata(foreach, (void *)EXAMPLE_RESP_BODY);
267+
hyper_executor_push(exec, foreach);
268+
269+
// No longer need the response
270+
hyper_response_free(resp);
271+
272+
break;
273+
case EXAMPLE_RESP_BODY:
274+
;
275+
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
276+
printf("body error!\n");
277+
err = hyper_task_value(task);
278+
goto fail;
279+
}
280+
281+
assert(hyper_task_type(task) == HYPER_TASK_EMPTY);
282+
283+
printf("\n -- Done! -- \n");
284+
285+
// Cleaning up before exiting
286+
hyper_task_free(task);
287+
hyper_executor_free(exec);
288+
free_conn_data(conn);
289+
290+
return 0;
291+
case EXAMPLE_NOT_SET:
292+
// A background task for hyper completed...
293+
hyper_task_free(task);
294+
break;
295+
}
296+
}
297+
298+
// All futures are pending on IO work, so select on the fds.
299+
300+
FD_ZERO(&fds_read);
301+
FD_ZERO(&fds_write);
302+
FD_ZERO(&fds_excep);
303+
304+
if (conn->read_waker) {
305+
FD_SET(conn->fd, &fds_read);
306+
}
307+
if (conn->write_waker) {
308+
FD_SET(conn->fd, &fds_write);
309+
}
310+
311+
int sel_ret = select(conn->fd + 1, &fds_read, &fds_write, &fds_excep, NULL);
312+
313+
if (sel_ret < 0) {
314+
printf("select() error\n");
315+
return 1;
316+
} else {
317+
if (FD_ISSET(conn->fd, &fds_read)) {
318+
hyper_waker_wake(conn->read_waker);
319+
conn->read_waker = NULL;
320+
}
321+
if (FD_ISSET(conn->fd, &fds_write)) {
322+
hyper_waker_wake(conn->write_waker);
323+
conn->write_waker = NULL;
324+
}
325+
}
326+
327+
}
328+
329+
return 0;
330+
331+
fail:
332+
if (err) {
333+
printf("error code: %d\n", hyper_error_code(err));
334+
// grab the error details
335+
char errbuf [256];
336+
size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf));
337+
printf("details: %.*s\n", (int) errlen, errbuf);
338+
339+
// clean up the error
340+
hyper_error_free(err);
341+
}
342+
return 1;
343+
}

‎capi/examples/upload.c

+386
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
#include <stdlib.h>
2+
#include <stdio.h>
3+
#include <unistd.h>
4+
#include <fcntl.h>
5+
#include <errno.h>
6+
#include <sys/select.h>
7+
#include <assert.h>
8+
9+
#include <sys/types.h>
10+
#include <sys/socket.h>
11+
#include <netdb.h>
12+
#include <string.h>
13+
14+
#include "hyper.h"
15+
16+
17+
struct conn_data {
18+
int fd;
19+
hyper_waker *read_waker;
20+
hyper_waker *write_waker;
21+
};
22+
23+
static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) {
24+
struct conn_data *conn = (struct conn_data *)userdata;
25+
ssize_t ret = read(conn->fd, buf, buf_len);
26+
27+
if (ret < 0) {
28+
int err = errno;
29+
if (err == EAGAIN) {
30+
// would block, register interest
31+
if (conn->read_waker != NULL) {
32+
hyper_waker_free(conn->read_waker);
33+
}
34+
conn->read_waker = hyper_context_waker(ctx);
35+
return HYPER_IO_PENDING;
36+
} else {
37+
// kaboom
38+
return HYPER_IO_ERROR;
39+
}
40+
} else {
41+
return ret;
42+
}
43+
}
44+
45+
static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) {
46+
struct conn_data *conn = (struct conn_data *)userdata;
47+
ssize_t ret = write(conn->fd, buf, buf_len);
48+
49+
if (ret < 0) {
50+
int err = errno;
51+
if (err == EAGAIN) {
52+
// would block, register interest
53+
if (conn->write_waker != NULL) {
54+
hyper_waker_free(conn->write_waker);
55+
}
56+
conn->write_waker = hyper_context_waker(ctx);
57+
return HYPER_IO_PENDING;
58+
} else {
59+
// kaboom
60+
return HYPER_IO_ERROR;
61+
}
62+
} else {
63+
return ret;
64+
}
65+
}
66+
67+
static void free_conn_data(struct conn_data *conn) {
68+
if (conn->read_waker) {
69+
hyper_waker_free(conn->read_waker);
70+
conn->read_waker = NULL;
71+
}
72+
if (conn->write_waker) {
73+
hyper_waker_free(conn->write_waker);
74+
conn->write_waker = NULL;
75+
}
76+
77+
free(conn);
78+
}
79+
80+
static int connect_to(const char *host, const char *port) {
81+
struct addrinfo hints;
82+
memset(&hints, 0, sizeof(struct addrinfo));
83+
hints.ai_family = AF_UNSPEC;
84+
hints.ai_socktype = SOCK_STREAM;
85+
86+
struct addrinfo *result, *rp;
87+
if (getaddrinfo(host, port, &hints, &result) != 0) {
88+
printf("dns failed for %s\n", host);
89+
return -1;
90+
}
91+
92+
int sfd;
93+
for (rp = result; rp != NULL; rp = rp->ai_next) {
94+
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
95+
if (sfd == -1) {
96+
continue;
97+
}
98+
99+
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
100+
break;
101+
} else {
102+
close(sfd);
103+
}
104+
}
105+
106+
freeaddrinfo(result);
107+
108+
// no address succeeded
109+
if (rp == NULL) {
110+
printf("connect failed for %s\n", host);
111+
return -1;
112+
}
113+
114+
return sfd;
115+
}
116+
117+
struct upload_body {
118+
int fd;
119+
char *buf;
120+
size_t len;
121+
};
122+
123+
static int poll_req_upload(void *userdata,
124+
hyper_context *ctx,
125+
hyper_buf **chunk) {
126+
struct upload_body* upload = userdata;
127+
128+
ssize_t res = read(upload->fd, upload->buf, upload->len);
129+
if (res < 0) {
130+
printf("error reading upload file: %d", errno);
131+
return HYPER_POLL_ERROR;
132+
} else if (res == 0) {
133+
// All done!
134+
*chunk = NULL;
135+
return HYPER_POLL_READY;
136+
} else {
137+
*chunk = hyper_buf_copy(upload->buf, res);
138+
return HYPER_POLL_READY;
139+
}
140+
}
141+
142+
static int print_each_header(void *userdata,
143+
const uint8_t *name,
144+
size_t name_len,
145+
const uint8_t *value,
146+
size_t value_len) {
147+
printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value);
148+
return HYPER_ITER_CONTINUE;
149+
}
150+
151+
typedef enum {
152+
EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set
153+
EXAMPLE_HANDSHAKE,
154+
EXAMPLE_SEND,
155+
EXAMPLE_RESP_BODY
156+
} example_id;
157+
158+
#define STR_ARG(XX) (uint8_t *)XX, strlen(XX)
159+
160+
int main(int argc, char *argv[]) {
161+
const char *file = argc > 1 ? argv[1] : NULL;
162+
const char *host = argc > 2 ? argv[2] : "httpbin.org";
163+
const char *port = argc > 3 ? argv[3] : "80";
164+
const char *path = argc > 4 ? argv[4] : "/post";
165+
166+
if (!file) {
167+
printf("Pass a file path as the first argument.\n");
168+
return 1;
169+
}
170+
171+
struct upload_body upload;
172+
upload.fd = open(file, O_RDONLY);
173+
174+
if (upload.fd < 0) {
175+
printf("error opening file to upload: %d", errno);
176+
return 1;
177+
}
178+
printf("connecting to port %s on %s...\n", port, host);
179+
180+
int fd = connect_to(host, port);
181+
if (fd < 0) {
182+
return 1;
183+
}
184+
printf("connected to %s, now upload to %s\n", host, path);
185+
186+
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
187+
printf("failed to set socket to non-blocking\n");
188+
return 1;
189+
}
190+
191+
upload.len = 8192;
192+
upload.buf = malloc(upload.len);
193+
194+
fd_set fds_read;
195+
fd_set fds_write;
196+
fd_set fds_excep;
197+
198+
struct conn_data *conn = malloc(sizeof(struct conn_data));
199+
200+
conn->fd = fd;
201+
conn->read_waker = NULL;
202+
conn->write_waker = NULL;
203+
204+
205+
// Hookup the IO
206+
hyper_io *io = hyper_io_new();
207+
hyper_io_set_userdata(io, (void *)conn);
208+
hyper_io_set_read(io, read_cb);
209+
hyper_io_set_write(io, write_cb);
210+
211+
printf("http handshake ...\n");
212+
213+
// We need an executor generally to poll futures
214+
const hyper_executor *exec = hyper_executor_new();
215+
216+
// Prepare client options
217+
hyper_clientconn_options *opts = hyper_clientconn_options_new();
218+
hyper_clientconn_options_exec(opts, exec);
219+
220+
hyper_task *handshake = hyper_clientconn_handshake(io, opts);
221+
hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE);
222+
223+
// Let's wait for the handshake to finish...
224+
hyper_executor_push(exec, handshake);
225+
226+
// This body will get filled in eventually...
227+
hyper_body *resp_body = NULL;
228+
229+
// The polling state machine!
230+
while (1) {
231+
// Poll all ready tasks and act on them...
232+
while (1) {
233+
hyper_task *task = hyper_executor_poll(exec);
234+
if (!task) {
235+
break;
236+
}
237+
hyper_task_return_type task_type = hyper_task_type(task);
238+
239+
switch ((example_id) hyper_task_userdata(task)) {
240+
case EXAMPLE_HANDSHAKE:
241+
;
242+
if (task_type == HYPER_TASK_ERROR) {
243+
printf("handshake error!\n");
244+
return 1;
245+
}
246+
assert(task_type == HYPER_TASK_CLIENTCONN);
247+
248+
printf("preparing http request ...\n");
249+
250+
hyper_clientconn *client = hyper_task_value(task);
251+
hyper_task_free(task);
252+
253+
// Prepare the request
254+
hyper_request *req = hyper_request_new();
255+
if (hyper_request_set_method(req, STR_ARG("POST"))) {
256+
printf("error setting method\n");
257+
return 1;
258+
}
259+
if (hyper_request_set_uri(req, STR_ARG(path))) {
260+
printf("error setting uri\n");
261+
return 1;
262+
}
263+
264+
hyper_headers *req_headers = hyper_request_headers(req);
265+
hyper_headers_set(req_headers, STR_ARG("host"), STR_ARG(host));
266+
267+
// Prepare the req body
268+
hyper_body *body = hyper_body_new();
269+
hyper_body_set_userdata(body, &upload);
270+
hyper_body_set_data_func(body, poll_req_upload);
271+
hyper_request_set_body(req, body);
272+
273+
// Send it!
274+
hyper_task *send = hyper_clientconn_send(client, req);
275+
hyper_task_set_userdata(send, (void *)EXAMPLE_SEND);
276+
printf("sending ...\n");
277+
hyper_executor_push(exec, send);
278+
279+
// For this example, no longer need the client
280+
hyper_clientconn_free(client);
281+
282+
break;
283+
case EXAMPLE_SEND:
284+
;
285+
if (task_type == HYPER_TASK_ERROR) {
286+
printf("send error!\n");
287+
return 1;
288+
}
289+
assert(task_type == HYPER_TASK_RESPONSE);
290+
291+
// Take the results
292+
hyper_response *resp = hyper_task_value(task);
293+
hyper_task_free(task);
294+
295+
uint16_t http_status = hyper_response_status(resp);
296+
297+
printf("\nResponse Status: %d\n", http_status);
298+
299+
hyper_headers *headers = hyper_response_headers(resp);
300+
hyper_headers_foreach(headers, print_each_header, NULL);
301+
printf("\n");
302+
303+
resp_body = hyper_response_body(resp);
304+
305+
// Set us up to peel data from the body a chunk at a time
306+
hyper_task *body_data = hyper_body_data(resp_body);
307+
hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY);
308+
hyper_executor_push(exec, body_data);
309+
310+
// No longer need the response
311+
hyper_response_free(resp);
312+
313+
break;
314+
case EXAMPLE_RESP_BODY:
315+
;
316+
if (task_type == HYPER_TASK_ERROR) {
317+
printf("body error!\n");
318+
return 1;
319+
}
320+
321+
if (task_type == HYPER_TASK_BUF) {
322+
hyper_buf *chunk = hyper_task_value(task);
323+
write(1, hyper_buf_bytes(chunk), hyper_buf_len(chunk));
324+
hyper_buf_free(chunk);
325+
hyper_task_free(task);
326+
327+
hyper_task *body_data = hyper_body_data(resp_body);
328+
hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY);
329+
hyper_executor_push(exec, body_data);
330+
331+
break;
332+
} else {
333+
assert(task_type == HYPER_TASK_EMPTY);
334+
hyper_task_free(task);
335+
hyper_body_free(resp_body);
336+
337+
printf("\n -- Done! -- \n");
338+
339+
// Cleaning up before exiting
340+
hyper_executor_free(exec);
341+
free_conn_data(conn);
342+
free(upload.buf);
343+
344+
return 0;
345+
}
346+
case EXAMPLE_NOT_SET:
347+
// A background task for hyper completed...
348+
hyper_task_free(task);
349+
break;
350+
}
351+
}
352+
353+
// All futures are pending on IO work, so select on the fds.
354+
355+
FD_ZERO(&fds_read);
356+
FD_ZERO(&fds_write);
357+
FD_ZERO(&fds_excep);
358+
359+
if (conn->read_waker) {
360+
FD_SET(conn->fd, &fds_read);
361+
}
362+
if (conn->write_waker) {
363+
FD_SET(conn->fd, &fds_write);
364+
}
365+
366+
int sel_ret = select(conn->fd + 1, &fds_read, &fds_write, &fds_excep, NULL);
367+
368+
if (sel_ret < 0) {
369+
printf("select() error\n");
370+
return 1;
371+
} else {
372+
if (FD_ISSET(conn->fd, &fds_read)) {
373+
hyper_waker_wake(conn->read_waker);
374+
conn->read_waker = NULL;
375+
}
376+
if (FD_ISSET(conn->fd, &fds_write)) {
377+
hyper_waker_wake(conn->write_waker);
378+
conn->write_waker = NULL;
379+
}
380+
}
381+
382+
}
383+
384+
385+
return 0;
386+
}

‎capi/gen_header.sh

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env bash
2+
3+
CAPI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4+
5+
WORK_DIR=`mktemp -d`
6+
7+
8+
# check if tmp dir was created
9+
if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then
10+
echo "Could not create temp dir"
11+
exit 1
12+
fi
13+
14+
header_file_backup="$CAPI_DIR/include/hyper.h.backup"
15+
16+
function cleanup {
17+
#echo "$WORK_DIR"
18+
rm -rf "$WORK_DIR"
19+
rm "$header_file_backup"
20+
}
21+
22+
trap cleanup EXIT
23+
24+
mkdir "$WORK_DIR/src"
25+
26+
# Fake a library
27+
cat > "$WORK_DIR/src/lib.rs" << EOF
28+
#[path = "$CAPI_DIR/../src/ffi/mod.rs"]
29+
pub mod ffi;
30+
EOF
31+
32+
# And its Cargo.toml
33+
cat > "$WORK_DIR/Cargo.toml" << EOF
34+
[package]
35+
name = "hyper"
36+
version = "0.0.0"
37+
edition = "2018"
38+
publish = false
39+
40+
[dependencies]
41+
EOF
42+
43+
cp "$CAPI_DIR/include/hyper.h" "$header_file_backup"
44+
45+
#cargo metadata --no-default-features --features ffi --format-version 1 > "$WORK_DIR/metadata.json"
46+
47+
cd $WORK_DIR
48+
49+
# Expand just the ffi module
50+
cargo rustc -- -Z unstable-options --pretty=expanded > expanded.rs 2>/dev/null
51+
52+
# Replace the previous copy with the single expanded file
53+
rm -rf ./src
54+
mkdir src
55+
mv expanded.rs src/lib.rs
56+
57+
58+
# Bindgen!
59+
cbindgen\
60+
-c "$CAPI_DIR/cbindgen.toml"\
61+
--lockfile "$CAPI_DIR/../Cargo.lock"\
62+
-o "$CAPI_DIR/include/hyper.h"\
63+
$1
64+
65+
bindgen_exit_code=$?
66+
67+
if [[ "--verify" == "$1" && "$bindgen_exit_code" != 0 ]]; then
68+
echo "diff generated (<) vs backup (>)"
69+
diff "$CAPI_DIR/include/hyper.h" "$header_file_backup"
70+
fi
71+
72+
exit $bindgen_exit_code

‎capi/include/hyper.h

+554
Large diffs are not rendered by default.

‎src/body/body.rs

+28
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ enum Kind {
5151
content_length: DecodedLength,
5252
recv: h2::RecvStream,
5353
},
54+
#[cfg(feature = "ffi")]
55+
Ffi(crate::ffi::UserBody),
5456
#[cfg(feature = "stream")]
5557
Wrapped(
5658
SyncWrapper<
@@ -260,6 +262,21 @@ impl Body {
260262
}
261263
}
262264

265+
#[cfg(feature = "ffi")]
266+
pub(crate) fn as_ffi_mut(&mut self) -> &mut crate::ffi::UserBody {
267+
match self.kind {
268+
Kind::Ffi(ref mut body) => return body,
269+
_ => {
270+
self.kind = Kind::Ffi(crate::ffi::UserBody::new());
271+
}
272+
}
273+
274+
match self.kind {
275+
Kind::Ffi(ref mut body) => body,
276+
_ => unreachable!(),
277+
}
278+
}
279+
263280
fn poll_inner(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<crate::Result<Bytes>>> {
264281
match self.kind {
265282
Kind::Once(ref mut val) => Poll::Ready(val.take().map(Ok)),
@@ -294,6 +311,9 @@ impl Body {
294311
None => Poll::Ready(None),
295312
},
296313

314+
#[cfg(feature = "ffi")]
315+
Kind::Ffi(ref mut body) => body.poll_data(cx),
316+
297317
#[cfg(feature = "stream")]
298318
Kind::Wrapped(ref mut s) => match ready!(s.get_mut().as_mut().poll_next(cx)) {
299319
Some(res) => Poll::Ready(Some(res.map_err(crate::Error::new_body))),
@@ -348,6 +368,10 @@ impl HttpBody for Body {
348368
}
349369
Err(e) => Poll::Ready(Err(crate::Error::new_h2(e))),
350370
},
371+
372+
#[cfg(feature = "ffi")]
373+
Kind::Ffi(ref mut body) => body.poll_trailers(cx),
374+
351375
_ => Poll::Ready(Ok(None)),
352376
}
353377
}
@@ -358,6 +382,8 @@ impl HttpBody for Body {
358382
Kind::Chan { content_length, .. } => content_length == DecodedLength::ZERO,
359383
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
360384
Kind::H2 { recv: ref h2, .. } => h2.is_end_stream(),
385+
#[cfg(feature = "ffi")]
386+
Kind::Ffi(..) => false,
361387
#[cfg(feature = "stream")]
362388
Kind::Wrapped(..) => false,
363389
}
@@ -384,6 +410,8 @@ impl HttpBody for Body {
384410
Kind::Chan { content_length, .. } => opt_len!(content_length),
385411
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
386412
Kind::H2 { content_length, .. } => opt_len!(content_length),
413+
#[cfg(feature = "ffi")]
414+
Kind::Ffi(..) => SizeHint::default(),
387415
}
388416
}
389417
}

‎src/error.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ pub(crate) enum User {
116116
/// User polled for an upgrade, but low-level API is not using upgrades.
117117
#[cfg(feature = "http1")]
118118
ManualUpgrade,
119+
120+
/// User aborted in an FFI callback.
121+
#[cfg(feature = "ffi")]
122+
AbortedByCallback,
119123
}
120124

121125
// Sentinel type to indicate the error was caused by a timeout.
@@ -179,8 +183,7 @@ impl Error {
179183
self
180184
}
181185

182-
#[cfg(feature = "http1")]
183-
#[cfg(feature = "server")]
186+
#[cfg(any(all(feature = "http1", feature = "server"), feature = "ffi"))]
184187
pub(crate) fn kind(&self) -> &Kind {
185188
&self.inner.kind
186189
}
@@ -336,6 +339,11 @@ impl Error {
336339
Error::new(Kind::Shutdown).with(cause)
337340
}
338341

342+
#[cfg(feature = "ffi")]
343+
pub(crate) fn new_user_aborted_by_callback() -> Error {
344+
Error::new_user(User::AbortedByCallback)
345+
}
346+
339347
#[cfg(feature = "http2")]
340348
pub(crate) fn new_h2(cause: ::h2::Error) -> Error {
341349
if cause.is_io() {
@@ -406,6 +414,8 @@ impl Error {
406414
Kind::User(User::NoUpgrade) => "no upgrade available",
407415
#[cfg(feature = "http1")]
408416
Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use",
417+
#[cfg(feature = "ffi")]
418+
Kind::User(User::AbortedByCallback) => "operation aborted by an application callback",
409419
}
410420
}
411421
}

‎src/ffi/body.rs

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
use std::ffi::c_void;
2+
use std::mem::ManuallyDrop;
3+
use std::ptr;
4+
use std::task::{Context, Poll};
5+
6+
use http::HeaderMap;
7+
use libc::{c_int, size_t};
8+
9+
use super::task::{hyper_context, hyper_task_return_type, AsTaskType, Task};
10+
use super::{UserDataPointer, HYPER_ITER_CONTINUE};
11+
use crate::body::{Body, Bytes, HttpBody as _};
12+
13+
pub struct hyper_body(pub(super) Body);
14+
15+
pub struct hyper_buf(pub(super) Bytes);
16+
17+
pub(crate) struct UserBody {
18+
data_func: hyper_body_data_callback,
19+
userdata: *mut c_void,
20+
}
21+
22+
// ===== Body =====
23+
24+
type hyper_body_foreach_callback = extern "C" fn(*mut c_void, *const hyper_buf) -> c_int;
25+
26+
type hyper_body_data_callback =
27+
extern "C" fn(*mut c_void, *mut hyper_context, *mut *mut hyper_buf) -> c_int;
28+
29+
ffi_fn! {
30+
/// Create a new "empty" body.
31+
///
32+
/// If not configured, this body acts as an empty payload.
33+
fn hyper_body_new() -> *mut hyper_body {
34+
Box::into_raw(Box::new(hyper_body(Body::empty())))
35+
}
36+
}
37+
38+
ffi_fn! {
39+
/// Free a `hyper_body *`.
40+
fn hyper_body_free(body: *mut hyper_body) {
41+
if body.is_null() {
42+
return;
43+
}
44+
45+
drop(unsafe { Box::from_raw(body) });
46+
}
47+
}
48+
49+
ffi_fn! {
50+
/// Return a task that will poll the body for the next buffer of data.
51+
///
52+
/// The task value may have different types depending on the outcome:
53+
///
54+
/// - `HYPER_TASK_BUF`: Success, and more data was received.
55+
/// - `HYPER_TASK_ERROR`: An error retrieving the data.
56+
/// - `HYPER_TASK_EMPTY`: The body has finished streaming data.
57+
///
58+
/// This does not consume the `hyper_body *`, so it may be used to again.
59+
/// However, it MUST NOT be used or freed until the related task completes.
60+
fn hyper_body_data(body: *mut hyper_body) -> *mut Task {
61+
// This doesn't take ownership of the Body, so don't allow destructor
62+
let mut body = ManuallyDrop::new(unsafe { Box::from_raw(body) });
63+
64+
Box::into_raw(Task::boxed(async move {
65+
body.0.data().await.map(|res| res.map(hyper_buf))
66+
}))
67+
}
68+
}
69+
70+
ffi_fn! {
71+
/// Return a task that will poll the body and execute the callback with each
72+
/// body chunk that is received.
73+
///
74+
/// The `hyper_buf` pointer is only a borrowed reference, it cannot live outside
75+
/// the execution of the callback. You must make a copy to retain it.
76+
///
77+
/// The callback should return `HYPER_ITER_CONTINUE` to continue iterating
78+
/// chunks as they are received, or `HYPER_ITER_BREAK` to cancel.
79+
///
80+
/// This will consume the `hyper_body *`, you shouldn't use it anymore or free it.
81+
fn hyper_body_foreach(body: *mut hyper_body, func: hyper_body_foreach_callback, userdata: *mut c_void) -> *mut Task {
82+
if body.is_null() {
83+
return ptr::null_mut();
84+
}
85+
86+
let mut body = unsafe { Box::from_raw(body) };
87+
let userdata = UserDataPointer(userdata);
88+
89+
Box::into_raw(Task::boxed(async move {
90+
while let Some(item) = body.0.data().await {
91+
let chunk = item?;
92+
if HYPER_ITER_CONTINUE != func(userdata.0, &hyper_buf(chunk)) {
93+
return Err(crate::Error::new_user_aborted_by_callback());
94+
}
95+
}
96+
Ok(())
97+
}))
98+
}
99+
}
100+
101+
ffi_fn! {
102+
/// Set userdata on this body, which will be passed to callback functions.
103+
fn hyper_body_set_userdata(body: *mut hyper_body, userdata: *mut c_void) {
104+
let b = unsafe { &mut *body };
105+
b.0.as_ffi_mut().userdata = userdata;
106+
}
107+
}
108+
109+
ffi_fn! {
110+
/// Set the data callback for this body.
111+
///
112+
/// The callback is called each time hyper needs to send more data for the
113+
/// body. It is passed the value from `hyper_body_set_userdata`.
114+
///
115+
/// If there is data available, the `hyper_buf **` argument should be set
116+
/// to a `hyper_buf *` containing the data, and `HYPER_POLL_READY` should
117+
/// be returned.
118+
///
119+
/// Returning `HYPER_POLL_READY` while the `hyper_buf **` argument points
120+
/// to `NULL` will indicate the body has completed all data.
121+
///
122+
/// If there is more data to send, but it isn't yet available, a
123+
/// `hyper_waker` should be saved from the `hyper_context *` argument, and
124+
/// `HYPER_POLL_PENDING` should be returned. You must wake the saved waker
125+
/// to signal the task when data is available.
126+
///
127+
/// If some error has occurred, you can return `HYPER_POLL_ERROR` to abort
128+
/// the body.
129+
fn hyper_body_set_data_func(body: *mut hyper_body, func: hyper_body_data_callback) {
130+
let b = unsafe { &mut *body };
131+
b.0.as_ffi_mut().data_func = func;
132+
}
133+
}
134+
135+
// ===== impl UserBody =====
136+
137+
impl UserBody {
138+
pub(crate) fn new() -> UserBody {
139+
UserBody {
140+
data_func: data_noop,
141+
userdata: std::ptr::null_mut(),
142+
}
143+
}
144+
145+
pub(crate) fn poll_data(&mut self, cx: &mut Context<'_>) -> Poll<Option<crate::Result<Bytes>>> {
146+
let mut out = std::ptr::null_mut();
147+
match (self.data_func)(self.userdata, hyper_context::wrap(cx), &mut out) {
148+
super::task::HYPER_POLL_READY => {
149+
if out.is_null() {
150+
Poll::Ready(None)
151+
} else {
152+
let buf = unsafe { Box::from_raw(out) };
153+
Poll::Ready(Some(Ok(buf.0)))
154+
}
155+
}
156+
super::task::HYPER_POLL_PENDING => Poll::Pending,
157+
super::task::HYPER_POLL_ERROR => {
158+
Poll::Ready(Some(Err(crate::Error::new_body_write_aborted())))
159+
}
160+
unexpected => Poll::Ready(Some(Err(crate::Error::new_body_write(format!(
161+
"unexpected hyper_body_data_func return code {}",
162+
unexpected
163+
))))),
164+
}
165+
}
166+
167+
pub(crate) fn poll_trailers(
168+
&mut self,
169+
_cx: &mut Context<'_>,
170+
) -> Poll<crate::Result<Option<HeaderMap>>> {
171+
Poll::Ready(Ok(None))
172+
}
173+
}
174+
175+
/// cbindgen:ignore
176+
extern "C" fn data_noop(
177+
_userdata: *mut c_void,
178+
_: *mut hyper_context<'_>,
179+
_: *mut *mut hyper_buf,
180+
) -> c_int {
181+
super::task::HYPER_POLL_READY
182+
}
183+
184+
unsafe impl Send for UserBody {}
185+
unsafe impl Sync for UserBody {}
186+
187+
// ===== Bytes =====
188+
189+
ffi_fn! {
190+
/// Create a new `hyper_buf *` by copying the provided bytes.
191+
///
192+
/// This makes an owned copy of the bytes, so the `buf` argument can be
193+
/// freed or changed afterwards.
194+
fn hyper_buf_copy(buf: *const u8, len: size_t) -> *mut hyper_buf {
195+
let slice = unsafe {
196+
std::slice::from_raw_parts(buf, len)
197+
};
198+
Box::into_raw(Box::new(hyper_buf(Bytes::copy_from_slice(slice))))
199+
}
200+
}
201+
202+
ffi_fn! {
203+
/// Get a pointer to the bytes in this buffer.
204+
///
205+
/// This should be used in conjunction with `hyper_buf_len` to get the length
206+
/// of the bytes data.
207+
///
208+
/// This pointer is borrowed data, and not valid once the `hyper_buf` is
209+
/// consumed/freed.
210+
fn hyper_buf_bytes(buf: *const hyper_buf) -> *const u8 {
211+
unsafe { (*buf).0.as_ptr() }
212+
}
213+
}
214+
215+
ffi_fn! {
216+
/// Get the length of the bytes this buffer contains.
217+
fn hyper_buf_len(buf: *const hyper_buf) -> size_t {
218+
unsafe { (*buf).0.len() }
219+
}
220+
}
221+
222+
ffi_fn! {
223+
/// Free this buffer.
224+
fn hyper_buf_free(buf: *mut hyper_buf) {
225+
drop(unsafe { Box::from_raw(buf) });
226+
}
227+
}
228+
229+
unsafe impl AsTaskType for hyper_buf {
230+
fn as_task_type(&self) -> hyper_task_return_type {
231+
hyper_task_return_type::HYPER_TASK_BUF
232+
}
233+
}

‎src/ffi/client.rs

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use std::sync::Arc;
2+
3+
use libc::c_int;
4+
5+
use crate::client::conn;
6+
use crate::rt::Executor as _;
7+
8+
use super::error::hyper_code;
9+
use super::http_types::{hyper_request, hyper_response};
10+
use super::io::Io;
11+
use super::task::{hyper_task_return_type, AsTaskType, Exec, Task, WeakExec};
12+
13+
pub struct hyper_clientconn_options {
14+
builder: conn::Builder,
15+
/// Use a `Weak` to prevent cycles.
16+
exec: WeakExec,
17+
}
18+
19+
pub struct hyper_clientconn {
20+
tx: conn::SendRequest<crate::Body>,
21+
}
22+
23+
// ===== impl hyper_clientconn =====
24+
25+
ffi_fn! {
26+
/// Starts an HTTP client connection handshake using the provided IO transport
27+
/// and options.
28+
///
29+
/// Both the `io` and the `options` are consumed in this function call.
30+
///
31+
/// The returned `hyper_task *` must be polled with an executor until the
32+
/// handshake completes, at which point the value can be taken.
33+
fn hyper_clientconn_handshake(io: *mut Io, options: *mut hyper_clientconn_options) -> *mut Task {
34+
if io.is_null() {
35+
return std::ptr::null_mut();
36+
}
37+
if options.is_null() {
38+
return std::ptr::null_mut();
39+
}
40+
41+
let options = unsafe { Box::from_raw(options) };
42+
let io = unsafe { Box::from_raw(io) };
43+
44+
Box::into_raw(Task::boxed(async move {
45+
options.builder.handshake::<_, crate::Body>(io)
46+
.await
47+
.map(|(tx, conn)| {
48+
options.exec.execute(Box::pin(async move {
49+
let _ = conn.await;
50+
}));
51+
hyper_clientconn { tx }
52+
})
53+
}))
54+
}
55+
}
56+
57+
ffi_fn! {
58+
/// Send a request on the client connection.
59+
///
60+
/// Returns a task that needs to be polled until it is ready. When ready, the
61+
/// task yields a `hyper_response *`.
62+
fn hyper_clientconn_send(conn: *mut hyper_clientconn, req: *mut hyper_request) -> *mut Task {
63+
if conn.is_null() {
64+
return std::ptr::null_mut();
65+
}
66+
if req.is_null() {
67+
return std::ptr::null_mut();
68+
}
69+
70+
let req = unsafe { Box::from_raw(req) };
71+
let fut = unsafe { &mut *conn }.tx.send_request(req.0);
72+
73+
let fut = async move {
74+
fut.await.map(hyper_response)
75+
};
76+
77+
Box::into_raw(Task::boxed(fut))
78+
}
79+
}
80+
81+
ffi_fn! {
82+
/// Free a `hyper_clientconn *`.
83+
fn hyper_clientconn_free(conn: *mut hyper_clientconn) {
84+
drop(unsafe { Box::from_raw(conn) });
85+
}
86+
}
87+
88+
unsafe impl AsTaskType for hyper_clientconn {
89+
fn as_task_type(&self) -> hyper_task_return_type {
90+
hyper_task_return_type::HYPER_TASK_CLIENTCONN
91+
}
92+
}
93+
94+
// ===== impl hyper_clientconn_options =====
95+
96+
ffi_fn! {
97+
/// Creates a new set of HTTP clientconn options to be used in a handshake.
98+
fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options {
99+
Box::into_raw(Box::new(hyper_clientconn_options {
100+
builder: conn::Builder::new(),
101+
exec: WeakExec::new(),
102+
}))
103+
}
104+
}
105+
106+
ffi_fn! {
107+
/// Free a `hyper_clientconn_options *`.
108+
fn hyper_clientconn_options_free(opts: *mut hyper_clientconn_options) {
109+
drop(unsafe { Box::from_raw(opts) });
110+
}
111+
}
112+
113+
ffi_fn! {
114+
/// Set the client background task executor.
115+
///
116+
/// This does not consume the `options` or the `exec`.
117+
fn hyper_clientconn_options_exec(opts: *mut hyper_clientconn_options, exec: *const Exec) {
118+
let opts = unsafe { &mut *opts };
119+
120+
let exec = unsafe { Arc::from_raw(exec) };
121+
let weak_exec = Exec::downgrade(&exec);
122+
std::mem::forget(exec);
123+
124+
opts.builder.executor(weak_exec.clone());
125+
opts.exec = weak_exec;
126+
}
127+
}
128+
129+
ffi_fn! {
130+
/// Set the whether to use HTTP2.
131+
///
132+
/// Pass `0` to disable, `1` to enable.
133+
fn hyper_clientconn_options_http2(opts: *mut hyper_clientconn_options, enabled: c_int) -> hyper_code {
134+
#[cfg(feature = "http2")]
135+
{
136+
let opts = unsafe { &mut *opts };
137+
opts.builder.http2_only(enabled != 0);
138+
hyper_code::HYPERE_OK
139+
}
140+
141+
#[cfg(not(feature = "http2"))]
142+
{
143+
drop(opts);
144+
drop(enabled);
145+
hyper_code::HYPERE_FEATURE_NOT_ENABLED
146+
}
147+
}
148+
}

‎src/ffi/error.rs

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use libc::size_t;
2+
3+
pub struct hyper_error(crate::Error);
4+
5+
#[repr(C)]
6+
pub enum hyper_code {
7+
/// All is well.
8+
HYPERE_OK,
9+
/// General error, details in the `hyper_error *`.
10+
HYPERE_ERROR,
11+
/// A function argument was invalid.
12+
HYPERE_INVALID_ARG,
13+
/// The IO transport returned an EOF when one wasn't expected.
14+
///
15+
/// This typically means an HTTP request or response was expected, but the
16+
/// connection closed cleanly without sending (all of) it.
17+
HYPERE_UNEXPECTED_EOF,
18+
/// Aborted by a user supplied callback.
19+
HYPERE_ABORTED_BY_CALLBACK,
20+
/// An optional hyper feature was not enabled.
21+
#[cfg_attr(feature = "http2", allow(unused))]
22+
HYPERE_FEATURE_NOT_ENABLED,
23+
}
24+
25+
// ===== impl hyper_error =====
26+
27+
impl hyper_error {
28+
fn code(&self) -> hyper_code {
29+
use crate::error::Kind as ErrorKind;
30+
use crate::error::User;
31+
32+
match self.0.kind() {
33+
ErrorKind::IncompleteMessage => hyper_code::HYPERE_UNEXPECTED_EOF,
34+
ErrorKind::User(User::AbortedByCallback) => hyper_code::HYPERE_ABORTED_BY_CALLBACK,
35+
// TODO: add more variants
36+
_ => hyper_code::HYPERE_ERROR
37+
}
38+
}
39+
40+
fn print_to(&self, dst: &mut [u8]) -> usize {
41+
use std::io::Write;
42+
43+
let mut dst = std::io::Cursor::new(dst);
44+
45+
// A write! error doesn't matter. As much as possible will have been
46+
// written, and the Cursor position will know how far that is (even
47+
// if that is zero).
48+
let _ = write!(dst, "{}", &self.0);
49+
dst.position() as usize
50+
}
51+
}
52+
53+
ffi_fn! {
54+
/// Frees a `hyper_error`.
55+
fn hyper_error_free(err: *mut hyper_error) {
56+
drop(unsafe { Box::from_raw(err) });
57+
}
58+
}
59+
60+
ffi_fn! {
61+
/// Get an equivalent `hyper_code` from this error.
62+
fn hyper_error_code(err: *const hyper_error) -> hyper_code {
63+
unsafe { &*err }.code()
64+
}
65+
}
66+
67+
ffi_fn! {
68+
/// Print the details of this error to a buffer.
69+
///
70+
/// The `dst_len` value must be the maximum length that the buffer can
71+
/// store.
72+
///
73+
/// The return value is number of bytes that were written to `dst`.
74+
fn hyper_error_print(err: *const hyper_error, dst: *mut u8, dst_len: size_t) -> size_t {
75+
let dst = unsafe {
76+
std::slice::from_raw_parts_mut(dst, dst_len)
77+
};
78+
unsafe { &*err }.print_to(dst)
79+
}
80+
}

‎src/ffi/http_types.rs

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
use libc::{c_int, size_t};
2+
use std::ffi::c_void;
3+
4+
use super::body::hyper_body;
5+
use super::error::hyper_code;
6+
use super::task::{hyper_task_return_type, AsTaskType};
7+
use super::HYPER_ITER_CONTINUE;
8+
use crate::header::{HeaderName, HeaderValue};
9+
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
10+
11+
// ===== impl Request =====
12+
13+
pub struct hyper_request(pub(super) Request<Body>);
14+
15+
pub struct hyper_response(pub(super) Response<Body>);
16+
17+
pub struct hyper_headers(pub(super) HeaderMap);
18+
19+
ffi_fn! {
20+
/// Construct a new HTTP request.
21+
fn hyper_request_new() -> *mut hyper_request {
22+
Box::into_raw(Box::new(hyper_request(Request::new(Body::empty()))))
23+
}
24+
}
25+
26+
ffi_fn! {
27+
/// Free an HTTP request if not going to send it on a client.
28+
fn hyper_request_free(req: *mut hyper_request) {
29+
drop(unsafe { Box::from_raw(req) });
30+
}
31+
}
32+
33+
ffi_fn! {
34+
/// Set the HTTP Method of the request.
35+
fn hyper_request_set_method(req: *mut hyper_request, method: *const u8, method_len: size_t) -> hyper_code {
36+
let bytes = unsafe {
37+
std::slice::from_raw_parts(method, method_len as usize)
38+
};
39+
match Method::from_bytes(bytes) {
40+
Ok(m) => {
41+
*unsafe { &mut *req }.0.method_mut() = m;
42+
hyper_code::HYPERE_OK
43+
},
44+
Err(_) => {
45+
hyper_code::HYPERE_INVALID_ARG
46+
}
47+
}
48+
}
49+
}
50+
51+
ffi_fn! {
52+
/// Set the URI of the request.
53+
fn hyper_request_set_uri(req: *mut hyper_request, uri: *const u8, uri_len: size_t) -> hyper_code {
54+
let bytes = unsafe {
55+
std::slice::from_raw_parts(uri, uri_len as usize)
56+
};
57+
match Uri::from_maybe_shared(bytes) {
58+
Ok(u) => {
59+
*unsafe { &mut *req }.0.uri_mut() = u;
60+
hyper_code::HYPERE_OK
61+
},
62+
Err(_) => {
63+
hyper_code::HYPERE_INVALID_ARG
64+
}
65+
}
66+
}
67+
}
68+
69+
ffi_fn! {
70+
/// Set the preferred HTTP version of the request.
71+
///
72+
/// The version value should be one of the `HYPER_HTTP_VERSION_` constants.
73+
///
74+
/// Note that this won't change the major HTTP version of the connection,
75+
/// since that is determined at the handshake step.
76+
fn hyper_request_set_version(req: *mut hyper_request, version: c_int) -> hyper_code {
77+
use http::Version;
78+
79+
*unsafe { &mut *req }.0.version_mut() = match version {
80+
super::HYPER_HTTP_VERSION_NONE => Version::HTTP_11,
81+
super::HYPER_HTTP_VERSION_1_0 => Version::HTTP_10,
82+
super::HYPER_HTTP_VERSION_1_1 => Version::HTTP_11,
83+
super::HYPER_HTTP_VERSION_2 => Version::HTTP_2,
84+
_ => {
85+
// We don't know this version
86+
return hyper_code::HYPERE_INVALID_ARG;
87+
}
88+
};
89+
hyper_code::HYPERE_OK
90+
}
91+
}
92+
93+
ffi_fn! {
94+
/// Gets a reference to the HTTP headers of this request
95+
///
96+
/// This is not an owned reference, so it should not be accessed after the
97+
/// `hyper_request` has been consumed.
98+
fn hyper_request_headers(req: *mut hyper_request) -> *mut hyper_headers {
99+
hyper_headers::wrap(unsafe { &mut *req }.0.headers_mut())
100+
}
101+
}
102+
103+
ffi_fn! {
104+
/// Set the body of the request.
105+
///
106+
/// The default is an empty body.
107+
///
108+
/// This takes ownership of the `hyper_body *`, you must not use it or
109+
/// free it after setting it on the request.
110+
fn hyper_request_set_body(req: *mut hyper_request, body: *mut hyper_body) -> hyper_code {
111+
let body = unsafe { Box::from_raw(body) };
112+
*unsafe { &mut *req }.0.body_mut() = body.0;
113+
hyper_code::HYPERE_OK
114+
}
115+
}
116+
117+
// ===== impl Response =====
118+
119+
ffi_fn! {
120+
/// Free an HTTP response after using it.
121+
fn hyper_response_free(resp: *mut hyper_response) {
122+
drop(unsafe { Box::from_raw(resp) });
123+
}
124+
}
125+
126+
ffi_fn! {
127+
/// Get the HTTP-Status code of this response.
128+
///
129+
/// It will always be within the range of 100-599.
130+
fn hyper_response_status(resp: *const hyper_response) -> u16 {
131+
unsafe { &*resp }.0.status().as_u16()
132+
}
133+
}
134+
135+
ffi_fn! {
136+
/// Get the HTTP version used by this response.
137+
///
138+
/// The returned value could be:
139+
///
140+
/// - `HYPER_HTTP_VERSION_1_0`
141+
/// - `HYPER_HTTP_VERSION_1_1`
142+
/// - `HYPER_HTTP_VERSION_2`
143+
/// - `HYPER_HTTP_VERSION_NONE` if newer (or older).
144+
fn hyper_response_version(resp: *const hyper_response) -> c_int {
145+
use http::Version;
146+
147+
match unsafe { &*resp }.0.version() {
148+
Version::HTTP_10 => super::HYPER_HTTP_VERSION_1_0,
149+
Version::HTTP_11 => super::HYPER_HTTP_VERSION_1_1,
150+
Version::HTTP_2 => super::HYPER_HTTP_VERSION_2,
151+
_ => super::HYPER_HTTP_VERSION_NONE,
152+
}
153+
}
154+
}
155+
156+
ffi_fn! {
157+
/// Gets a reference to the HTTP headers of this response.
158+
///
159+
/// This is not an owned reference, so it should not be accessed after the
160+
/// `hyper_response` has been freed.
161+
fn hyper_response_headers(resp: *mut hyper_response) -> *mut hyper_headers {
162+
hyper_headers::wrap(unsafe { &mut *resp }.0.headers_mut())
163+
}
164+
}
165+
166+
ffi_fn! {
167+
/// Take ownership of the body of this response.
168+
///
169+
/// It is safe to free the response even after taking ownership of its body.
170+
fn hyper_response_body(resp: *mut hyper_response) -> *mut hyper_body {
171+
let body = std::mem::take(unsafe { &mut *resp }.0.body_mut());
172+
Box::into_raw(Box::new(hyper_body(body)))
173+
}
174+
}
175+
176+
unsafe impl AsTaskType for hyper_response {
177+
fn as_task_type(&self) -> hyper_task_return_type {
178+
hyper_task_return_type::HYPER_TASK_RESPONSE
179+
}
180+
}
181+
182+
// ===== impl Headers =====
183+
184+
type hyper_headers_foreach_callback =
185+
extern "C" fn(*mut c_void, *const u8, size_t, *const u8, size_t) -> c_int;
186+
187+
impl hyper_headers {
188+
pub(crate) fn wrap(cx: &mut HeaderMap) -> &mut hyper_headers {
189+
// A struct with only one field has the same layout as that field.
190+
unsafe { std::mem::transmute::<&mut HeaderMap, &mut hyper_headers>(cx) }
191+
}
192+
}
193+
194+
ffi_fn! {
195+
/// Iterates the headers passing each name and value pair to the callback.
196+
///
197+
/// The `userdata` pointer is also passed to the callback.
198+
///
199+
/// The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or
200+
/// `HYPER_ITER_BREAK` to stop.
201+
fn hyper_headers_foreach(headers: *const hyper_headers, func: hyper_headers_foreach_callback, userdata: *mut c_void) {
202+
for (name, value) in unsafe { &*headers }.0.iter() {
203+
let name_ptr = name.as_str().as_bytes().as_ptr();
204+
let name_len = name.as_str().as_bytes().len();
205+
let val_ptr = value.as_bytes().as_ptr();
206+
let val_len = value.as_bytes().len();
207+
208+
if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) {
209+
break;
210+
}
211+
}
212+
}
213+
}
214+
215+
ffi_fn! {
216+
/// Sets the header with the provided name to the provided value.
217+
///
218+
/// This overwrites any previous value set for the header.
219+
fn hyper_headers_set(headers: *mut hyper_headers, name: *const u8, name_len: size_t, value: *const u8, value_len: size_t) -> hyper_code {
220+
let headers = unsafe { &mut *headers };
221+
match unsafe { raw_name_value(name, name_len, value, value_len) } {
222+
Ok((name, value)) => {
223+
headers.0.insert(name, value);
224+
hyper_code::HYPERE_OK
225+
}
226+
Err(code) => code,
227+
}
228+
}
229+
}
230+
231+
ffi_fn! {
232+
/// Adds the provided value to the list of the provided name.
233+
///
234+
/// If there were already existing values for the name, this will append the
235+
/// new value to the internal list.
236+
fn hyper_headers_add(headers: *mut hyper_headers, name: *const u8, name_len: size_t, value: *const u8, value_len: size_t) -> hyper_code {
237+
let headers = unsafe { &mut *headers };
238+
239+
match unsafe { raw_name_value(name, name_len, value, value_len) } {
240+
Ok((name, value)) => {
241+
headers.0.append(name, value);
242+
hyper_code::HYPERE_OK
243+
}
244+
Err(code) => code,
245+
}
246+
}
247+
}
248+
249+
unsafe fn raw_name_value(
250+
name: *const u8,
251+
name_len: size_t,
252+
value: *const u8,
253+
value_len: size_t,
254+
) -> Result<(HeaderName, HeaderValue), hyper_code> {
255+
let name = std::slice::from_raw_parts(name, name_len);
256+
let name = match HeaderName::from_bytes(name) {
257+
Ok(name) => name,
258+
Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG),
259+
};
260+
let value = std::slice::from_raw_parts(value, value_len);
261+
let value = match HeaderValue::from_bytes(value) {
262+
Ok(val) => val,
263+
Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG),
264+
};
265+
266+
Ok((name, value))
267+
}

‎src/ffi/io.rs

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
use std::ffi::c_void;
2+
use std::pin::Pin;
3+
use std::task::{Context, Poll};
4+
5+
use libc::size_t;
6+
use tokio::io::{AsyncRead, AsyncWrite};
7+
8+
use super::task::hyper_context;
9+
10+
pub const HYPER_IO_PENDING: size_t = 0xFFFFFFFF;
11+
pub const HYPER_IO_ERROR: size_t = 0xFFFFFFFE;
12+
13+
type hyper_io_read_callback =
14+
extern "C" fn(*mut c_void, *mut hyper_context<'_>, *mut u8, size_t) -> size_t;
15+
type hyper_io_write_callback =
16+
extern "C" fn(*mut c_void, *mut hyper_context<'_>, *const u8, size_t) -> size_t;
17+
18+
pub struct Io {
19+
read: hyper_io_read_callback,
20+
write: hyper_io_write_callback,
21+
userdata: *mut c_void,
22+
}
23+
24+
ffi_fn! {
25+
/// Create a new IO type used to represent a transport.
26+
///
27+
/// The read and write functions of this transport should be set with
28+
/// `hyper_io_set_read` and `hyper_io_set_write`.
29+
fn hyper_io_new() -> *mut Io {
30+
Box::into_raw(Box::new(Io {
31+
read: read_noop,
32+
write: write_noop,
33+
userdata: std::ptr::null_mut(),
34+
}))
35+
}
36+
}
37+
38+
ffi_fn! {
39+
/// Free an unused `hyper_io *`.
40+
///
41+
/// This is typically only useful if you aren't going to pass ownership
42+
/// of the IO handle to hyper, such as with `hyper_clientconn_handshake()`.
43+
fn hyper_io_free(io: *mut Io) {
44+
drop(unsafe { Box::from_raw(io) });
45+
}
46+
}
47+
48+
ffi_fn! {
49+
/// Set the user data pointer for this IO to some value.
50+
///
51+
/// This value is passed as an argument to the read and write callbacks.
52+
fn hyper_io_set_userdata(io: *mut Io, data: *mut c_void) {
53+
unsafe { &mut *io }.userdata = data;
54+
}
55+
}
56+
57+
ffi_fn! {
58+
/// Set the read function for this IO transport.
59+
///
60+
/// Data that is read from the transport should be put in the `buf` pointer,
61+
/// up to `buf_len` bytes. The number of bytes read should be the return value.
62+
///
63+
/// It is undefined behavior to try to access the bytes in the `buf` pointer,
64+
/// unless you have already written them yourself. It is also undefined behavior
65+
/// to return that more bytes have been written than actually set on the `buf`.
66+
///
67+
/// If there is no data currently available, a waker should be claimed from
68+
/// the `ctx` and registered with whatever polling mechanism is used to signal
69+
/// when data is available later on. The return value should be
70+
/// `HYPER_IO_PENDING`.
71+
///
72+
/// If there is an irrecoverable error reading data, then `HYPER_IO_ERROR`
73+
/// should be the return value.
74+
fn hyper_io_set_read(io: *mut Io, func: hyper_io_read_callback) {
75+
unsafe { &mut *io }.read = func;
76+
}
77+
}
78+
79+
ffi_fn! {
80+
/// Set the write function for this IO transport.
81+
///
82+
/// Data from the `buf` pointer should be written to the transport, up to
83+
/// `buf_len` bytes. The number of bytes written should be the return value.
84+
///
85+
/// If no data can currently be written, the `waker` should be cloned and
86+
/// registered with whatever polling mechanism is used to signal when data
87+
/// is available later on. The return value should be `HYPER_IO_PENDING`.
88+
///
89+
/// Yeet.
90+
///
91+
/// If there is an irrecoverable error reading data, then `HYPER_IO_ERROR`
92+
/// should be the return value.
93+
fn hyper_io_set_write(io: *mut Io, func: hyper_io_write_callback) {
94+
unsafe { &mut *io }.write = func;
95+
}
96+
}
97+
98+
/// cbindgen:ignore
99+
extern "C" fn read_noop(
100+
_userdata: *mut c_void,
101+
_: *mut hyper_context<'_>,
102+
_buf: *mut u8,
103+
_buf_len: size_t,
104+
) -> size_t {
105+
0
106+
}
107+
108+
/// cbindgen:ignore
109+
extern "C" fn write_noop(
110+
_userdata: *mut c_void,
111+
_: *mut hyper_context<'_>,
112+
_buf: *const u8,
113+
_buf_len: size_t,
114+
) -> size_t {
115+
0
116+
}
117+
118+
impl AsyncRead for Io {
119+
fn poll_read(
120+
self: Pin<&mut Self>,
121+
cx: &mut Context<'_>,
122+
buf: &mut tokio::io::ReadBuf<'_>,
123+
) -> Poll<std::io::Result<()>> {
124+
let buf_ptr = unsafe { buf.unfilled_mut() }.as_mut_ptr() as *mut u8;
125+
let buf_len = buf.remaining();
126+
127+
match (self.read)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) {
128+
HYPER_IO_PENDING => Poll::Pending,
129+
HYPER_IO_ERROR => Poll::Ready(Err(std::io::Error::new(
130+
std::io::ErrorKind::Other,
131+
"io error",
132+
))),
133+
ok => {
134+
// We have to trust that the user's read callback actually
135+
// filled in that many bytes... :(
136+
unsafe { buf.assume_init(ok) };
137+
buf.advance(ok);
138+
Poll::Ready(Ok(()))
139+
}
140+
}
141+
}
142+
}
143+
144+
impl AsyncWrite for Io {
145+
fn poll_write(
146+
self: Pin<&mut Self>,
147+
cx: &mut Context<'_>,
148+
buf: &[u8],
149+
) -> Poll<std::io::Result<usize>> {
150+
let buf_ptr = buf.as_ptr();
151+
let buf_len = buf.len();
152+
153+
match (self.write)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) {
154+
HYPER_IO_PENDING => Poll::Pending,
155+
HYPER_IO_ERROR => Poll::Ready(Err(std::io::Error::new(
156+
std::io::ErrorKind::Other,
157+
"io error",
158+
))),
159+
ok => Poll::Ready(Ok(ok)),
160+
}
161+
}
162+
163+
fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<std::io::Result<()>> {
164+
Poll::Ready(Ok(()))
165+
}
166+
167+
fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<std::io::Result<()>> {
168+
Poll::Ready(Ok(()))
169+
}
170+
}
171+
172+
unsafe impl Send for Io {}
173+
unsafe impl Sync for Io {}

‎src/ffi/macros.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
macro_rules! ffi_fn {
2+
($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) -> $ret:ty $body:block) => {
3+
$(#[$doc])*
4+
#[no_mangle]
5+
pub extern fn $name($($arg: $arg_ty),*) -> $ret {
6+
use std::panic::{self, AssertUnwindSafe};
7+
8+
match panic::catch_unwind(AssertUnwindSafe(move || $body)) {
9+
Ok(v) => v,
10+
Err(_) => {
11+
// TODO: We shouldn't abort, but rather figure out how to
12+
// convert into the return type that the function errored.
13+
eprintln!("panic unwind caught, aborting");
14+
std::process::abort();
15+
}
16+
}
17+
}
18+
};
19+
20+
($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) $body:block) => {
21+
ffi_fn!($(#[$doc])* fn $name($($arg: $arg_ty),*) -> () $body);
22+
};
23+
}

‎src/ffi/mod.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// We have a lot of c-types in here, stop warning about their names!
2+
#![allow(non_camel_case_types)]
3+
4+
// We may eventually allow the FFI to be enabled without `client` or `http1`,
5+
// that is why we don't auto enable them as `ffi = ["client", "http1"]` in
6+
// the `Cargo.toml`.
7+
//
8+
// But for now, give a clear message that this compile error is expected.
9+
#[cfg(not(all(feature = "client", feature = "http1")))]
10+
compile_error!("The `ffi` feature currently requires the `client` and `http1` features.");
11+
12+
#[cfg(not(hyper_unstable_ffi))]
13+
compile_error!(
14+
"\
15+
The `ffi` feature is unstable, and requires the \
16+
`RUSTFLAGS='--cfg hyper_unstable_ffi'` environment variable to be set.\
17+
"
18+
);
19+
20+
#[macro_use]
21+
mod macros;
22+
23+
mod body;
24+
mod client;
25+
mod error;
26+
mod http_types;
27+
mod io;
28+
mod task;
29+
30+
pub(crate) use self::body::UserBody;
31+
32+
pub const HYPER_ITER_CONTINUE: libc::c_int = 0;
33+
#[allow(unused)]
34+
pub const HYPER_ITER_BREAK: libc::c_int = 1;
35+
36+
pub const HYPER_HTTP_VERSION_NONE: libc::c_int = 0;
37+
pub const HYPER_HTTP_VERSION_1_0: libc::c_int = 10;
38+
pub const HYPER_HTTP_VERSION_1_1: libc::c_int = 11;
39+
pub const HYPER_HTTP_VERSION_2: libc::c_int = 20;
40+
41+
struct UserDataPointer(*mut std::ffi::c_void);
42+
43+
// We don't actually know anything about this pointer, it's up to the user
44+
// to do the right thing.
45+
unsafe impl Send for UserDataPointer {}
46+
47+
/// cbindgen:ignore
48+
static VERSION_CSTR: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
49+
50+
ffi_fn! {
51+
/// Returns a static ASCII (null terminated) string of the hyper version.
52+
fn hyper_version() -> *const libc::c_char {
53+
VERSION_CSTR.as_ptr() as _
54+
}
55+
}

‎src/ffi/task.rs

+415
Large diffs are not rendered by default.

‎src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ pub mod rt;
8787
pub mod service;
8888
pub mod upgrade;
8989

90+
#[cfg(feature = "ffi")]
91+
mod ffi;
92+
9093
cfg_proto! {
9194
mod headers;
9295
mod proto;

‎src/proto/h1/dispatch.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ cfg_client! {
5858
impl<D, Bs, I, T> Dispatcher<D, Bs, I, T>
5959
where
6060
D: Dispatch<
61-
PollItem = MessageHead<T::Outgoing>,
62-
PollBody = Bs,
63-
RecvItem = MessageHead<T::Incoming>,
64-
> + Unpin,
61+
PollItem = MessageHead<T::Outgoing>,
62+
PollBody = Bs,
63+
RecvItem = MessageHead<T::Incoming>,
64+
> + Unpin,
6565
D::PollError: Into<Box<dyn StdError + Send + Sync>>,
6666
I: AsyncRead + AsyncWrite + Unpin,
6767
T: Http1Transaction + Unpin,
@@ -405,10 +405,10 @@ where
405405
impl<D, Bs, I, T> Future for Dispatcher<D, Bs, I, T>
406406
where
407407
D: Dispatch<
408-
PollItem = MessageHead<T::Outgoing>,
409-
PollBody = Bs,
410-
RecvItem = MessageHead<T::Incoming>,
411-
> + Unpin,
408+
PollItem = MessageHead<T::Outgoing>,
409+
PollBody = Bs,
410+
RecvItem = MessageHead<T::Incoming>,
411+
> + Unpin,
412412
D::PollError: Into<Box<dyn StdError + Send + Sync>>,
413413
I: AsyncRead + AsyncWrite + Unpin,
414414
T: Http1Transaction + Unpin,

0 commit comments

Comments
 (0)
Please sign in to comment.