Edit

kc3-lang/libevent/test/regress_http.c

Branch :

  • Show log

    Commit

  • Author : Nick Mathewson
    Date : 2009-05-13 20:36:56
    Hash : 83f46e51
    Message : Do not use the "evbuffer_" prefix to denote parts of bufferevents. This is a bit of an interface doozy, but it's really needed in order to be able to document this stuff without apologizing it. This patch does the following renamings: evbuffercb -> bufferevent_data_cb everrorcb -> bufferevent_event_cb EVBUFFER_(READ,WRITE,...) -> BEV_EVENT_(...) EVBUFFER_(INPUT,OUTPUT) -> bufferevent_get_(input,output) All the old names are available in event2/bufferevent_compat.h svn:r1283

  • test/regress_http.c
  • /*
     * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu>
     * Copyright (c) 2007-2009 Niels Provos and Nick Mathewson
     *
     * 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 <winsock2.h>
    #include <windows.h>
    #endif
    
    #ifdef HAVE_CONFIG_H
    #include "event-config.h"
    #endif
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #ifdef _EVENT_HAVE_SYS_TIME_H
    #include <sys/time.h>
    #endif
    #include <sys/queue.h>
    #ifndef WIN32
    #include <sys/socket.h>
    #include <signal.h>
    #include <unistd.h>
    #include <netdb.h>
    #endif
    #include <fcntl.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    
    #include "event.h"
    #include "evhttp.h"
    #include "log-internal.h"
    #include "util-internal.h"
    #include "http-internal.h"
    #include "regress.h"
    
    static struct evhttp *http;
    /* set if a test needs to call loopexit on a base */
    static struct event_base *base;
    
    static char const BASIC_REQUEST_BODY[] = "This is funny";
    
    static void http_basic_cb(struct evhttp_request *req, void *arg);
    static void http_chunked_cb(struct evhttp_request *req, void *arg);
    static void http_post_cb(struct evhttp_request *req, void *arg);
    static void http_put_cb(struct evhttp_request *req, void *arg);
    static void http_delete_cb(struct evhttp_request *req, void *arg);
    static void http_delay_cb(struct evhttp_request *req, void *arg);
    static void http_large_delay_cb(struct evhttp_request *req, void *arg);
    static void http_dispatcher_cb(struct evhttp_request *req, void *arg);
    
    static struct evhttp *
    http_setup(short *pport, struct event_base *base)
    {
    	int i;
    	struct evhttp *myhttp;
    	short port = -1;
    
    	/* Try a few different ports */
    	myhttp = evhttp_new(base);
    	for (i = 0; i < 50; ++i) {
    		if (evhttp_bind_socket(myhttp, "127.0.0.1", 8080 + i) != -1) {
    			port = 8080 + i;
    			break;
    		}
    	}
    
    	if (port == -1)
    		event_errx(1, "Could not start web server");
    
    	/* Register a callback for certain types of requests */
    	evhttp_set_cb(myhttp, "/test", http_basic_cb, NULL);
    	evhttp_set_cb(myhttp, "/chunked", http_chunked_cb, NULL);
    	evhttp_set_cb(myhttp, "/streamed", http_chunked_cb, NULL);
    	evhttp_set_cb(myhttp, "/postit", http_post_cb, NULL);
    	evhttp_set_cb(myhttp, "/putit", http_put_cb, NULL);
    	evhttp_set_cb(myhttp, "/deleteit", http_delete_cb, NULL);
    	evhttp_set_cb(myhttp, "/delay", http_delay_cb, NULL);
    	evhttp_set_cb(myhttp, "/largedelay", http_large_delay_cb, NULL);
    	evhttp_set_cb(myhttp, "/", http_dispatcher_cb, NULL);
    
    	*pport = port;
    	return (myhttp);
    }
    
    #ifndef NI_MAXSERV
    #define NI_MAXSERV 1024
    #endif
    
    static int
    http_connect(const char *address, u_short port)
    {
    	/* Stupid code for connecting */
    #ifdef WIN32
    	struct hostent *he;
    	struct sockaddr_in sin;
    #else
    	struct addrinfo ai, *aitop;
    	char strport[NI_MAXSERV];
    #endif
    	struct sockaddr *sa;
    	int slen;
    	int fd;
    
    #ifdef WIN32
    	if (!(he = gethostbyname(address))) {
    		event_warn("gethostbyname");
    	}
    	memcpy(&sin.sin_addr, he->h_addr_list[0], he->h_length);
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(port);
    	slen = sizeof(struct sockaddr_in);
    	sa = (struct sockaddr*)&sin;
    #else
    	memset(&ai, 0, sizeof (ai));
    	ai.ai_family = AF_INET;
    	ai.ai_socktype = SOCK_STREAM;
    	evutil_snprintf(strport, sizeof (strport), "%d", port);
    	if (getaddrinfo(address, strport, &ai, &aitop) != 0) {
    		event_warn("getaddrinfo");
    		return (-1);
    	}
    	sa = aitop->ai_addr;
    	slen = aitop->ai_addrlen;
    #endif
    
    	fd = socket(AF_INET, SOCK_STREAM, 0);
    	if (fd == -1)
    		event_err(1, "socket failed");
    
    	evutil_make_socket_nonblocking(fd);
    	if (connect(fd, sa, slen) == -1) {
    #ifdef WIN32
    		int tmp_err = WSAGetLastError();
    		if (tmp_err != WSAEINPROGRESS && tmp_err != WSAEINVAL &&
    		    tmp_err != WSAEWOULDBLOCK)
    			event_err(1, "connect failed");
    #else
    		if (errno != EINPROGRESS)
    			event_err(1, "connect failed");
    #endif
    	}
    
    #ifndef WIN32
    	freeaddrinfo(aitop);
    #endif
    
    	return (fd);
    }
    
    static void
    http_readcb(struct bufferevent *bev, void *arg)
    {
    	const char *what = BASIC_REQUEST_BODY;
    
     	event_debug(("%s: %s\n", __func__, EVBUFFER_DATA(bufferevent_get_input(bev))));
    
    	if (evbuffer_find(bufferevent_get_input(bev),
    		(const unsigned char*) what, strlen(what)) != NULL) {
    		struct evhttp_request *req = evhttp_request_new(NULL, NULL);
    		enum message_read_status done;
    
    		req->kind = EVHTTP_RESPONSE;
    		done = evhttp_parse_firstline(req, bufferevent_get_input(bev));
    		if (done != ALL_DATA_READ)
    			goto out;
    
    		done = evhttp_parse_headers(req, bufferevent_get_input(bev));
    		if (done != ALL_DATA_READ)
    			goto out;
    
    		if (done == 1 &&
    		    evhttp_find_header(req->input_headers,
    			"Content-Type") != NULL)
    			test_ok++;
    
    	 out:
    		evhttp_request_free(req);
    		bufferevent_disable(bev, EV_READ);
    		if (base)
    			event_base_loopexit(base, NULL);
    		else
    			event_loopexit(NULL);
    	}
    }
    
    static void
    http_writecb(struct bufferevent *bev, void *arg)
    {
    	if (EVBUFFER_LENGTH(bufferevent_get_output(bev)) == 0) {
    		/* enable reading of the reply */
    		bufferevent_enable(bev, EV_READ);
    		test_ok++;
    	}
    }
    
    static void
    http_errorcb(struct bufferevent *bev, short what, void *arg)
    {
    	test_ok = -2;
    	event_loopexit(NULL);
    }
    
    static void
    http_basic_cb(struct evhttp_request *req, void *arg)
    {
    	struct evbuffer *evb = evbuffer_new();
    	int empty = evhttp_find_header(req->input_headers, "Empty") != NULL;
    	event_debug(("%s: called\n", __func__));
    	evbuffer_add_printf(evb, BASIC_REQUEST_BODY);
    
    	/* For multi-line headers test */
    	{
    		const char *multi =
    		    evhttp_find_header(req->input_headers,"X-multi");
    		if (multi) {
    			if (strcmp("END", multi + strlen(multi) - 3) == 0)
    				test_ok++;
    			if (evhttp_find_header(req->input_headers, "X-Last"))
    				test_ok++;
    		}
    	}
    
    	/* injecting a bad content-length */
    	if (evhttp_find_header(req->input_headers, "X-Negative"))
    		evhttp_add_header(req->output_headers,
    		    "Content-Length", "-100");
    
    	/* allow sending of an empty reply */
    	evhttp_send_reply(req, HTTP_OK, "Everything is fine",
    	    !empty ? evb : NULL);
    
    	evbuffer_free(evb);
    }
    
    static char const* const CHUNKS[] = {
    	"This is funny",
    	"but not hilarious.",
    	"bwv 1052"
    };
    
    struct chunk_req_state {
    	struct evhttp_request *req;
    	int i;
    };
    
    static void
    http_chunked_trickle_cb(evutil_socket_t fd, short events, void *arg)
    {
    	struct evbuffer *evb = evbuffer_new();
    	struct chunk_req_state *state = arg;
    	struct timeval when = { 0, 0 };
    
    	evbuffer_add_printf(evb, "%s", CHUNKS[state->i]);
    	evhttp_send_reply_chunk(state->req, evb);
    	evbuffer_free(evb);
    
    	if (++state->i < sizeof(CHUNKS)/sizeof(CHUNKS[0])) {
    		event_once(-1, EV_TIMEOUT,
    		    http_chunked_trickle_cb, state, &when);
    	} else {
    		evhttp_send_reply_end(state->req);
    		free(state);
    	}
    }
    
    static void
    http_chunked_cb(struct evhttp_request *req, void *arg)
    {
    	struct timeval when = { 0, 0 };
    	struct chunk_req_state *state = malloc(sizeof(struct chunk_req_state));
    	event_debug(("%s: called\n", __func__));
    
    	memset(state, 0, sizeof(struct chunk_req_state));
    	state->req = req;
    
     	if (strcmp(evhttp_request_uri(req), "/streamed") == 0) {
     		evhttp_add_header(req->output_headers, "Content-Length", "39");
     	}
    
     	/* generate a chunked/streamed reply */
    	evhttp_send_reply_start(req, HTTP_OK, "Everything is fine");
    
    	/* but trickle it across several iterations to ensure we're not
    	 * assuming it comes all at once */
    	event_once(-1, EV_TIMEOUT, http_chunked_trickle_cb, state, &when);
    }
    
    static void
    http_complete_write(int fd, short what, void *arg)
    {
    	struct bufferevent *bev = arg;
    	const char *http_request = "host\r\n"
    	    "Connection: close\r\n"
    	    "\r\n";
    	bufferevent_write(bev, http_request, strlen(http_request));
    }
    
    static void
    http_basic_test(void)
    {
    	struct timeval tv;
    	struct bufferevent *bev;
    	int fd;
    	const char *http_request;
    	short port = -1;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	/* bind to a second socket */
    	if (evhttp_bind_socket(http, "127.0.0.1", port + 1) == -1) {
    		fprintf(stdout, "FAILED (bind)\n");
    		exit(1);
    	}
    
    	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);
    
    	/* first half of the http request */
    	http_request =
    	    "GET /test HTTP/1.1\r\n"
    	    "Host: some";
    
    	bufferevent_write(bev, http_request, strlen(http_request));
    	timerclear(&tv);
    	tv.tv_usec = 10000;
    	event_once(-1, EV_TIMEOUT, http_complete_write, bev, &tv);
    
    	event_dispatch();
    
    	tt_assert(test_ok == 3);
    
    	/* connect to the second port */
    	bufferevent_free(bev);
    	EVUTIL_CLOSESOCKET(fd);
    
    	fd = http_connect("127.0.0.1", port + 1);
    
    	/* 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"
    	    "Connection: close\r\n"
    	    "\r\n";
    
    	bufferevent_write(bev, http_request, strlen(http_request));
    
    	event_dispatch();
    
    	bufferevent_free(bev);
    	EVUTIL_CLOSESOCKET(fd);
    
    	evhttp_free(http);
    
    	tt_assert(test_ok == 5);
     end:
    	;
    }
    
    static void
    http_delay_reply(evutil_socket_t fd, short what, void *arg)
    {
    	struct evhttp_request *req = arg;
    
    	evhttp_send_reply(req, HTTP_OK, "Everything is fine", NULL);
    
    	++test_ok;
    }
    
    static void
    http_delay_cb(struct evhttp_request *req, void *arg)
    {
    	struct timeval tv;
    	timerclear(&tv);
    	tv.tv_sec = 0;
    	tv.tv_usec = 200 * 1000;
    
    	event_once(-1, EV_TIMEOUT, http_delay_reply, req, &tv);
    }
    
    static struct evhttp_connection *delayed_client;
    
    static void
    http_large_delay_cb(struct evhttp_request *req, void *arg)
    {
    	struct timeval tv;
    	timerclear(&tv);
    	tv.tv_sec = 3;
    
    	event_once(-1, EV_TIMEOUT, http_delay_reply, req, &tv);
    	evhttp_connection_fail(delayed_client, EVCON_HTTP_EOF);
    }
    
    /*
     * HTTP DELETE test,  just piggyback on the basic test
     */
    
    static void
    http_delete_cb(struct evhttp_request *req, void *arg)
    {
    	struct evbuffer *evb = evbuffer_new();
    	int empty = evhttp_find_header(req->input_headers, "Empty") != NULL;
    
    	/* Expecting a DELETE request */
    	if (req->type != EVHTTP_REQ_DELETE) {
    		fprintf(stdout, "FAILED (delete type)\n");
    		exit(1);
    	}
    
    	event_debug(("%s: called\n", __func__));
    	evbuffer_add_printf(evb, BASIC_REQUEST_BODY);
    
    	/* allow sending of an empty reply */
    	evhttp_send_reply(req, HTTP_OK, "Everything is fine",
    	    !empty ? evb : NULL);
    
    	evbuffer_free(evb);
    }
    
    static void
    http_delete_test(void)
    {
    	struct bufferevent *bev;
    	int fd;
    	const char *http_request;
    	short port = -1;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	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 =
    	    "DELETE /deleteit HTTP/1.1\r\n"
    	    "Host: somehost\r\n"
    	    "Connection: close\r\n"
    	    "\r\n";
    
    	bufferevent_write(bev, http_request, strlen(http_request));
    
    	event_dispatch();
    
    	bufferevent_free(bev);
    	EVUTIL_CLOSESOCKET(fd);
    
    	evhttp_free(http);
    
    	tt_int_op(test_ok, ==, 2);
     end:
    	;
    }
    
    static void http_request_done(struct evhttp_request *, void *);
    static void http_request_empty_done(struct evhttp_request *, void *);
    
    static void
    _http_connection_test(int persistent)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/*
    	 * 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, (void*) BASIC_REQUEST_BODY);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) {
    		fprintf(stdout, "FAILED\n");
    		exit(1);
    	}
    
    	event_dispatch();
    
    	tt_assert(test_ok);
    
    	/* try to make another request over the same connection */
    	test_ok = 0;
    
    	req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	/*
    	 * if our connections are not supposed to be persistent; request
    	 * a close from the server.
    	 */
    	if (!persistent)
    		evhttp_add_header(req->output_headers, "Connection", "close");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) {
    		tt_abort_msg("couldn't make request");
    	}
    
    	event_dispatch();
    
    	/* make another request: request empty reply */
    	test_ok = 0;
    
    	req = evhttp_request_new(http_request_empty_done, NULL);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Empty", "itis");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) {
    		tt_abort_msg("Couldn't make request");
    		exit(1);
    	}
    
    	event_dispatch();
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    static void
    http_connection_test(void)
    {
    	_http_connection_test(0);
    }
    static void
    http_persist_connection_test(void)
    {
    	_http_connection_test(1);
    }
    
    static void
    http_request_never_call(struct evhttp_request *req, void *arg)
    {
    	fprintf(stdout, "FAILED\n");
    	exit(1);
    }
    
    static void
    http_do_cancel(evutil_socket_t fd, short what, void *arg)
    {
    	struct evhttp_request *req = arg;
    	struct timeval tv;
    	timerclear(&tv);
    	tv.tv_sec = 0;
    	tv.tv_usec = 500 * 1000;
    
    	evhttp_cancel_request(req);
    
    	event_loopexit(&tv);
    
    	++test_ok;
    }
    
    static void
    http_cancel_test(void)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    	struct timeval tv;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/*
    	 * At this point, we want to schedule a request to the HTTP
    	 * server using our make request method.
    	 */
    
    	req = evhttp_request_new(http_request_never_call, NULL);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	/* We give ownership of the request to the connection */
    	tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/delay"),
    		  !=, -1);
    
    	timerclear(&tv);
    	tv.tv_sec = 0;
    	tv.tv_usec = 100 * 1000;
    
    	event_once(-1, EV_TIMEOUT, http_do_cancel, req, &tv);
    
    	event_dispatch();
    
    	tt_int_op(test_ok, ==, 2);
    
    	/* try to make another request over the same connection */
    	test_ok = 0;
    
    	req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	/* We give ownership of the request to the connection */
    	tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test"),
    		  !=, -1);
    
    	event_dispatch();
    
    	/* make another request: request empty reply */
    	test_ok = 0;
    
    	req = evhttp_request_new(http_request_empty_done, NULL);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Empty", "itis");
    
    	/* We give ownership of the request to the connection */
    	tt_int_op(evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test"),
    		  !=, -1);
    
    	event_dispatch();
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    static void
    http_request_done(struct evhttp_request *req, void *arg)
    {
    	const char *what = arg;
    
    	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->input_buffer) != strlen(what)) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	test_ok = 1;
    	event_loopexit(NULL);
    }
    
    static void
    http_request_expect_error(struct evhttp_request *req, void *arg)
    {
    	if (req->response_code == HTTP_OK) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	test_ok = 1;
    	event_loopexit(NULL);
    }
    
    /* test virtual hosts */
    static void
    http_virtual_host_test(void)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    	struct evhttp *second = NULL, *third = NULL;
    
    	http = http_setup(&port, NULL);
    
    	/* virtual host */
    	second = evhttp_new(NULL);
    	evhttp_set_cb(second, "/funnybunny", http_basic_cb, NULL);
    	third = evhttp_new(NULL);
    	evhttp_set_cb(third, "/blackcoffee", http_basic_cb, NULL);
    
    	if (evhttp_add_virtual_host(http, "foo.com", second) == -1) {
    		tt_abort_msg("Couldn't add vhost");
    	}
    
    	if (evhttp_add_virtual_host(http, "bar.*.foo.com", third) == -1) {
    		tt_abort_msg("Couldn't add wildcarded vhost");
    	}
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/* make a request with a different host and expect an error */
    	req = evhttp_request_new(http_request_expect_error, NULL);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET,
    		"/funnybunny") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
    	tt_assert(test_ok == 1);
    
    	test_ok = 0;
    
    	/* make a request with the right host and expect a response */
    	req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "foo.com");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET,
    		"/funnybunny") == -1) {
    		fprintf(stdout, "FAILED\n");
    		exit(1);
    	}
    
    	event_dispatch();
    
    	tt_assert(test_ok == 1);
    
    	test_ok = 0;
    
    	/* make a request with the right host and expect a response */
    	req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "bar.magic.foo.com");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET,
    		"/blackcoffee") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
    	tt_assert(test_ok == 1)
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    
    /* test date header and content length */
    
    static void
    http_request_empty_done(struct evhttp_request *req, void *arg)
    {
    	if (req->response_code != HTTP_OK) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	if (evhttp_find_header(req->input_headers, "Date") == NULL) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    
    	if (evhttp_find_header(req->input_headers, "Content-Length") == NULL) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	if (strcmp(evhttp_find_header(req->input_headers, "Content-Length"),
    		"0")) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	if (EVBUFFER_LENGTH(req->input_buffer) != 0) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	test_ok = 1;
    	event_loopexit(NULL);
    }
    
    /*
     * HTTP DISPATCHER test
     */
    
    void
    http_dispatcher_cb(struct evhttp_request *req, void *arg)
    {
    
    	struct evbuffer *evb = evbuffer_new();
    	event_debug(("%s: called\n", __func__));
    	evbuffer_add_printf(evb, "DISPATCHER_TEST");
    
    	evhttp_send_reply(req, HTTP_OK, "Everything is fine", evb);
    
    	evbuffer_free(evb);
    }
    
    static void
    http_dispatcher_test_done(struct evhttp_request *req, void *arg)
    {
    	const char *what = "DISPATCHER_TEST";
    
    	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 (content type)\n");
    		exit(1);
    	}
    
    	if (EVBUFFER_LENGTH(req->input_buffer) != strlen(what)) {
    		fprintf(stderr, "FAILED (length %zu vs %zu)\n",
    		    EVBUFFER_LENGTH(req->input_buffer), strlen(what));
    		exit(1);
    	}
    
    	if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) {
    		fprintf(stderr, "FAILED (data)\n");
    		exit(1);
    	}
    
    	test_ok = 1;
    	event_loopexit(NULL);
    }
    
    static void
    http_dispatcher_test(void)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/* also bind to local host */
    	evhttp_connection_set_local_address(evcon, "127.0.0.1");
    
    	/*
    	 * At this point, we want to schedule an HTTP GET request
    	 * server using our make request method.
    	 */
    
    	req = evhttp_request_new(http_dispatcher_test_done, NULL);
    	tt_assert(req);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    /*
     * HTTP POST test.
     */
    
    void http_postrequest_done(struct evhttp_request *, void *);
    
    #define POST_DATA "Okay.  Not really printf"
    
    static void
    http_post_test(void)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/*
    	 * 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);
    	tt_assert(req);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    	evbuffer_add_printf(req->output_buffer, POST_DATA);
    
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/postit") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
    	evhttp_connection_free(evcon);
    	evhttp_free(http);
    
    	tt_int_op(test_ok, ==, 1);
     end:
    	;
    }
    
    void
    http_post_cb(struct evhttp_request *req, void *arg)
    {
    	struct evbuffer *evb;
    	event_debug(("%s: called\n", __func__));
    
    	/* Yes, we are expecting a post request */
    	if (req->type != EVHTTP_REQ_POST) {
    		fprintf(stdout, "FAILED (post type)\n");
    		exit(1);
    	}
    
    	if (EVBUFFER_LENGTH(req->input_buffer) != strlen(POST_DATA)) {
    		fprintf(stdout, "FAILED (length: %zu vs %zu)\n",
    		    EVBUFFER_LENGTH(req->input_buffer), strlen(POST_DATA));
    		exit(1);
    	}
    
    	if (memcmp(EVBUFFER_DATA(req->input_buffer), POST_DATA,
    		strlen(POST_DATA))) {
    		fprintf(stdout, "FAILED (data)\n");
    		fprintf(stdout, "Got :%s\n", EVBUFFER_DATA(req->input_buffer));
    		fprintf(stdout, "Want:%s\n", POST_DATA);
    		exit(1);
    	}
    
    	evb = evbuffer_new();
    	evbuffer_add_printf(evb, BASIC_REQUEST_BODY);
    
    	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 = BASIC_REQUEST_BODY;
    
    	if (req == NULL) {
    		fprintf(stderr, "FAILED (timeout)\n");
    		exit(1);
    	}
    
    	if (req->response_code != HTTP_OK) {
    
    		fprintf(stderr, "FAILED (response code)\n");
    		exit(1);
    	}
    
    	if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) {
    		fprintf(stderr, "FAILED (content type)\n");
    		exit(1);
    	}
    
    	if (EVBUFFER_LENGTH(req->input_buffer) != strlen(what)) {
    		fprintf(stderr, "FAILED (length %zu vs %zu)\n",
    		    EVBUFFER_LENGTH(req->input_buffer), strlen(what));
    		exit(1);
    	}
    
    	if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) {
    		fprintf(stderr, "FAILED (data)\n");
    		exit(1);
    	}
    
    	test_ok = 1;
    	event_loopexit(NULL);
    }
    
    /*
     * HTTP PUT test, basically just like POST, but ...
     */
    
    void http_putrequest_done(struct evhttp_request *, void *);
    
    #define PUT_DATA "Hi, I'm some PUT data"
    
    static void
    http_put_test(void)
    {
      short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/*
    	 * Schedule the HTTP PUT request
    	 */
    
    	req = evhttp_request_new(http_putrequest_done, NULL);
    	tt_assert(req);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "someotherhost");
    	evbuffer_add_printf(req->output_buffer, PUT_DATA);
    
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_PUT, "/putit") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
    	evhttp_connection_free(evcon);
    	evhttp_free(http);
    
    	tt_int_op(test_ok, ==, 1);
     end:
    	;
    }
    
    void
    http_put_cb(struct evhttp_request *req, void *arg)
    {
    	struct evbuffer *evb;
    	event_debug(("%s: called\n", __func__));
    
    	/* Expecting a PUT request */
    	if (req->type != EVHTTP_REQ_PUT) {
    		fprintf(stdout, "FAILED (put type)\n");
    		exit(1);
    	}
    
    	if (EVBUFFER_LENGTH(req->input_buffer) != strlen(PUT_DATA)) {
    		fprintf(stdout, "FAILED (length: %zu vs %zu)\n",
    		    EVBUFFER_LENGTH(req->input_buffer), strlen(PUT_DATA));
    		//exit(1);
    	}
    
    	if (memcmp(EVBUFFER_DATA(req->input_buffer), PUT_DATA,
    		strlen(PUT_DATA))) {
    		fprintf(stdout, "FAILED (data)\n");
    		fprintf(stdout, "Got :%s\n", EVBUFFER_DATA(req->input_buffer));
    		fprintf(stdout, "Want:%s\n", PUT_DATA);
    		exit(1);
    	}
    
    	evb = evbuffer_new();
    	evbuffer_add_printf(evb, "That ain't funny");
    
    	evhttp_send_reply(req, HTTP_OK, "Everything is great", evb);
    
    	evbuffer_free(evb);
    }
    
    void
    http_putrequest_done(struct evhttp_request *req, void *arg)
    {
    	const char *what = "That ain't funny";
    
    	if (req == NULL) {
    		fprintf(stderr, "FAILED (timeout)\n");
    		exit(1);
    	}
    
    	if (req->response_code != HTTP_OK) {
    
    		fprintf(stderr, "FAILED (response code)\n");
    		exit(1);
    	}
    
    	if (evhttp_find_header(req->input_headers, "Content-Type") == NULL) {
    		fprintf(stderr, "FAILED (content type)\n");
    		exit(1);
    	}
    
    	if (EVBUFFER_LENGTH(req->input_buffer) != strlen(what)) {
    		fprintf(stderr, "FAILED (length %zu vs %zu)\n",
    		    EVBUFFER_LENGTH(req->input_buffer), strlen(what));
    		exit(1);
    	}
    
    	if (memcmp(EVBUFFER_DATA(req->input_buffer), what, strlen(what)) != 0) {
    		fprintf(stderr, "FAILED (data)\n");
    		exit(1);
    	}
    
    	test_ok = 1;
    	event_loopexit(NULL);
    }
    
    static void
    http_failure_readcb(struct bufferevent *bev, void *arg)
    {
    	const char *what = "400 Bad Request";
    	if (evbuffer_find(bufferevent_get_input(bev),
    		(const unsigned char*) what, strlen(what)) != NULL) {
    		test_ok = 2;
    		bufferevent_disable(bev, EV_READ);
    		event_loopexit(NULL);
    	}
    }
    
    /*
     * Testing that the HTTP server can deal with a malformed request.
     */
    static void
    http_failure_test(void)
    {
    	struct bufferevent *bev;
    	int fd;
    	const char *http_request;
    	short port = -1;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	fd = http_connect("127.0.0.1", port);
    
    	/* Stupid thing to send a request */
    	bev = bufferevent_new(fd, http_failure_readcb, http_writecb,
    	    http_errorcb, NULL);
    
    	http_request = "illegal request\r\n";
    
    	bufferevent_write(bev, http_request, strlen(http_request));
    
    	event_dispatch();
    
    	bufferevent_free(bev);
    	EVUTIL_CLOSESOCKET(fd);
    
    	evhttp_free(http);
    
    	tt_int_op(test_ok, ==, 2);
     end:
    	;
    }
    
    static void
    close_detect_done(struct evhttp_request *req, void *arg)
    {
    	struct timeval tv;
    	tt_assert(req);
    	tt_assert(req->response_code == HTTP_OK);
    
    	test_ok = 1;
    
     end:
    	timerclear(&tv);
    	tv.tv_sec = 3;
    	event_loopexit(&tv);
    }
    
    static void
    close_detect_launch(int fd, short what, void *arg)
    {
    	struct evhttp_connection *evcon = arg;
    	struct evhttp_request *req;
    
    	req = evhttp_request_new(close_detect_done, NULL);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) {
    		tt_fail_msg("Couldn't make request");
    	}
    }
    
    static void
    close_detect_cb(struct evhttp_request *req, void *arg)
    {
    	struct evhttp_connection *evcon = arg;
    	struct timeval tv;
    
    	if (req != NULL && req->response_code != HTTP_OK) {
    		tt_abort_msg("Failed");
    	}
    
    	timerclear(&tv);
    	tv.tv_sec = 3;   /* longer than the http time out */
    
    	/* launch a new request on the persistent connection in 3 seconds */
    	event_once(-1, EV_TIMEOUT, close_detect_launch, evcon, &tv);
     end:
    	;
    }
    
    
    static void
    _http_close_detection(int with_delay)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    
    	test_ok = 0;
    	http = http_setup(&port, NULL);
    
    	/* 2 second timeout */
    	evhttp_set_timeout(http, 1);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    	delayed_client = evcon;
    
    	/*
    	 * At this point, we want to schedule a request to the HTTP
    	 * server using our make request method.
    	 */
    
    	req = evhttp_request_new(close_detect_cb, evcon);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon,
    	    req, EVHTTP_REQ_GET, with_delay ? "/largedelay" : "/test") == -1) {
    		tt_abort_msg("couldn't make request");
    		exit(1);
    	}
    
    	event_dispatch();
    
    	/* at this point, the http server should have no connection */
    	tt_assert(TAILQ_FIRST(&http->connections) == NULL);
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    static void
    http_close_detection_test(void)
    {
    	_http_close_detection(0);
    }
    static void
    http_close_detection_delay_test(void)
    {
    	_http_close_detection(1);
    }
    
    static void
    http_highport_test(void)
    {
    	int i = -1;
    	struct evhttp *myhttp = NULL;
    
    	/* Try a few different ports */
    	for (i = 0; i < 50; ++i) {
    		myhttp = evhttp_start("127.0.0.1", 65535 - i);
    		if (myhttp != NULL) {
    			test_ok = 1;
    			evhttp_free(myhttp);
    			return;
    		}
    	}
    
    	tt_fail_msg("Couldn't get a high port");
    }
    
    static void
    http_bad_header_test(void *ptr)
    {
    	struct evkeyvalq headers;
    
    	TAILQ_INIT(&headers);
    
    	tt_want(evhttp_add_header(&headers, "One", "Two") == 0);
    	tt_want(evhttp_add_header(&headers, "One", "Two\r\n Three") == 0);
    	tt_want(evhttp_add_header(&headers, "One\r", "Two") == -1);
    	tt_want(evhttp_add_header(&headers, "One\n", "Two") == -1);
    	tt_want(evhttp_add_header(&headers, "One", "Two\r") == -1);
    	tt_want(evhttp_add_header(&headers, "One", "Two\n") == -1);
    
    	evhttp_clear_headers(&headers);
    }
    
    static int validate_header(
    	const struct evkeyvalq* headers,
    	const char *key, const char *value) 
    {
    	const char *real_val = evhttp_find_header(headers, key);
    	tt_assert(real_val != NULL);
    	tt_want(strcmp(real_val, value) == 0);
    end:
    	return (0);
    }
    
    static void
    http_parse_query_test(void *ptr)
    {
    	struct evkeyvalq headers;
    
    	TAILQ_INIT(&headers);
    	
    	evhttp_parse_query("http://www.test.com/?q=test", &headers);
    	tt_want(validate_header(&headers, "q", "test") == 0);
    	evhttp_clear_headers(&headers);
    
    	evhttp_parse_query("http://www.test.com/?q=test&foo=bar", &headers);
    	tt_want(validate_header(&headers, "q", "test") == 0);
    	tt_want(validate_header(&headers, "foo", "bar") == 0);
    	evhttp_clear_headers(&headers);
    
    	evhttp_parse_query("http://www.test.com/?q=test+foo", &headers);
    	tt_want(validate_header(&headers, "q", "test foo") == 0);
    	evhttp_clear_headers(&headers);
    
    	evhttp_parse_query("http://www.test.com/?q=test%0Afoo", &headers);
    	tt_want(validate_header(&headers, "q", "test\nfoo") == 0);
    	evhttp_clear_headers(&headers);
    
    	evhttp_parse_query("http://www.test.com/?q=test%0Dfoo", &headers);
    	tt_want(validate_header(&headers, "q", "test\rfoo") == 0);
    	evhttp_clear_headers(&headers);
    }
    
    static void
    http_base_test(void)
    {
    	struct event_base *tmp;
            struct bufferevent *bev;
    	int fd;
    	const char *http_request;
    	short port = -1;
    
    	base = event_init();
    
    	/*
    	 * create another bogus base - which is being used by all subsequen
    	 * tests - yuck!
    	 */
    	tmp = event_init();
    
    	http = http_setup(&port, base);
    
    	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);
    	bufferevent_base_set(base, bev);
    
    	http_request =
    	    "GET /test HTTP/1.1\r\n"
    	    "Host: somehost\r\n"
    	    "Connection: close\r\n"
    	    "\r\n";
    
    	bufferevent_write(bev, http_request, strlen(http_request));
    
    	event_base_dispatch(base);
    
    	bufferevent_free(bev);
    	EVUTIL_CLOSESOCKET(fd);
    
    	evhttp_free(http);
    
    	event_base_free(base);
    	base = tmp;
    
    	test_ok = (test_ok == 2);
    }
    
    /*
     * the server is just going to close the connection if it times out during
     * reading the headers.
     */
    
    static void
    http_incomplete_readcb(struct bufferevent *bev, void *arg)
    {
    	test_ok = -1;
    	event_loopexit(NULL);
    }
    
    static void
    http_incomplete_errorcb(struct bufferevent *bev, short what, void *arg)
    {
    	if (what == (EVBUFFER_READ | EVBUFFER_EOF))
    		test_ok++;
    	else
    		test_ok = -2;
    	event_loopexit(NULL);
    }
    
    static void
    http_incomplete_writecb(struct bufferevent *bev, void *arg)
    {
    #ifndef SHUT_WR
    #define SHUT_WR 1
    #endif
    	if (arg != NULL) {
    		int fd = *(int *)arg;
    		/* terminate the write side to simulate EOF */
    		shutdown(fd, SHUT_WR);
    	}
    	if (EVBUFFER_LENGTH(bufferevent_get_output(bev)) == 0) {
    		/* enable reading of the reply */
    		bufferevent_enable(bev, EV_READ);
    		test_ok++;
    	}
    }
    
    static void
    _http_incomplete_test(int use_timeout)
    {
    	struct bufferevent *bev;
    	int fd;
    	const char *http_request;
    	short port = -1;
    	struct timeval tv_start, tv_end;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    	evhttp_set_timeout(http, 1);
    
    	fd = http_connect("127.0.0.1", port);
    
    	/* Stupid thing to send a request */
    	bev = bufferevent_new(fd,
    	    http_incomplete_readcb, http_incomplete_writecb,
    	    http_incomplete_errorcb, use_timeout ? NULL : &fd);
    
    	http_request =
    	    "GET /test HTTP/1.1\r\n"
    	    "Host: somehost\r\n";
    
    	bufferevent_write(bev, http_request, strlen(http_request));
    
    	evutil_gettimeofday(&tv_start, NULL);
    
    	event_dispatch();
    
    	evutil_gettimeofday(&tv_end, NULL);
    	evutil_timersub(&tv_end, &tv_start, &tv_end);
    
    	bufferevent_free(bev);
    	if (use_timeout) {
    		EVUTIL_CLOSESOCKET(fd);
    	}
    
    	evhttp_free(http);
    
    	if (use_timeout && tv_end.tv_sec >= 3) {
    		tt_abort_msg("time");
    	} else if (!use_timeout && tv_end.tv_sec >= 1) {
    		/* we should be done immediately */
    		tt_abort_msg("time");
    	}
    
    	tt_int_op(test_ok, ==, 2);
     end:
    	;
    }
    static void
    http_incomplete_test(void)
    {
    	_http_incomplete_test(0);
    }
    static void
    http_incomplete_timeout_test(void)
    {
    	_http_incomplete_test(1);
    }
    
    /*
     * the server is going to reply with chunked data.
     */
    
    static void
    http_chunked_readcb(struct bufferevent *bev, void *arg)
    {
    	/* nothing here */
    }
    
    static void
    http_chunked_errorcb(struct bufferevent *bev, short what, void *arg)
    {
    	if (!test_ok)
    		goto out;
    
    	test_ok = -1;
    
    	if ((what & EVBUFFER_EOF) != 0) {
    		struct evhttp_request *req = evhttp_request_new(NULL, NULL);
    		const char *header;
    		enum message_read_status done;
    
    		req->kind = EVHTTP_RESPONSE;
    		done = evhttp_parse_firstline(req, bufferevent_get_input(bev));
    		if (done != ALL_DATA_READ)
    			goto out;
    
    		done = evhttp_parse_headers(req, bufferevent_get_input(bev));
    		if (done != ALL_DATA_READ)
    			goto out;
    
    		header = evhttp_find_header(req->input_headers, "Transfer-Encoding");
    		if (header == NULL || strcmp(header, "chunked"))
    			goto out;
    
    		header = evhttp_find_header(req->input_headers, "Connection");
    		if (header == NULL || strcmp(header, "close"))
    			goto out;
    
    		header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF);
    		if (header == NULL)
    			goto out;
    		/* 13 chars */
    		if (strcmp(header, "d"))
    			goto out;
    		free((char*)header);
    
    		if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 13),
    			"This is funny", 13))
    			goto out;
    
    		evbuffer_drain(bufferevent_get_input(bev), 13 + 2);
    
    		header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF);
    		if (header == NULL)
    			goto out;
    		/* 18 chars */
    		if (strcmp(header, "12"))
    			goto out;
    		free((char *)header);
    
    		if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 18),
    			"but not hilarious.", 18))
    			goto out;
    
    		evbuffer_drain(bufferevent_get_input(bev), 18 + 2);
    
    		header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF);
    		if (header == NULL)
    			goto out;
    		/* 8 chars */
    		if (strcmp(header, "8"))
    			goto out;
    		free((char *)header);
    
    		if (strncmp((char *)evbuffer_pullup(bufferevent_get_input(bev), 8),
    			"bwv 1052.", 8))
    			goto out;
    
    		evbuffer_drain(bufferevent_get_input(bev), 8 + 2);
    
    		header = evbuffer_readln(bufferevent_get_input(bev), NULL, EVBUFFER_EOL_CRLF);
    		if (header == NULL)
    			goto out;
    		/* 0 chars */
    		if (strcmp(header, "0"))
    			goto out;
    		free((char *)header);
    
    		test_ok = 2;
    
    		evhttp_request_free(req);
    	}
    
    out:
    	event_loopexit(NULL);
    }
    
    static void
    http_chunked_writecb(struct bufferevent *bev, void *arg)
    {
    	if (EVBUFFER_LENGTH(bufferevent_get_output(bev)) == 0) {
    		/* enable reading of the reply */
    		bufferevent_enable(bev, EV_READ);
    		test_ok++;
    	}
    }
    
    static void
    http_chunked_request_done(struct evhttp_request *req, void *arg)
    {
    	if (req->response_code != HTTP_OK) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	if (evhttp_find_header(req->input_headers,
    		"Transfer-Encoding") == NULL) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	if (EVBUFFER_LENGTH(req->input_buffer) != 13 + 18 + 8) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	if (strncmp((char *)evbuffer_pullup(req->input_buffer, 13 + 18 + 8),
    		"This is funnybut not hilarious.bwv 1052",
    		13 + 18 + 8)) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	test_ok = 1;
    	event_loopexit(NULL);
    }
    
    static void
    http_chunk_out_test(void)
    {
    	struct bufferevent *bev;
    	int fd;
    	const char *http_request;
    	short port = -1;
    	struct timeval tv_start, tv_end;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    	int i;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	fd = http_connect("127.0.0.1", port);
    
    	/* Stupid thing to send a request */
    	bev = bufferevent_new(fd,
    	    http_chunked_readcb, http_chunked_writecb,
    	    http_chunked_errorcb, NULL);
    
    	http_request =
    	    "GET /chunked HTTP/1.1\r\n"
    	    "Host: somehost\r\n"
    	    "Connection: close\r\n"
    	    "\r\n";
    
    	bufferevent_write(bev, http_request, strlen(http_request));
    
    	evutil_gettimeofday(&tv_start, NULL);
    
    	event_dispatch();
    
    	bufferevent_free(bev);
    
    	evutil_gettimeofday(&tv_end, NULL);
    	evutil_timersub(&tv_end, &tv_start, &tv_end);
    
    	tt_int_op(tv_end.tv_sec, <, 1);
    
    	tt_int_op(test_ok, ==, 2);
    
    	/* now try again with the regular connection object */
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/* make two requests to check the keepalive behavior */
    	for (i = 0; i < 2; i++) {
    		test_ok = 0;
    		req = evhttp_request_new(http_chunked_request_done, NULL);
    
    		/* Add the information that we care about */
    		evhttp_add_header(req->output_headers, "Host", "somehost");
    
    		/* We give ownership of the request to the connection */
    		if (evhttp_make_request(evcon, req,
    			EVHTTP_REQ_GET, "/chunked") == -1) {
    			tt_abort_msg("Couldn't make request");
    		}
    
    		event_dispatch();
    
    		tt_assert(test_ok == 1);
    	}
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    static void
    http_stream_out_test(void)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/*
    	 * 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,
    	    (void *)"This is funnybut not hilarious.bwv 1052");
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/streamed")
    	    == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    static void
    http_stream_in_chunk(struct evhttp_request *req, void *arg)
    {
    	struct evbuffer *reply = arg;
    
    	if (req->response_code != HTTP_OK) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	evbuffer_add_buffer(reply, req->input_buffer);
    }
    
    static void
    http_stream_in_done(struct evhttp_request *req, void *arg)
    {
    	if (evbuffer_get_length(req->input_buffer) != 0) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	event_loopexit(NULL);
    }
    
    /**
     * Makes a request and reads the response in chunks.
     */
    static void
    _http_stream_in_test(char const *url,
        size_t expected_len, char const *expected)
    {
    	struct evhttp_connection *evcon;
    	struct evbuffer *reply = evbuffer_new();
    	struct evhttp_request *req = NULL;
    	short port = -1;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	req = evhttp_request_new(http_stream_in_done, reply);
    	evhttp_request_set_chunked_cb(req, http_stream_in_chunk);
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, url) == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
    	if (evbuffer_get_length(reply) != expected_len) {
    		TT_DIE(("reply length %zu; expected %zu; FAILED (%s)\n",
    				EVBUFFER_LENGTH(reply), expected_len,
    				evbuffer_pullup(reply, -1)));
    	}
    
    	if (memcmp(evbuffer_pullup(reply, -1), expected, expected_len) != 0) {
    		tt_abort_msg("Memory mismatch");
    	}
    
    	test_ok = 1;
     end:
    	if (reply)
    		evbuffer_free(reply);
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    static void
    http_stream_in_test(void)
    {
    	_http_stream_in_test("/chunked", 13 + 18 + 8,
    	    "This is funnybut not hilarious.bwv 1052");
    
    	_http_stream_in_test("/test", strlen(BASIC_REQUEST_BODY),
    	    BASIC_REQUEST_BODY);
    }
    
    static void
    http_stream_in_cancel_chunk(struct evhttp_request *req, void *arg)
    {
    	tt_int_op(req->response_code, ==, HTTP_OK);
    
     end:
    	evhttp_cancel_request(req);
    	event_loopexit(NULL);
    }
    
    static void
    http_stream_in_cancel_done(struct evhttp_request *req, void *arg)
    {
    	/* should never be called */
    	tt_fail_msg("In cancel done");
    }
    
    static void
    http_stream_in_cancel_test(void)
    {
    	struct evhttp_connection *evcon;
    	struct evhttp_request *req = NULL;
    	short port = -1;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	req = evhttp_request_new(http_stream_in_cancel_done, NULL);
    	evhttp_request_set_chunked_cb(req, http_stream_in_cancel_chunk);
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/chunked") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
    	test_ok = 1;
     end:
    	evhttp_connection_free(evcon);
    	evhttp_free(http);
    
    }
    
    static void
    http_connection_retry_done(struct evhttp_request *req, void *arg)
    {
    	tt_assert(req);
    	tt_int_op(req->response_code, !=, HTTP_OK);
    	if (evhttp_find_header(req->input_headers, "Content-Type") != NULL) {
    		tt_abort_msg("(content type)\n");
    	}
    
    	tt_uint_op(EVBUFFER_LENGTH(req->input_buffer), ==, 0);
    
    	test_ok = 1;
     end:
    	event_loopexit(NULL);
    }
    
    static void
    http_make_web_server(int fd, short what, void *arg)
    {
    	short port = -1;
    	http = http_setup(&port, NULL);
    }
    
    static void
    http_connection_retry_test(void)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    	struct timeval tv, tv_start, tv_end;
    
    	test_ok = 0;
    
    	/* auto detect the port */
    	http = http_setup(&port, NULL);
    	evhttp_free(http);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	evhttp_connection_set_timeout(evcon, 1);
    	/* also bind to local host */
    	evhttp_connection_set_local_address(evcon, "127.0.0.1");
    
    	/*
    	 * At this point, we want to schedule an HTTP GET request
    	 * server using our make request method.
    	 */
    
    	req = evhttp_request_new(http_connection_retry_done, NULL);
    	tt_assert(req);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET,
    		"/?arg=val") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	evutil_gettimeofday(&tv_start, NULL);
    	event_dispatch();
    	evutil_gettimeofday(&tv_end, NULL);
    	evutil_timersub(&tv_end, &tv_start, &tv_end);
    	tt_int_op(tv_end.tv_sec, <, 1);
    
    	tt_int_op(test_ok, ==, 1);
    
    	/*
    	 * now test the same but with retries
    	 */
    	test_ok = 0;
    
    	evhttp_connection_set_timeout(evcon, 1);
    	evhttp_connection_set_retries(evcon, 1);
    
    	req = evhttp_request_new(http_connection_retry_done, NULL);
    	tt_assert(req);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET,
    		"/?arg=val") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	evutil_gettimeofday(&tv_start, NULL);
    	event_dispatch();
    	evutil_gettimeofday(&tv_end, NULL);
    	evutil_timersub(&tv_end, &tv_start, &tv_end);
    	tt_int_op(tv_end.tv_sec, >, 1);
    	tt_int_op(tv_end.tv_sec, <, 6);
    
    	tt_assert(test_ok == 1);
    
    	/*
    	 * now test the same but with retries and give it a web server
    	 * at the end
    	 */
    	test_ok = 0;
    
    	evhttp_connection_set_timeout(evcon, 1);
    	evhttp_connection_set_retries(evcon, 3);
    
    	req = evhttp_request_new(http_dispatcher_test_done, NULL);
    	tt_assert(req);
    
    	/* Add the information that we care about */
    	evhttp_add_header(req->output_headers, "Host", "somehost");
    
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET,
    		"/?arg=val") == -1) {
    		tt_abort_msg("Couldn't make request");
    
    	}
    
    	/* start up a web server one second after the connection tried
    	 * to send a request
    	 */
    	timerclear(&tv);
    	tv.tv_sec = 1;
    	event_once(-1, EV_TIMEOUT, http_make_web_server, NULL, &tv);
    
    	evutil_gettimeofday(&tv_start, NULL);
    	event_dispatch();
    	evutil_gettimeofday(&tv_end, NULL);
    
    	evutil_timersub(&tv_end, &tv_start, &tv_end);
    
    	tt_int_op(tv_end.tv_sec, >, 1);
    	tt_int_op(tv_end.tv_sec, <, 6);
    
    	tt_int_op(test_ok, ==, 1);
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    static void
    http_primitives(void *ptr)
    {
    	char *escaped = NULL;
    	struct evhttp *http;
    
    	escaped = evhttp_htmlescape("<script>");
    	tt_str_op(escaped, ==, "&lt;script&gt;");
    	free(escaped);
    
    	escaped = evhttp_htmlescape("\"\'&");
    	tt_str_op(escaped, ==, "&quot;&#039;&amp;");
    
    	http = evhttp_new(NULL);
    	tt_int_op(evhttp_set_cb(http, "/test", http_basic_cb, NULL), ==, 0);
    	tt_int_op(evhttp_set_cb(http, "/test", http_basic_cb, NULL), ==, -1);
    	tt_int_op(evhttp_del_cb(http, "/test"), ==, 0);
    	tt_int_op(evhttp_del_cb(http, "/test"), ==, -1);
    	tt_int_op(evhttp_set_cb(http, "/test", http_basic_cb, NULL), ==, 0);
    	evhttp_free(http);
    
     end:
    	if (escaped)
    		free(escaped);
    }
    
    static void
    http_multi_line_header_test(void)
    {
    	struct bufferevent *bev= NULL;
    	int fd = -1;
    	const char *http_start_request;
    	short port = -1;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	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_start_request =
    	    "GET /test HTTP/1.1\r\n"
    	    "Host: somehost\r\n"
    	    "Connection: close\r\n"
    	    "X-Multi:  aaaaaaaa\r\n"
    	    " a\r\n"
    	    "\tEND\r\n"
    	    "X-Last: last\r\n"
    	    "\r\n";
    
    	bufferevent_write(bev, http_start_request, strlen(http_start_request));
    
    	event_dispatch();
    
    	tt_int_op(test_ok, ==, 4);
     end:
    	if (bev)
    		bufferevent_free(bev);
    	if (fd >= 0)
    		close(fd);
    	if (http)
    		evhttp_free(http);
    }
    
    static void
    http_request_bad(struct evhttp_request *req, void *arg)
    {
    	if (req != NULL) {
    		fprintf(stderr, "FAILED\n");
    		exit(1);
    	}
    
    	test_ok = 1;
    	event_loopexit(NULL);
    }
    
    static void
    http_negative_content_length_test(void)
    {
    	short port = -1;
    	struct evhttp_connection *evcon = NULL;
    	struct evhttp_request *req = NULL;
    
    	test_ok = 0;
    
    	http = http_setup(&port, NULL);
    
    	evcon = evhttp_connection_new("127.0.0.1", port);
    	tt_assert(evcon);
    
    	/*
    	 * At this point, we want to schedule a request to the HTTP
    	 * server using our make request method.
    	 */
    
    	req = evhttp_request_new(http_request_bad, NULL);
    
    	/* Cause the response to have a negative content-length */
    	evhttp_add_header(req->output_headers, "X-Negative", "makeitso");
    
    	/* We give ownership of the request to the connection */
    	if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/test") == -1) {
    		tt_abort_msg("Couldn't make request");
    	}
    
    	event_dispatch();
    
     end:
    	if (evcon)
    		evhttp_connection_free(evcon);
    	if (http)
    		evhttp_free(http);
    }
    
    #define HTTP_LEGACY(name)						\
    	{ #name, run_legacy_test_fn, TT_ISOLATED|TT_LEGACY, &legacy_setup, \
                        http_##name##_test }
    
    struct testcase_t http_testcases[] = {
    	{ "primitives", http_primitives, 0, NULL, NULL },
    	HTTP_LEGACY(base),
    	{ "bad_headers", http_bad_header_test, 0, NULL, NULL },
    	{ "parse_query", http_parse_query_test, 0, NULL, NULL },
    	HTTP_LEGACY(basic),
    	HTTP_LEGACY(cancel),
    	HTTP_LEGACY(virtual_host),
    	HTTP_LEGACY(post),
    	HTTP_LEGACY(put),
    	HTTP_LEGACY(delete),
    	HTTP_LEGACY(failure),
    	HTTP_LEGACY(connection),
    	HTTP_LEGACY(persist_connection),
    	HTTP_LEGACY(close_detection),
    	HTTP_LEGACY(close_detection_delay),
    	HTTP_LEGACY(incomplete),
    	HTTP_LEGACY(incomplete_timeout),
    
    	HTTP_LEGACY(highport),
    	HTTP_LEGACY(dispatcher),
    	HTTP_LEGACY(multi_line_header),
    	HTTP_LEGACY(negative_content_length),
    	HTTP_LEGACY(chunk_out),
    	HTTP_LEGACY(stream_out),
    
    	HTTP_LEGACY(stream_in),
    	HTTP_LEGACY(stream_in_cancel),
    
    	HTTP_LEGACY(connection_retry),
    
    	END_OF_TESTCASES
    };