diff options
Diffstat (limited to 'contrib/serf/outgoing.c')
-rw-r--r-- | contrib/serf/outgoing.c | 558 |
1 files changed, 368 insertions, 190 deletions
diff --git a/contrib/serf/outgoing.c b/contrib/serf/outgoing.c index 2c63e96..a12746c 100644 --- a/contrib/serf/outgoing.c +++ b/contrib/serf/outgoing.c @@ -16,6 +16,7 @@ #include <apr_pools.h> #include <apr_poll.h> #include <apr_version.h> +#include <apr_portable.h> #include "serf.h" #include "serf_bucket_util.h" @@ -113,30 +114,34 @@ apr_status_t serf__conn_update_pollset(serf_connection_t *conn) /* ### not true. we only want to read IF we have sent some data */ desc.reqevents |= APR_POLLIN; - /* If the connection is not closing down and - * has unwritten data or - * there are any requests that still have buckets to write out, - * then we want to write. - */ - if (conn->vec_len && - conn->state != SERF_CONN_CLOSING) - desc.reqevents |= APR_POLLOUT; - else { - serf_request_t *request = conn->requests; + /* Don't write if OpenSSL told us that it needs to read data first. */ + if (conn->stop_writing != 1) { - if ((conn->probable_keepalive_limit && - conn->completed_requests > conn->probable_keepalive_limit) || - (conn->max_outstanding_requests && - conn->completed_requests - conn->completed_responses >= - conn->max_outstanding_requests)) { - /* we wouldn't try to write any way right now. */ - } + /* If the connection is not closing down and + * has unwritten data or + * there are any requests that still have buckets to write out, + * then we want to write. + */ + if (conn->vec_len && + conn->state != SERF_CONN_CLOSING) + desc.reqevents |= APR_POLLOUT; else { - while (request != NULL && request->req_bkt == NULL && - request->written) - request = request->next; - if (request != NULL) - desc.reqevents |= APR_POLLOUT; + serf_request_t *request = conn->requests; + + if ((conn->probable_keepalive_limit && + conn->completed_requests > conn->probable_keepalive_limit) || + (conn->max_outstanding_requests && + conn->completed_requests - conn->completed_responses >= + conn->max_outstanding_requests)) { + /* we wouldn't try to write any way right now. */ + } + else { + while (request != NULL && request->req_bkt == NULL && + request->written) + request = request->next; + if (request != NULL) + desc.reqevents |= APR_POLLOUT; + } } } } @@ -177,6 +182,108 @@ static void check_buckets_drained(serf_connection_t *conn) #endif +static void destroy_ostream(serf_connection_t *conn) +{ + if (conn->ostream_head != NULL) { + serf_bucket_destroy(conn->ostream_head); + conn->ostream_head = NULL; + conn->ostream_tail = NULL; + } +} + +static apr_status_t detect_eof(void *baton, serf_bucket_t *aggregate_bucket) +{ + serf_connection_t *conn = baton; + conn->hit_eof = 1; + return APR_EAGAIN; +} + +static apr_status_t do_conn_setup(serf_connection_t *conn) +{ + apr_status_t status; + serf_bucket_t *ostream; + + if (conn->ostream_head == NULL) { + conn->ostream_head = serf_bucket_aggregate_create(conn->allocator); + } + + if (conn->ostream_tail == NULL) { + conn->ostream_tail = serf__bucket_stream_create(conn->allocator, + detect_eof, + conn); + } + + ostream = conn->ostream_tail; + + status = (*conn->setup)(conn->skt, + &conn->stream, + &ostream, + conn->setup_baton, + conn->pool); + if (status) { + /* extra destroy here since it wasn't added to the head bucket yet. */ + serf_bucket_destroy(conn->ostream_tail); + destroy_ostream(conn); + return status; + } + + serf_bucket_aggregate_append(conn->ostream_head, + ostream); + + return status; +} + +/* Set up the input and output stream buckets. + When a tunnel over an http proxy is needed, create a socket bucket and + empty aggregate bucket for sending and receiving unencrypted requests + over the socket. + + After the tunnel is there, or no tunnel was needed, ask the application + to create the input and output buckets, which should take care of the + [en/de]cryption. + */ + +static apr_status_t prepare_conn_streams(serf_connection_t *conn, + serf_bucket_t **istream, + serf_bucket_t **ostreamt, + serf_bucket_t **ostreamh) +{ + apr_status_t status; + + if (conn->stream == NULL) { + conn->latency = apr_time_now() - conn->connect_time; + } + + /* Do we need a SSL tunnel first? */ + if (conn->state == SERF_CONN_CONNECTED) { + /* If the connection does not have an associated bucket, then + * call the setup callback to get one. + */ + if (conn->stream == NULL) { + status = do_conn_setup(conn); + if (status) { + return status; + } + } + *ostreamt = conn->ostream_tail; + *ostreamh = conn->ostream_head; + *istream = conn->stream; + } else { + /* SSL tunnel needed and not set up yet, get a direct unencrypted + stream for this socket */ + if (conn->stream == NULL) { + *istream = serf_bucket_socket_create(conn->skt, + conn->allocator); + } + /* Don't create the ostream bucket chain including the ssl_encrypt + bucket yet. This ensure the CONNECT request is sent unencrypted + to the proxy. */ + *ostreamt = *ostreamh = conn->ssltunnel_ostream; + } + + return APR_SUCCESS; +} + /* Create and connect sockets for any connections which don't have them * yet. This is the core of our lazy-connect behavior. */ @@ -186,6 +293,7 @@ apr_status_t serf__open_connections(serf_context_t *ctx) for (i = ctx->conns->nelts; i--; ) { serf_connection_t *conn = GET_CONN(ctx, i); + serf__authn_info_t *authn_info; apr_status_t status; apr_socket_t *skt; @@ -240,7 +348,7 @@ apr_status_t serf__open_connections(serf_context_t *ctx) serf__log_skt(SOCK_VERBOSE, __FILE__, skt, "connected socket for conn 0x%x, status %d\n", conn, status); - if (status != APR_SUCCESS) { + if (status != APR_SUCCESS) { if (!APR_STATUS_IS_EINPROGRESS(status)) return status; } @@ -253,21 +361,33 @@ apr_status_t serf__open_connections(serf_context_t *ctx) prepare this connection (it might be possible to skip some part of the handshaking). */ if (ctx->proxy_address) { - if (conn->ctx->proxy_authn_info.scheme) - conn->ctx->proxy_authn_info.scheme->init_conn_func(407, conn, - conn->pool); + authn_info = &ctx->proxy_authn_info; + if (authn_info->scheme) { + authn_info->scheme->init_conn_func(authn_info->scheme, 407, + conn, conn->pool); + } } - if (conn->ctx->authn_info.scheme) - conn->ctx->authn_info.scheme->init_conn_func(401, conn, - conn->pool); + authn_info = serf__get_authn_info_for_server(conn); + if (authn_info->scheme) { + authn_info->scheme->init_conn_func(authn_info->scheme, 401, + conn, conn->pool); + } /* Does this connection require a SSL tunnel over the proxy? */ if (ctx->proxy_address && strcmp(conn->host_info.scheme, "https") == 0) serf__ssltunnel_connect(conn); - else + else { + serf_bucket_t *dummy1, *dummy2; + conn->state = SERF_CONN_CONNECTED; + status = prepare_conn_streams(conn, &conn->stream, + &dummy1, &dummy2); + if (status) { + return status; + } + } } return APR_SUCCESS; @@ -396,15 +516,6 @@ static apr_status_t remove_connection(serf_context_t *ctx, &desc, conn); } -static void destroy_ostream(serf_connection_t *conn) -{ - if (conn->ostream_head != NULL) { - serf_bucket_destroy(conn->ostream_head); - conn->ostream_head = NULL; - conn->ostream_tail = NULL; - } -} - /* A socket was closed, inform the application. */ static void handle_conn_closed(serf_connection_t *conn, apr_status_t status) { @@ -492,7 +603,7 @@ static apr_status_t socket_writev(serf_connection_t *conn) status = apr_socket_sendv(conn->skt, conn->vec, conn->vec_len, &written); - if (status && !APR_STATUS_IS_EAGAIN(status)) + if (status && !APR_STATUS_IS_EAGAIN(status)) serf__log_skt(SOCK_VERBOSE, __FILE__, conn->skt, "socket_sendv error %d\n", status); @@ -535,99 +646,29 @@ static apr_status_t socket_writev(serf_connection_t *conn) return status; } -static apr_status_t detect_eof(void *baton, serf_bucket_t *aggregate_bucket) -{ - serf_connection_t *conn = baton; - conn->hit_eof = 1; - return APR_EAGAIN; -} - -static apr_status_t do_conn_setup(serf_connection_t *conn) +static apr_status_t setup_request(serf_request_t *request) { + serf_connection_t *conn = request->conn; apr_status_t status; - serf_bucket_t *ostream; - - if (conn->ostream_head == NULL) { - conn->ostream_head = serf_bucket_aggregate_create(conn->allocator); - } - - if (conn->ostream_tail == NULL) { - conn->ostream_tail = serf__bucket_stream_create(conn->allocator, - detect_eof, - conn); - } - - ostream = conn->ostream_tail; - - status = (*conn->setup)(conn->skt, - &conn->stream, - &ostream, - conn->setup_baton, - conn->pool); - if (status) { - /* extra destroy here since it wasn't added to the head bucket yet. */ - serf_bucket_destroy(conn->ostream_tail); - destroy_ostream(conn); - return status; - } - - serf_bucket_aggregate_append(conn->ostream_head, - ostream); + /* Now that we are about to serve the request, allocate a pool. */ + apr_pool_create(&request->respool, conn->pool); + request->allocator = serf_bucket_allocator_create(request->respool, + NULL, NULL); + apr_pool_cleanup_register(request->respool, request, + clean_resp, clean_resp); + + /* Fill in the rest of the values for the request. */ + status = request->setup(request, request->setup_baton, + &request->req_bkt, + &request->acceptor, + &request->acceptor_baton, + &request->handler, + &request->handler_baton, + request->respool); return status; } -/* Set up the input and output stream buckets. - When a tunnel over an http proxy is needed, create a socket bucket and - empty aggregate bucket for sending and receiving unencrypted requests - over the socket. - - After the tunnel is there, or no tunnel was needed, ask the application - to create the input and output buckets, which should take care of the - [en/de]cryption. -*/ - -static apr_status_t prepare_conn_streams(serf_connection_t *conn, - serf_bucket_t **istream, - serf_bucket_t **ostreamt, - serf_bucket_t **ostreamh) -{ - apr_status_t status; - - if (conn->stream == NULL) { - conn->latency = apr_time_now() - conn->connect_time; - } - - /* Do we need a SSL tunnel first? */ - if (conn->state == SERF_CONN_CONNECTED) { - /* If the connection does not have an associated bucket, then - * call the setup callback to get one. - */ - if (conn->stream == NULL) { - status = do_conn_setup(conn); - if (status) { - return status; - } - } - *ostreamt = conn->ostream_tail; - *ostreamh = conn->ostream_head; - *istream = conn->stream; - } else { - /* SSL tunnel needed and not set up yet, get a direct unencrypted - stream for this socket */ - if (conn->stream == NULL) { - *istream = serf_bucket_socket_create(conn->skt, - conn->allocator); - } - /* Don't create the ostream bucket chain including the ssl_encrypt - bucket yet. This ensure the CONNECT request is sent unencrypted - to the proxy. */ - *ostreamt = *ostreamh = conn->ssltunnel_ostream; - } - - return APR_SUCCESS; -} - /* write data out to the connection */ static apr_status_t write_to_connection(serf_connection_t *conn) { @@ -717,32 +758,23 @@ static apr_status_t write_to_connection(serf_connection_t *conn) } if (request->req_bkt == NULL) { - /* Now that we are about to serve the request, allocate a pool. */ - apr_pool_create(&request->respool, conn->pool); - request->allocator = serf_bucket_allocator_create(request->respool, - NULL, NULL); - apr_pool_cleanup_register(request->respool, request, - clean_resp, clean_resp); - - /* Fill in the rest of the values for the request. */ - read_status = request->setup(request, request->setup_baton, - &request->req_bkt, - &request->acceptor, - &request->acceptor_baton, - &request->handler, - &request->handler_baton, - request->respool); - + read_status = setup_request(request); if (read_status) { /* Something bad happened. Propagate any errors. */ return read_status; } + } + if (!request->written) { request->written = 1; serf_bucket_aggregate_append(ostreamt, request->req_bkt); } /* ### optimize at some point by using read_for_sendfile */ + /* TODO: now that read_iovec will effectively try to return as much + data as available, we probably don't want to read ALL_AVAIL, but + a lower number, like the size of one or a few TCP packets, the + available TCP buffer size ... */ read_status = serf_bucket_read_iovec(ostreamh, SERF_READ_ALL_AVAIL, IOV_MAX, @@ -750,17 +782,24 @@ static apr_status_t write_to_connection(serf_connection_t *conn) &conn->vec_len); if (!conn->hit_eof) { - if (APR_STATUS_IS_EAGAIN(read_status) || - read_status == SERF_ERROR_WAIT_CONN) { + if (APR_STATUS_IS_EAGAIN(read_status)) { /* We read some stuff, but should not try to read again. */ stop_reading = 1; - - /* ### we should avoid looking for writability for a while so - ### that (hopefully) something will appear in the bucket so - ### we can actually write something. otherwise, we could - ### end up in a CPU spin: socket wants something, but we - ### don't have anything (and keep returning EAGAIN) - */ + } + else if (read_status == SERF_ERROR_WAIT_CONN) { + /* The bucket told us that it can't provide more data until + more data is read from the socket. This normally happens + during a SSL handshake. + + We should avoid looking for writability for a while so + that (hopefully) something will appear in the bucket so + we can actually write something. otherwise, we could + end up in a CPU spin: socket wants something, but we + don't have anything (and keep returning EAGAIN) + */ + conn->stop_writing = 1; + conn->dirty_conn = 1; + conn->ctx->dirty_pollset = 1; } else if (read_status && !APR_STATUS_IS_EOF(read_status)) { /* Something bad happened. Propagate any errors. */ @@ -790,6 +829,9 @@ static apr_status_t write_to_connection(serf_connection_t *conn) if (read_status == SERF_ERROR_WAIT_CONN) { stop_reading = 1; + conn->stop_writing = 1; + conn->dirty_conn = 1; + conn->ctx->dirty_pollset = 1; } else if (read_status && conn->hit_eof && conn->vec_len == 0) { /* If we hit the end of the request bucket and all of its data has @@ -895,6 +937,57 @@ static apr_status_t handle_async_response(serf_connection_t *conn, return status; } + +apr_status_t +serf__provide_credentials(serf_context_t *ctx, + char **username, + char **password, + serf_request_t *request, void *baton, + int code, const char *authn_type, + const char *realm, + apr_pool_t *pool) +{ + serf_connection_t *conn = request->conn; + serf_request_t *authn_req = request; + apr_status_t status; + + if (request->ssltunnel == 1 && + conn->state == SERF_CONN_SETUP_SSLTUNNEL) { + /* This is a CONNECT request to set up an SSL tunnel over a proxy. + This request is created by serf, so if the proxy requires + authentication, we can't ask the application for credentials with + this request. + + Solution: setup the first request created by the application on + this connection, and use that request and its handler_baton to + call back to the application. */ + + authn_req = request->next; + /* assert: app_request != NULL */ + if (!authn_req) + return APR_EGENERAL; + + if (!authn_req->req_bkt) { + apr_status_t status; + + status = setup_request(authn_req); + /* If we can't setup a request, don't bother setting up the + ssl tunnel. */ + if (status) + return status; + } + } + + /* Ask the application. */ + status = (*ctx->cred_cb)(username, password, + authn_req, authn_req->handler_baton, + code, authn_type, realm, pool); + if (status) + return status; + + return APR_SUCCESS; +} + /* read data from the connection */ static apr_status_t read_from_connection(serf_connection_t *conn) { @@ -907,6 +1000,14 @@ static apr_status_t read_from_connection(serf_connection_t *conn) */ serf_request_t *request = conn->requests; + /* If the stop_writing flag was set on the connection, reset it now because + there is some data to read. */ + if (conn->stop_writing) { + conn->stop_writing = 0; + conn->dirty_conn = 1; + conn->ctx->dirty_pollset = 1; + } + /* assert: request != NULL */ if ((status = apr_pool_create(&tmppool, conn->pool)) != APR_SUCCESS) @@ -1137,6 +1238,20 @@ apr_status_t serf__process_connection(serf_connection_t *conn, if (conn->completed_requests && !conn->probable_keepalive_limit) { return reset_connection(conn, 1); } +#ifdef SO_ERROR + /* If possible, get the error from the platform's socket layer and + convert it to an APR status code. */ + { + apr_os_sock_t osskt; + if (!apr_os_sock_get(&osskt, conn->skt)) { + int error; + apr_socklen_t l = sizeof(error); + + if (!getsockopt(osskt, SOL_SOCKET, SO_ERROR, (char*)&error, &l)) + return APR_FROM_OS_ERROR(error); + } + } +#endif return APR_EGENERAL; } if ((events & APR_POLLOUT) != 0) { @@ -1180,7 +1295,8 @@ serf_connection_t *serf_connection_create( apr_pool_create(&conn->skt_pool, conn->pool); /* register a cleanup */ - apr_pool_cleanup_register(conn->pool, conn, clean_conn, apr_pool_cleanup_null); + apr_pool_cleanup_register(conn->pool, conn, clean_conn, + apr_pool_cleanup_null); /* Add the connection to the context. */ *(serf_connection_t **)apr_array_push(ctx->conns) = conn; @@ -1227,7 +1343,12 @@ apr_status_t serf_connection_create2( c->host_url = apr_uri_unparse(c->pool, &host_info, APR_URI_UNP_OMITPATHINFO); - c->host_info = host_info; + + /* Store the host info without the path on the connection. */ + (void)apr_uri_parse(c->pool, c->host_url, &(c->host_info)); + if (!c->host_info.port) { + c->host_info.port = apr_uri_port_of_scheme(c->host_info.scheme); + } *conn = c; @@ -1330,11 +1451,12 @@ void serf_connection_set_async_responses( conn->async_handler_baton = handler_baton; } - -serf_request_t *serf_connection_request_create( - serf_connection_t *conn, - serf_request_setup_t setup, - void *setup_baton) +static serf_request_t * +create_request(serf_connection_t *conn, + serf_request_setup_t setup, + void *setup_baton, + int priority, + int ssltunnel) { serf_request_t *request; @@ -1346,10 +1468,25 @@ serf_request_t *serf_connection_request_create( request->respool = NULL; request->req_bkt = NULL; request->resp_bkt = NULL; - request->priority = 0; + request->priority = priority; request->written = 0; + request->ssltunnel = ssltunnel; request->next = NULL; + return request; +} + +serf_request_t *serf_connection_request_create( + serf_connection_t *conn, + serf_request_setup_t setup, + void *setup_baton) +{ + serf_request_t *request; + + request = create_request(conn, setup, setup_baton, + 0, /* priority */ + 0 /* ssl tunnel */); + /* Link the request to the end of the request chain. */ link_requests(&conn->requests, &conn->requests_tail, request); @@ -1360,26 +1497,18 @@ serf_request_t *serf_connection_request_create( return request; } - -serf_request_t *serf_connection_priority_request_create( - serf_connection_t *conn, - serf_request_setup_t setup, - void *setup_baton) +static serf_request_t * +priority_request_create(serf_connection_t *conn, + int ssltunnelreq, + serf_request_setup_t setup, + void *setup_baton) { serf_request_t *request; serf_request_t *iter, *prev; - request = serf_bucket_mem_alloc(conn->allocator, sizeof(*request)); - request->conn = conn; - request->setup = setup; - request->setup_baton = setup_baton; - request->handler = NULL; - request->respool = NULL; - request->req_bkt = NULL; - request->resp_bkt = NULL; - request->priority = 1; - request->written = 0; - request->next = NULL; + request = create_request(conn, setup, setup_baton, + 1, /* priority */ + ssltunnelreq); /* Link the new request after the last written request. */ iter = conn->requests; @@ -1391,10 +1520,17 @@ serf_request_t *serf_connection_priority_request_create( iter = iter->next; } - /* Advance to next non priority request */ - while (iter != NULL && iter->priority) { - prev = iter; - iter = iter->next; + /* A CONNECT request to setup an ssltunnel has absolute priority over all + other requests on the connection, so: + a. add it first to the queue + b. ensure that other priority requests are added after the CONNECT + request */ + if (!request->ssltunnel) { + /* Advance to next non priority request */ + while (iter != NULL && iter->priority) { + prev = iter; + iter = iter->next; + } } if (prev) { @@ -1412,6 +1548,24 @@ serf_request_t *serf_connection_priority_request_create( return request; } +serf_request_t *serf_connection_priority_request_create( + serf_connection_t *conn, + serf_request_setup_t setup, + void *setup_baton) +{ + return priority_request_create(conn, + 0, /* not a ssltunnel CONNECT request */ + setup, setup_baton); +} + +serf_request_t *serf__ssltunnel_request_create(serf_connection_t *conn, + serf_request_setup_t setup, + void *setup_baton) +{ + return priority_request_create(conn, + 1, /* This is a ssltunnel CONNECT request */ + setup, setup_baton); +} apr_status_t serf_request_cancel(serf_request_t *request) { @@ -1466,29 +1620,53 @@ serf_bucket_t *serf_request_bucket_request_create( serf_bucket_t *req_bkt, *hdrs_bkt; serf_connection_t *conn = request->conn; serf_context_t *ctx = conn->ctx; + int ssltunnel; + + ssltunnel = ctx->proxy_address && + (strcmp(conn->host_info.scheme, "https") == 0); req_bkt = serf_bucket_request_create(method, uri, body, allocator); hdrs_bkt = serf_bucket_request_get_headers(req_bkt); - /* Proxy? */ - if (ctx->proxy_address && conn->host_url) + /* Use absolute uri's in requests to a proxy. USe relative uri's in + requests directly to a server or sent through an SSL tunnel. */ + if (ctx->proxy_address && conn->host_url && + !(ssltunnel && !request->ssltunnel)) { + serf_bucket_request_set_root(req_bkt, conn->host_url); + } if (conn->host_info.hostinfo) serf_bucket_headers_setn(hdrs_bkt, "Host", conn->host_info.hostinfo); - /* Setup server authorization headers */ - if (ctx->authn_info.scheme) - ctx->authn_info.scheme->setup_request_func(HOST, 0, conn, request, + /* Setup server authorization headers, unless this is a CONNECT request. */ + if (!request->ssltunnel) { + serf__authn_info_t *authn_info; + authn_info = serf__get_authn_info_for_server(conn); + if (authn_info->scheme) + authn_info->scheme->setup_request_func(HOST, 0, conn, request, method, uri, hdrs_bkt); + } - /* Setup proxy authorization headers */ - if (ctx->proxy_authn_info.scheme) - ctx->proxy_authn_info.scheme->setup_request_func(PROXY, 0, conn, - request, - method, uri, hdrs_bkt); + /* Setup proxy authorization headers. + Don't set these headers on the requests to the server if we're using + an SSL tunnel, only on the CONNECT request to setup the tunnel. */ + if (ctx->proxy_authn_info.scheme) { + if (strcmp(conn->host_info.scheme, "https") == 0) { + if (request->ssltunnel) + ctx->proxy_authn_info.scheme->setup_request_func(PROXY, 0, conn, + request, + method, uri, + hdrs_bkt); + } else { + ctx->proxy_authn_info.scheme->setup_request_func(PROXY, 0, conn, + request, + method, uri, + hdrs_bkt); + } + } return req_bkt; } |