From: Niels Provos Date: Sun, 22 Jan 2006 05:08:50 +0000 (+0000) Subject: I often need some very simple HTTP functionality, so this is a first stab X-Git-Tag: release-2.0.1-alpha~751 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a3bb4a035fe27dcdf52158d115c4caea153dabf5;p=libevent I often need some very simple HTTP functionality, so this is a first stab at integrating something really simple with HTTP. The interface is still evolving as I start messing with it. Not all the interfaces are properly exported yet. I am also trying to figure out how to intelligently hide the details about the different structures from users, so that that things can be changed around later. svn:r196 --- diff --git a/Makefile.am b/Makefile.am index b861df4b..c9ec0481 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,7 +23,8 @@ EXTRA_DIST = acconfig.h event.h event-internal.h log.h evsignal.h event.3 \ lib_LTLIBRARIES = libevent.la -libevent_la_SOURCES = event.c buffer.c evbuffer.c log.c event_tagging.c +libevent_la_SOURCES = event.c buffer.c evbuffer.c log.c event_tagging.c \ + http.c http.h libevent_la_LIBADD = @LTLIBOBJS@ libevent_la_LDFLAGS = -release @VERSION@ -version-info 1:3:0 diff --git a/event.h b/event.h index 55af00ee..cf575061 100644 --- a/event.h +++ b/event.h @@ -270,7 +270,7 @@ int evbuffer_add_vprintf(struct evbuffer *, const char *fmt, va_list ap); void evbuffer_drain(struct evbuffer *, size_t); int evbuffer_write(struct evbuffer *, int); int evbuffer_read(struct evbuffer *, int, int); -u_char *evbuffer_find(struct evbuffer *, u_char *, size_t); +u_char *evbuffer_find(struct evbuffer *, const u_char *, size_t); void evbuffer_setcb(struct evbuffer *, void (*)(struct evbuffer *, size_t, size_t, void *), void *); /* @@ -316,6 +316,44 @@ int evtag_unmarshal_string(struct evbuffer *evbuf, u_int8_t need_tag, int evtag_unmarshal_timeval(struct evbuffer *evbuf, u_int8_t need_tag, struct timeval *ptv); +/* + * Basic support for HTTP serving + */ + +/* Response codes */ +#define HTTP_OK 200 +#define HTTP_MOVEPERM 301 +#define HTTP_MOVETEMP 302 +#define HTTP_NOTFOUND 404 + +struct evhttp; +struct evhttp_request; + +/* Start an HTTP server on the specified address and port */ +struct evhttp *evhttp_start(const char *address, u_short port); + +void evhttp_free(struct evhttp* http); + +/* Set a callback for a specified URI */ +void evhttp_set_cb(struct evhttp *, const char *, + void (*)(struct evhttp_request *, void *), void *); + +/* Set a callback for all requests that are not caught by specific callbacks */ +void evhttp_set_gencb(struct evhttp *, + void (*)(struct evhttp_request *, void *), void *); + +void evhttp_send_error(struct evhttp_request *, int, const char *); +void evhttp_send_reply(struct evhttp_request *, int, const char *, + struct evbuffer *); + +/* Interfaces for making requests */ +enum evhttp_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD }; +enum evhttp_request_kind { EVHTTP_REQUEST, EVHTTP_RESPONSE }; + +struct evhttp_request *evhttp_request_new( + void (*cb)(struct evhttp_request *, void *), void *arg); +void evhttp_request_free(struct evhttp_request *req); + #ifdef __cplusplus } #endif diff --git a/event_rpcgen.py b/event_rpcgen.py index bb57835b..d6a579d1 100755 --- a/event_rpcgen.py +++ b/event_rpcgen.py @@ -58,7 +58,7 @@ class Struct: def PrintTags(self, file): """Prints the tag definitions for a structure.""" print >>file, '/* Tag definition for %s */' % self._name - print >>file, 'enum {' + print >>file, 'enum %s_ {' % self._name.lower() for entry in self._entries: print >>file, ' %s=%d,' % (self.EntryTagName(entry), entry.Tag()) diff --git a/http.c b/http.c new file mode 100644 index 00000000..b6821095 --- /dev/null +++ b/http.c @@ -0,0 +1,1420 @@ +/* + * Copyright 2002, 2003, 2005 Niels Provos + * All rights reserved. + * + */ + +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_IOCCOM_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_TIME_H +#include +#endif +#include +#include +#include + +#undef timeout_pending +#undef timeout_initialized + +#include "event.h" +#include "log.h" +#include "http.h" + +extern int debug; + +static int make_socket_ai(int (*f)(int, const struct sockaddr *, socklen_t), + struct addrinfo *); +static int make_socket(int (*)(int, const struct sockaddr *, socklen_t), + const char *, short); +static void name_from_addr(struct sockaddr *, socklen_t, char **, char **); + +void evhttp_write(int, short, void *); + +static const char * +html_replace(char ch) +{ + static char buf[2]; + + switch (ch) { + case '<': + return "<"; + case '>': + return ">"; + case '"': + return """; + case '\'': + return "'"; + case '&': + return "&"; + default: + break; + } + + /* Echo the character back */ + buf[0] = ch; + buf[1] = '\0'; + + return buf; +} + +/* + * Replaces <, >, ", ' and & with <, >, ", + * ' and & correspondingly. + * + * The returned string needs to be freed by the caller. + */ + +char * +evhttp_htmlescape(const char *html) +{ + int i, new_size = 0; + char *escaped_html, *p; + + for (i = 0; i < strlen(html); ++i) + new_size += strlen(html_replace(html[i])); + + p = escaped_html = malloc(new_size + 1); + if (escaped_html == NULL) + event_err(1, "%s: malloc(%d)", __func__, new_size + 1); + for (i = 0; i < strlen(html); ++i) { + const char *replaced = html_replace(html[i]); + /* this is length checked */ + strcpy(p, replaced); + p += strlen(replaced); + } + + *p = '\0'; + + return (escaped_html); +} + +const char * +evhttp_method(enum evhttp_cmd_type type) +{ + const char *method; + + switch (type) { + case EVHTTP_REQ_GET: + method = "GET"; + break; + case EVHTTP_REQ_POST: + method = "POST"; + break; + case EVHTTP_REQ_HEAD: + method = "HEAD"; + break; + default: + method = NULL; + break; + } + + return (method); +} + +void +evhttp_form_response(struct evbuffer *buf, struct evhttp_request *req) +{ + /* Clean out the buffer */ + evbuffer_drain(buf, buf->off); + + /* Create the header fields */ + evhttp_make_header(buf, req); + + /* Append the response buffer */ + evbuffer_add(buf, req->buffer->buffer, req->buffer->off); +} + +void +evhttp_write_buffer(struct evhttp_request *req, struct evbuffer *buffer, + void (*cb)(struct evhttp_request *, void *), void *arg) +{ + struct timeval tv; + + event_debug(("%s: preparing to write buffer\n", __func__)); + + if (req->buffer != buffer) { + if (req->buffer != NULL) + evbuffer_free(req->buffer); + req->buffer = buffer; + } + + /* Set call back */ + + req->cb = cb; + req->cb_arg = arg; + + event_set(&req->ev, req->fd, EV_WRITE, evhttp_write, req); + timerclear(&tv); + tv.tv_sec = HTTP_WRITE_TIMEOUT; + event_add(&req->ev, &tv); +} + +void +evhttp_make_header(struct evbuffer *buf, struct evhttp_request *req) +{ + char line[1024]; + struct evkeyval *header; + const char *method; + + /* First we make a few tiny modifications */ + if (req->kind == EVHTTP_REQUEST) { + evhttp_remove_header(req->output_headers, "Accept-Encoding"); + evhttp_remove_header(req->output_headers, "Proxy-Connection"); + evhttp_remove_header(req->output_headers, "Connection"); + evhttp_add_header(req->output_headers, "Connection", "close"); + req->minor = 0; + + /* Generate request line */ + method = evhttp_method(req->type); + snprintf(line, sizeof(line), "%s %s HTTP/%d.%d\r\n", + method, req->uri, req->major, req->minor); + evbuffer_add(buf, line, strlen(line)); + + /* Add the content length on a post request if missing */ + if (req->type == EVHTTP_REQ_POST && + evhttp_find_header(req->output_headers, + "Content-Length") == NULL) { + char size[12]; + snprintf(size, sizeof(size), "%d", + EVBUFFER_LENGTH(req->buffer)); + evhttp_add_header(req->output_headers, + "Content-Length", size); + } + } else { + snprintf(line, sizeof(line), "HTTP/%d.%d %d %s\r\n", + req->major, req->minor, req->response_code, + req->response_code_line); + evbuffer_add(buf, line, strlen(line)); + + /* Potentially add headers */ + if (evhttp_find_header(req->output_headers, + "Content-Type") == NULL) { + evhttp_add_header(req->output_headers, + "Content-Type", "text/html; charset=ISO-8859-1"); + } + } + + TAILQ_FOREACH(header, req->output_headers, next) { + snprintf(line, sizeof(line), "%s: %s\r\n", + header->key, header->value); + evbuffer_add(buf, line, strlen(line)); + } + evbuffer_add(buf, "\r\n", 2); + + if (req->kind == EVHTTP_REQUEST) { + int len = req->buffer->off; + + /* Add the POST data */ + if (len > 0) + evbuffer_add(buf, req->buffer->buffer, len); + } +} + +/* Separated host, port and file from URI */ + +int +evhttp_hostportfile(char *url, char **phost, u_short *pport, char **pfile) +{ + static char host[1024]; + static char file[1024]; + char *p, *p2; + int len; + u_short port; + + len = strlen(HTTP_PREFIX); + if (strncasecmp(url, HTTP_PREFIX, len)) + return (-1); + + url += len; + + /* We might overrun */ + if (strlcpy(host, url, sizeof (host)) >= sizeof(host)) + return (-1); + + p = strchr(host, '/'); + if (p != NULL) { + *p = '\0'; + p2 = p + 1; + } else + p2 = NULL; + + if (pfile != NULL) { + /* Generate request file */ + if (p2 == NULL) + p2 = ""; + snprintf(file, sizeof(file), "/%s", p2); + } + + p = strchr(host, ':'); + if (p != NULL) { + *p = '\0'; + port = atoi(p + 1); + + if (port == 0) + return (-1); + } else + port = HTTP_DEFAULTPORT; + + if (phost != NULL) + *phost = host; + if (pport != NULL) + *pport = port; + if (pfile != NULL) + *pfile = file; + + return (0); +} + +void +evhttp_fail(struct evhttp_request *req) +{ + evhttp_request_free(req); +} + +void +evhttp_write(int fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + struct timeval tv; + int n; + + if (what == EV_TIMEOUT) { + evhttp_fail(req); + return; + } + + n = evbuffer_write(req->buffer, fd); + if (n == -1) { + event_warn("%s: evbuffer_write", __func__); + evhttp_fail(req); + return; + } + + if (n == 0) { + event_warnx("%s: write nothing\n", __func__); + evhttp_fail(req); + return; + } + + if (req->buffer->off != 0) { + timerclear(&tv); + tv.tv_sec = HTTP_WRITE_TIMEOUT; + event_add(&req->ev, &tv); + return; + } + + /* Activate our call back */ + (*req->cb)(req, req->cb_arg); +} + +/* + * Reads data into a buffer structure until no more data + * can be read on the file descriptor or we have read all + * the data that we wanted to read. + * Execute callback when done. + */ + +void +evhttp_read(int fd, short what, void *arg) +{ + struct evhttp_request *req = arg; + struct timeval tv; + int n; + + if (what == EV_TIMEOUT) { + evhttp_fail(req); + return; + } + + n = evbuffer_read(req->buffer, fd, req->ntoread); + event_debug(("%s: got %d on %d\n", __func__, n, req->fd)); + + if (n == -1) { + event_warn("%s: evbuffer_read", __func__); + evhttp_fail(req); + } + + /* Adjust the amount of data that we have left to read */ + if (req->ntoread > 0) + req->ntoread -= n; + + if (n == 0 || req->ntoread == 0) { + (*req->cb)(req, req->cb_arg); + return; + } + + timerclear(&tv); + tv.tv_sec = HTTP_READ_TIMEOUT; + event_add(&req->ev, &tv); +} + +void +evhttp_write_requestcb(struct evhttp_request *req, void *arg) +{ + /* Restore the original callbacks */ + req->cb = req->save_cb; + req->cb_arg = req->save_cbarg; + + /* This is after writing the request to the server */ + + /* We are done writing our header and are now expecting the response */ + req->kind = EVHTTP_RESPONSE; + + evhttp_start_read(req); +} + +/* + * Clean up a connection object + */ + +void +evhttp_connection_free(struct evhttp_connection *evcon) +{ + event_del(&evcon->ev); + + if (evcon->fd != -1) + close(evcon->fd); + + if (evcon->address != NULL) + free(evcon->address); + + free(evcon); +} + +/* + * Call back for asynchronous connection attempt. + */ + +void +evhttp_connectioncb(int fd, short what, void *arg) +{ + struct evhttp_connection *evcon = arg; + int error; + socklen_t errsz = sizeof(error); + + if (what == EV_TIMEOUT) { + event_warnx("%s: connection timeout for \"%s:%d\" on %d\n", + __func__, evcon->address, evcon->port, evcon->fd); + goto cleanup; + } + + /* Check if the connection completed */ + if (getsockopt(evcon->fd, SOL_SOCKET, SO_ERROR, &error, + &errsz) == -1) { + event_warn("%s: getsockopt for \"%s:%d\" on %d", + __func__, evcon->address, evcon->port, evcon->fd); + goto cleanup; + } + + if (error) { + event_warnx("%s: connect failed for \"%s:%d\" on %d: %s\n", + __func__, evcon->address, evcon->port, evcon->fd, + strerror(error)); + goto cleanup; + } + + /* We are connected to the server now */ + event_debug(("%s: connected to \"%s:%d\" on %d\n", + __func__, evcon->address, evcon->port, evcon->fd)); + + /* We are turning the connection object over to the user */ + (*evcon->cb)(evcon, evcon->cb_arg); + return; + + cleanup: + /* Signal error to the user */ + (*evcon->cb)(NULL, evcon->cb_arg); + + evhttp_connection_free(evcon); + return; +} + +/* + * Check if we got a valid response code. + */ + +int +evhttp_valid_response_code(int code) +{ + if (code == 0) + return (0); + + return (1); +} + +/* Parses the status line of a web server */ + +int +evhttp_parse_response_line(struct evhttp_request *req, char *line) +{ + char *protocol; + char *number; + char *readable; + + protocol = strsep(&line, " "); + if (line == NULL) + return (-1); + number = strsep(&line, " "); + if (line == NULL) + return (-1); + readable = line; + + if (strcmp(protocol, "HTTP/1.0") == 0) { + req->major = 1; + req->minor = 0; + } else if (strcmp(protocol, "HTTP/1.1") == 0) { + req->major = 1; + req->minor = 1; + } else { + event_warnx("%s: bad protocol \"%s\" on %d\n", + __func__, protocol, req->fd); + return (-1); + } + + req->response_code = atoi(number); + if (!evhttp_valid_response_code(req->response_code)) { + event_warnx("%s: bad response code \"%s\" on %d\n", + __func__, number, req->fd); + return (-1); + } + + if ((req->response_code_line = strdup(readable)) == NULL) + event_err(1, "%s: strdup", __func__); + + return (0); +} + +/* Parse the first line of a HTTP request */ + +int +evhttp_parse_request_line(struct evhttp_request *req, char *line) +{ + char *method; + char *uri; + char *version; + + /* Parse the request line */ + method = strsep(&line, " "); + if (line == NULL) + return (-1); + uri = strsep(&line, " "); + if (line == NULL) + return (-1); + version = strsep(&line, " "); + if (line != NULL) + return (-1); + + /* First line */ + if (strcmp(method, "GET") == 0) { + req->type = EVHTTP_REQ_GET; + } else if (strcmp(method, "POST") == 0) { + req->type = EVHTTP_REQ_POST; + } else if (strcmp(method, "HEAD") == 0) { + req->type = EVHTTP_REQ_HEAD; + } else { + event_warnx("%s: bad method %s on fd %d\n", + __func__, method, req->fd); + return (-1); + } + + if (strcmp(version, "HTTP/1.0") == 0) { + req->major = 1; + req->minor = 0; + } else if (strcmp(version, "HTTP/1.1") == 0) { + req->major = 1; + req->minor = 1; + } else { + event_warnx("%s: bad version %s on %d\n", + __func__, version, req->fd); + return (-1); + } + + if ((req->uri = strdup(uri)) == NULL) { + event_warn("%s: strdup", __func__); + return (-1); + } + + return (0); +} + +char * +evhttp_find_header(struct evkeyvalq *headers, const char *key) +{ + struct evkeyval *header; + + TAILQ_FOREACH(header, headers, next) { + if (strcasecmp(header->key, key) == 0) + return (header->value); + } + + return (NULL); +} + +void +evhttp_clear_headers(struct evkeyvalq *headers) +{ + struct evkeyval *header; + + for (header = TAILQ_FIRST(headers); + header != NULL; + header = TAILQ_FIRST(headers)) { + TAILQ_REMOVE(headers, header, next); + free(header->key); + free(header->value); + free(header); + } +} + +void +evhttp_remove_header(struct evkeyvalq *headers, const char *key) +{ + struct evkeyval *header; + + TAILQ_FOREACH(header, headers, next) { + if (strcasecmp(header->key, key) == 0) + break; + } + + if (header == NULL) + return; + + /* Free and remove the header that we found */ + TAILQ_REMOVE(headers, header, next); + free(header->key); + free(header->value); + free(header); +} + +int +evhttp_add_header(struct evkeyvalq *headers, const char *key, const char *value) +{ + struct evkeyval *header; + + header = calloc(1, sizeof(struct evkeyval)); + if (header == NULL) { + event_warn("%s: calloc", __func__); + return (-1); + } + if ((header->key = strdup(key)) == NULL) { + event_warn("%s: strdup", __func__); + return (-1); + } + if ((header->value = strdup(value)) == NULL) { + event_warn("%s: strdup", __func__); + return (-1); + } + + TAILQ_INSERT_TAIL(headers, header, next); + + return (0); +} + +/* + * Parses header lines from a request or a response into the specified + * request object given an event buffer. + * + * Returns + * -1 on error + * 0 when we need to read more headers + * 1 when all headers have been read. + */ + +int +evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer) +{ + u_char *endp; + int done = 0; + + struct evkeyvalq* headers = req->input_headers; + while ((endp = evbuffer_find(buffer, "\r\n", 2)) != NULL) { + char *skey, *svalue; + + if (strncmp(buffer->buffer, "\r\n", 2) == 0) { + evbuffer_drain(buffer, 2); + /* Last header - Done */ + done = 1; + break; + } + + *endp = '\0'; + endp += 2; + + event_debug(("%s: Got: %s\n", __func__, buffer->buffer)); + + /* Processing of header lines */ + if (req->got_firstline == 0) { + switch (req->kind) { + case EVHTTP_REQUEST: + if (evhttp_parse_request_line(req, buffer->buffer) == -1) + return (-1); + break; + case EVHTTP_RESPONSE: + if (evhttp_parse_response_line(req, buffer->buffer) == -1) + return (-1); + break; + default: + return (-1); + } + req->got_firstline = 1; + } else { + /* Regular header */ + svalue = buffer->buffer; + skey = strsep(&svalue, ":"); + if (svalue == NULL) + return (-1); + + svalue += strspn(svalue, " "); + + if (evhttp_add_header(headers, skey, svalue) == -1) + return (-1); + } + + /* Move the uncompleted headers forward */ + evbuffer_drain(buffer, endp - buffer->buffer); + } + + return (done); +} + +void +evhttp_get_body(struct evhttp_request *req) +{ + struct timeval tv; + char *content_length; + char *connection; + struct evkeyvalq *headers = req->input_headers; + + /* If this is a request without a body, then we are done */ + if (req->kind == EVHTTP_REQUEST && req->type != EVHTTP_REQ_POST) { + (*req->cb)(req, req->cb_arg); + return; + } + + content_length = evhttp_find_header(headers, "Content-Length"); + connection = evhttp_find_header(headers, "Connection"); + + if (content_length == NULL && connection == NULL) + req->ntoread = -1; + else if (content_length == NULL && + strcasecmp(connection, "Close") != 0) { + /* Bad combination, we don't know when it will end */ + event_warnx("%s: we got no content length, but the server" + " wants to keep the connection open: %s.\n", + __func__, connection); + evhttp_fail(req); + return; + } else if (content_length == NULL) + req->ntoread = -1; + else + req->ntoread = atoi(content_length); + + event_debug(("%s: bytes to read: %d (in buffer %d)\n", + __func__, req->ntoread, req->buffer->off)); + + if (req->ntoread > 0) + req->ntoread -= req->buffer->off; + + if (req->ntoread == 0) { + (*req->cb)(req, req->cb_arg); + return; + } + + event_set(&req->ev, req->fd, EV_READ, evhttp_read, req); + timerclear(&tv); + tv.tv_sec = HTTP_READ_TIMEOUT; + event_add(&req->ev, &tv); +} + +void +evhttp_read_header(int fd, short what, void *arg) +{ + struct timeval tv; + struct evhttp_request *req = arg; + int n, res; + + if (what == EV_TIMEOUT) { + event_warnx("%s: timeout on %d\n", __func__, fd); + evhttp_request_free(req); + return; + } + + n = evbuffer_read(req->buffer, fd, -1); + if (n == 0) { + event_warnx("%s: no more data on %d\n", __func__, fd); + evhttp_request_free(req); + return; + } + if (n == -1) { + event_warnx("%s: bad read on %d\n", __func__, fd); + evhttp_request_free(req); + return; + } + + res = evhttp_parse_lines(req, req->buffer); + if (res == -1) { + /* Error while reading, terminate */ + event_warnx("%s: bad header lines on %d\n", __func__, fd); + evhttp_request_free(req); + return; + } else if (res == 0) { + /* Need more header lines */ + timerclear(&tv); + tv.tv_sec = HTTP_READ_TIMEOUT; + event_add(&req->ev, &tv); + return; + } + + /* Done reading headers, do the real work */ + switch (req->kind) { + case EVHTTP_REQUEST: + event_debug(("%s: checking for post data on %d\n", + __func__, req->fd)); + evhttp_get_body(req); + break; + + case EVHTTP_RESPONSE: + event_debug(("%s: starting to read body for \"%s\" on %d\n", + __func__, req->assoc_req->host, req->fd)); + evhttp_get_body(req); + break; + + default: + event_warnx("%s: bad header on %d\n", __func__, fd); + evhttp_fail(req); + break; + } +} + +/* + * Creates a TCP connection to the specified port and executes a callback + * when finished. Failure of sucess is indicate by the passed connection + * object. + */ + +struct evhttp_connection * +evhttp_connect(const char *address, unsigned short port, + void (*cb)(struct evhttp_connection *, void *), void *cb_arg) +{ + struct evhttp_connection *evcon = NULL; + struct timeval tv; + + event_debug(("Attempting connection to %s:%d\n", address, port)); + + if ((evcon = calloc(1, sizeof(struct evhttp_connection))) == NULL) { + event_warn("%s: calloc failed", __func__); + goto error; + } + + evcon->fd = -1; + evcon->port = port; + if ((evcon->address = strdup(address)) == NULL) { + event_warn("%s: strdup failed", __func__); + goto error; + } + + /* Let the user name when something interesting happened */ + evcon->cb = cb; + evcon->cb_arg = cb_arg; + + /* Do async connection to HTTP server */ + if ((evcon->fd = make_socket(connect, address, port)) == -1) { + event_warn("%s: failed to connect to \"%s:%d\"", + __func__, address, port); + goto error; + } + + /* Set up a callback for successful connection setup */ + event_set(&evcon->ev, evcon->fd, EV_WRITE, evhttp_connectioncb, evcon); + timerclear(&tv); + tv.tv_sec = HTTP_CONNECT_TIMEOUT; + event_add(&evcon->ev, &tv); + + return (evcon); + + error: + evhttp_connection_free(evcon); + return (NULL); +} + +/* + * Don't know if we just want to pass a file descriptor or the evcon object. + * In theory we might use this to queue requests on the connection object. + */ + +int +evhttp_start_request(struct evhttp_connection *evcon, + struct evhttp_request *req, + enum evhttp_cmd_type type, const char *uri) +{ + struct evbuffer *evbuf = evbuffer_new(); + + if (evbuf == NULL) + return (-1); + + /* We are making a request */ + req->fd = evcon->fd; + req->kind = EVHTTP_REQUEST; + req->type = type; + if (req->uri != NULL) + free(req->uri); + if ((req->uri = strdup(uri)) == NULL) + goto error; + + /* Set the protocol version if it is not supplied */ + if (!req->major && !req->minor) { + req->major = 1; + req->minor = 1; + } + + /* Create the header from the store arguments */ + evhttp_make_header(evbuf, req); + + /* Schedule the write */ + req->save_cb = req->cb; + req->save_cbarg = req->cb_arg; + + /* evbuf is being freed when the request finishes */ + evhttp_write_buffer(req, evbuf, evhttp_write_requestcb, NULL); + return (0); + + error: + evbuffer_free(evbuf); + return (-1); +} + +/* + * Reads data from file descriptor into request structure + * Request structure needs to be set up correctly. + */ + +void +evhttp_start_read(struct evhttp_request *req) +{ + struct timeval tv; + + /* Set up an event to read the headers */ + event_set(&req->ev, req->fd, EV_READ, evhttp_read_header, req); + + timerclear(&tv); + tv.tv_sec = HTTP_READ_TIMEOUT; + event_add(&req->ev, &tv); +} + +void +evhttp_send_done(struct evhttp_request *req, void *arg) +{ + evhttp_request_free(req); +} + +/* + * Returns an error page. + */ + +void +evhttp_send_error(struct evhttp_request *req, int error, const char *reason) +{ + char *fmt = "\n" + "%d %s\n" + "\n" + "

