基於libevent的http客戶端,並可以請求https
阿新 • • 發佈:2019-01-07
這個可以用libevent實現http客戶端的post get等功能
http_client.h
http_client.c#ifndef __HTTP_CLIENT_H__ #define __HTTP_CLIENT_H__ #include <sys/queue.h> enum http_method { HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD, HTTP_METHOD_PUT, HTTP_METHOD_DELETE, }; struct http_client; struct http_header { size_t key_len; const char *key; size_t value_len; const char *value; TAILQ_ENTRY(http_header) entry; }; struct http_response { struct http_request *request; uint16_t status; size_t url_len; const char *url; size_t body_len; const char *body; bool body_chunked; TAILQ_HEAD(,http_header) headers; }; struct http_request; /* Callback functions */ typedef void (*cb_http_response)(struct http_client *, struct http_request *, struct http_response *, void *baton); typedef void (*cb_http_disconnect)(struct http_client *, void *baton); /** * New http_client connection instance * * @param hostname Hostname or ip address of web server. * @param port * @param ssl * * @return struct http_client* Client connection handle. */ struct http_client *http_client_new(const char *hostname, const char *port, SSL_CTX* ssl_ctx); /** * Close http_client connection, cancel all pending requests. * * @param api */ void http_client_free(struct http_client *api); /** * Queue a new HTTP client request. * * @param api * @param method * @param uri_len * @param uri * * @return struct http_request* */ struct http_request *http_client_request(struct http_client *api, enum http_method method, size_t uri_len, const char *uri); int http_client_request_add_header(struct http_request *request, const char *key, const char *value); int http_client_request_add_body(struct http_request *request, size_t body_len, const char *body); /** * Make this request pending. * * @param request * @return int success / failure flag. */ int http_client_request_dispatch(struct http_request *request, cb_http_response callback, void *baton); /* TODO: */ int http_client_cancel(struct http_request *request); #endif /* __HTTP_CLIENT_H__ */
/* libc */ #include <stdbool.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <sys/queue.h> /* libevent */ #include <event2/event-config.h> #include <event2/event.h> /* openssl */ #include <openssl/bio.h> #include <openssl/ssl.h> /* http_client */ #include "http_client.h" #define VERSION_MAJOR 0 #define VERSION_MINOR 1 #define FLAGS_HAVE_USER_AGENT 0x0001 #define FLAGS_HAVE_CONTENT_LENGTH 0x0002 #define FLAGS_HAVE_HOST 0x0004 #define FLAGS_HAVE_KEEP_ALIVE 0x0008 enum connection_fault { FAULT_OKAY, FAULT_CLOSE, FAULT_ERROR, FAULT_FREE, }; enum parse_state { PARSE_ERROR = -1, PARSE_MORE_DATA = 0, PARSE_NEXT_STATE = 1, }; enum http_state { HTTP_NOT_CONNECTED, HTTP_CONNECTING, HTTP_IDLE, HTTP_READING_STATUS_LINE, HTTP_READING_HEADERS, HTTP_READING_BODY, HTTP_READING_DONE, HTTP_SENDING, }; struct http_request { struct http_client *api; struct evbuffer *output; enum http_method method; size_t uri_len; const char *uri; TAILQ_HEAD(,http_header) headers; size_t body_len; const char *body; /* User per request baton. */ cb_http_response callback; void *baton; TAILQ_ENTRY(http_request) entry; }; struct http_client { uint16_t version[2]; const char* host; struct { const char *hostname; const char *port; SSL_CTX* ssl_ctx; SSL* ssl; BIO* buffer_io; struct event ev; } connection; enum http_state state; cb_http_disconnect cb_disconnect; TAILQ_HEAD(, http_request) pending_request; struct http_response *current; struct evbuffer *input; void *baton; }; /*====================================================================================================================== * * Function prototypes * *====================================================================================================================== */ static void libevent_interopt(int fd, short what, void *baton); static int read_data(struct http_client *api); static int write_request(struct http_client *api); static void connection_fault(struct http_client *api, enum connection_fault fault); static int next_request(struct http_client *api); static BIO* connect_remote_host(struct http_client *api); /*====================================================================================================================== * * Util functions. * *====================================================================================================================== */ static void *memdup(const void *src, size_t len) { void *output; if ((output = malloc(len)) == NULL) { return NULL; } return memcpy(output, src, len); } static void reschedule_event(struct event *ev, int fd, short what, void *baton) { event_del(ev); event_set(ev, fd, what, libevent_interopt, baton); event_add(ev, NULL); } static const char *get_method_str(enum http_method method) { switch (method) { case HTTP_METHOD_GET: return "GET"; case HTTP_METHOD_POST: return "POST"; default: return NULL; } } static struct http_header* find_header(struct http_response *response, const char *name) { struct http_header *cur; TAILQ_FOREACH(cur, &response->headers, entry) { if (strncmp(cur->key, name, cur->key_len) == 0) return cur; } return NULL; } /*====================================================================================================================== * * Request / response creation destruction. * *====================================================================================================================== */ static struct http_header* new_header(const char *key, size_t key_len, const char *value, size_t value_len) { struct http_header* header = calloc(1, sizeof(*header)); if (header == NULL) { goto FAIL; } header->key_len = key_len; header->value_len = value_len; if ((header->key = strndup(key, key_len)) == NULL) { goto FAIL_TWO; } if ((header->value = strndup(value, value_len)) == NULL) { goto FAIL_TWO; } return header; FAIL_TWO: free((void *) header->key); free((void *) header->value); FAIL: free(header); return NULL; } static struct http_response* new_response(struct http_request *request) { struct http_response *response = calloc(1, sizeof(*response)); if (response == NULL) { return NULL; } response->request = request; TAILQ_INIT(&response->headers); return response; FAIL: free(response); return NULL; } static void free_response(struct http_response *response) { while (!TAILQ_EMPTY(&response->headers)) { struct http_header *cur = TAILQ_FIRST(&response->headers); TAILQ_REMOVE(&response->headers, cur, entry); free(cur); } free(response); } static struct http_request* new_request(struct http_client *api) { struct http_request *request; if ((request = calloc(1, sizeof(*request))) == NULL) goto FAIL_ONE; if ((request->output = evbuffer_new()) == NULL) goto FAIL_TWO; TAILQ_INIT(&request->headers); request->api = api; return request; FAIL_TWO: free(request); FAIL_ONE: return NULL; } static void free_request(struct http_request *request) { while (!TAILQ_EMPTY(&request->headers)) { struct http_header *cur = TAILQ_FIRST(&request->headers); TAILQ_REMOVE(&request->headers, cur, entry); free(cur); } free((void *) request->uri); free((void *) request->body); evbuffer_free(request->output); free(request); } /*====================================================================================================================== * * Parse response * *====================================================================================================================== */ static bool is_connection_close(struct http_client *api, struct http_response *response) { struct http_header *header = find_header(response, "Connection"); if (header == NULL) { goto SKIP; } if (api->version[VERSION_MINOR] == 1) { if (header && strncasecmp(header->value, "Close", header->value_len)) return false; } else if (api->version[VERSION_MINOR] == 0) { if (strncasecmp(header->value, "Keep-Alive", header->value_len) == 0) return true; } SKIP: return (api->version[VERSION_MINOR] == 1); } static ssize_t get_body_len(struct http_response *response) { struct http_header *header = find_header(response, "Content-Length"); if (header) return atoi(header->value); return -1; } static bool is_response_chunked(struct http_client *api, struct http_response *response) { return false; } int find_body_len(struct http_client *api, struct http_response *response) { if (is_response_chunked(api, response)) { response->body_chunked = true; return 0; } ssize_t body_len = get_body_len(response); if (body_len < 0) { return -1; } response->body_len = body_len; return 0; } static enum parse_state read_status_line(struct http_client *api, struct http_response *response) { enum parse_state status; char *line = evbuffer_readln(api->input, NULL, EVBUFFER_EOL_CRLF_STRICT); char *token = line; /* Need more data. */ if (line == NULL) { return PARSE_MORE_DATA; } char *protocol = strsep(&token, " "); char *status_code = strsep(&token, " "); if (protocol == NULL || status_code == NULL) { status = PARSE_ERROR; goto DONE; } if (strcmp(protocol, "HTTP/1.0") == 0) { api->version[VERSION_MINOR] = 0; } else if (strcmp(protocol, "HTTP/1.1") == 0) { api->version[VERSION_MINOR] = 1; } else { /* Unknow protocol version. */ status = PARSE_ERROR; } response->status = atoi(status_code); status = PARSE_NEXT_STATE; DONE: free(line); return status; } static enum parse_state read_header_line(struct http_client *api, struct http_response *response) { char *line, *sep, *value; do { line = evbuffer_readln(api->input, NULL, EVBUFFER_EOL_CRLF_STRICT); if (line == NULL) { /* Need more data. */ free(line); return PARSE_MORE_DATA; } else if (*line == '\t' || *line == ' ') { /* Header contination. */ /* TODO: */ } else if (*line == '\0') { /* Header / body boundry. */ free(line); /* Figure out the body we should be looking for. */ if (find_body_len(api, response) == -1) return PARSE_ERROR; return PARSE_NEXT_STATE; } if ((sep = index(line, ':')) == NULL) return PARSE_ERROR; for (value = sep + 1; value != '\0'; value++) { if (*value == ' ' || *value == '\t') { continue; } else { break; } } struct http_header* header = new_header(line, sep - line, value, strlen(value)); if (header == NULL) { return PARSE_ERROR; } TAILQ_INSERT_TAIL(&response->headers, header, entry); free(line); } while (1); } static enum parse_state read_body(struct http_client *api, struct http_response *response) { /* Special case that cannot have a body: * - 1xx status * - 204 status * - 304 status * - HEAD method */ if ((response->status >= 100 && response->status <= 199) || response->status == 204 || response->status == 304 || response->request->method == HTTP_METHOD_HEAD) { return PARSE_NEXT_STATE; } /* Chunked encomding. */ if (0) return PARSE_MORE_DATA; return (EVBUFFER_LENGTH(api->input) >= response->body_len) ? PARSE_NEXT_STATE : PARSE_MORE_DATA; } static int dispatch_response(struct http_client *api, struct http_response *response) { struct http_request *request = TAILQ_FIRST(&api->pending_request); TAILQ_REMOVE(&api->pending_request, request, entry); /* Tempoary pointer into the evbuffer. */ response->body = EVBUFFER_DATA(api->input); if (request->callback != NULL) { request->callback(api, request, response, request->baton); } /* Get rid of the body from the buffer. */ evbuffer_drain(api->input, response->body_len); if (response->body_chunked == true) { /* Chunked, already closed connection */ } else if (is_connection_close(api, response)) { if (response != NULL) { connection_fault(api, FAULT_CLOSE); } } else { api->state = HTTP_IDLE; } /* Free response & request. */ free_response(response); free_request(request); api->current = NULL; return 0; } static int read_response(struct http_client *api) { enum parse_state result; do { switch (api->state) { case HTTP_READING_STATUS_LINE: result = read_status_line(api, api->current); break; case HTTP_READING_HEADERS: result = read_header_line(api, api->current); break; case HTTP_READING_BODY: result = read_body(api, api->current); break; case HTTP_READING_DONE: if ((result = dispatch_response(api, api->current)) == 0) { next_request(api); return 0; } } switch (result) { /* Need more data. */ case PARSE_MORE_DATA: break; /* Increment FSM to next state. */ case PARSE_NEXT_STATE: api->state++; break; /* Error. */ case PARSE_ERROR: default: connection_fault(api, FAULT_ERROR); return -1; } } while (result == 1); } /*====================================================================================================================== * * Create request buffer * *====================================================================================================================== */ static int add_req_line(struct http_request *request) { const char *method_str = get_method_str(request->method); return evbuffer_add_printf(request->output, "%s %.*s HTTP/%i.%i\r\n", method_str, request->uri_len, request->uri, request->api->version[VERSION_MAJOR], request->api->version[VERSION_MINOR]); } static int add_header(struct evbuffer *buffer, struct http_header *header) { return evbuffer_add_printf(buffer, "%.*s: %.*s\r\n", header->key_len, header->key_len, header->value_len, header->value); } static int add_headers(struct http_request *request) { int flags = 0; /* Add other headers */ struct http_header *cur; TAILQ_FOREACH(cur, &request->headers, entry) { if (strncmp(cur->key, "Host", cur->key_len) == 0) flags |= FLAGS_HAVE_HOST; else if (strncmp(cur->key, "Content-Length", cur->key_len) == 0) flags |= FLAGS_HAVE_CONTENT_LENGTH; else if (strncmp(cur->key, "User-Agent", cur->key_len) == 0) flags |= FLAGS_HAVE_USER_AGENT; else if (strncmp(cur->key, "Connection", cur->key_len) == 0) flags |= FLAGS_HAVE_KEEP_ALIVE; add_header(request->output, cur); } if (!(flags & FLAGS_HAVE_HOST)) { evbuffer_add_printf(request->output, "Host: %s\r\n", request->api->connection.hostname); } if (!(flags & FLAGS_HAVE_CONTENT_LENGTH) && request->body_len > 0) { evbuffer_add_printf(request->output, "Conntent-Length: %i\r\n", request->body_len); } if (!(flags & FLAGS_HAVE_USER_AGENT)) { evbuffer_add_printf(request->output, "User-Agent: Admeld http_client\r\n"); } if (!(flags & FLAGS_HAVE_KEEP_ALIVE)) { evbuffer_add_printf(request->output, "Connection: Keep-Alive\r\n"); } return 0; } static int add_body(struct http_request *request) { if (request->body_len > 0) { evbuffer_add(request->output, request->body, request->body_len); } return 0; } static int next_request(struct http_client *api) { if (TAILQ_EMPTY(&api->pending_request)) { event_del(&api->connection.ev); api->state = HTTP_IDLE; return 0; } /* Schedule write. */ if (event_pending(&api->connection.ev, EV_READ | EV_WRITE, NULL)) { reschedule_event(&api->connection.ev, api->connection.ev.ev_fd, EV_WRITE, api); } else { int fd; BIO_get_fd(api->connection.buffer_io, &fd); event_set(&api->connection.ev, fd, EV_WRITE, libevent_interopt, api); event_add(&api->connection.ev, NULL); } api->state = HTTP_SENDING; return 0; } /*====================================================================================================================== * * Connection write, read, parse state machine. * *====================================================================================================================== */ static void connection_fault(struct http_client *api, enum connection_fault fault) { /* Fault all pending request. */ if (fault != FAULT_CLOSE) { struct http_request *request; for (request = TAILQ_FIRST(&api->pending_request); request != NULL; request = TAILQ_FIRST(&api->pending_request)) { /* Run callback, with an error response. */ if (request->callback != NULL) request->callback(api, request, NULL, request->baton); TAILQ_REMOVE(&api->pending_request, request, entry); free_request(request); } } if (fault == FAULT_CLOSE && api->current) { if (api->current->body_chunked == true) { /* Set the length, it's the whole buffer. */ api->current->body_len = EVBUFFER_LENGTH(api->input); dispatch_response(api, api->current); } /* TODO: Error, cancel pending request / response. */ } /* Run disconnect callback. */ if (api->cb_disconnect != NULL) api->cb_disconnect(api, api->baton); /* TODO: close connection. */ /* Set status FSM. */ api->state = HTTP_NOT_CONNECTED; /* Reset BIO but try to keep resume SSL session on reconnect. */ if (api->connection.ssl_ctx) { BIO_reset(api->connection.buffer_io); } /* Process remaning requests. */ if (fault == FAULT_CLOSE && !TAILQ_EMPTY(&api->pending_request)) { if ((api->connection.buffer_io = connect_remote_host(api)) == NULL) { /* Error, dump the queue. */ connection_fault(api, FAULT_ERROR); } } } static int ssl_negociate(struct http_client *api) { /* Do handshake. */ /* Verify ceficate. */ if (SSL_get_verify_result(api->connection.ssl) != X509_V_OK) return -1; /* Wait till we can write out request out. */ reschedule_event(&api->connection.ev, api->connection.ev.ev_fd, EV_WRITE, api); return 0; } static void libevent_interopt(int fd, short what, void *baton) { int result; struct http_client *api = (struct http_client *) baton; switch (api->state) { case HTTP_CONNECTING: if (api->connection.ssl_ctx) { if (ssl_negociate(api) == -1) { } return; } else { api->state = HTTP_SENDING; /* And fall through. */ } case HTTP_SENDING: /* Write the data out. */ write_request(api); return; case HTTP_READING_STATUS_LINE: case HTTP_READING_HEADERS: case HTTP_READING_BODY: do { /* Read into evbuffer */ if ((result = read_data(api)) == -1) return; } while (result == 1); /* Do the read callback. */ read_response(api); return; case HTTP_IDLE: /* */ ; } } /*====================================================================================================================== * * Network IO * *====================================================================================================================== */ static int write_request(struct http_client *api) { struct http_request *request; if (TAILQ_EMPTY(&api->pending_request)) { return 0; } request = TAILQ_FIRST(&api->pending_request); int result = BIO_write(api->connection.buffer_io, EVBUFFER_DATA(request->output), EVBUFFER_LENGTH(request->output)); if (result < 1) { if (BIO_should_retry(api->connection.buffer_io)) { return 0; } connection_fault(api, FAULT_ERROR); return -1; } /* Drain the data we wrote out. */ evbuffer_drain(request->output, result); if (EVBUFFER_LENGTH(request->output) == 0) { if ((api->current = new_response(request)) == NULL) return -1; api->state = HTTP_READING_STATUS_LINE; /* Turn off write notification and turn on read notification. */ reschedule_event(&api->connection.ev, api->connection.ev.ev_fd, EV_READ, api); } return 0; } static int read_data(struct http_client *api) { char buffer[1024]; int result = BIO_read(api->connection.buffer_io, buffer, sizeof(buffer)); if (result > 0) { evbuffer_add(api->input, buffer, result); return (result >= sizeof(buffer)); } else if (result == 0) { /* Other side closed the connection */ connection_fault(api, FAULT_CLOSE); return -1; } /* We'll try again in the future again. */ if (BIO_should_retry(api->connection.buffer_io)) { return 0; } /* Error */ connection_fault(api, FAULT_ERROR); return -1; } static BIO* connect_remote_host(struct http_client *api) { BIO *result; if (api->connection.ssl_ctx != NULL) { result = BIO_new_ssl_connect(api->connection.ssl_ctx); if (result) { BIO_set_conn_hostname(result, api->connection.hostname); BIO_get_ssl(result, &api->connection.ssl); SSL_set_mode(api->connection.ssl, SSL_MODE_AUTO_RETRY); } } else { result = BIO_new_connect((char *) api->connection.hostname); } if (result == NULL) { goto FAIL_ONE; } BIO_set_conn_port(result, api->connection.port); /* Set it to non-blocking mode. */ BIO_set_nbio(result, 1); if (BIO_do_connect(result) == -1) { if (BIO_should_retry(result)) { int reason = BIO_get_retry_reason(result); } else { goto FAIL_TWO; } } int fd; BIO_get_fd(result, &fd); short what = (api->connection.ssl_ctx) ? EV_READ : EV_WRITE; event_set(&api->connection.ev, fd, what, libevent_interopt, api); event_add(&api->connection.ev, NULL); api->state = HTTP_CONNECTING; return result; FAIL_TWO: BIO_free_all(result); FAIL_ONE: return NULL; } static int setup_ssl(struct http_client *api, SSL_CTX* ctx) { api->connection.ssl_ctx = ctx; api->connection.ssl = NULL; } /*====================================================================================================================== * * Public API * *====================================================================================================================== */ struct http_client *http_client_new(const char *hostname, const char *port, SSL_CTX* ssl_ctx) { struct http_client *api = calloc(1, sizeof(*api)); if (api == NULL) { goto FAIL_ONE; } api->version[VERSION_MAJOR] = 1; api->version[VERSION_MINOR] = 1; api->state = HTTP_NOT_CONNECTED; TAILQ_INIT(&api->pending_request); if ((api->connection.hostname = strdup(hostname)) == NULL) goto FAIL_TWO; if ((api->connection.port = strdup(port)) == NULL) goto FAIL_TWO; if (ssl_ctx != NULL) { if (setup_ssl(api, ssl_ctx) == -1) goto FAIL_TWO; } if ((api->input = evbuffer_new()) == NULL) { goto FAIL_THREE; } return api; FAIL_THREE: evbuffer_free(api->input); FAIL_TWO: free((void *) api->connection.hostname); free((void *) api->connection.port); free(api); FAIL_ONE: return NULL; } void http_client_free(struct http_client *api) { BIO_free_all(api->connection.buffer_io); connection_fault(api, FAULT_FREE); free(api); } struct http_request *http_client_request(struct http_client *api, enum http_method method, size_t uri_len, const char *uri) { struct http_request *request = new_request(api); if (request == NULL) return NULL; request->method = method; request->uri_len = uri_len; if ((request->uri = memdup(uri, uri_len)) == NULL) { goto FAIL; } return request; FAIL: free_request(request); return NULL; } int http_client_request_add_header(struct http_request *request, const char *key, const char *value) { struct http_header *header = malloc(sizeof(*header)); if (header == NULL) { goto FAIL_ONE; } header->key_len = strlen(key); header->value_len = strlen(value); if ((header->key = memdup(key, header->key_len)) == NULL) { goto FAIL_TWO; } if ((header->value = memdup(value, header->value_len)) == NULL) { goto FAIL_THREE; } TAILQ_INSERT_TAIL(&request->headers, header, entry); return 0; FAIL_THREE: free((void *) header->key); FAIL_TWO: free(header); FAIL_ONE: return -1; } int http_client_request_add_body(struct http_request *request, size_t body_len, const char *body) { if ((request->body = malloc(body_len)) == NULL) { return -1; } memcpy((void *) request->body, body, body_len); request->body_len = body_len; return 0; } int http_client_request_dispatch(struct http_request *request, cb_http_response callback, void *baton) { struct http_client *api = request->api; BIO *bio; request->callback = callback; request->baton = baton; if (add_req_line(request) == -1) { return -1; } if (add_headers(request) == -1) { return -1; } if (add_body(request) == -1) { return -1; } if (evbuffer_add_printf(request->output, "\r\n") == -1) return -1; /* Stick in the stack for sending it out. */ TAILQ_INSERT_TAIL(&request->api->pending_request, request, entry); switch (request->api->state) { case HTTP_NOT_CONNECTED: if ((bio = connect_remote_host(request->api)) == NULL) { return -1; } api->connection.buffer_io = bio; return 0; case HTTP_IDLE: /* Schedule processing of the next request. */ next_request(api); } return 0; }
test.c
/* libc */ #include <stdbool.h> #include <string.h> #include <stdio.h> #include <unistd.h> /* libevent */ #include <event2/event-config.h> #include <event2/event.h> /* openssl */ #include <openssl/bio.h> #include <openssl/ssl.h> #include <openssl/err.h> #include "http_client.h" void callback(struct http_client *api, struct http_request *req, struct http_response *response, void *baton) { printf("%.*s", response->body_len, response->body); } int main(int argc, char *argv[]) { bool do_ssl = false; const char *url = argv[1]; const char *domain = argv[1]; const char *port = argv[2]; const char *uri = argv[3]; if (argc < 4) { printf("ERROR: Need url, port and uri\n"); return 1; } SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); event_init(); struct http_client *client = http_client_new(domain, port, NULL); struct http_request *req = http_client_request(client, HTTP_METHOD_GET, strlen(uri), uri); http_client_request_dispatch(req, callback, NULL); event_loop(0); return 1; }