diff --git a/event.3 b/event.3 index 536775ff..2e594c4a 100644 --- a/event.3 +++ b/event.3 @@ -75,7 +75,9 @@ .Nm evbuffer_write , .Nm evbuffer_read , .Nm evbuffer_find , -.Nm evbuffer_readline +.Nm evbuffer_readline , +.Nm evhttp_start , +.Nm evhttp_free .Nd execute a function when a specific event occurs .Sh SYNOPSIS .Fd #include @@ -172,6 +174,10 @@ .Fn "evbuffer_find" "struct evbuffer *buf" "u_char *data" "size_t size" .Ft "char *" .Fn "evbuffer_readline" "struct evbuffer *buf" +.Ft "struct evhttp *" +.Fn "evhttp_start" "const char *address" "u_short port" +.Ft "void" +.Fn "evhttp_free" "struct evhttp* http" .Ft int .Fa (*event_sigcb)(void) ; .Ft int @@ -513,6 +519,28 @@ Both functions return the amount of data written or read. .Pp If multiple bases are in use, bufferevent_base_set() must be called before enabling the bufferevent for the first time. +.Sh NON-BLOCKING HTTP SUPPORT +.Nm libevent +provides a very thin HTTP layer that can be used both to host an HTTP +server and also to make HTTP requests. +An HTTP server can be created by calling +.Fn evhttp_start . +When the HTTP server is no longer used, it can be freed via +.Fn evhttp_free . +.Pp +To be notified of HTTP requests, a user needs to register callbacks with the +HTTP server. +This can be done by calling +.Fn evhttp_set_cb . +The second argument is the URI for which a callback is being registered. +The corresponding callback will receive an +.Va struct evhttp_request +object that contains all information about the request. +.Pp +This section does not document all the possible function calls, please +check +.Va event.h +for the public interfaces. .Sh RETURN VALUES Upon successful completion .Fn event_add diff --git a/event.h b/event.h index 8787cc0b..6001c40f 100644 --- a/event.h +++ b/event.h @@ -353,7 +353,7 @@ struct evhttp_request; struct evhttp *evhttp_start(const char *address, u_short port); /* - * Free the previously create HTTP server. Works only if not requests are + * Free the previously create HTTP server. Works only if no requests are * currently being served. */ void evhttp_free(struct evhttp* http); @@ -376,15 +376,18 @@ enum evhttp_cmd_type { EVHTTP_REQ_GET, EVHTTP_REQ_POST, EVHTTP_REQ_HEAD }; struct evhttp_request *evhttp_request_new( void (*cb)(struct evhttp_request *, void *), void *arg); void evhttp_request_free(struct evhttp_request *req); +const char *evhttp_request_uri(struct evhttp_request *req); /* Interfaces for dealing with HTTP headers */ -char *evhttp_find_header(struct evkeyvalq *, const char *); -void evhttp_remove_header(struct evkeyvalq *, const char *); +const char *evhttp_find_header(struct evkeyvalq *, const char *); +int evhttp_remove_header(struct evkeyvalq *, const char *); int evhttp_add_header(struct evkeyvalq *, const char *, const char *); void evhttp_clear_headers(struct evkeyvalq *); +/* Miscellaneous utility functions */ void evhttp_parse_query(const char *uri, struct evkeyvalq *); +char *evhttp_htmlescape(const char *html); #ifdef __cplusplus } #endif diff --git a/http.c b/http.c index bc3042a1..40fb1030 100644 --- a/http.c +++ b/http.c @@ -1,7 +1,28 @@ /* - * Copyright 2002, 2003, 2005 Niels Provos + * Copyright (c) 2002-2006 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. */ #include @@ -149,7 +170,8 @@ evhttp_form_response(struct evbuffer *buf, struct evhttp_request *req) evhttp_make_header(buf, req); /* Append the response buffer */ - evbuffer_add(buf, req->buffer->buffer, req->buffer->off); + evbuffer_add(buf, + EVBUFFER_DATA(req->buffer), EVBUFFER_LENGTH(req->buffer)); } void @@ -177,6 +199,9 @@ evhttp_write_buffer(struct evhttp_request *req, struct evbuffer *buffer, event_add(&req->ev, &tv); } +/* + * Create the headers need for an HTTP reply + */ static void evhttp_make_header_request(struct evbuffer *buf, struct evhttp_request *req) { @@ -205,6 +230,9 @@ evhttp_make_header_request(struct evbuffer *buf, struct evhttp_request *req) } } +/* + * Create the headers needed for an HTTP reply + */ static void evhttp_make_header_response(struct evbuffer *buf, struct evhttp_request *req) { @@ -249,7 +277,7 @@ evhttp_make_header(struct evbuffer *buf, struct evhttp_request *req) /* Add the POST data */ if (len > 0) - evbuffer_add(buf, req->buffer->buffer, len); + evbuffer_add(buf, EVBUFFER_DATA(req->buffer), len); } } @@ -339,7 +367,7 @@ evhttp_write(int fd, short what, void *arg) return; } - if (req->buffer->off != 0) { + if (EVBUFFER_LENGTH(req->buffer) != 0) { timerclear(&tv); tv.tv_sec = HTTP_WRITE_TIMEOUT; event_add(&req->ev, &tv); @@ -580,7 +608,7 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line) return (0); } -char * +const char * evhttp_find_header(struct evkeyvalq *headers, const char *key) { struct evkeyval *header; @@ -608,7 +636,12 @@ evhttp_clear_headers(struct evkeyvalq *headers) } } -void +/* + * Returns 0, if the header was successfully removed. + * Returns -1, if the header could not be found. + */ + +int evhttp_remove_header(struct evkeyvalq *headers, const char *key) { struct evkeyval *header; @@ -619,13 +652,15 @@ evhttp_remove_header(struct evkeyvalq *headers, const char *key) } if (header == NULL) - return; + return (-1); /* Free and remove the header that we found */ TAILQ_REMOVE(headers, header, next); free(header->key); free(header->value); free(header); + + return (0); } int @@ -639,10 +674,13 @@ evhttp_add_header(struct evkeyvalq *headers, const char *key, const char *value) return (-1); } if ((header->key = strdup(key)) == NULL) { + free(header); event_warn("%s: strdup", __func__); return (-1); } if ((header->value = strdup(value)) == NULL) { + free(header->key); + free(header); event_warn("%s: strdup", __func__); return (-1); } @@ -672,7 +710,7 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer) while ((endp = evbuffer_find(buffer, "\r\n", 2)) != NULL) { char *skey, *svalue; - if (strncmp(buffer->buffer, "\r\n", 2) == 0) { + if (strncmp(EVBUFFER_DATA(buffer), "\r\n", 2) == 0) { evbuffer_drain(buffer, 2); /* Last header - Done */ done = 1; @@ -682,17 +720,17 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer) *endp = '\0'; endp += 2; - event_debug(("%s: Got: %s\n", __func__, buffer->buffer)); + event_debug(("%s: Got: %s\n", __func__, EVBUFFER_DATA(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) + if (evhttp_parse_request_line(req, EVBUFFER_DATA(buffer)) == -1) return (-1); break; case EVHTTP_RESPONSE: - if (evhttp_parse_response_line(req, buffer->buffer) == -1) + if (evhttp_parse_response_line(req, EVBUFFER_DATA(buffer)) == -1) return (-1); break; default: @@ -701,7 +739,7 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer) req->got_firstline = 1; } else { /* Regular header */ - svalue = buffer->buffer; + svalue = EVBUFFER_DATA(buffer); skey = strsep(&svalue, ":"); if (svalue == NULL) return (-1); @@ -713,7 +751,7 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer) } /* Move the uncompleted headers forward */ - evbuffer_drain(buffer, endp - buffer->buffer); + evbuffer_drain(buffer, endp - EVBUFFER_DATA(buffer)); } return (done); @@ -723,8 +761,8 @@ void evhttp_get_body(struct evhttp_request *req) { struct timeval tv; - char *content_length; - char *connection; + const char *content_length; + const char *connection; struct evkeyvalq *headers = req->input_headers; /* If this is a request without a body, then we are done */ @@ -752,10 +790,10 @@ evhttp_get_body(struct evhttp_request *req) req->ntoread = atoi(content_length); event_debug(("%s: bytes to read: %d (in buffer %d)\n", - __func__, req->ntoread, req->buffer->off)); + __func__, req->ntoread, EVBUFFER_LENGTH(req->buffer))); if (req->ntoread > 0) - req->ntoread -= req->buffer->off; + req->ntoread -= EVBUFFER_LENGTH(req->buffer); if (req->ntoread == 0) { (*req->cb)(req, req->cb_arg); @@ -918,13 +956,6 @@ evhttp_make_request(struct evhttp_connection *evcon, /* Create the header from the store arguments */ evhttp_make_header(evbuf, req); - /* - * If this was a post request or for other reasons we need to append - * our post data to the request. - */ - evbuffer_add_buffer(evbuf, req->buffer); - - /* Schedule the write */ req->save_cb = req->cb; req->save_cbarg = req->cb_arg; @@ -1307,6 +1338,17 @@ evhttp_request_free(struct evhttp_request *req) free(req); } +/* + * Allows for inspection of the request URI + */ + +const char * +evhttp_request_uri(struct evhttp_request *req) { + if (req->uri == NULL) + event_debug(("%s: request %p has no uri\n", req)); + return (req->uri); +} + /* * Takes a file descriptor to read a request from. * The callback is executed once the whole request has been read. diff --git a/test/regress_http.c b/test/regress_http.c index 17597e97..f96fa69d 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2004 Niels Provos + * Copyright (c) 2003-2006 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,6 +62,7 @@ extern int test_ok; static struct evhttp *http; void http_basic_cb(struct evhttp_request *req, void *arg); +void http_post_cb(struct evhttp_request *req, void *arg); struct evhttp * http_setup(short *pport) @@ -84,6 +85,7 @@ http_setup(short *pport) /* Register a callback for certain types of requests */ evhttp_set_cb(myhttp, "/test", http_basic_cb, NULL); + evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL); *pport = port; return (myhttp); @@ -232,11 +234,6 @@ http_connection_test(void) 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); @@ -260,6 +257,11 @@ http_connectcb(struct evhttp_connection *evcon, void *arg) exit (1); } + /* + * At this point, we want to schedule a request to the HTTP + * server using our make request method. + */ + req = evhttp_request_new(http_request_done, NULL); /* Add the information that we care about */ @@ -282,8 +284,7 @@ http_request_done(struct evhttp_request *req, void *arg) exit(1); } - if (evhttp_find_header(req->input_headers, - "Content-Type") == NULL) { + if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) { fprintf(stderr, "FAILED\n"); exit(1); } @@ -302,9 +303,137 @@ http_request_done(struct evhttp_request *req, void *arg) event_loopexit(NULL); } +/* + * HTTP POST test. + */ + +void http_connect_forpostcb(struct evhttp_connection *evcon, void *arg); + +void +http_post_test(void) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + + test_ok = 0; + fprintf(stdout, "Testing HTTP POST Request: "); + + http = http_setup(&port); + + evcon = evhttp_connect("127.0.0.1", port, http_connect_forpostcb, NULL); + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + event_dispatch(); + + evhttp_connection_free(evcon); + evhttp_free(http); + + if (test_ok != 1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); +} + +void http_postrequest_done(struct evhttp_request *, void *); + +#define POST_DATA "Okay. Not really printf" + +void +http_connect_forpostcb(struct evhttp_connection *evcon, void *arg) +{ + struct evhttp_request *req = NULL; + + if (evcon == NULL) { + fprintf(stdout, "FAILED\n"); + exit (1); + } + + /* + * At this point, we want to schedule an HTTP POST request + * server using our make request method. + */ + + req = evhttp_request_new(http_postrequest_done, NULL); + + /* Add the information that we care about */ + evhttp_add_header(req->output_headers, "Host", "somehost"); + evbuffer_add_printf(req->buffer, POST_DATA); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) { + fprintf(stdout, "FAILED\n"); + exit(1); + } +} + +void +http_post_cb(struct evhttp_request *req, void *arg) +{ + event_debug((stderr, "%s: called\n", __func__)); + + /* Yes, we are expecting a post request */ + if (req->type != EVHTTP_REQ_POST) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + if (EVBUFFER_LENGTH(req->buffer) != strlen(POST_DATA)) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + if (strcmp(EVBUFFER_DATA(req->buffer), POST_DATA)) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + 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_postrequest_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(); + http_post_test(); }