File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / http / http_client.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 3 months ago) by misho
Branches: libpdel, MAIN
CVS tags: v0_5_3, HEAD
libpdel


/*
 * Copyright (c) 2001-2002 Packet Design, LLC.
 * All rights reserved.
 * 
 * Subject to the following obligations and disclaimer of warranty,
 * use and redistribution of this software, in source or object code
 * forms, with or without modifications are expressly permitted by
 * Packet Design; provided, however, that:
 * 
 *    (i)  Any and all reproductions of the source or object code
 *         must include the copyright notice above and the following
 *         disclaimer of warranties; and
 *    (ii) No rights are granted, in any manner or form, to use
 *         Packet Design trademarks, including the mark "PACKET DESIGN"
 *         on advertising, endorsements, or otherwise except as such
 *         appears in the above copyright notice or in the software.
 * 
 * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
 * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
 * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
 * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
 * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
 * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
 * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
 * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
 * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 PACKET DESIGN IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/queue.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#include <openssl/ssl.h>
#include <openssl/err.h>

#include "structs/structs.h"
#include "structs/type/array.h"

#include "io/ssl_fp.h"
#include "util/typed_mem.h"

#include "http/http_defs.h"
#include "http/http_server.h"
#include "http/http_internal.h"

#define HTTP_CLIENT_TIMEOUT	90	/* timeout in seconds */

/* Cleanup state for http_client_connect() */
struct http_client_connect_state {
	int	sock;
};

/* HTTP client */
struct http_client {
	char			*user_agent;	/* client user agent name */
	SSL_CTX			*ssl;		/* ssl context */
	http_logger_t		*logger;	/* error logging routine */
	struct http_connection_cache
				*cache;		/* cached connections */
	u_int			max_conn;	/* max number of connections */
	pthread_mutex_t		mutex;		/* mutex for "condvar" */
	pthread_cond_t		condvar;	/* connection condition var */
	u_int			num_conn;	/* number active connections */
	TAILQ_HEAD(,http_client_connection)
				list;		/* active connections */
};

/* HTTP client connection */
struct http_client_connection {
	struct http_client	*client;	/* client who owns me */
	struct sockaddr_in	peer;		/* peer address */
	struct http_connection	*conn;		/* connection to server */
	char			*reason;	/* reason for failure */
	u_char			got_response;	/* response read from server */
	TAILQ_ENTRY(http_client_connection)
				next;		/* next in client list */
};

/* Internal functions */
static void	http_client_connect_cleanup(void *arg);

/*********************************************************************
			HTTP_CLIENT FUNCTIONS
*********************************************************************/

/*
 * Create a new client.
 */
struct http_client *
http_client_create(struct pevent_ctx *ctx, const char *user_agent,
	u_int max_conn, u_int max_cache, u_int max_cache_idle,
	http_logger_t *logger)
{
	struct http_client *client;
	int got_mutex = 0;
	int got_cond = 0;

	/* Sanity check */
	if (max_conn <= max_cache) {
		errno = EINVAL;
		return (NULL);
	}

	/* Create new client */
	if ((client = MALLOC("http_client", sizeof(*client))) == NULL)
		return (NULL);
	memset(client, 0, sizeof(*client));
	client->logger = logger;
	client->max_conn = max_conn;
	TAILQ_INIT(&client->list);

	/* Copy user agent */
	if ((client->user_agent = STRDUP("http_client.user_agent",
	    user_agent)) == NULL)
		goto fail;

	/* Initialize connection cache */
	if ((client->cache = _http_connection_cache_create(
	    ctx, max_cache, max_cache_idle)) == NULL)
		goto fail;

	/* Initialize mutex */
	if ((errno = pthread_mutex_init(&client->mutex, NULL)) != 0)
		goto fail;
	got_mutex = 1;

	/* Initialize condition variable */
	if ((errno = pthread_cond_init(&client->condvar, NULL)) != 0)
		goto fail;
	got_cond = 1;

	/* Done */
	return (client);

fail:
	/* Clean up after failure */
	if (got_cond)
		pthread_cond_destroy(&client->condvar);
	if (got_mutex)
		pthread_mutex_destroy(&client->mutex);
	_http_connection_cache_destroy(&client->cache);
	FREE("http_client.user_agent", client->user_agent);
	return (NULL);
}

/*
 * Destroy an HTTP client.
 *
 * This will return -1 with errno = EBUSY if there are any
 * associated connections still active.
 */