Method Not Implemented

\n" + "Invalid method in request

\n" + "\n"; + + struct evbuffer *buf = evbuffer_new(); + + evhttp_response_code(req, error, reason); + + evbuffer_add_printf(buf, fmt, error, reason); + + evhttp_send_page(req, buf); + + evbuffer_free(buf); +} + +/* Requires that headers and response code are already set up */ + +static __inline void +evhttp_send(struct evhttp_request *req, struct evbuffer *databuf) +{ + struct evbuffer *buf = req->buffer; + + evbuffer_drain(buf, -1); + + /* Adds headers to the response */ + evhttp_make_header(buf, req); + + evbuffer_add_buffer(buf, databuf); + + evhttp_write_buffer(req, buf, evhttp_send_done, NULL); +} + +void +evhttp_send_reply(struct evhttp_request *req, int code, const char *reason, + struct evbuffer *databuf) +{ + evhttp_response_code(req, code, reason); + + evhttp_send(req, databuf); +} + +void +evhttp_response_code(struct evhttp_request *req, int code, const char *reason) +{ + req->kind = EVHTTP_RESPONSE; + req->response_code = code; + if (req->response_code_line != NULL) + free(req->response_code_line); + req->response_code_line = strdup(reason); +} + +void +evhttp_send_page(struct evhttp_request *req, struct evbuffer *databuf) +{ + if (req->kind != EVHTTP_RESPONSE) + evhttp_response_code(req, 200, "OK"); + + evhttp_clear_headers(req->output_headers); + evhttp_add_header(req->output_headers, "Content-Type", "text/html"); + evhttp_add_header(req->output_headers, "Connection", "close"); + + evhttp_send(req, databuf); +} + +/* + * Helper function to parse out arguments in a query. + * The arguments are separated by key and value. + */ + +void +evhttp_parse_query(const char *uri, struct evkeyvalq *headers) +{ + char *line; + char *argument; + char *p; + + TAILQ_INIT(headers); + + if (strchr(uri, '?') == NULL) + return; + + if ((line = strdup(uri)) == NULL) + event_err(1, "%s: strdup", __func__); + + + argument = line; + + /* We already know that there has to be a ? */ + strsep(&argument, "?"); + + p = argument; + while (p != NULL && *p != '\0') { + char *key, *value; + argument = strsep(&p, "&"); + + value = argument; + key = strsep(&value, "="); + if (value == NULL) + goto error; + + event_warnx("Got: %s -> %s\n", key, value); + evhttp_add_header(headers, key, value); + } + + error: + free(line); +} + +void +evhttp_handle_request(struct evhttp_request *req, void *arg) +{ + struct evhttp *http = arg; + struct evhttp_cb *cb; + + /* Test for different URLs */ + TAILQ_FOREACH(cb, &http->callbacks, next) { + int res; + char *p = strchr(req->uri, '?'); + if (p == NULL) + res = strcmp(cb->what, req->uri) == 0; + else + res = strncmp(cb->what, req->uri, + (size_t)(p - req->uri)) == 0; + if (res) { + (*cb->cb)(req, cb->cbarg); + return; + } + } + + /* Generic call back */ + if (http->gencb) { + (*http->gencb)(req, http->gencbarg); + return; + } else { + /* We need to send a 404 here */ + char *fmt = "" + "404 Not Found" + "" + "

