1. 程式人生 > >基於libevent的http客戶端,並可以請求https

基於libevent的http客戶端,並可以請求https

這個可以用libevent實現http客戶端的post get等功能

http_client.h

#ifndef __HTTP_CLIENT_H__
#define __HTTP_CLIENT_H__

#include <sys/queue.h>

enum http_method
{
  HTTP_METHOD_GET,
  HTTP_METHOD_POST,
  HTTP_METHOD_HEAD,
  HTTP_METHOD_PUT,
  HTTP_METHOD_DELETE,
};

struct http_client;

struct http_header
{
  size_t      key_len;
  const char *key;

  size_t      value_len;
  const char *value;

  TAILQ_ENTRY(http_header) entry;
};

struct http_response
{
  struct http_request *request;

  uint16_t          status;
    
  size_t            url_len;
  const char       *url;

  size_t            body_len;
  const char       *body;
  bool              body_chunked;

  TAILQ_HEAD(,http_header) headers;
};

struct http_request;

/* Callback functions */
typedef void (*cb_http_response)(struct http_client *, struct http_request *, struct http_response *, void *baton);
typedef void (*cb_http_disconnect)(struct http_client *, void *baton);

/** 
 * New http_client connection instance 
 * 
 * @param hostname  Hostname or ip address of web server.
 * @param port 
 * @param ssl 
 * 
 * @return struct http_client* Client connection handle.
 */
struct http_client *http_client_new(const char *hostname, const char *port, SSL_CTX* ssl_ctx);

/**
 * Close http_client connection, cancel all pending requests.
 * 
 * @param api 
 */
void http_client_free(struct http_client *api);

/**
 * Queue a new HTTP client request.
 * 
 * @param api 
 * @param method 
 * @param uri_len 
 * @param uri 
 * 
 * @return struct http_request* 
 */
struct http_request *http_client_request(struct http_client *api, enum http_method method, size_t uri_len,
                                         const char *uri);

int http_client_request_add_header(struct http_request *request, const char *key, const char *value);

int http_client_request_add_body(struct http_request *request, size_t body_len, const char *body);

/**
 * Make this request pending.
 * 
 * @param request 
 * @return int success / failure flag.
 */
int http_client_request_dispatch(struct http_request *request, cb_http_response callback, void *baton);

/* TODO: */
int http_client_cancel(struct http_request *request);

#endif /* __HTTP_CLIENT_H__ */
http_client.c
/* libc */
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
/* libevent */
#include <event2/event-config.h>
#include <event2/event.h>
/* openssl */
#include <openssl/bio.h>
#include <openssl/ssl.h>
/* http_client */
#include "http_client.h"

#define VERSION_MAJOR 0
#define VERSION_MINOR 1

#define FLAGS_HAVE_USER_AGENT       0x0001
#define FLAGS_HAVE_CONTENT_LENGTH   0x0002
#define FLAGS_HAVE_HOST             0x0004
#define FLAGS_HAVE_KEEP_ALIVE       0x0008

enum connection_fault {
	FAULT_OKAY, FAULT_CLOSE, FAULT_ERROR, FAULT_FREE,
};

enum parse_state {
	PARSE_ERROR = -1, PARSE_MORE_DATA = 0, PARSE_NEXT_STATE = 1,
};

enum http_state {
	HTTP_NOT_CONNECTED,
	HTTP_CONNECTING,
	HTTP_IDLE,
	HTTP_READING_STATUS_LINE,
	HTTP_READING_HEADERS,
	HTTP_READING_BODY,
	HTTP_READING_DONE,
	HTTP_SENDING,
};

struct http_request {
	struct http_client *api;

	struct evbuffer *output;

	enum http_method method;

	size_t uri_len;
	const char *uri;

	TAILQ_HEAD(,http_header)
	headers;

	size_t body_len;
	const char *body;

	/* User per request baton. */
	cb_http_response callback;
	void *baton;

TAILQ_ENTRY(http_request) entry;
};