int
http_client_destroy(struct http_client **clientp)
{
	struct http_client *client = *clientp;
	int r;

	/* Sanity */
	if (client == NULL)
		return (0);

	/* Acquire mutex */
	r = pthread_mutex_lock(&client->mutex);
	assert(r == 0);

	/* Check for active connections */
	if (client->num_conn != 0) {
		r = pthread_mutex_unlock(&client->mutex);
		assert(r == 0);
		errno = EBUSY;
		return (-1);
	}

	/* Shut it down */
	if (client->ssl != NULL)
		SSL_CTX_free(client->ssl);
	_http_connection_cache_destroy(&client->cache);
	r = pthread_mutex_unlock(&client->mutex);
	assert(r == 0);
	pthread_cond_destroy(&client->condvar);
	pthread_mutex_destroy(&client->mutex);
	FREE("http_client.user_agent", client->user_agent);
	FREE("http_client", client);
	*clientp = NULL;
	return (0);
}

/*********************************************************************
		HTTP_CLIENT_CONNECTION FUNCTIONS
*********************************************************************/

/*
 * Create a new HTTP connection object from this client.
 */
struct http_client_connection *
http_client_connect(struct http_client *client,
	struct in_addr ip, u_int16_t port, int https)
{
	struct http_client_connection *cc = NULL;
	struct http_client_connect_state state;
	struct sockaddr_in sin;
	FILE *fp = NULL;
	int sock = -1;
	int ret;
	int r;

	/* Acquire mutex, release if canceled */
	r = pthread_mutex_lock(&client->mutex);
	assert(r == 0);
	pthread_cleanup_push((void (*)(void *))pthread_mutex_unlock,
	    &client->mutex);

	/* Initialize SSL context for this client if not already done */
	if (https && client->ssl == NULL) {

		/* Initialize SSL stuff */
		_http_ssl_init();

		/* Initialize new SSL context for this client */
		if ((client->ssl = SSL_CTX_new(
		    SSLv23_client_method())) == NULL) {
			ssl_log(NULL, NULL);
			goto fail;
		}
	}

	/* Set up peer address */
	memset(&sin, 0, sizeof(sin));
#if !defined(__linux__) && !defined(__sun__)
	sin.sin_len = sizeof(sin);
#endif
	sin.sin_family = AF_INET;
	sin.sin_addr = ip;
	sin.sin_port = htons(port);

	/* See if we have a cached connection to this server */
	if (client->cache != NULL
	    && _http_connection_cache_get(client->cache, &sin,
	      https ? client->ssl : NULL, &fp, &sock) == 0)
		goto connected;

	/* Wait if too many connections already exist */
	while (client->num_conn >= client->max_conn)
		pthread_cond_wait(&client->condvar, &client->mutex);

	/* Get socket */
	if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
		(*client->logger)(LOG_ERR,
		    "%s: %s", "socket", strerror(errno));
		goto fail;
	}

	/* Release mutex */
	r = pthread_mutex_unlock(&client->mutex);
	assert(r == 0);

	/* Don't leak socket if thread is canceled */
	state.sock = sock;
	pthread_cleanup_push(http_client_connect_cleanup, &state);

	/* Connect to peer */
	ret = connect(sock, (struct sockaddr *)&sin, sizeof(sin));

	/* Remove cleanup hook */
	pthread_cleanup_pop(0);

	/* Acquire mutex */
	r = pthread_mutex_lock(&client->mutex);
	assert(r == 0);

	/* Check if connected */
	if (ret == -1) {
		(*client->logger)(LOG_ERR, "failed to connect to %s:%u: %s",
		    inet_ntoa(ip), port, strerror(errno));
		goto fail;
	}

connected:
	/* Create new client connection object */
	if ((cc = MALLOC("http_client_connection", sizeof(*cc))) == NULL)
		goto fail;
	memset(cc, 0, sizeof(*cc));
	cc->client = client;
	cc->peer = sin;

	/* Create new connection */
	if ((cc->conn = _http_connection_create(fp, sock, 0, ip, port,
	    client->ssl, client->logger, HTTP_CLIENT_TIMEOUT)) == NULL)
		goto fail;
	fp = NULL;
	sock = -1;

	/* Try to do keep-alive */
	cc->conn->keep_alive = 1;

	/* Add to client connection list */
	TAILQ_INSERT_TAIL(&client->list, cc, next);
	client->num_conn++;

	/* Set some default request headers */
	if (http_request_set_header(cc->conn->req, 0,
	      HTTP_HEADER_USER_AGENT, "%s", client->user_agent) == -1
	    || http_request_set_header(cc->conn->req, 0,
	      HTTP_HEADER_HOST, "%s:%u", inet_ntoa(ip), port) == -1
	    || http_request_set_header(cc->conn->req, 0,
	      HTTP_HEADER_ACCEPT, "*/*") == -1
	    || http_request_set_header(cc->conn->req, 0,
	      HTTP_HEADER_ACCEPT_CHARSET, "iso-8859-1") == -1
	    || http_request_set_header(cc->conn->req, 0,
	      HTTP_HEADER_ACCEPT_ENCODING, "identity") == -1)
		goto fail;

	/* Done */
	goto done;

