| /* ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include <apr_pools.h> |
| #include <apr_poll.h> |
| #include <apr_version.h> |
| #include <apr_portable.h> |
| #include <apr_strings.h> |
| |
| #include "serf.h" |
| #include "serf_bucket_util.h" |
| |
| #include "serf_private.h" |
| |
| /* forward definitions */ |
| static apr_status_t read_from_connection(serf_connection_t *conn); |
| static apr_status_t write_to_connection(serf_connection_t *conn); |
| static apr_status_t hangup_connection(serf_connection_t *conn); |
| |
| #define REQS_IN_PROGRESS(conn) \ |
| ((conn)->completed_requests - (conn)->completed_responses) |
| |
| /* cleanup for sockets */ |
| static apr_status_t clean_skt(void *data) |
| { |
| serf_connection_t *conn = data; |
| apr_status_t status = APR_SUCCESS; |
| |
| if (conn->skt) { |
| status = apr_socket_close(conn->skt); |
| conn->skt = NULL; |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, conn->config, |
| "closed socket, status %d\n", status); |
| serf_config_remove_value(conn->config, SERF_CONFIG_CONN_LOCALIP); |
| serf_config_remove_value(conn->config, SERF_CONFIG_CONN_REMOTEIP); |
| } |
| |
| return status; |
| } |
| |
| /* cleanup for conns */ |
| static apr_status_t clean_conn(void *data) |
| { |
| serf_connection_t *conn = data; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, conn->config, |
| "cleaning up connection 0x%p\n", conn); |
| serf_connection_close(conn); |
| |
| return APR_SUCCESS; |
| } |
| |
| static int |
| request_pending(serf_request_t **next_req, serf_connection_t *conn) |
| { |
| /* Prepare the next request */ |
| if (conn->framing_type != SERF_CONNECTION_FRAMING_TYPE_NONE |
| && (conn->pipelining || (!conn->pipelining && REQS_IN_PROGRESS(conn) == 0))) |
| { |
| /* Skip all requests that have been written completely but we're still |
| waiting for a response. */ |
| serf_request_t *request = conn->unwritten_reqs; |
| |
| if (next_req) |
| *next_req = request; |
| |
| if (request != NULL) { |
| return TRUE; |
| } |
| } |
| else if (next_req) |
| *next_req = NULL; |
| |
| return FALSE; |
| } |
| |
| /* Check if there is data waiting to be sent over the socket. This can happen |
| in two situations: |
| - The connection queue has atleast one request with unwritten data. |
| - All requests are written and the ssl layer wrote some data while reading |
| the response. This can happen when the server triggers a renegotiation, |
| e.g. after the first and only request on that connection was received. |
| Returns 1 if data is pending on CONN, NULL if not. |
| If NEXT_REQ is not NULL, it will be filled in with the next available request |
| with unwritten data. */ |
| static int |
| request_or_data_pending(serf_request_t **next_req, serf_connection_t *conn) |
| { |
| if (request_pending(next_req, conn)) |
| return TRUE; |
| |
| return serf_pump__data_pending(&conn->pump); |
| } |
| |
| /* Update the pollset for this connection. We tweak the pollset based on |
| * whether we want to read and/or write, given conditions within the |
| * connection. If the connection is not (yet) in the pollset, then it |
| * will be added. |
| */ |
| apr_status_t serf__conn_update_pollset(serf_connection_t *conn) |
| { |
| serf_context_t *ctx = conn->ctx; |
| apr_status_t status; |
| apr_pollfd_t desc = { 0 }; |
| bool data_waiting; |
| |
| if (!conn->skt) { |
| return APR_SUCCESS; |
| } |
| |
| /* Remove the socket from the poll set. */ |
| desc.desc_type = APR_POLL_SOCKET; |
| desc.desc.s = conn->skt; |
| desc.reqevents = conn->io.reqevents; |
| |
| status = ctx->pollset_rm(ctx->pollset_baton, |
| &desc, &conn->io); |
| if (status && !APR_STATUS_IS_NOTFOUND(status)) |
| return status; |
| |
| /* Now put it back in with the correct read/write values. */ |
| desc.reqevents = APR_POLLHUP | APR_POLLERR; |
| |
| /* If we are not connected yet, we just want to know when we are */ |
| if (conn->wait_for_connect) { |
| data_waiting = true; |
| desc.reqevents |= APR_POLLOUT; |
| } |
| else { |
| /* Directly look at the connection data. While this may look |
| more expensive than the cheap checks later this peek is |
| just checking a bit of ram. |
| |
| But it also has the nice side effect of removing references |
| from the aggregate to requests that are done. |
| */ |
| data_waiting = serf_pump__data_pending(&conn->pump) |
| && !conn->pump.done_writing; |
| |
| if (data_waiting) { |
| desc.reqevents |= APR_POLLOUT; |
| } |
| } |
| |
| if ((conn->written_reqs || conn->unwritten_reqs) && |
| conn->state != SERF_CONN_INIT) { |
| /* If there are any outstanding events, then we want to read. */ |
| /* ### not true. we only want to read IF we have sent some data */ |
| desc.reqevents |= APR_POLLIN; |
| |
| /* Don't write if OpenSSL told us that it needs to read data first. */ |
| if (! conn->pump.stop_writing && !data_waiting) { |
| |
| /* This check is duplicated in write_to_connection() */ |
| if ((conn->probable_keepalive_limit && |
| conn->completed_requests > conn->probable_keepalive_limit) || |
| (conn->max_outstanding_requests && |
| REQS_IN_PROGRESS(conn) >= conn->max_outstanding_requests)) { |
| |
| /* we wouldn't try to write any way right now. */ |
| } |
| else if (request_pending(NULL, conn)) { |
| desc.reqevents |= APR_POLLOUT; |
| } |
| } |
| } |
| |
| /* If we can have async responses, always look for something to read. */ |
| if (conn->framing_type != SERF_CONNECTION_FRAMING_TYPE_HTTP1 |
| || conn->async_responses) |
| { |
| desc.reqevents |= APR_POLLIN; |
| } |
| |
| /* save our reqevents, so we can pass it in to remove later. */ |
| conn->io.reqevents = desc.reqevents; |
| |
| /* Note: even if we don't want to read/write this socket, we still |
| * want to poll it for hangups and errors. |
| */ |
| return ctx->pollset_add(ctx->pollset_baton, |
| &desc, &conn->io); |
| } |
| |
| #ifdef SERF_DEBUG_BUCKET_USE |
| |
| /* Make sure all response buckets were drained. */ |
| static void check_buckets_drained(serf_connection_t *conn) |
| { |
| serf_request_t *request = conn->written_reqs; |
| |
| for ( ; request ; request = request->next ) { |
| if (request->resp_bkt != NULL) { |
| /* ### crap. can't do this. this allocator may have un-drained |
| * ### REQUEST buckets. |
| */ |
| /* serf_debug__entered_loop(request->resp_bkt->allocator); */ |
| /* ### for now, pretend we closed the conn (resets the tracking) */ |
| serf_debug__closed_conn(request->resp_bkt->allocator); |
| } |
| } |
| } |
| |
| #endif |
| |
| /* Destroys all outstanding write information, to allow cleanup of subpools |
| that may still have data in these buckets to continue */ |
| void serf__connection_pre_cleanup(serf_connection_t *conn) |
| { |
| serf_request_t *rq; |
| conn->pump.vec_len = 0; |
| |
| serf_pump__done(&conn->pump); |
| |
| /* Tell all written request that they are free to destroy themselves */ |
| rq = conn->written_reqs; |
| while (rq != NULL) { |
| if (rq->writing == SERF_WRITING_STARTED |
| || rq->writing == SERF_WRITING_DONE) { |
| |
| rq->writing = SERF_WRITING_FINISHED; |
| } |
| rq = rq->next; |
| } |
| |
| /* Destroy the requests that were queued up to destroy later */ |
| while ((rq = conn->done_reqs)) { |
| conn->done_reqs = rq->next; |
| |
| rq->writing = SERF_WRITING_FINISHED; |
| serf__destroy_request(rq); |
| } |
| conn->done_reqs = conn->done_reqs_tail = NULL; |
| } |
| |
| apr_status_t serf_connection__perform_setup(serf_connection_t *conn) |
| { |
| apr_status_t status; |
| serf_bucket_t *ostream, *stream; |
| |
| /* ### dunno what the hell this is about. this latency stuff got |
| ### added, and who knows whether it should stay... */ |
| conn->latency = apr_time_now() - conn->connect_time; |
| |
| serf_pump__prepare_setup(&conn->pump); |
| |
| ostream = conn->pump.ostream_tail; |
| |
| status = (*conn->setup)(conn->skt, |
| &stream, |
| &ostream, |
| conn->setup_baton, |
| conn->pool); |
| if (status) { |
| /* extra destroy here since it wasn't added to the head bucket yet. */ |
| serf_pump__complete_setup(&conn->pump, NULL, NULL); |
| serf__connection_pre_cleanup(conn); |
| return status; |
| } |
| |
| serf_pump__complete_setup(&conn->pump, stream, ostream); |
| |
| /* We typically have one of two scenarios, based on whether the |
| application decided to encrypt this connection: |
| |
| PLAIN: |
| |
| conn->stream = SOCKET(skt) |
| conn->ostream_head = AGGREGATE(ostream_tail) |
| conn->ostream_tail = STREAM(<detect_eof>, REQ1, REQ2, ...) |
| |
| ENCRYPTED: |
| |
| conn->stream = DECRYPT(SOCKET(skt)) |
| conn->ostream_head = AGGREGATE(ENCRYPT(ostream_tail)) |
| conn->ostream_tail = STREAM(<detect_eof>, REQ1, REQ2, ...) |
| |
| where STREAM is an internal variant of AGGREGATE. |
| */ |
| |
| return status; |
| } |
| |
| static apr_status_t connect_connection(serf_connection_t *conn) |
| { |
| serf_context_t *ctx = conn->ctx; |
| apr_status_t status; |
| |
| serf_pump__store_ipaddresses_in_config(&conn->pump); |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, conn->config, |
| "socket for conn 0x%p connected\n", conn); |
| |
| /* If the authentication was already started on another connection, |
| prepare this connection (it might be possible to skip some |
| part of the handshaking). */ |
| if (ctx->proxy_address) { |
| status = serf__auth_setup_connection(PROXY, conn); |
| if (status) { |
| return status; |
| } |
| } |
| |
| status = serf__auth_setup_connection(HOST, conn); |
| if (status) |
| return status; |
| |
| /* 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 { |
| conn->state = SERF_CONN_CONNECTED; |
| |
| status = serf_connection__perform_setup(conn); |
| |
| if (status) |
| return status; |
| } |
| |
| 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. |
| */ |
| apr_status_t serf__open_connections(serf_context_t *ctx) |
| { |
| int i; |
| |
| for (i = ctx->conns->nelts; i--; ) { |
| serf_connection_t *conn = GET_CONN(ctx, i); |
| apr_status_t status; |
| apr_socket_t *skt; |
| |
| conn->seen_in_pollset = 0; |
| |
| if (conn->skt != NULL) { |
| #ifdef SERF_DEBUG_BUCKET_USE |
| check_buckets_drained(conn); |
| #endif |
| continue; |
| } |
| |
| /* Delay opening until we have something to deliver! */ |
| if (conn->unwritten_reqs == NULL) { |
| continue; |
| } |
| |
| apr_pool_clear(conn->skt_pool); |
| status = apr_socket_create(&skt, conn->address->family, |
| SOCK_STREAM, |
| #if APR_MAJOR_VERSION > 0 |
| APR_PROTO_TCP, |
| #endif |
| conn->skt_pool); |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, conn->config, |
| "created socket for conn 0x%p, status %d\n", conn, status); |
| if (status != APR_SUCCESS) |
| return status; |
| |
| apr_pool_cleanup_register(conn->skt_pool, conn, clean_skt, |
| apr_pool_cleanup_null); |
| |
| /* Set the socket to be non-blocking */ |
| if ((status = apr_socket_timeout_set(skt, 0)) != APR_SUCCESS) |
| return status; |
| |
| /* Disable Nagle's algorithm */ |
| if ((status = apr_socket_opt_set(skt, |
| APR_TCP_NODELAY, 1)) != APR_SUCCESS) |
| return status; |
| |
| /* Configured. Store it into the connection now. */ |
| conn->skt = skt; |
| |
| /* Remember time when we started connecting to server to calculate |
| network latency. */ |
| conn->connect_time = apr_time_now(); |
| |
| /* Now that the socket is set up, let's connect it. This should |
| * return immediately. |
| */ |
| status = apr_socket_connect(skt, conn->address); |
| if (status != APR_SUCCESS) { |
| if (!APR_STATUS_IS_EINPROGRESS(status)) |
| return status; |
| |
| /* Keep track of when we really connect */ |
| conn->wait_for_connect = true; |
| } |
| |
| serf_pump__init(&conn->pump, &conn->io, skt, conn->config, |
| conn->allocator, conn->pool); |
| |
| status = serf_config_set_string(conn->config, |
| SERF_CONFIG_CONN_PIPELINING, |
| (conn->max_outstanding_requests != 1 && |
| conn->pipelining == 1) ? "Y" : "N"); |
| if (status) |
| return status; |
| |
| /* Flag our pollset as dirty now that we have a new socket. */ |
| serf_io__set_pollset_dirty(&conn->io); |
| |
| if (! conn->wait_for_connect) { |
| status = connect_connection(conn); |
| |
| if (status) |
| return status; |
| } |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| /* Read the 'Connection' header from the response. Return SERF_ERROR_CLOSING if |
| * the header contains value 'close' indicating the server is closing the |
| * connection right after this response. |
| * Otherwise returns APR_SUCCESS. |
| */ |
| static apr_status_t is_conn_closing(serf_bucket_t *response) |
| { |
| serf_bucket_t *hdrs; |
| const char *val; |
| |
| hdrs = serf_bucket_response_get_headers(response); |
| val = serf_bucket_headers_get(hdrs, "Connection"); |
| if (val && strcasecmp("close", val) == 0) |
| { |
| return SERF_ERROR_CLOSING; |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| static apr_status_t remove_connection(serf_context_t *ctx, |
| serf_connection_t *conn) |
| { |
| apr_pollfd_t desc = { 0 }; |
| |
| desc.desc_type = APR_POLL_SOCKET; |
| desc.desc.s = conn->skt; |
| desc.reqevents = conn->io.reqevents; |
| |
| return ctx->pollset_rm(ctx->pollset_baton, |
| &desc, &conn->io); |
| } |
| |
| /* A socket was closed, inform the application. */ |
| static void handle_conn_closed(serf_connection_t *conn, apr_status_t status) |
| { |
| (*conn->closed)(conn, conn->closed_baton, status, |
| conn->pool); |
| } |
| |
| static apr_status_t reset_connection(serf_connection_t *conn, |
| int requeue_requests) |
| { |
| serf_context_t *ctx = conn->ctx; |
| apr_status_t status; |
| serf_request_t *old_reqs; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, conn->config, |
| "reset connection 0x%p\n", conn); |
| |
| conn->probable_keepalive_limit = conn->completed_responses; |
| conn->completed_requests = 0; |
| conn->completed_responses = 0; |
| |
| /* Clear the unwritten_reqs queue, so the application can requeue cancelled |
| requests on it for the new socket. */ |
| old_reqs = conn->unwritten_reqs; |
| conn->unwritten_reqs = NULL; |
| conn->unwritten_reqs_tail = NULL; |
| |
| serf__connection_pre_cleanup(conn); |
| |
| if (conn->protocol_baton && conn->perform_pre_teardown) |
| conn->perform_pre_teardown(conn); |
| |
| /* First, cancel all written requests for which we haven't received a |
| response yet. Inform the application that the request is cancelled, |
| so it can requeue them if needed. */ |
| while (conn->written_reqs) { |
| serf__cancel_request(conn->written_reqs, &conn->written_reqs, |
| requeue_requests); |
| } |
| conn->written_reqs_tail = NULL; |
| |
| /* Handle all outstanding unwritten requests. |
| TODO: what about a partially written request? */ |
| while (old_reqs) { |
| /* If we haven't started to write the connection, bring it over |
| * unchanged to our new socket. |
| * Do not copy a CONNECT request to the new connection, the ssl tunnel |
| * setup code will create a new CONNECT request already. |
| */ |
| if (requeue_requests && (old_reqs->writing == SERF_WRITING_NONE) && |
| !old_reqs->ssltunnel) { |
| |
| serf_request_t *req = old_reqs; |
| old_reqs = old_reqs->next; |
| req->next = NULL; |
| serf__link_requests(&conn->unwritten_reqs, |
| &conn->unwritten_reqs_tail, |
| req); |
| } |
| else { |
| /* We don't want to requeue the request or this request was partially |
| written. Inform the application that the request is cancelled. */ |
| serf__cancel_request(old_reqs, &old_reqs, requeue_requests); |
| } |
| } |
| |
| /* Requests queue has been prepared for a new socket, close the old one. */ |
| if (conn->skt != NULL) { |
| remove_connection(ctx, conn); |
| status = clean_skt(conn); |
| if (conn->closed != NULL) { |
| handle_conn_closed(conn, status); |
| } |
| } |
| |
| if (conn->pump.stream != NULL) { |
| serf_bucket_destroy(conn->pump.stream); |
| conn->pump.stream = NULL; |
| } |
| |
| /* Don't try to resume any writes */ |
| conn->pump.vec_len = 0; |
| |
| serf_io__set_pollset_dirty(&conn->io); |
| conn->state = SERF_CONN_INIT; |
| |
| conn->pump.hit_eof = 0; |
| conn->connect_time = 0; |
| conn->latency = -1; |
| conn->pump.stop_writing = false; |
| conn->write_now = false; |
| /* conn->pipelining */ |
| |
| conn->framing_type = SERF_CONNECTION_FRAMING_TYPE_HTTP1; |
| |
| if (conn->protocol_baton) { |
| conn->perform_teardown(conn); |
| conn->protocol_baton = NULL; |
| } |
| |
| conn->perform_read = read_from_connection; |
| conn->perform_write = write_to_connection; |
| conn->perform_hangup = hangup_connection; |
| conn->perform_teardown = NULL; |
| conn->perform_pre_teardown = NULL; |
| conn->perform_cancel_request = NULL; |
| conn->perform_prioritize_request = NULL; |
| |
| conn->status = APR_SUCCESS; |
| |
| /* Let our context know that we've 'reset' the socket already. */ |
| conn->seen_in_pollset |= APR_POLLHUP; |
| |
| /* Recalculate the current list length */ |
| conn->nr_of_written_reqs = 0; |
| conn->nr_of_unwritten_reqs = serf__req_list_length(conn->unwritten_reqs); |
| |
| /* Found the connection. Closed it. All done. */ |
| return APR_SUCCESS; |
| } |
| |
| |
| apr_status_t serf__connection_flush(serf_connection_t *conn, |
| bool fetch_new) |
| { |
| return serf_pump__write(&conn->pump, fetch_new); |
| } |
| |
| /* Implements serf_bucket_event_callback_t and is called (potentially |
| more than once) after the request buckets are completely read. |
| |
| At this time we know the request is written, but we can't destroy |
| the buckets yet as they might still be referenced by the connection |
| vecs. */ |
| static apr_status_t request_writing_done(void *baton, |
| apr_uint64_t bytes_read) |
| { |
| serf_request_t *request = baton; |
| |
| if (request->writing == SERF_WRITING_STARTED) { |
| request->writing = SERF_WRITING_DONE; |
| |
| /* TODO: Handle request done */ |
| } |
| return APR_EOF; /* Done with the event bucket */ |
| } |
| |
| |
| /* Implements serf_bucket_event_callback_t and is called after the |
| request buckets are no longer needed. More precisely the outgoing |
| buckets are already destroyed. */ |
| static apr_status_t request_writing_finished(void *baton, |
| apr_uint64_t bytes_read) |
| { |
| serf_request_t *request = baton; |
| serf_connection_t *conn = request->conn; |
| |
| request->req_bkt = NULL; /* Bucket is destroyed by now */ |
| |
| if (request->writing == SERF_WRITING_DONE) { |
| request->writing = SERF_WRITING_FINISHED; |
| |
| /* Move the request to the written queue */ |
| serf__link_requests(&conn->written_reqs, &conn->written_reqs_tail, |
| request); |
| conn->nr_of_written_reqs++; |
| conn->unwritten_reqs = conn->unwritten_reqs->next; |
| conn->nr_of_unwritten_reqs--; |
| request->next = NULL; |
| |
| /* If our connection has async responses enabled, we're not |
| * going to get a reply back, so kill the request. |
| */ |
| if (conn->async_responses) { |
| conn->unwritten_reqs = request->next; |
| conn->nr_of_unwritten_reqs--; |
| serf__destroy_request(request); |
| } |
| |
| conn->completed_requests++; |
| } |
| /* Destroy (all) requests that are now safe to destroy, |
| Typically non or just the finished one */ |
| { |
| serf_request_t *last = NULL; |
| serf_request_t **rq = &conn->done_reqs; |
| while (*rq) { |
| request = *rq; |
| if ((*rq)->writing == SERF_WRITING_FINISHED) { |
| request = *rq; |
| *rq = request->next; |
| serf__destroy_request(request); |
| } |
| else { |
| last = *rq; |
| rq = &last->next; |
| } |
| } |
| |
| conn->done_reqs_tail = last; |
| } |
| |
| return APR_EOF; /* Done with event bucket. Status is ignored */ |
| } |
| |
| /* write data out to the connection */ |
| static apr_status_t write_to_connection(serf_connection_t *conn) |
| { |
| /* Keep reading and sending until we run out of stuff to read, or |
| * writing would block. |
| */ |
| while (1) { |
| serf_request_t *request; |
| apr_status_t status; |
| apr_status_t read_status; |
| |
| /* If we have unwritten data in iovecs, then write what we can |
| directly. */ |
| status = serf__connection_flush(conn, FALSE); |
| if (APR_STATUS_IS_EAGAIN(status)) |
| return APR_SUCCESS; |
| else if (status) |
| return status; |
| |
| /* If we're setting up an ssl tunnel, we can't send real requests |
| as yet, as they need to be encrypted and our encrypt buckets |
| aren't created yet as we still need to read the unencrypted |
| response of the CONNECT request. */ |
| if (conn->state == SERF_CONN_SETUP_SSLTUNNEL |
| && REQS_IN_PROGRESS(conn) > 0) |
| { |
| /* But flush out SSL data when necessary! */ |
| status = serf__connection_flush(conn, TRUE); |
| if (APR_STATUS_IS_EAGAIN(status)) |
| return APR_SUCCESS; |
| |
| return status; |
| } |
| |
| /* We try to limit the number of in-flight requests so that we |
| don't have to repeat too many if the connection drops. |
| |
| This check matches that in serf__conn_update_pollset() |
| */ |
| if ((conn->probable_keepalive_limit && |
| conn->completed_requests > conn->probable_keepalive_limit) || |
| (conn->max_outstanding_requests && |
| REQS_IN_PROGRESS(conn) >= conn->max_outstanding_requests)) { |
| |
| serf_io__set_pollset_dirty(&conn->io); |
| |
| /* backoff for now. */ |
| return APR_SUCCESS; |
| } |
| |
| /* We may need to move forward to a request which has something |
| * to write. |
| */ |
| if (!request_or_data_pending(&request, conn)) { |
| /* No more requests (with data) are registered with the |
| * connection, and no data is pending on the outgoing stream. |
| * Let's update the pollset so that we don't try to write to this |
| * socket again. |
| */ |
| serf_io__set_pollset_dirty(&conn->io); |
| return APR_SUCCESS; |
| } |
| |
| if (request && request->writing == SERF_WRITING_NONE) { |
| serf_bucket_t *event_bucket; |
| |
| if (request->req_bkt == NULL) { |
| read_status = serf__setup_request(request); |
| if (read_status) { |
| /* Something bad happened. Propagate any errors. */ |
| return read_status; |
| } |
| } |
| |
| request->writing = SERF_WRITING_STARTED; |
| |
| /* And now add an event bucket to keep track of when the request |
| has been completely written */ |
| event_bucket = serf__bucket_event_create(request->req_bkt, |
| request, |
| NULL, |
| request_writing_done, |
| request_writing_finished, |
| conn->allocator); |
| serf_pump__add_output(&conn->pump, event_bucket, false); |
| } |
| |
| /* If we got some data, then deliver it. */ |
| /* ### what to do if we got no data?? is that a problem? */ |
| status = serf__connection_flush(conn, TRUE); |
| if (APR_STATUS_IS_EAGAIN(status)) |
| return APR_SUCCESS; |
| else if (status) |
| return status; |
| |
| } |
| /* NOTREACHED */ |
| } |
| |
| |
| |
| /* An async response message was received from the server. */ |
| static apr_status_t handle_async_response(serf_connection_t *conn, |
| apr_pool_t *pool) |
| { |
| apr_status_t status; |
| |
| if (conn->current_async_response == NULL) { |
| conn->current_async_response = |
| (*conn->async_acceptor)(NULL, conn->pump.stream, |
| conn->async_acceptor_baton, pool); |
| } |
| |
| status = (*conn->async_handler)(NULL, conn->current_async_response, |
| conn->async_handler_baton, pool); |
| |
| if (APR_STATUS_IS_EOF(status)) { |
| serf_bucket_destroy(conn->current_async_response); |
| conn->current_async_response = NULL; |
| status = APR_SUCCESS; |
| } |
| |
| return status; |
| } |
| |
| /* read data from the connection */ |
| static apr_status_t read_from_connection(serf_connection_t *conn) |
| { |
| apr_status_t status; |
| apr_pool_t *tmppool; |
| apr_status_t close_connection = APR_SUCCESS; |
| |
| /* assert: request != NULL */ |
| |
| if ((status = apr_pool_create(&tmppool, conn->pool)) != APR_SUCCESS) |
| return status; |
| |
| /* Invoke response handlers until we have no more work. */ |
| while (1) { |
| serf_request_t *request; |
| apr_pool_clear(tmppool); |
| |
| /* Whatever is coming in on the socket corresponds to the first request |
| * on our chain. |
| */ |
| request = conn->written_reqs; |
| if (!request) { |
| /* Request wasn't completely written yet! */ |
| request = conn->unwritten_reqs; |
| } |
| |
| /* We have a different codepath when we can have async responses. */ |
| if (conn->async_responses) { |
| /* TODO What about socket errors? */ |
| status = handle_async_response(conn, tmppool); |
| if (APR_STATUS_IS_EAGAIN(status)) { |
| status = APR_SUCCESS; |
| goto error; |
| } |
| if (status) { |
| goto error; |
| } |
| continue; |
| } |
| |
| /* We are reading a response for a request we haven't |
| * written yet! |
| * |
| * This shouldn't normally happen EXCEPT: |
| * |
| * 1) when the other end has closed the socket and we're |
| * pending an EOF return. |
| * 2) Doing the initial SSL handshake - we'll get EAGAIN |
| * as the SSL buckets will hide the handshake from us |
| * but not return any data. |
| * 3) When the server sends us an SSL alert. |
| * |
| * In these cases, we should not receive any actual user data. |
| * |
| * 4) When the server sends a error response, like 408 Request timeout. |
| * This response should be passed to the application. |
| * |
| * If we see an EOF (due to either an expired timeout or the server |
| * sending the SSL 'close notify' shutdown alert), we'll reset the |
| * connection and open a new one. |
| */ |
| if (request->req_bkt || request->writing == SERF_WRITING_NONE) { |
| const char *data; |
| apr_size_t len; |
| |
| status = serf_bucket_peek(conn->pump.stream, &data, &len); |
| |
| if (APR_STATUS_IS_EOF(status)) { |
| reset_connection(conn, 1); |
| status = APR_SUCCESS; |
| goto error; |
| } |
| else if (APR_STATUS_IS_EAGAIN(status) && !len) { |
| status = APR_SUCCESS; |
| goto error; |
| } else if (status && !APR_STATUS_IS_EAGAIN(status)) { |
| /* Read error */ |
| goto error; |
| } |
| |
| /* Unexpected response from the server */ |
| if (conn->write_now) { |
| conn->write_now = false; |
| status = conn->perform_write(conn); |
| |
| if (!SERF_BUCKET_READ_ERROR(status)) |
| status = APR_SUCCESS; |
| } |
| } |
| |
| if (conn->framing_type != SERF_CONNECTION_FRAMING_TYPE_HTTP1) |
| break; |
| |
| /* If the request doesn't have a response bucket, then call the |
| * acceptor to get one created. |
| */ |
| if (request->resp_bkt == NULL) { |
| if (! request->acceptor) { |
| /* Request wasn't even setup. |
| Server replying before it received anything? */ |
| return SERF_ERROR_BAD_HTTP_RESPONSE; |
| } |
| |
| request->resp_bkt = (*request->acceptor)(request, conn->pump.stream, |
| request->acceptor_baton, |
| tmppool); |
| apr_pool_clear(tmppool); |
| |
| /* Share the configuration with the response bucket(s) */ |
| serf_bucket_set_config(request->resp_bkt, conn->config); |
| } |
| |
| status = serf__handle_response(request, tmppool); |
| |
| /* If we received APR_SUCCESS, run this loop again. */ |
| if (!status) { |
| continue; |
| } |
| |
| /* If our response handler says it can't do anything more, we now |
| * treat that as a success. |
| */ |
| if (APR_STATUS_IS_EAGAIN(status)) { |
| /* It is possible that while reading the response, the ssl layer |
| has prepared some data to send. If this was the last request, |
| serf will not check for socket writability, so force this here. |
| */ |
| if (request_or_data_pending(&request, conn) && !request) { |
| serf_io__set_pollset_dirty(&conn->io); |
| } |
| status = APR_SUCCESS; |
| goto error; |
| } |
| |
| close_connection = is_conn_closing(request->resp_bkt); |
| |
| if (!APR_STATUS_IS_EOF(status) && |
| close_connection != SERF_ERROR_CLOSING) { |
| /* Whether success, or an error, there is no more to do unless |
| * this request has been completed. |
| */ |
| goto error; |
| } |
| |
| /* The response has been fully-read, so that means the request has |
| * either been fully-delivered (most likely), or that we don't need to |
| * write the rest of it anymore, e.g. when a 408 Request timeout was |
| $ received. |
| * Remove it from our queue and loop to read another response. |
| */ |
| if (request == conn->written_reqs) { |
| conn->written_reqs = request->next; |
| conn->nr_of_written_reqs--; |
| } else { |
| conn->unwritten_reqs = request->next; |
| conn->nr_of_unwritten_reqs--; |
| } |
| |
| serf__destroy_request(request); |
| |
| request = conn->written_reqs; |
| if (!request) { |
| /* Received responses for all written requests */ |
| conn->written_reqs_tail = NULL; |
| /* Request wasn't completely written yet! */ |
| request = conn->unwritten_reqs; |
| if (!request) |
| conn->unwritten_reqs_tail = NULL; |
| } |
| |
| conn->completed_responses++; |
| |
| /* We have received a response. If there are no more outstanding |
| requests on this connection, we should stop polling for READ events |
| for now. */ |
| if (!conn->written_reqs && !conn->unwritten_reqs) { |
| serf_io__set_pollset_dirty(&conn->io); |
| } |
| |
| /* This means that we're being advised that the connection is done. */ |
| if (close_connection == SERF_ERROR_CLOSING) { |
| reset_connection(conn, 1); |
| if (APR_STATUS_IS_EOF(status)) |
| status = APR_SUCCESS; |
| goto error; |
| } |
| |
| /* The server is suddenly deciding to serve more responses than we've |
| * seen before. |
| * |
| * Let our requests go. |
| */ |
| if (conn->probable_keepalive_limit && |
| conn->completed_responses >= conn->probable_keepalive_limit) { |
| conn->probable_keepalive_limit = 0; |
| } |
| |
| /* If we just ran out of requests or have unwritten requests, then |
| * update the pollset. We don't want to read from this socket any |
| * more. We are definitely done with this loop, too. |
| */ |
| if (request == NULL || request->writing == SERF_WRITING_NONE) { |
| serf_io__set_pollset_dirty(&conn->io); |
| status = APR_SUCCESS; |
| goto error; |
| } |
| } |
| |
| error: |
| /* ### This code handles some specific errors as a retry. |
| Eventually we should move to a handling where the application |
| can tell us if this is really a good idea for specific requests */ |
| |
| if (status == SERF_ERROR_SSL_NEGOTIATE_IN_PROGRESS) { |
| /* This connection uses HTTP pipelining and the server asked for a |
| renegotiation (e.g. to access the requested resource a specific |
| client certificate is required). |
| |
| Because of a known problem in OpenSSL this won't work most of the |
| time, so as a workaround, when the server asks for a renegotiation |
| on a connection using HTTP pipelining, we reset the connection, |
| disable pipelining and reconnect to the server. */ |
| serf__log(LOGLVL_WARNING, LOGCOMP_CONN, __FILE__, conn->config, |
| "The server requested renegotiation. Disable HTTP " |
| "pipelining and reset the connection 0x%p.\n", conn); |
| |
| serf__connection_set_pipelining(conn, 0); |
| reset_connection(conn, 1); |
| status = APR_SUCCESS; |
| } |
| else if (status == SERF_ERROR_REQUEST_LOST |
| || APR_STATUS_IS_ECONNRESET(status) |
| || APR_STATUS_IS_ECONNABORTED(status)) { |
| |
| /* Some systems will not generate a HUP poll event for these errors |
| so we handle the ECONNRESET issue and ECONNABORT here. */ |
| |
| /* If the connection was ever good, be optimistic & try again. |
| If it has never tried again (incl. a retry), fail. */ |
| if (conn->completed_responses) { |
| reset_connection(conn, 1); |
| status = APR_SUCCESS; |
| } |
| else if (status == SERF_ERROR_REQUEST_LOST) { |
| status = SERF_ERROR_ABORTED_CONNECTION; |
| } |
| } |
| |
| apr_pool_destroy(tmppool); |
| return status; |
| } |
| |
| /* The connection got reset by the server. On Windows this can happen |
| when all data is read, so just cleanup the connection and open a new one. |
| |
| If we haven't had any successful responses on this connection, |
| then error out as it is likely a server issue. */ |
| static apr_status_t hangup_connection(serf_connection_t *conn) |
| { |
| if (conn->completed_responses) { |
| return reset_connection(conn, 1); |
| } |
| |
| return SERF_ERROR_ABORTED_CONNECTION; |
| } |
| |
| /* process all events on the connection */ |
| static apr_status_t process_connection(serf_connection_t *conn, |
| apr_int16_t events) |
| { |
| apr_status_t status; |
| #ifdef SERF_DEBUG_BUCKET_USE |
| serf_request_t *rq; |
| #endif |
| |
| #ifdef SERF_DEBUG_BUCKET_USE |
| serf_debug__entered_loop(conn->allocator); |
| |
| for (rq = conn->written_reqs; rq; rq = rq->next) { |
| if (rq->allocator) |
| serf_debug__entered_loop(rq->allocator); |
| } |
| |
| for (rq = conn->done_reqs; rq; rq = rq->next) { |
| if (rq->allocator) |
| serf_debug__entered_loop(rq->allocator); |
| } |
| #endif |
| |
| /* POLLHUP/ERR should come after POLLIN so if there's an error message or |
| * the like sitting on the connection, we give the app a chance to read |
| * it before we trigger a reset condition. |
| */ |
| if ((events & APR_POLLIN) != 0 |
| && !conn->wait_for_connect) { |
| |
| /* If the stop_writing flag was set on the connection, reset it now |
| because there is some data to read. */ |
| if (conn->pump.stop_writing) { |
| conn->pump.stop_writing = false; |
| serf_io__set_pollset_dirty(&conn->io); |
| } |
| |
| if ((status = conn->perform_read(conn)) != APR_SUCCESS) |
| return status; |
| |
| /* If we decided to reset our connection, return now as we don't |
| * want to write. |
| */ |
| if ((conn->seen_in_pollset & APR_POLLHUP) != 0) { |
| return APR_SUCCESS; |
| } |
| } |
| if ((events & APR_POLLHUP) != 0) { |
| /* The connection got reset by the server. */ |
| return conn->perform_hangup(conn); |
| } |
| if ((events & APR_POLLERR) != 0) { |
| /* We might be talking to a buggy HTTP server that doesn't |
| * do lingering-close. (httpd < 2.1.8 does this.) |
| * |
| * See: |
| * |
| * http://issues.apache.org/bugzilla/show_bug.cgi?id=35292 |
| */ |
| 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)) { |
| status = APR_FROM_OS_ERROR(error); |
| |
| /* Handle fallback for multi-homed servers. |
| |
| ### Improve algorithm to find better than just 'next'? |
| |
| Current Windows versions already handle re-ordering for |
| api users by using statistics on the recently failed |
| connections to order the list of addresses. */ |
| if (conn->completed_requests == 0 |
| && conn->address->next != NULL |
| && (APR_STATUS_IS_ECONNREFUSED(status) |
| || APR_STATUS_IS_TIMEUP(status) |
| || APR_STATUS_IS_ENETUNREACH(status))) { |
| |
| conn->address = conn->address->next; |
| return reset_connection(conn, 1); |
| } |
| |
| return status; |
| } |
| } |
| } |
| #endif |
| return APR_EGENERAL; |
| } |
| if ((events & APR_POLLOUT) != 0) { |
| if (conn->wait_for_connect) { |
| conn->wait_for_connect = false; |
| |
| /* We are now connected. Socket is now usable */ |
| serf_io__set_pollset_dirty(&conn->io); |
| |
| if ((status = connect_connection(conn)) != APR_SUCCESS) |
| return status; |
| } |
| |
| if ((status = conn->perform_write(conn)) != APR_SUCCESS) |
| return status; |
| } |
| return APR_SUCCESS; |
| } |
| |
| apr_status_t serf__process_connection(serf_connection_t *conn, |
| apr_int16_t events) |
| { |
| serf_context_t *ctx = conn->ctx; |
| apr_pollfd_t tdesc = { 0 }; |
| |
| /* If this connection has already failed, return the error again, and try |
| * to remove it from the pollset again |
| */ |
| if (conn->status) { |
| tdesc.desc_type = APR_POLL_SOCKET; |
| tdesc.desc.s = conn->skt; |
| tdesc.reqevents = conn->io.reqevents; |
| ctx->pollset_rm(ctx->pollset_baton, |
| &tdesc, &conn->io); |
| return conn->status; |
| } |
| /* apr_pollset_poll() can return a conn multiple times... */ |
| if ((conn->seen_in_pollset & events) != 0 || |
| (conn->seen_in_pollset & APR_POLLHUP) != 0) { |
| return APR_SUCCESS; |
| } |
| |
| conn->seen_in_pollset |= events; |
| |
| if ((conn->status = process_connection(conn, events)) != APR_SUCCESS) |
| { |
| /* it's possible that the connection was already reset and thus the |
| socket cleaned up. */ |
| if (conn->skt) { |
| tdesc.desc_type = APR_POLL_SOCKET; |
| tdesc.desc.s = conn->skt; |
| tdesc.reqevents = conn->io.reqevents; |
| ctx->pollset_rm(ctx->pollset_baton, |
| &tdesc, &conn->io); |
| } |
| return conn->status; |
| } |
| return APR_SUCCESS; |
| } |
| |
| serf_connection_t *serf_connection_create( |
| serf_context_t *ctx, |
| apr_sockaddr_t *address, |
| serf_connection_setup_t setup, |
| void *setup_baton, |
| serf_connection_closed_t closed, |
| void *closed_baton, |
| apr_pool_t *pool) |
| { |
| serf_connection_t *conn = apr_pcalloc(pool, sizeof(*conn)); |
| |
| conn->ctx = ctx; |
| conn->status = APR_SUCCESS; |
| /* Ignore server address if proxy was specified. */ |
| conn->address = ctx->proxy_address ? ctx->proxy_address : address; |
| conn->setup = setup; |
| conn->setup_baton = setup_baton; |
| conn->closed = closed; |
| conn->closed_baton = closed_baton; |
| conn->pool = pool; |
| conn->allocator = serf_bucket_allocator_create(pool, NULL, NULL); |
| conn->io.type = SERF_IO_CONN; |
| conn->io.u.conn = conn; |
| conn->io.ctx = ctx; |
| conn->io.dirty_conn = false; |
| conn->io.reqevents = 0; |
| conn->state = SERF_CONN_INIT; |
| conn->latency = -1; /* unknown */ |
| conn->write_now = false; |
| conn->wait_for_connect = false; |
| conn->pipelining = 1; |
| conn->framing_type = SERF_CONNECTION_FRAMING_TYPE_HTTP1; |
| conn->perform_read = read_from_connection; |
| conn->perform_write = write_to_connection; |
| conn->perform_hangup = hangup_connection; |
| conn->perform_teardown = NULL; |
| conn->perform_pre_teardown = NULL; |
| conn->perform_cancel_request = NULL; |
| conn->perform_prioritize_request = NULL; |
| conn->protocol_baton = NULL; |
| |
| conn->written_reqs = conn->written_reqs_tail = NULL; |
| conn->nr_of_written_reqs = 0; |
| |
| conn->unwritten_reqs = conn->unwritten_reqs_tail = NULL; |
| conn->nr_of_unwritten_reqs = 0; |
| |
| conn->done_reqs = conn->done_reqs_tail = 0; |
| |
| /* Create a subpool for our connection. */ |
| apr_pool_create(&conn->skt_pool, conn->pool); |
| |
| /* register a cleanup */ |
| 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; |
| |
| return conn; |
| } |
| |
| apr_status_t serf_connection_create2( |
| serf_connection_t **conn, |
| serf_context_t *ctx, |
| apr_uri_t host_info, |
| serf_connection_setup_t setup, |
| void *setup_baton, |
| serf_connection_closed_t closed, |
| void *closed_baton, |
| apr_pool_t *pool) |
| { |
| return serf_connection_create3(conn, ctx, host_info, NULL, |
| setup, setup_baton, |
| closed, closed_baton, |
| pool); |
| } |
| |
| |
| apr_status_t serf_connection_create3( |
| serf_connection_t **conn, |
| serf_context_t *ctx, |
| apr_uri_t host_info, |
| apr_sockaddr_t *host_address, |
| serf_connection_setup_t setup, |
| void *setup_baton, |
| serf_connection_closed_t closed, |
| void *closed_baton, |
| apr_pool_t *pool) |
| { |
| apr_status_t status = APR_SUCCESS; |
| serf_config_t *config; |
| serf_connection_t *c; |
| |
| /* Set the port number explicitly, needed to create the socket later. */ |
| if (!host_info.port) { |
| host_info.port = apr_uri_port_of_scheme(host_info.scheme); |
| } |
| |
| /* Only lookup the address of the server if no proxy server was |
| configured. */ |
| if (!ctx->proxy_address) { |
| if (!host_address) { |
| status = apr_sockaddr_info_get(&host_address, |
| host_info.hostname, |
| APR_UNSPEC, host_info.port, 0, pool); |
| if (status) |
| return status; |
| } |
| } else { |
| host_address = NULL; |
| } |
| |
| c = serf_connection_create(ctx, host_address, setup, setup_baton, |
| closed, closed_baton, pool); |
| |
| /* We're not interested in the path following the hostname. */ |
| c->host_url = apr_uri_unparse(c->pool, |
| &host_info, |
| APR_URI_UNP_OMITPATHINFO | |
| APR_URI_UNP_OMITUSERINFO); |
| |
| /* 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); |
| } |
| |
| /* Store the connection specific info in the configuration store */ |
| status = serf__config_store_create_conn_config(c, &config); |
| if (status) |
| return status; |
| c->config = config; |
| serf_config_set_stringc(config, SERF_CONFIG_HOST_NAME, |
| c->host_info.hostname); |
| serf_config_set_stringc(config, SERF_CONFIG_HOST_PORT, |
| apr_itoa(ctx->pool, c->host_info.port)); |
| |
| *conn = c; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, c->config, |
| "created connection 0x%p\n", c); |
| |
| return status; |
| } |
| |
| apr_status_t serf_connection_reset( |
| serf_connection_t *conn) |
| { |
| return reset_connection(conn, 0); |
| } |
| |
| |
| apr_status_t serf_connection_close( |
| serf_connection_t *conn) |
| { |
| int i; |
| serf_context_t *ctx = conn->ctx; |
| apr_status_t status; |
| |
| for (i = ctx->conns->nelts; i--; ) { |
| serf_connection_t *conn_seq = GET_CONN(ctx, i); |
| |
| if (conn_seq == conn) { |
| |
| /* Clean up the write bucket first, as this marks all partially written |
| requests as fully written, allowing more efficient cleanup */ |
| serf__connection_pre_cleanup(conn); |
| |
| if (conn->protocol_baton && conn->perform_pre_teardown) |
| conn->perform_pre_teardown(conn); |
| |
| /* The application asked to close the connection, no need to notify |
| it for each cancelled request. */ |
| while (conn->written_reqs) { |
| serf__cancel_request(conn->written_reqs, &conn->written_reqs, 0); |
| } |
| while (conn->unwritten_reqs) { |
| serf__cancel_request(conn->unwritten_reqs, &conn->unwritten_reqs, 0); |
| } |
| if (conn->skt != NULL) { |
| remove_connection(ctx, conn); |
| status = clean_skt(conn); |
| if (conn->closed != NULL) { |
| handle_conn_closed(conn, status); |
| } |
| } |
| if (conn->pump.stream != NULL) { |
| serf_bucket_destroy(conn->pump.stream); |
| conn->pump.stream = NULL; |
| } |
| |
| if (conn->protocol_baton) { |
| conn->perform_teardown(conn); |
| conn->protocol_baton = NULL; |
| } |
| |
| /* Remove the connection from the context. We don't want to |
| * deal with it any more. |
| */ |
| if (i < ctx->conns->nelts - 1) { |
| /* move later connections over this one. */ |
| memmove( |
| &GET_CONN(ctx, i), |
| &GET_CONN(ctx, i + 1), |
| (ctx->conns->nelts - i - 1) * sizeof(serf_connection_t *)); |
| } |
| --ctx->conns->nelts; |
| |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, conn->config, |
| "closed connection 0x%p\n", conn); |
| |
| /* Found the connection. Closed it. All done. */ |
| return APR_SUCCESS; |
| } |
| } |
| |
| /* We didn't find the specified connection. */ |
| /* ### doc talks about this w.r.t poll structures. use something else? */ |
| return APR_NOTFOUND; |
| } |
| |
| |
| void serf_connection_set_max_outstanding_requests( |
| serf_connection_t *conn, |
| unsigned int max_requests) |
| { |
| if (max_requests == 0) |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, conn->config, |
| "Set max. nr. of outstanding requests for this " |
| "connection to unlimited.\n"); |
| else |
| serf__log(LOGLVL_DEBUG, LOGCOMP_CONN, __FILE__, conn->config, |
| "Limit max. nr. of outstanding requests for this " |
| "connection to %u.\n", max_requests); |
| |
| conn->max_outstanding_requests = max_requests; |
| } |
| |
| /* Disable HTTP pipelining, ensure that only one request is outstanding at a |
| time. This is an internal method, an application that wants to disable |
| HTTP pipelining can achieve this by calling: |
| serf_connection_set_max_outstanding_requests(conn, 1) . |
| */ |
| void serf__connection_set_pipelining(serf_connection_t *conn, int enabled) |
| { |
| conn->pipelining = enabled; |
| } |
| |
| void serf_connection_set_async_responses( |
| serf_connection_t *conn, |
| serf_response_acceptor_t acceptor, |
| void *acceptor_baton, |
| serf_response_handler_t handler, |
| void *handler_baton) |
| { |
| conn->async_responses = 1; |
| conn->async_acceptor = acceptor; |
| conn->async_acceptor_baton = acceptor_baton; |
| conn->async_handler = handler; |
| conn->async_handler_baton = handler_baton; |
| } |
| |
| void serf_connection_set_framing_type( |
| serf_connection_t *conn, |
| serf_connection_framing_type_t framing_type) |
| { |
| conn->framing_type = framing_type; |
| |
| if (conn->skt) { |
| serf_io__set_pollset_dirty(&conn->io); |
| conn->pump.stop_writing = false; |
| conn->write_now = true; |
| |
| /* Close down existing protocol */ |
| if (conn->protocol_baton) { |
| if (conn->perform_pre_teardown) |
| conn->perform_pre_teardown(conn); |
| conn->perform_teardown(conn); |
| conn->protocol_baton = NULL; |
| } |
| } |
| |
| /* Reset to default */ |
| conn->perform_read = read_from_connection; |
| conn->perform_write = write_to_connection; |
| conn->perform_hangup = hangup_connection; |
| conn->perform_cancel_request = NULL; |
| conn->perform_prioritize_request = NULL; |
| conn->perform_teardown = NULL; |
| conn->perform_pre_teardown = NULL; |
| |
| switch (framing_type) { |
| case SERF_CONNECTION_FRAMING_TYPE_HTTP2: |
| serf__http2_protocol_init(conn); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| apr_interval_time_t serf_connection_get_latency(serf_connection_t *conn) |
| { |
| if (conn->ctx->proxy_address) { |
| /* Detecting network latency for proxied connection is not implemented |
| yet. */ |
| return -1; |
| } |
| |
| return conn->latency; |
| } |
| |
| unsigned int serf_connection_queued_requests(serf_connection_t *conn) |
| { |
| return conn->nr_of_unwritten_reqs; |
| } |
| |
| unsigned int serf_connection_pending_requests(serf_connection_t *conn) |
| { |
| return conn->nr_of_unwritten_reqs + conn->nr_of_written_reqs; |
| } |