/* * Copyright (C) 2015 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file * * Hyper Text Transfer Protocol (HTTP) connection management * */ #include #include #include #include #include #include #include #include #include #include #include /** HTTP pooled connection expiry time */ #define HTTP_CONN_EXPIRY ( 10 * TICKS_PER_SEC ) /** HTTP connection pool */ static LIST_HEAD ( http_connection_pool ); /** * Identify HTTP scheme * * @v uri URI * @ret scheme HTTP scheme, or NULL */ static struct http_scheme * http_scheme ( struct uri *uri ) { struct http_scheme *scheme; /* Sanity check */ if ( ! uri->scheme ) return NULL; /* Identify scheme */ for_each_table_entry ( scheme, HTTP_SCHEMES ) { if ( strcmp ( uri->scheme, scheme->name ) == 0 ) return scheme; } return NULL; } /** * Free HTTP connection * * @v refcnt Reference count */ static void http_conn_free ( struct refcnt *refcnt ) { struct http_connection *conn = container_of ( refcnt, struct http_connection, refcnt ); /* Free connection */ uri_put ( conn->uri ); free ( conn ); } /** * Close HTTP connection * * @v conn HTTP connection * @v rc Reason for close */ static void http_conn_close ( struct http_connection *conn, int rc ) { /* Remove from connection pool, if applicable */ pool_del ( &conn->pool ); /* Shut down interfaces */ intf_shutdown ( &conn->socket, rc ); intf_shutdown ( &conn->xfer, rc ); if ( rc == 0 ) { DBGC2 ( conn, "HTTPCONN %p closed %s://%s\n", conn, conn->scheme->name, conn->uri->host ); } else { DBGC ( conn, "HTTPCONN %p closed %s://%s: %s\n", conn, conn->scheme->name, conn->uri->host, strerror ( rc ) ); } } /** * Disconnect idle HTTP connection * * @v pool Pooled connection */ static void http_conn_expired ( struct pooled_connection *pool ) { struct http_connection *conn = container_of ( pool, struct http_connection, pool ); /* Close connection */ http_conn_close ( conn, 0 /* Not an error to close idle connection */ ); } /** * Receive data from transport layer interface * * @v http HTTP connection * @v iobuf I/O buffer * @v meta Transfer metadata * @ret rc Return status code */ static int http_conn_socket_deliver ( struct http_connection *conn, struct io_buffer *iobuf, struct xfer_metadata *meta ) { /* Mark connection as alive */ pool_alive ( &conn->pool ); /* Pass on to data transfer interface */ return xfer_deliver ( &conn->xfer, iobuf, meta ); } /** * Close HTTP connection transport layer interface * * @v http HTTP connection * @v rc Reason for close */ static void http_conn_socket_close ( struct http_connection *conn, int rc ) { /* If we are reopenable (i.e. we are a recycled connection * from the connection pool, and we have received no data from * the underlying socket since we were pooled), then suggest * that the client should reopen the connection. */ if ( pool_is_reopenable ( &conn->pool ) ) pool_reopen ( &conn->xfer ); /* Close the connection */ http_conn_close ( conn, rc ); } /** * Recycle this connection after closing * * @v http HTTP connection */ static void http_conn_xfer_recycle ( struct http_connection *conn ) { /* Mark connection as recyclable */ pool_recyclable ( &conn->pool ); DBGC2 ( conn, "HTTPCONN %p keepalive enabled\n", conn ); } /** * Close HTTP connection data transfer interface * * @v conn HTTP connection * @v rc Reason for close */ static void http_conn_xfer_close ( struct http_connection *conn, int rc ) { /* Add to the connection pool if keepalive is enabled and no * error occurred. */ if ( ( rc == 0 ) && pool_is_recyclable ( &conn->pool ) ) { intf_restart ( &conn->xfer, rc ); pool_add ( &conn->pool, &http_connection_pool, HTTP_CONN_EXPIRY ); DBGC2 ( conn, "HTTPCONN %p pooled %s://%s\n", conn, conn->scheme->name, conn->uri->host ); return; } /* Otherwise, close the connection */ http_conn_close ( conn, rc ); } /** HTTP connection socket interface operations */ static struct interface_operation http_conn_socket_operations[] = { INTF_OP ( xfer_deliver, struct http_connection *, http_conn_socket_deliver ), INTF_OP ( intf_close, struct http_connection *, http_conn_socket_close ), }; /** HTTP connection socket interface descriptor */ static struct interface_descriptor http_conn_socket_desc = INTF_DESC_PASSTHRU ( struct http_connection, socket, http_conn_socket_operations, xfer ); /** HTTP connection data transfer interface operations */ static struct interface_operation http_conn_xfer_operations[] = { INTF_OP ( pool_recycle, struct http_connection *, http_conn_xfer_recycle ), INTF_OP ( intf_close, struct http_connection *, http_conn_xfer_close ), }; /** HTTP connection data transfer interface descriptor */ static struct interface_descriptor http_conn_xfer_desc = INTF_DESC_PASSTHRU ( struct http_connection, xfer, http_conn_xfer_operations, socket ); /** * Connect to an HTTP server * * @v xfer Data transfer interface * @v uri Connection URI * @ret rc Return status code * * HTTP connections are pooled. The caller should be prepared to * receive a pool_reopen() message. */ int http_connect ( struct interface *xfer, struct uri *uri ) { struct http_connection *conn; struct http_scheme *scheme; struct sockaddr_tcpip server; struct interface *socket; unsigned int port; int rc; /* Identify scheme */ scheme = http_scheme ( uri ); if ( ! scheme ) return -ENOTSUP; /* Sanity check */ if ( ! uri->host ) return -EINVAL; /* Identify port */ port = uri_port ( uri, scheme->port ); /* Look for a reusable connection in the pool. Reuse the most * recent connection in order to accommodate authentication * schemes that break the stateless nature of HTTP and rely on * the same connection being reused for authentication * responses. */ list_for_each_entry_reverse ( conn, &http_connection_pool, pool.list ) { /* Sanity checks */ assert ( conn->uri != NULL ); assert ( conn->uri->host != NULL ); /* Reuse connection, if possible */ if ( ( scheme == conn->scheme ) && ( strcmp ( uri->host, conn->uri->host ) == 0 ) && ( port == uri_port ( conn->uri, scheme->port ) ) ) { /* Remove from connection pool, stop timer, * attach to parent interface, and return. */ pool_del ( &conn->pool ); intf_plug_plug ( &conn->xfer, xfer ); DBGC2 ( conn, "HTTPCONN %p reused %s://%s:%d\n", conn, conn->scheme->name, conn->uri->host, port ); return 0; } } /* Allocate and initialise structure */ conn = zalloc ( sizeof ( *conn ) ); if ( ! conn ) { rc = -ENOMEM; goto err_alloc; } ref_init ( &conn->refcnt, http_conn_free ); conn->uri = uri_get ( uri ); conn->scheme = scheme; intf_init ( &conn->socket, &http_conn_socket_desc, &conn->refcnt ); intf_init ( &conn->xfer, &http_conn_xfer_desc, &conn->refcnt ); pool_init ( &conn->pool, http_conn_expired, &conn->refcnt ); /* Open socket */ memset ( &server, 0, sizeof ( server ) ); server.st_port = htons ( port ); socket = &conn->socket; if ( scheme->filter && ( ( rc = scheme->filter ( socket, uri->host, &socket ) ) != 0 ) ) goto err_filter; if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM, ( struct sockaddr * ) &server, uri->host, NULL ) ) != 0 ) goto err_open; /* Attach to parent interface, mortalise self, and return */ intf_plug_plug ( &conn->xfer, xfer ); ref_put ( &conn->refcnt ); DBGC2 ( conn, "HTTPCONN %p created %s://%s:%d\n", conn, conn->scheme->name, conn->uri->host, port ); return 0; err_open: err_filter: DBGC2 ( conn, "HTTPCONN %p could not create %s://%s:%d: %s\n", conn, conn->scheme->name, conn->uri->host, port, strerror ( rc ) ); http_conn_close ( conn, rc ); ref_put ( &conn->refcnt ); err_alloc: return rc; }