fail:
	/* Cleanup after failure */
	if (cc != NULL) {
		if (cc->conn != NULL)
			_http_connection_free(&cc->conn);
		FREE("http_client_connection", cc);
		cc = NULL;
	}
	if (fp != NULL)
		fclose(fp);
	else if (sock != -1)
		(void)close(sock);

done:;
	/* Done */
	pthread_cleanup_pop(1);
	return (cc);
}

/*
 * Get local IP address.
 */
struct in_addr
http_client_get_local_ip(struct http_client_connection *cc)
{
	return (cc->conn->local_ip);
}

/*
 * Get local port.
 */
u_int16_t
http_client_get_local_port(struct http_client_connection *cc)
{
	return (cc->conn->local_port);
}

/*
 * Get request associated with client.
 */
struct http_request *
http_client_get_request(struct http_client_connection *cc)
{
	return (cc->conn->req);
}

/*
 * Get response associated with client connection.
 */
struct http_response *
http_client_get_response(struct http_client_connection *cc)
{
	struct http_request *const req = cc->conn->req;
	struct http_response *resp = cc->conn->resp;
	char buf[128];

	/* Already got it? */
	if (cc->got_response)
		return (resp);

	/* Send request headers (if not sent already) */
	if (http_request_send_headers(req) == -1) {
		snprintf(buf, sizeof(buf), "Error sending request: %s",
		    strerror(errno));
		return (NULL);
	}

	/* Send back body (it was buffered) */
	_http_message_send_body(req->msg);

	/* Read response from server */
	cc->got_response = 1;
	if (_http_response_read(cc->conn, buf, sizeof(buf)) == -1)
		resp = NULL;

	/* Save reason message */
	cc->reason = STRDUP("http_client_connection.reason", buf);
	return (resp);
}

/*
 * Close a client connection. The connection is cached if appropriate.
 */
void
http_client_close(struct http_client_connection **ccp)
{
	struct http_client_connection *const cc = *ccp;
	struct http_connection *conn;
	struct http_client *client;
	struct http_response *resp;
	int r;

	/* Sanity */
	if (cc == NULL)
		return;
	*ccp = NULL;

	/* Get client and connection */
	client = cc->client;
	conn = cc->conn;

	/* Acquire client mutex */
	r = pthread_mutex_lock(&client->mutex);
	assert(r == 0);

	/* Cache connection socket if appropriate */
	if (conn->keep_alive
	    && client->cache != NULL
	    && (resp = http_client_get_response(cc)) != NULL
	    && _http_head_want_keepalive(resp->msg->head)
	    && _http_connection_cache_put(client->cache,
	      &cc->peer, client->ssl, conn->fp, conn->sock) == 0) {
		conn->fp = NULL;
		conn->sock = -1;
	}

	/* Remove from active connection list and wakeup next waiting thread */
	TAILQ_REMOVE(&client->list, cc, next);
	if (client->num_conn-- == client->max_conn)
		pthread_cond_signal(&client->condvar);

	/* Release client mutex */
	r = pthread_mutex_unlock(&client->mutex);
	assert(r == 0);

	/* Shutdown this connection */
	_http_connection_free(&cc->conn);
	FREE("http_client_connection.reason", cc->reason);
	FREE("http_client_connection", cc);
}

/*
 * Get response error/reason string.
 */
const char *
http_client_get_reason(struct http_client_connection *cc)
{
	if (cc->reason == NULL)
		return ("Uknown error");
	return (cc->reason);
}

/*********************************************************************
			INTERNAL FUNCTIONS
*********************************************************************/

/*
 * Cleanup for http_client_connect() if thread is canceled.
 */
static void
http_client_connect_cleanup(void *arg)
{
	const struct http_client_connect_state *const state = arg;

	close(state->sock);
}


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>