Annotation of embedaddon/libpdel/http/http_client.c, revision 1.1
1.1 ! misho 1:
! 2: /*
! 3: * Copyright (c) 2001-2002 Packet Design, LLC.
! 4: * All rights reserved.
! 5: *
! 6: * Subject to the following obligations and disclaimer of warranty,
! 7: * use and redistribution of this software, in source or object code
! 8: * forms, with or without modifications are expressly permitted by
! 9: * Packet Design; provided, however, that:
! 10: *
! 11: * (i) Any and all reproductions of the source or object code
! 12: * must include the copyright notice above and the following
! 13: * disclaimer of warranties; and
! 14: * (ii) No rights are granted, in any manner or form, to use
! 15: * Packet Design trademarks, including the mark "PACKET DESIGN"
! 16: * on advertising, endorsements, or otherwise except as such
! 17: * appears in the above copyright notice or in the software.
! 18: *
! 19: * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
! 20: * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
! 21: * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
! 22: * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
! 23: * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
! 24: * OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
! 25: * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
! 26: * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
! 27: * RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
! 28: * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
! 29: * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
! 30: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
! 31: * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
! 32: * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
! 33: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
! 34: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
! 35: * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
! 36: * THE POSSIBILITY OF SUCH DAMAGE.
! 37: *
! 38: * Author: Archie Cobbs <archie@freebsd.org>
! 39: */
! 40:
! 41: #include <sys/types.h>
! 42: #include <sys/socket.h>
! 43: #include <sys/syslog.h>
! 44: #include <sys/queue.h>
! 45:
! 46: #include <netinet/in_systm.h>
! 47: #include <netinet/in.h>
! 48: #include <arpa/inet.h>
! 49:
! 50: #include <assert.h>
! 51: #include <stdio.h>
! 52: #include <stdlib.h>
! 53: #include <stdarg.h>
! 54: #include <string.h>
! 55: #include <unistd.h>
! 56: #include <pthread.h>
! 57:
! 58: #include <openssl/ssl.h>
! 59: #include <openssl/err.h>
! 60:
! 61: #include "structs/structs.h"
! 62: #include "structs/type/array.h"
! 63:
! 64: #include "io/ssl_fp.h"
! 65: #include "util/typed_mem.h"
! 66:
! 67: #include "http/http_defs.h"
! 68: #include "http/http_server.h"
! 69: #include "http/http_internal.h"
! 70:
! 71: #define HTTP_CLIENT_TIMEOUT 90 /* timeout in seconds */
! 72:
! 73: /* Cleanup state for http_client_connect() */
! 74: struct http_client_connect_state {
! 75: int sock;
! 76: };
! 77:
! 78: /* HTTP client */
! 79: struct http_client {
! 80: char *user_agent; /* client user agent name */
! 81: SSL_CTX *ssl; /* ssl context */
! 82: http_logger_t *logger; /* error logging routine */
! 83: struct http_connection_cache
! 84: *cache; /* cached connections */
! 85: u_int max_conn; /* max number of connections */
! 86: pthread_mutex_t mutex; /* mutex for "condvar" */
! 87: pthread_cond_t condvar; /* connection condition var */
! 88: u_int num_conn; /* number active connections */
! 89: TAILQ_HEAD(,http_client_connection)
! 90: list; /* active connections */
! 91: };
! 92:
! 93: /* HTTP client connection */
! 94: struct http_client_connection {
! 95: struct http_client *client; /* client who owns me */
! 96: struct sockaddr_in peer; /* peer address */
! 97: struct http_connection *conn; /* connection to server */
! 98: char *reason; /* reason for failure */
! 99: u_char got_response; /* response read from server */
! 100: TAILQ_ENTRY(http_client_connection)
! 101: next; /* next in client list */
! 102: };
! 103:
! 104: /* Internal functions */
! 105: static void http_client_connect_cleanup(void *arg);
! 106:
! 107: /*********************************************************************
! 108: HTTP_CLIENT FUNCTIONS
! 109: *********************************************************************/
! 110:
! 111: /*
! 112: * Create a new client.
! 113: */
! 114: struct http_client *
! 115: http_client_create(struct pevent_ctx *ctx, const char *user_agent,
! 116: u_int max_conn, u_int max_cache, u_int max_cache_idle,
! 117: http_logger_t *logger)
! 118: {
! 119: struct http_client *client;
! 120: int got_mutex = 0;
! 121: int got_cond = 0;
! 122:
! 123: /* Sanity check */
! 124: if (max_conn <= max_cache) {
! 125: errno = EINVAL;
! 126: return (NULL);
! 127: }
! 128:
! 129: /* Create new client */
! 130: if ((client = MALLOC("http_client", sizeof(*client))) == NULL)
! 131: return (NULL);
! 132: memset(client, 0, sizeof(*client));
! 133: client->logger = logger;
! 134: client->max_conn = max_conn;
! 135: TAILQ_INIT(&client->list);
! 136:
! 137: /* Copy user agent */
! 138: if ((client->user_agent = STRDUP("http_client.user_agent",
! 139: user_agent)) == NULL)
! 140: goto fail;
! 141:
! 142: /* Initialize connection cache */
! 143: if ((client->cache = _http_connection_cache_create(
! 144: ctx, max_cache, max_cache_idle)) == NULL)
! 145: goto fail;
! 146:
! 147: /* Initialize mutex */
! 148: if ((errno = pthread_mutex_init(&client->mutex, NULL)) != 0)
! 149: goto fail;
! 150: got_mutex = 1;
! 151:
! 152: /* Initialize condition variable */
! 153: if ((errno = pthread_cond_init(&client->condvar, NULL)) != 0)
! 154: goto fail;
! 155: got_cond = 1;
! 156:
! 157: /* Done */
! 158: return (client);
! 159:
! 160: fail:
! 161: /* Clean up after failure */
! 162: if (got_cond)
! 163: pthread_cond_destroy(&client->condvar);
! 164: if (got_mutex)
! 165: pthread_mutex_destroy(&client->mutex);
! 166: _http_connection_cache_destroy(&client->cache);
! 167: FREE("http_client.user_agent", client->user_agent);
! 168: return (NULL);
! 169: }
! 170:
! 171: /*
! 172: * Destroy an HTTP client.
! 173: *
! 174: * This will return -1 with errno = EBUSY if there are any
! 175: * associated connections still active.
! 176: */
! 177: int
! 178: http_client_destroy(struct http_client **clientp)
! 179: {
! 180: struct http_client *client = *clientp;
! 181: int r;
! 182:
! 183: /* Sanity */
! 184: if (client == NULL)
! 185: return (0);
! 186:
! 187: /* Acquire mutex */
! 188: r = pthread_mutex_lock(&client->mutex);
! 189: assert(r == 0);
! 190:
! 191: /* Check for active connections */
! 192: if (client->num_conn != 0) {
! 193: r = pthread_mutex_unlock(&client->mutex);
! 194: assert(r == 0);
! 195: errno = EBUSY;
! 196: return (-1);
! 197: }
! 198:
! 199: /* Shut it down */
! 200: if (client->ssl != NULL)
! 201: SSL_CTX_free(client->ssl);
! 202: _http_connection_cache_destroy(&client->cache);
! 203: r = pthread_mutex_unlock(&client->mutex);
! 204: assert(r == 0);
! 205: pthread_cond_destroy(&client->condvar);
! 206: pthread_mutex_destroy(&client->mutex);
! 207: FREE("http_client.user_agent", client->user_agent);
! 208: FREE("http_client", client);
! 209: *clientp = NULL;
! 210: return (0);
! 211: }
! 212:
! 213: /*********************************************************************
! 214: HTTP_CLIENT_CONNECTION FUNCTIONS
! 215: *********************************************************************/
! 216:
! 217: /*
! 218: * Create a new HTTP connection object from this client.
! 219: */
! 220: struct http_client_connection *
! 221: http_client_connect(struct http_client *client,
! 222: struct in_addr ip, u_int16_t port, int https)
! 223: {
! 224: struct http_client_connection *cc = NULL;
! 225: struct http_client_connect_state state;
! 226: struct sockaddr_in sin;
! 227: FILE *fp = NULL;
! 228: int sock = -1;
! 229: int ret;
! 230: int r;
! 231:
! 232: /* Acquire mutex, release if canceled */
! 233: r = pthread_mutex_lock(&client->mutex);
! 234: assert(r == 0);
! 235: pthread_cleanup_push((void (*)(void *))pthread_mutex_unlock,
! 236: &client->mutex);
! 237:
! 238: /* Initialize SSL context for this client if not already done */
! 239: if (https && client->ssl == NULL) {
! 240:
! 241: /* Initialize SSL stuff */
! 242: _http_ssl_init();
! 243:
! 244: /* Initialize new SSL context for this client */
! 245: if ((client->ssl = SSL_CTX_new(
! 246: SSLv23_client_method())) == NULL) {
! 247: ssl_log(NULL, NULL);
! 248: goto fail;
! 249: }
! 250: }
! 251:
! 252: /* Set up peer address */
! 253: memset(&sin, 0, sizeof(sin));
! 254: #if !defined(__linux__) && !defined(__sun__)
! 255: sin.sin_len = sizeof(sin);
! 256: #endif
! 257: sin.sin_family = AF_INET;
! 258: sin.sin_addr = ip;
! 259: sin.sin_port = htons(port);
! 260:
! 261: /* See if we have a cached connection to this server */
! 262: if (client->cache != NULL
! 263: && _http_connection_cache_get(client->cache, &sin,
! 264: https ? client->ssl : NULL, &fp, &sock) == 0)
! 265: goto connected;
! 266:
! 267: /* Wait if too many connections already exist */
! 268: while (client->num_conn >= client->max_conn)
! 269: pthread_cond_wait(&client->condvar, &client->mutex);
! 270:
! 271: /* Get socket */
! 272: if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
! 273: (*client->logger)(LOG_ERR,
! 274: "%s: %s", "socket", strerror(errno));
! 275: goto fail;
! 276: }
! 277:
! 278: /* Release mutex */
! 279: r = pthread_mutex_unlock(&client->mutex);
! 280: assert(r == 0);
! 281:
! 282: /* Don't leak socket if thread is canceled */
! 283: state.sock = sock;
! 284: pthread_cleanup_push(http_client_connect_cleanup, &state);
! 285:
! 286: /* Connect to peer */
! 287: ret = connect(sock, (struct sockaddr *)&sin, sizeof(sin));
! 288:
! 289: /* Remove cleanup hook */
! 290: pthread_cleanup_pop(0);
! 291:
! 292: /* Acquire mutex */
! 293: r = pthread_mutex_lock(&client->mutex);
! 294: assert(r == 0);
! 295:
! 296: /* Check if connected */
! 297: if (ret == -1) {
! 298: (*client->logger)(LOG_ERR, "failed to connect to %s:%u: %s",
! 299: inet_ntoa(ip), port, strerror(errno));
! 300: goto fail;
! 301: }
! 302:
! 303: connected:
! 304: /* Create new client connection object */
! 305: if ((cc = MALLOC("http_client_connection", sizeof(*cc))) == NULL)
! 306: goto fail;
! 307: memset(cc, 0, sizeof(*cc));
! 308: cc->client = client;
! 309: cc->peer = sin;
! 310:
! 311: /* Create new connection */
! 312: if ((cc->conn = _http_connection_create(fp, sock, 0, ip, port,
! 313: client->ssl, client->logger, HTTP_CLIENT_TIMEOUT)) == NULL)
! 314: goto fail;
! 315: fp = NULL;
! 316: sock = -1;
! 317:
! 318: /* Try to do keep-alive */
! 319: cc->conn->keep_alive = 1;
! 320:
! 321: /* Add to client connection list */
! 322: TAILQ_INSERT_TAIL(&client->list, cc, next);
! 323: client->num_conn++;
! 324:
! 325: /* Set some default request headers */
! 326: if (http_request_set_header(cc->conn->req, 0,
! 327: HTTP_HEADER_USER_AGENT, "%s", client->user_agent) == -1
! 328: || http_request_set_header(cc->conn->req, 0,
! 329: HTTP_HEADER_HOST, "%s:%u", inet_ntoa(ip), port) == -1
! 330: || http_request_set_header(cc->conn->req, 0,
! 331: HTTP_HEADER_ACCEPT, "*/*") == -1
! 332: || http_request_set_header(cc->conn->req, 0,
! 333: HTTP_HEADER_ACCEPT_CHARSET, "iso-8859-1") == -1
! 334: || http_request_set_header(cc->conn->req, 0,
! 335: HTTP_HEADER_ACCEPT_ENCODING, "identity") == -1)
! 336: goto fail;
! 337:
! 338: /* Done */
! 339: goto done;
! 340:
! 341: fail:
! 342: /* Cleanup after failure */
! 343: if (cc != NULL) {
! 344: if (cc->conn != NULL)
! 345: _http_connection_free(&cc->conn);
! 346: FREE("http_client_connection", cc);
! 347: cc = NULL;
! 348: }
! 349: if (fp != NULL)
! 350: fclose(fp);
! 351: else if (sock != -1)
! 352: (void)close(sock);
! 353:
! 354: done:;
! 355: /* Done */
! 356: pthread_cleanup_pop(1);
! 357: return (cc);
! 358: }
! 359:
! 360: /*
! 361: * Get local IP address.
! 362: */
! 363: struct in_addr
! 364: http_client_get_local_ip(struct http_client_connection *cc)
! 365: {
! 366: return (cc->conn->local_ip);
! 367: }
! 368:
! 369: /*
! 370: * Get local port.
! 371: */
! 372: u_int16_t
! 373: http_client_get_local_port(struct http_client_connection *cc)
! 374: {
! 375: return (cc->conn->local_port);
! 376: }
! 377:
! 378: /*
! 379: * Get request associated with client.
! 380: */
! 381: struct http_request *
! 382: http_client_get_request(struct http_client_connection *cc)
! 383: {
! 384: return (cc->conn->req);
! 385: }
! 386:
! 387: /*
! 388: * Get response associated with client connection.
! 389: */
! 390: struct http_response *
! 391: http_client_get_response(struct http_client_connection *cc)
! 392: {
! 393: struct http_request *const req = cc->conn->req;
! 394: struct http_response *resp = cc->conn->resp;
! 395: char buf[128];
! 396:
! 397: /* Already got it? */
! 398: if (cc->got_response)
! 399: return (resp);
! 400:
! 401: /* Send request headers (if not sent already) */
! 402: if (http_request_send_headers(req) == -1) {
! 403: snprintf(buf, sizeof(buf), "Error sending request: %s",
! 404: strerror(errno));
! 405: return (NULL);
! 406: }
! 407:
! 408: /* Send back body (it was buffered) */
! 409: _http_message_send_body(req->msg);
! 410:
! 411: /* Read response from server */
! 412: cc->got_response = 1;
! 413: if (_http_response_read(cc->conn, buf, sizeof(buf)) == -1)
! 414: resp = NULL;
! 415:
! 416: /* Save reason message */
! 417: cc->reason = STRDUP("http_client_connection.reason", buf);
! 418: return (resp);
! 419: }
! 420:
! 421: /*
! 422: * Close a client connection. The connection is cached if appropriate.
! 423: */
! 424: void
! 425: http_client_close(struct http_client_connection **ccp)
! 426: {
! 427: struct http_client_connection *const cc = *ccp;
! 428: struct http_connection *conn;
! 429: struct http_client *client;
! 430: struct http_response *resp;
! 431: int r;
! 432:
! 433: /* Sanity */
! 434: if (cc == NULL)
! 435: return;
! 436: *ccp = NULL;
! 437:
! 438: /* Get client and connection */
! 439: client = cc->client;
! 440: conn = cc->conn;
! 441:
! 442: /* Acquire client mutex */
! 443: r = pthread_mutex_lock(&client->mutex);
! 444: assert(r == 0);
! 445:
! 446: /* Cache connection socket if appropriate */
! 447: if (conn->keep_alive
! 448: && client->cache != NULL
! 449: && (resp = http_client_get_response(cc)) != NULL
! 450: && _http_head_want_keepalive(resp->msg->head)
! 451: && _http_connection_cache_put(client->cache,
! 452: &cc->peer, client->ssl, conn->fp, conn->sock) == 0) {
! 453: conn->fp = NULL;
! 454: conn->sock = -1;
! 455: }
! 456:
! 457: /* Remove from active connection list and wakeup next waiting thread */
! 458: TAILQ_REMOVE(&client->list, cc, next);
! 459: if (client->num_conn-- == client->max_conn)
! 460: pthread_cond_signal(&client->condvar);
! 461:
! 462: /* Release client mutex */
! 463: r = pthread_mutex_unlock(&client->mutex);
! 464: assert(r == 0);
! 465:
! 466: /* Shutdown this connection */
! 467: _http_connection_free(&cc->conn);
! 468: FREE("http_client_connection.reason", cc->reason);
! 469: FREE("http_client_connection", cc);
! 470: }
! 471:
! 472: /*
! 473: * Get response error/reason string.
! 474: */
! 475: const char *
! 476: http_client_get_reason(struct http_client_connection *cc)
! 477: {
! 478: if (cc->reason == NULL)
! 479: return ("Uknown error");
! 480: return (cc->reason);
! 481: }
! 482:
! 483: /*********************************************************************
! 484: INTERNAL FUNCTIONS
! 485: *********************************************************************/
! 486:
! 487: /*
! 488: * Cleanup for http_client_connect() if thread is canceled.
! 489: */
! 490: static void
! 491: http_client_connect_cleanup(void *arg)
! 492: {
! 493: const struct http_client_connect_state *const state = arg;
! 494:
! 495: close(state->sock);
! 496: }
! 497:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>