struct http_client {
uint16_t version[2];
const char* host;

struct {
	const char *hostname;
	const char *port;

	SSL_CTX* ssl_ctx;
	SSL* ssl;
	BIO* buffer_io;

	struct event ev;
} connection;

enum http_state state;

cb_http_disconnect cb_disconnect;

TAILQ_HEAD(, http_request)
pending_request;

struct http_response *current;
struct evbuffer *input;

void *baton;
};

/*======================================================================================================================
 * 
 *  Function prototypes
 * 
 *====================================================================================================================== 
 */

static void libevent_interopt(int fd, short what, void *baton);
static int read_data(struct http_client *api);
static int write_request(struct http_client *api);
static void connection_fault(struct http_client *api,
	enum connection_fault fault);
static int next_request(struct http_client *api);
static BIO* connect_remote_host(struct http_client *api);

/*======================================================================================================================
 * 
 *  Util functions.
 * 
 *====================================================================================================================== 
 */

static
void *memdup(const void *src, size_t len) {
void *output;

if ((output = malloc(len)) == NULL) {
	return NULL;
}

return memcpy(output, src, len);
}

static
void reschedule_event(struct event *ev, int fd, short what, void *baton) {
event_del(ev);
event_set(ev, fd, what, libevent_interopt, baton);
event_add(ev, NULL);
}

static
const char *get_method_str(enum http_method method) {
switch (method) {
case HTTP_METHOD_GET:
	return "GET";
case HTTP_METHOD_POST:
	return "POST";
default:
	return NULL;
}
}

static struct http_header* find_header(struct http_response *response,
	const char *name) {
struct http_header *cur;

TAILQ_FOREACH(cur, &response->headers, entry)
{
	if (strncmp(cur->key, name, cur->key_len) == 0)
		return cur;
}

return NULL;
}

/*======================================================================================================================
 * 
 *  Request / response creation destruction.
 * 
 *====================================================================================================================== 
 */

static struct http_header* new_header(const char *key, size_t key_len,
	const char *value, size_t value_len) {
struct http_header* header = calloc(1, sizeof(*header));

if (header == NULL) {
	goto FAIL;
}

header->key_len = key_len;
header->value_len = value_len;

if ((header->key = strndup(key, key_len)) == NULL) {
	goto FAIL_TWO;
}

if ((header->value = strndup(value, value_len)) == NULL) {
	goto FAIL_TWO;
}

return header;

FAIL_TWO: free((void *) header->key);
free((void *) header->value);
FAIL: free(header);
return NULL;
}

static struct http_response* new_response(struct http_request *request) {
struct http_response *response = calloc(1, sizeof(*response));

if (response == NULL) {
	return NULL;
}

response->request = request;
TAILQ_INIT(&response->headers);

return response;

FAIL: free(response);
return NULL;
}

static
void free_response(struct http_response *response) {
while (!TAILQ_EMPTY(&response->headers)) {
	struct http_header *cur = TAILQ_FIRST(&response->headers);
	TAILQ_REMOVE(&response->headers, cur, entry);
	free(cur);
}

free(response);
}

static struct http_request* new_request(struct http_client *api) {
struct http_request *request;

if ((request = calloc(1, sizeof(*request))) == NULL)
	goto FAIL_ONE;

if ((request->output = evbuffer_new()) == NULL)
	goto FAIL_TWO;

TAILQ_INIT(&request->headers);
request->api = api;

return request;

FAIL_TWO: free(request);
FAIL_ONE: return NULL;
}

static
void free_request(struct http_request *request) {
while (!TAILQ_EMPTY(&request->headers)) {
	struct http_header *cur = TAILQ_FIRST(&request->headers);
	TAILQ_REMOVE(&request->headers, cur, entry);
	free(cur);
}

free((void *) request->uri);
free((void *) request->body);

evbuffer_free(request->output);
free(request);
}