Not Found

" + "

The requested URL %s was not found on this server.

" + "\n"; + + char *escaped_html = evhttp_htmlescape(req->uri); + struct evbuffer *buf = evbuffer_new(); + + evhttp_response_code(req, HTTP_NOTFOUND, "Not Found"); + + evbuffer_add_printf(buf, fmt, escaped_html); + + free(escaped_html); + + evhttp_send_page(req, buf); + + evbuffer_free(buf); + } +} + +static void +accept_socket(int fd, short what, void *arg) +{ + struct evhttp *http = arg; + struct sockaddr_storage ss; + socklen_t addrlen = sizeof(ss); + int nfd; + + if ((nfd = accept(fd, (struct sockaddr *)&ss, &addrlen)) == -1) { + event_warn("%s: bad accept", __func__); + return; + } + + evhttp_get_request(nfd, (struct sockaddr *)&ss, addrlen, + evhttp_handle_request, http); +} + +static int +bind_socket(struct evhttp *http, const char *address, u_short port) +{ + struct event *ev = &http->bind_ev; + int fd; + + if ((fd = make_socket(bind, address, port)) == -1) + return (-1); + + if (listen(fd, 10) == -1) { + event_warn("%s: listen", __func__); + return (-1); + } + + /* Schedule the socket for accepting */ + event_set(ev, fd, EV_READ | EV_PERSIST, accept_socket, http); + event_add(ev, NULL); + + event_debug(("Bound to port %d - Awaiting connections ... ", port)); + + return (0); +} + +/* + * Start a web server on the specified address and port. + */ + +struct evhttp * +evhttp_start(const char *address, u_short port) +{ + struct evhttp *http; + + if ((http = calloc(1, sizeof(struct evhttp))) == NULL) { + event_warn("%s: calloc", __func__); + return (NULL); + } + + TAILQ_INIT(&http->callbacks); + + if (bind_socket(http, address, port) == -1) { + free(http); + return (NULL); + } + + return (http); +} + +void +evhttp_free(struct evhttp* http) +{ + struct evhttp_cb *http_cb; + int fd = http->bind_ev.ev_fd; + + /* Remove the accepting part */ + event_del(&http->bind_ev); + close(fd); + + while ((http_cb = TAILQ_FIRST(&http->callbacks)) != NULL) { + TAILQ_REMOVE(&http->callbacks, http_cb, next); + free(http_cb->what); + free(http_cb); + } + + free(http); +} + +void +evhttp_set_cb(struct evhttp *http, const char *uri, + void (*cb)(struct evhttp_request *, void *), void *cbarg) +{ + struct evhttp_cb *http_cb; + + if ((http_cb = calloc(1, sizeof(struct evhttp_cb))) == NULL) + event_err(1, "%s: calloc", __func__); + + http_cb->what = strdup(uri); + http_cb->cb = cb; + http_cb->cbarg = cbarg; + + TAILQ_INSERT_TAIL(&http->callbacks, http_cb, next); +} + +void +evhttp_set_gencb(struct evhttp *http, + void (*cb)(struct evhttp_request *, void *), void *cbarg) +{ + http->gencb = cb; + http->gencbarg = cbarg; +} + +/* + * Request related functions + */ + +struct evhttp_request * +evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg) +{ + struct evhttp_request *req = NULL; + + /* Allocate request structure */ + if ((req = calloc(1, sizeof(struct evhttp_request))) == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + + req->fd = -1; + req->kind = EVHTTP_RESPONSE; + req->input_headers = calloc(1, sizeof(struct evkeyvalq)); + if (req->input_headers == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + TAILQ_INIT(req->input_headers); + + req->output_headers = calloc(1, sizeof(struct evkeyvalq)); + if (req->output_headers == NULL) { + event_warn("%s: calloc", __func__); + goto error; + } + TAILQ_INIT(req->output_headers); + + req->buffer = evbuffer_new(); + + req->cb = cb; + req->cb_arg = arg; + + return (req); + + error: + if (req != NULL) + evhttp_request_free(req); + return (NULL); +} + +void +evhttp_request_free(struct evhttp_request *req) +{ + if (req->fd != -1) + close(req->fd); + if (req->remote_host != NULL) + free(req->remote_host); + if (req->uri != NULL) + free(req->uri); + if (req->response_code_line != NULL) + free(req->response_code_line); + + if (event_initialized(&req->ev)) + event_del(&req->ev); + + evhttp_clear_headers(req->input_headers); + free(req->input_headers); + + evhttp_clear_headers(req->output_headers); + free(req->output_headers); + + if (req->buffer != NULL) + evbuffer_free(req->buffer); + free(req); +} + +/* + * Takes a file descriptor to read a request from. + * The callback is executed once the whole request has been read. + */ + +void +evhttp_get_request(int fd, struct sockaddr *sa, socklen_t salen, + void (*cb)(struct evhttp_request *, void *), void *arg) +{ + struct evhttp_request *req; + char *hostname, *portname; + + name_from_addr(sa, salen, &hostname, &portname); + event_debug(("%s: new request from %s:%s on %d\n", + __func__, hostname, portname, fd)); + + if ((req = evhttp_request_new(cb, arg)) == NULL) + return; + + req->fd = fd; + req->kind = EVHTTP_REQUEST; + + if ((req->remote_host = strdup(hostname)) == NULL) + event_err(1, "%s: strdup", __func__); + req->remote_port = atoi(portname); + + evhttp_start_read(req); +} + + +/* + * Network helper functions that we do not want to export to the rest of + * the world. + */ + +static struct addrinfo * +addr_from_name(char *address) +{ + struct addrinfo ai, *aitop; + + memset(&ai, 0, sizeof (ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_RAW; + ai.ai_flags = 0; + if (getaddrinfo(address, NULL, &ai, &aitop) != 0) { + event_warn("getaddrinfo"); + return (NULL); + } + + return (aitop); +} + +static void +name_from_addr(struct sockaddr *sa, socklen_t salen, + char **phost, char **pport) +{ + static char ntop[NI_MAXHOST]; + static char strport[NI_MAXSERV]; + + if (getnameinfo(sa, salen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV) != 0) + event_err(1, "getnameinfo failed"); + + *phost = ntop; + *pport = strport; +} + +/* Either connect or bind */ + +static int +make_socket_ai(int (*f)(int, const struct sockaddr *, socklen_t), + struct addrinfo *ai) +{ + struct linger linger; + int fd, on = 1; + int serrno; + + /* Create listen socket */ + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + event_warn("socket"); + return (-1); + } + + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { + event_warn("fcntl(O_NONBLOCK)"); + goto out; + } + + if (fcntl(fd, F_SETFD, 1) == -1) { + event_warn("fcntl(F_SETFD)"); + goto out; + } + + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)); + linger.l_onoff = 1; + linger.l_linger = 5; + setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); + + if ((f)(fd, ai->ai_addr, ai->ai_addrlen) == -1) { + if (errno != EINPROGRESS) { + goto out; + } + } + + return (fd); + + out: + serrno = errno; + close(fd); + errno = serrno; + return (-1); +} + +static int +make_socket(int (*f)(int, const struct sockaddr *, socklen_t), + const char *address, short port) +{ + struct addrinfo ai, *aitop; + char strport[NI_MAXSERV]; + int fd; + + memset(&ai, 0, sizeof (ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_STREAM; + ai.ai_flags = f != connect ? AI_PASSIVE : 0; + snprintf(strport, sizeof (strport), "%d", port); + if (getaddrinfo(address, strport, &ai, &aitop) != 0) { + event_warn("getaddrinfo"); + return (-1); + } + + fd = make_socket_ai(f, aitop); + + freeaddrinfo(aitop); + + return (fd); +} diff --git a/test/Makefile.am b/test/Makefile.am index 11a20d92..157d261e 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -13,7 +13,8 @@ test_init_SOURCES = test-init.c test_eof_SOURCES = test-eof.c test_weof_SOURCES = test-weof.c test_time_SOURCES = test-time.c -regress_SOURCES = regress.c regress.gen.c regress.gen.h +regress_SOURCES = regress.c regress.h regress_http.c \ + regress.gen.c regress.gen.h bench_SOURCES = bench.c regress.gen.c regress.gen.h: regress.rpc diff --git a/test/regress.c b/test/regress.c index 5217617c..f43e70c9 100644 --- a/test/regress.c +++ b/test/regress.c @@ -39,23 +39,28 @@ #ifdef HAVE_SYS_TIME_H #include #endif +#include #ifndef WIN32 #include #include #include #endif +#include #include #include #include #include #include -#include +#include "event.h" +#include "log.h" +#include "http.h" +#include "regress.h" #include "regress.gen.h" -static int pair[2]; -static int test_ok; +int pair[2]; +int test_ok; static int called; static char wbuf[4096]; static char rbuf[4096]; @@ -872,6 +877,8 @@ main (int argc, char **argv) /* Initalize the event library */ event_base = event_init(); + http_suite(); + test_simpleread(); test_simplewrite(); diff --git a/test/regress.h b/test/regress.h new file mode 100644 index 00000000..3006830c --- /dev/null +++ b/test/regress.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2000-2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _REGRESS_H_ +#define _REGRESS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void http_suite(void); +void http_basic_test(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _REGRESS_H_ */ diff --git a/test/regress_http.c b/test/regress_http.c new file mode 100644 index 00000000..a7c1423a --- /dev/null +++ b/test/regress_http.c @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2003, 2004 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#ifndef WIN32 +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "event.h" +#include "log.h" +#include "http.h" + +extern int pair[]; +extern int test_ok; + +static struct evhttp *http; + +void http_basic_cb(struct evhttp_request *req, void *arg); + +struct evhttp * +http_setup(short *pport) +{ + int i; + struct evhttp *myhttp; + short port = -1; + + /* Try a few different ports */ + for (i = 0; i < 50; ++i) { + myhttp = evhttp_start("127.0.0.1", 8080 + i); + if (myhttp != NULL) { + port = 8080 + i; + break; + } + } + + if (port == -1) + event_errx(1, "Could not start web server"); + + evhttp_set_cb(myhttp, "/test", http_basic_cb, NULL); + + *pport = port; + return (myhttp); +} + +int +http_connect(const char *address, u_short port) +{ + /* Stupid code for connecting */ + struct addrinfo ai, *aitop; + char strport[NI_MAXSERV]; + int fd; + + memset(&ai, 0, sizeof (ai)); + ai.ai_family = AF_INET; + ai.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof (strport), "%d", port); + if (getaddrinfo(address, strport, &ai, &aitop) != 0) { + event_warn("getaddrinfo"); + return (-1); + } + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + event_err(1, "socket failed"); + + if (connect(fd, aitop->ai_addr, aitop->ai_addrlen) == -1) + event_err(1, "connect failed"); + + freeaddrinfo(aitop); + + return (fd); +} + +void +http_readcb(struct bufferevent *bev, void *arg) +{ + const char *what = "This is funny"; + + event_debug(("%s: %s\n", __func__, EVBUFFER_DATA(bev->input))); + + if (evbuffer_find(bev->input, what, strlen(what)) != NULL) { + struct evhttp_request *req = evhttp_request_new(NULL, NULL); + req->kind = EVHTTP_RESPONSE; + int done = evhttp_parse_lines(req, bev->input); + + if (done == 1 && + evhttp_find_header(req->input_headers, + "Content-Type") != NULL) + test_ok++; + evhttp_request_free(req); + event_loopexit(NULL); + } +} + +void +http_writecb(struct bufferevent *bev, void *arg) +{ + if (EVBUFFER_LENGTH(bev->output) == 0) { + /* enable reading of the reply */ + bufferevent_enable(bev, EV_READ); + test_ok++; + } +} + +void +http_errorcb(struct bufferevent *bev, short what, void *arg) +{ + test_ok = -2; + event_loopexit(NULL); +} + +void +http_basic_cb(struct evhttp_request *req, void *arg) +{ + event_debug((stderr, "%s: called\n", __func__)); + + struct evbuffer *evb = evbuffer_new(); + evbuffer_add_printf(evb, "This is funny"); + + evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb); + + evbuffer_free(evb); +} + +void +http_basic_test(void) +{ + struct bufferevent *bev; + int fd; + char *http_request; + short port = -1; + + test_ok = 0; + fprintf(stdout, "Testing Basic HTTP Server: "); + + http = http_setup(&port); + + fd = http_connect("127.0.0.1", port); + + /* Stupid thing to send a request */ + bev = bufferevent_new(fd, http_readcb, http_writecb, + http_errorcb, NULL); + + http_request = + "GET /test HTTP/1.1\r\n" + "Host: somehost \r\n" + "\r\n"; + + bufferevent_write(bev, http_request, strlen(http_request)); + + event_dispatch(); + + bufferevent_free(bev); + close(fd); + + evhttp_free(http); + + if (test_ok != 2) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +void http_connectcb(struct evhttp_connection *evcon, void *arg); + +void +http_connection_test(void) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + + test_ok = 0; + fprintf(stdout, "Testing Basic HTTP Connection: "); + + http = http_setup(&port); + + evcon = evhttp_connect("127.0.0.1", port, http_connectcb, NULL); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + /* + * At this point, we want to schedule a request to the HTTP + * server using our start request method. + */ + + evhttp_connection_free(evcon); + evhttp_free(http); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +void http_request_done(struct evhttp_request *, void *); + +void +http_connectcb(struct evhttp_connection *evcon, void *arg) +{ + struct evhttp_request *req = NULL; + + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit (1); + } + + req = evhttp_request_new(http_request_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + + if (evhttp_start_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } +} + +void +http_request_done(struct evhttp_request *req, void *arg) +{ + const char *what = "This is funny"; + + if (req->response_code != HTTP_OK) { + + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (evhttp_find_header(req->input_headers, + "Content-Type") == NULL) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (EVBUFFER_LENGTH(req->buffer) != strlen(what)) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + if (memcmp(EVBUFFER_DATA(req->buffer), what, strlen(what)) != 0) { + fprintf(stderr, "FAILED\n"); + exit(1); + } + + test_ok = 1; + event_loopexit(NULL); +} + +void +http_suite(void) +{ + http_basic_test(); + http_connection_test(); +}