- Since the `if` condition already causes the loop to `break`, the `else` is not necessary. We wouldn't have reached the `else` block, anyway, if the prior `if` condition passed. - We are more likely to poll a successful chunk than finish the request or throw an error. Thus, it is best if we go for the optimistic route and check for the successful case first.
344 lines
9.7 KiB
C
344 lines
9.7 KiB
C
#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) {
|
|
return ret;
|
|
}
|
|
|
|
if (errno != EAGAIN) {
|
|
// kaboom
|
|
return HYPER_IO_ERROR;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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) {
|
|
return ret;
|
|
}
|
|
|
|
if (errno != EAGAIN) {
|
|
// kaboom
|
|
return HYPER_IO_ERROR;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 (hyper v%s) ...\n", hyper_version());
|
|
|
|
// 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);
|
|
const uint8_t *rp = hyper_response_reason_phrase(resp);
|
|
size_t rp_len = hyper_response_reason_phrase_len(resp);
|
|
|
|
printf("\nResponse Status: %d %.*s\n", http_status, (int) rp_len, rp);
|
|
|
|
hyper_headers *headers = hyper_response_headers(resp);
|
|
hyper_headers_foreach(headers, print_each_header, NULL);
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|