/*======================================================================================================================
 * 
 *  Parse response
 * 
 *====================================================================================================================== 
 */

static bool is_connection_close(struct http_client *api,
	struct http_response *response) {
struct http_header *header = find_header(response, "Connection");

if (header == NULL) {
	goto SKIP;
}

if (api->version[VERSION_MINOR] == 1) {
	if (header && strncasecmp(header->value, "Close", header->value_len))
		return false;
} else if (api->version[VERSION_MINOR] == 0) {
	if (strncasecmp(header->value, "Keep-Alive", header->value_len) == 0)
		return true;
}

SKIP: return (api->version[VERSION_MINOR] == 1);
}

static ssize_t get_body_len(struct http_response *response) {
struct http_header *header = find_header(response, "Content-Length");

if (header)
	return atoi(header->value);

return -1;
}

static bool is_response_chunked(struct http_client *api,
	struct http_response *response) {
return false;
}

int find_body_len(struct http_client *api, struct http_response *response) {
if (is_response_chunked(api, response)) {
	response->body_chunked = true;
	return 0;
}

ssize_t body_len = get_body_len(response);

if (body_len < 0) {
	return -1;
}

response->body_len = body_len;

return 0;
}

static enum parse_state read_status_line(struct http_client *api,
	struct http_response *response) {
enum parse_state status;
char *line = evbuffer_readln(api->input, NULL, EVBUFFER_EOL_CRLF_STRICT);
char *token = line;

/* Need more data. */
if (line == NULL) {
	return PARSE_MORE_DATA;
}

char *protocol = strsep(&token, " ");
char *status_code = strsep(&token, " ");

if (protocol == NULL || status_code == NULL) {
	status = PARSE_ERROR;
	goto DONE;
}

if (strcmp(protocol, "HTTP/1.0") == 0) {
	api->version[VERSION_MINOR] = 0;
} else if (strcmp(protocol, "HTTP/1.1") == 0) {
	api->version[VERSION_MINOR] = 1;
} else {
	/* Unknow protocol version. */
	status = PARSE_ERROR;
}

response->status = atoi(status_code);

status = PARSE_NEXT_STATE;

DONE: free(line);
return status;
}

static enum parse_state read_header_line(struct http_client *api,
	struct http_response *response) {
char *line, *sep, *value;

do {
	line = evbuffer_readln(api->input, NULL, EVBUFFER_EOL_CRLF_STRICT);

	if (line == NULL) {
		/* Need more data. */
		free(line);
		return PARSE_MORE_DATA;
	} else if (*line == '\t' || *line == ' ') {
		/* Header contination. */
		/* TODO: */
	} else if (*line == '\0') {
		/* Header / body boundry. */
		free(line);

		/* Figure out the body we should be looking for. */
		if (find_body_len(api, response) == -1)
			return PARSE_ERROR;

		return PARSE_NEXT_STATE;
	}

	if ((sep = index(line, ':')) == NULL)
		return PARSE_ERROR;

	for (value = sep + 1; value != '\0'; value++) {
		if (*value == ' ' || *value == '\t') {
			continue;
		} else {
			break;
		}
	}

	struct http_header* header = new_header(line, sep - line, value,
			strlen(value));

	if (header == NULL) {
		return PARSE_ERROR;
	}

	TAILQ_INSERT_TAIL(&response->headers, header, entry);

	free(line);
} while (1);
}

static enum parse_state read_body(struct http_client *api,
	struct http_response *response) {
/* Special case that cannot have a body:
 *   - 1xx status
 *   - 204 status
 *   - 304 status
 *   - HEAD method
 */
if ((response->status >= 100 && response->status <= 199)
		|| response->status == 204 || response->status == 304
		|| response->request->method == HTTP_METHOD_HEAD) {
	return PARSE_NEXT_STATE;
}

/* Chunked encomding. */
if (0)
	return PARSE_MORE_DATA;

return (EVBUFFER_LENGTH(api->input) >= response->body_len) ?
		PARSE_NEXT_STATE : PARSE_MORE_DATA;
}

