make a simple test for HTTP POST requests
authorNiels Provos <provos@gmail.com>
Mon, 27 Feb 2006 02:27:37 +0000 (02:27 +0000)
committerNiels Provos <provos@gmail.com>
Mon, 27 Feb 2006 02:27:37 +0000 (02:27 +0000)
svn:r205

event.3
event.h
http.c
test/regress_http.c

diff --git a/event.3 b/event.3
index 536775ff439884c616e3478550b460e571bcbc08..2e594c4a444423907fe0381882091028f0376c39 100644 (file)
--- 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 <sys/time.h>
 .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 8787cc0b7236e19e32bf82c90d4844059649867a..6001c40fa0ae2df567f81ee4660d96397c70b263 100644 (file)
--- 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 bc3042a193f1c89d1a4b8befbbb8336ef6409e4e..40fb1030907b7a1b5593f11c821c2e306e022a6a 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1,7 +1,28 @@
 /*
- * Copyright 2002, 2003, 2005 Niels Provos <provos@citi.umich.edu>
+ * Copyright (c) 2002-2006 Niels Provos <provos@citi.umich.edu>
  * 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 <sys/param.h>
@@ -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.
index 17597e97078a2be6e0d51e3a9f3a96a6f8d7e498..f96fa69d89d1e43b88938ba0a9c9be6bf3558e28 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2004 Niels Provos <provos@citi.umich.edu>
+ * Copyright (c) 2003-2006 Niels Provos <provos@citi.umich.edu>
  * 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();
 }