make a simple test for HTTP POST requests

svn:r205
This commit is contained in:
Niels Provos 2006-02-27 02:27:37 +00:00
parent 60192b4625
commit 38b33048eb
4 changed files with 238 additions and 36 deletions

30
event.3
View File

@ -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>
@ -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

View File

@ -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

90
http.c
View File

@ -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.

View 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();
}