static
int dispatch_response(struct http_client *api, struct http_response *response) {
struct http_request *request = TAILQ_FIRST(&api->pending_request);

TAILQ_REMOVE(&api->pending_request, request, entry);

/* Tempoary pointer into the evbuffer. */
response->body = EVBUFFER_DATA(api->input);

if (request->callback != NULL) {
	request->callback(api, request, response, request->baton);
}

/* Get rid of the body from the buffer. */
evbuffer_drain(api->input, response->body_len);

if (response->body_chunked == true) {
	/* Chunked, already closed connection */
} else if (is_connection_close(api, response)) {
	if (response != NULL) {
		connection_fault(api, FAULT_CLOSE);
	}
} else {
	api->state = HTTP_IDLE;
}

/* Free response & request. */
free_response(response);
free_request(request);

api->current = NULL;

return 0;
}

static
int read_response(struct http_client *api) {
enum parse_state result;

do {
	switch (api->state) {
	case HTTP_READING_STATUS_LINE:
		result = read_status_line(api, api->current);
		break;
	case HTTP_READING_HEADERS:
		result = read_header_line(api, api->current);
		break;
	case HTTP_READING_BODY:
		result = read_body(api, api->current);
		break;
	case HTTP_READING_DONE:
		if ((result = dispatch_response(api, api->current)) == 0) {
			next_request(api);
			return 0;
		}
	}

	switch (result) {
	/* Need more data. */
	case PARSE_MORE_DATA:
		break;
		/* Increment FSM to next state. */
	case PARSE_NEXT_STATE:
		api->state++;
		break;
		/* Error. */
	case PARSE_ERROR:
	default:
		connection_fault(api, FAULT_ERROR);
		return -1;
	}
} while (result == 1);
}

/*======================================================================================================================
 * 
 *  Create request buffer
 * 
 *====================================================================================================================== 
 */

static
int add_req_line(struct http_request *request) {
const char *method_str = get_method_str(request->method);

return evbuffer_add_printf(request->output, "%s %.*s HTTP/%i.%i\r\n",
		method_str, request->uri_len, request->uri,
		request->api->version[VERSION_MAJOR],
		request->api->version[VERSION_MINOR]);
}

static
int add_header(struct evbuffer *buffer, struct http_header *header) {
return evbuffer_add_printf(buffer, "%.*s: %.*s\r\n", header->key_len,
		header->key_len, header->value_len, header->value);
}

static
int add_headers(struct http_request *request) {
int flags = 0;

/* Add other headers */
struct http_header *cur;
TAILQ_FOREACH(cur, &request->headers, entry)
{

	if (strncmp(cur->key, "Host", cur->key_len) == 0)
		flags |= FLAGS_HAVE_HOST;
	else if (strncmp(cur->key, "Content-Length", cur->key_len) == 0)
		flags |= FLAGS_HAVE_CONTENT_LENGTH;
	else if (strncmp(cur->key, "User-Agent", cur->key_len) == 0)
		flags |= FLAGS_HAVE_USER_AGENT;
	else if (strncmp(cur->key, "Connection", cur->key_len) == 0)
		flags |= FLAGS_HAVE_KEEP_ALIVE;

	add_header(request->output, cur);
}

if (!(flags & FLAGS_HAVE_HOST)) {
	evbuffer_add_printf(request->output, "Host: %s\r\n",
			request->api->connection.hostname);
}

if (!(flags & FLAGS_HAVE_CONTENT_LENGTH) && request->body_len > 0) {
	evbuffer_add_printf(request->output, "Conntent-Length: %i\r\n",
			request->body_len);
}

if (!(flags & FLAGS_HAVE_USER_AGENT)) {
	evbuffer_add_printf(request->output, "User-Agent: Admeld http_client\r\n");
}

if (!(flags & FLAGS_HAVE_KEEP_ALIVE)) {
	evbuffer_add_printf(request->output, "Connection: Keep-Alive\r\n");
}

return 0;
}

static
int add_body(struct http_request *request) {
if (request->body_len > 0) {
	evbuffer_add(request->output, request->body, request->body_len);
}

return 0;
}

static
int next_request(struct http_client *api) {
if (TAILQ_EMPTY(&api->pending_request)) {
	event_del(&api->connection.ev);
	api->state = HTTP_IDLE;
	return 0;
}

/* Schedule write. */
if (event_pending(&api->connection.ev, EV_READ | EV_WRITE, NULL)) {
	reschedule_event(&api->connection.ev, api->connection.ev.ev_fd, EV_WRITE,
			api);
} else {
	int fd;
	BIO_get_fd(api->connection.buffer_io, &fd);

	event_set(&api->connection.ev, fd, EV_WRITE, libevent_interopt, api);
	event_add(&api->connection.ev, NULL);
}

api->state = HTTP_SENDING;

return 0;
}

/*======================================================================================================================
 * 
 *  Connection write, read, parse state machine.
 * 
 *====================================================================================================================== 
 */

static
void connection_fault(struct http_client *api, enum connection_fault fault) {
/* Fault all pending request. */
if (fault != FAULT_CLOSE) {
	struct http_request *request;
	for (request = TAILQ_FIRST(&api->pending_request); request != NULL;
			request = TAILQ_FIRST(&api->pending_request)) {

		/* Run callback, with an error response. */
		if (request->callback != NULL)
			request->callback(api, request, NULL, request->baton);

		TAILQ_REMOVE(&api->pending_request, request, entry);
		free_request(request);
	}
}

if (fault == FAULT_CLOSE && api->current) {
	if (api->current->body_chunked == true) {
		/* Set the length, it's the whole buffer. */
		api->current->body_len = EVBUFFER_LENGTH(api->input);

		dispatch_response(api, api->current);
	}

	/* TODO: Error, cancel pending request / response. */
}

/* Run disconnect callback. */
if (api->cb_disconnect != NULL)
	api->cb_disconnect(api, api->baton);

/* TODO: close connection. */

/* Set status FSM. */
api->state = HTTP_NOT_CONNECTED;

/* Reset BIO but try to keep resume SSL session on reconnect. */
if (api->connection.ssl_ctx) {
	BIO_reset(api->connection.buffer_io);
}

/* Process remaning requests. */
if (fault == FAULT_CLOSE && !TAILQ_EMPTY(&api->pending_request)) {
	if ((api->connection.buffer_io = connect_remote_host(api)) == NULL) {
		/* Error, dump the queue. */
		connection_fault(api, FAULT_ERROR);
	}
}
}

static
int ssl_negociate(struct http_client *api) {
/* Do handshake. */

/* Verify ceficate. */
if (SSL_get_verify_result(api->connection.ssl) != X509_V_OK)
	return -1;

/* Wait till we can write out request out. */
reschedule_event(&api->connection.ev, api->connection.ev.ev_fd, EV_WRITE, api);

return 0;
}

static
void libevent_interopt(int fd, short what, void *baton) {
int result;
struct http_client *api = (struct http_client *) baton;

switch (api->state) {
case HTTP_CONNECTING:
	if (api->connection.ssl_ctx) {
		if (ssl_negociate(api) == -1) {

		}

		return;
	} else {
		api->state = HTTP_SENDING;
		/* And fall through. */
	}
case HTTP_SENDING:
	/* Write the data out. */
	write_request(api);
	return;
case HTTP_READING_STATUS_LINE:
case HTTP_READING_HEADERS:
case HTTP_READING_BODY:
	do {
		/* Read into evbuffer */
		if ((result = read_data(api)) == -1)
			return;
	} while (result == 1);

	/* Do the read callback. */
	read_response(api);
	return;
case HTTP_IDLE:
	/*  */
	;
}
}

/*======================================================================================================================
 * 
 *  Network IO
 * 
 *====================================================================================================================== 
 */

static
int write_request(struct http_client *api) {
struct http_request *request;

if (TAILQ_EMPTY(&api->pending_request)) {
	return 0;
}

request = TAILQ_FIRST(&api->pending_request);

int result = BIO_write(api->connection.buffer_io,
		EVBUFFER_DATA(request->output), EVBUFFER_LENGTH(request->output));

if (result < 1) {
	if (BIO_should_retry(api->connection.buffer_io)) {
		return 0;
	}

	connection_fault(api, FAULT_ERROR);
	return -1;
}

/* Drain the data we wrote out. */
evbuffer_drain(request->output, result);

if (EVBUFFER_LENGTH(request->output) == 0) {
	if ((api->current = new_response(request)) == NULL)
		return -1;

	api->state = HTTP_READING_STATUS_LINE;

	/* Turn off write notification and turn on read notification. */
	reschedule_event(&api->connection.ev, api->connection.ev.ev_fd, EV_READ,
			api);
}

return 0;
}

static
int read_data(struct http_client *api) {
char buffer[1024];

int result = BIO_read(api->connection.buffer_io, buffer, sizeof(buffer));

if (result > 0) {
	evbuffer_add(api->input, buffer, result);
	return (result >= sizeof(buffer));
} else if (result == 0) {
	/* Other side closed the connection */
	connection_fault(api, FAULT_CLOSE);
	return -1;
}

/* We'll try again in the future again. */
if (BIO_should_retry(api->connection.buffer_io)) {
	return 0;
}

/* Error */
connection_fault(api, FAULT_ERROR);
return -1;
}

static BIO* connect_remote_host(struct http_client *api) {
BIO *result;

if (api->connection.ssl_ctx != NULL) {
	result = BIO_new_ssl_connect(api->connection.ssl_ctx);

	if (result) {
		BIO_set_conn_hostname(result, api->connection.hostname);
		BIO_get_ssl(result, &api->connection.ssl);
		SSL_set_mode(api->connection.ssl, SSL_MODE_AUTO_RETRY);
	}

} else {
	result = BIO_new_connect((char *) api->connection.hostname);
}

if (result == NULL) {
	goto FAIL_ONE;
}

BIO_set_conn_port(result, api->connection.port);

/* Set it to non-blocking mode. */
BIO_set_nbio(result, 1);

if (BIO_do_connect(result) == -1) {

	if (BIO_should_retry(result)) {
		int reason = BIO_get_retry_reason(result);
	} else {
		goto FAIL_TWO;
	}
}

int fd;
BIO_get_fd(result, &fd);
short what = (api->connection.ssl_ctx) ? EV_READ : EV_WRITE;

event_set(&api->connection.ev, fd, what, libevent_interopt, api);
event_add(&api->connection.ev, NULL);

api->state = HTTP_CONNECTING;

return result;

FAIL_TWO: BIO_free_all(result);
FAIL_ONE: return NULL;
}

static
int setup_ssl(struct http_client *api, SSL_CTX* ctx) {
api->connection.ssl_ctx = ctx;
api->connection.ssl = NULL;
}

/*======================================================================================================================
 * 
 *  Public API 
 * 
 *====================================================================================================================== 
 */

struct http_client *http_client_new(const char *hostname, const char *port,
	SSL_CTX* ssl_ctx) {
struct http_client *api = calloc(1, sizeof(*api));

if (api == NULL) {
	goto FAIL_ONE;
}

api->version[VERSION_MAJOR] = 1;
api->version[VERSION_MINOR] = 1;

api->state = HTTP_NOT_CONNECTED;

TAILQ_INIT(&api->pending_request);

if ((api->connection.hostname = strdup(hostname)) == NULL)
	goto FAIL_TWO;
if ((api->connection.port = strdup(port)) == NULL)
	goto FAIL_TWO;

if (ssl_ctx != NULL) {
	if (setup_ssl(api, ssl_ctx) == -1)
		goto FAIL_TWO;
}

if ((api->input = evbuffer_new()) == NULL) {
	goto FAIL_THREE;
}

return api;

FAIL_THREE: evbuffer_free(api->input);
FAIL_TWO: free((void *) api->connection.hostname);
free((void *) api->connection.port);
free(api);
FAIL_ONE: return NULL;
}

void http_client_free(struct http_client *api) {
BIO_free_all(api->connection.buffer_io);

connection_fault(api, FAULT_FREE);

free(api);
}

struct http_request *http_client_request(struct http_client *api,
	enum http_method method, size_t uri_len, const char *uri) {
struct http_request *request = new_request(api);

if (request == NULL)
	return NULL;

request->method = method;
request->uri_len = uri_len;

if ((request->uri = memdup(uri, uri_len)) == NULL) {
	goto FAIL;
}

return request;

FAIL: free_request(request);
return NULL;
}

int http_client_request_add_header(struct http_request *request,
	const char *key, const char *value) {
struct http_header *header = malloc(sizeof(*header));

if (header == NULL) {
	goto FAIL_ONE;
}

header->key_len = strlen(key);
header->value_len = strlen(value);

if ((header->key = memdup(key, header->key_len)) == NULL) {
	goto FAIL_TWO;
}

if ((header->value = memdup(value, header->value_len)) == NULL) {
	goto FAIL_THREE;
}

TAILQ_INSERT_TAIL(&request->headers, header, entry);
return 0;

FAIL_THREE: free((void *) header->key);
FAIL_TWO: free(header);
FAIL_ONE: return -1;
}

int http_client_request_add_body(struct http_request *request, size_t body_len,
	const char *body) {
if ((request->body = malloc(body_len)) == NULL) {
	return -1;
}

memcpy((void *) request->body, body, body_len);
request->body_len = body_len;

return 0;
}

int http_client_request_dispatch(struct http_request *request,
	cb_http_response callback, void *baton) {
struct http_client *api = request->api;
BIO *bio;

request->callback = callback;
request->baton = baton;

if (add_req_line(request) == -1) {
	return -1;
}

if (add_headers(request) == -1) {
	return -1;
}

if (add_body(request) == -1) {
	return -1;
}

if (evbuffer_add_printf(request->output, "\r\n") == -1)
	return -1;

/* Stick in the stack for sending it out. */
TAILQ_INSERT_TAIL(&request->api->pending_request, request, entry);

switch (request->api->state) {
case HTTP_NOT_CONNECTED:
	if ((bio = connect_remote_host(request->api)) == NULL) {
		return -1;
	}

	api->connection.buffer_io = bio;
	return 0;
case HTTP_IDLE:
	/* Schedule processing of the next request. */
	next_request(api);
}

return 0;
}



test.c
/* libc */
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
/* libevent */
#include <event2/event-config.h>
#include <event2/event.h>
/* openssl */
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#include "http_client.h"

void callback(struct http_client *api, struct http_request *req, struct http_response *response, void *baton)
{
  printf("%.*s", response->body_len, response->body);
}

int main(int argc, char *argv[])
{
  bool          do_ssl = false;
  const char   *url = argv[1];

  const char   *domain = argv[1];
  const char   *port = argv[2];
  const char   *uri = argv[3];

  if (argc < 4) {
    printf("ERROR: Need url, port and uri\n");
    return 1;
  }

  SSL_load_error_strings();
  ERR_load_BIO_strings();
  OpenSSL_add_all_algorithms();

  event_init();

  struct http_client   *client = http_client_new(domain, port, NULL);
  struct http_request  *req = http_client_request(client, HTTP_METHOD_GET, strlen(uri), uri);

  http_client_request_dispatch(req, callback, NULL);

  event_loop(0);

  return 1;
}