From: Jim Jagielski Date: Mon, 16 Aug 2010 18:36:19 +0000 (+0000) Subject: For backends which are HTTP/1.1, do a quick test (ping) X-Git-Tag: 2.3.7~18 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=87703dd78c31d52c7b22a77a60f02da0c0b7fcc1;p=apache For backends which are HTTP/1.1, do a quick test (ping) of the "connection" via 100-Continue for reverse proxies... ACO and Filip Hanik also helped out with the idea... git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@986090 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 1a223076f7..b18b82e733 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,10 @@ Changes with Apache 2.3.7 mod_dav, mod_cache, mod_session: Fix Handling of requests without a path segment. PR: 49246 [Mark Drayton, Jeff Trawick] + *) mod_proxy_http: Support the 'ping' property for backend HTTP/1.1 servers + via leveraging 100-Continue as the initial "request". + [Jim Jagielski] + *) core/mod_authz_core: Introduce new access_checker_ex hook that enables mod_authz_core to bypass authentication if access should be allowed by IP address/env var/... [Stefan Fritsch] diff --git a/docs/manual/mod/mod_proxy.xml b/docs/manual/mod/mod_proxy.xml index b32d13a8e7..abdb2ce667 100644 --- a/docs/manual/mod/mod_proxy.xml +++ b/docs/manual/mod/mod_proxy.xml @@ -743,17 +743,20 @@ expressions ping 0 - Ping property tells webserver to send a CPING - request on ajp13 connection before forwarding a request. - The parameter is the delay in seconds to wait for the - CPONG reply. - This features has been added to avoid problem with hung and - busy Tomcat's and require ajp13 ping/pong support which has - been implemented on Tomcat 3.3.2+, 4.1.28+ and 5.0.13+. + Ping property tells the webserver to "test" the connection to + the backend before forwarding the request. For AJP, it causes + mod_proxy_ajpto send a CPING + request on the ajp13 connection (implemented on Tomcat 3.3.2+, 4.1.28+ + and 5.0.13+). For HTTP, it causes mod_proxy_http + to send a 100-Continue to the backend (only valid for + HTTP/1.1 - for non HTTP/1.1 backends, this property has no + effect). In both cases the parameter is the delay in seconds to wait + for the reply. + This feature has been added to avoid problems with hung and + busy backends. This will increase the network traffic during the normal operation which could be an issue, but it will lower the traffic in case some of the cluster nodes are down or busy. - Currently this has an effect only for AJP. By adding a postfix of ms the delay can be also set in milliseconds. diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c index 44def30543..b668fbe00d 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -669,7 +669,7 @@ static int spool_reqbody_cl(apr_pool_t *p, static int ap_proxy_http_request(apr_pool_t *p, request_rec *r, - proxy_conn_rec *p_conn, conn_rec *origin, + proxy_conn_rec *p_conn, proxy_worker *worker, proxy_server_conf *conf, apr_uri_t *uri, char *url, char *server_portstr) @@ -694,6 +694,8 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r, int force10, rv; apr_table_t *headers_in_copy; proxy_dir_conf *dconf; + conn_rec *origin = p_conn->connection; + int do_100_continue; dconf = ap_get_module_config(r->per_dir_config, &proxy_module); header_brigade = apr_brigade_create(p, origin->bucket_alloc); @@ -702,6 +704,11 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r, * Send the HTTP/1.1 request to the remote server */ + do_100_continue = (worker->ping_timeout_set + && !r->header_only + && (PROXYREQ_REVERSE == r->proxyreq) + && !(apr_table_get(r->subprocess_env, "force-proxy-request-1.0"))); + if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) { /* * According to RFC 2616 8.2.3 we are not allowed to forward an @@ -791,6 +798,14 @@ int ap_proxy_http_request(apr_pool_t *p, request_rec *r, ); } + /* Use HTTP/1.1 100-Continue as quick "HTTP ping" test + * to backend + */ + if (do_100_continue) { + apr_table_mergen(r->headers_in, "Expect", "100-Continue"); + r->expecting_100 = 1; + } + /* X-Forwarded-*: handling * * XXX Privacy Note: @@ -1350,7 +1365,7 @@ apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n, request_rec static apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, proxy_conn_rec *backend, - conn_rec *origin, + proxy_worker *worker, proxy_server_conf *conf, char *server_portstr) { conn_rec *c = r->connection; @@ -1375,9 +1390,32 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, int proxy_status = OK; const char *original_status_line = r->status_line; const char *proxy_status_line = NULL; - + conn_rec *origin = backend->connection; + apr_interval_time_t old_timeout = 0; + + int do_100_continue; + + do_100_continue = (worker->ping_timeout_set + && !r->header_only + && (PROXYREQ_REVERSE == r->proxyreq) + && !(apr_table_get(r->subprocess_env, "force-proxy-request-1.0"))); + + bb = apr_brigade_create(p, c->bucket_alloc); pass_bb = apr_brigade_create(p, c->bucket_alloc); + + /* Setup for 100-Continue timeout if appropriate */ + if (do_100_continue) { + apr_socket_timeout_get(backend->sock, &old_timeout); + if (worker->ping_timeout != old_timeout) { + apr_status_t rc; + rc = apr_socket_timeout_set(backend->sock, worker->ping_timeout); + if (rc != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server, + "proxy: could not set 100-Continue timeout"); + } + } + } /* Get response from the remote server, and pass it up the * filter chain @@ -1406,6 +1444,9 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, if (APR_STATUS_IS_TIMEUP(rc)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "proxy: read timeout"); + if (do_100_continue) { + return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE, "Timeout on 100-Continue"); + } } /* * If we are a reverse proxy request shutdown the connection @@ -1641,6 +1682,12 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, if (ap_is_HTTP_INFO(proxy_status)) { interim_response++; + /* Reset to old timeout iff we've adjusted it */ + if (do_100_continue + && (r->status == HTTP_CONTINUE) + && (worker->ping_timeout != old_timeout)) { + apr_socket_timeout_set(backend->sock, old_timeout); + } } else { interim_response = 0; @@ -1923,6 +1970,7 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker, proxy_conn_rec *backend = NULL; int is_ssl = 0; conn_rec *c = r->connection; + int retry = 0; /* * Use a shorter-lived pool to reduce memory usage * and avoid a memory leak @@ -1990,48 +2038,68 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker, backend->close = 1; } - /* Step One: Determine Who To Connect To */ - if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend, - uri, &url, proxyname, + while (retry < 2) { + char *locurl = url; + + /* Step One: Determine Who To Connect To */ + if ((status = ap_proxy_determine_connection(p, r, conf, worker, backend, + uri, &locurl, proxyname, proxyport, server_portstr, sizeof(server_portstr))) != OK) - goto cleanup; - - /* Step Two: Make the Connection */ - if (ap_proxy_connect_backend(proxy_function, backend, worker, r->server)) { - status = HTTP_SERVICE_UNAVAILABLE; - goto cleanup; - } - - /* Step Three: Create conn_rec */ - if (!backend->connection) { - if ((status = ap_proxy_connection_create(proxy_function, backend, - c, r->server)) != OK) - goto cleanup; - /* - * On SSL connections set a note on the connection what CN is - * requested, such that mod_ssl can check if it is requested to do - * so. + break; + + /* Step Two: Make the Connection */ + if (ap_proxy_connect_backend(proxy_function, backend, worker, r->server)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, + "proxy: HTTP: failed to make connection to backend: %s", + backend->hostname); + status = HTTP_SERVICE_UNAVAILABLE; + break; + } + + /* Step Three: Create conn_rec */ + if (!backend->connection) { + if ((status = ap_proxy_connection_create(proxy_function, backend, + c, r->server)) != OK) + break; + /* + * On SSL connections set a note on the connection what CN is + * requested, such that mod_ssl can check if it is requested to do + * so. + */ + if (is_ssl) { + apr_table_set(backend->connection->notes, "proxy-request-hostname", + uri->hostname); + } + } + + /* Step Four: Send the Request + * On the off-chance that we forced a 100-Continue as a + * kinda HTTP ping test, allow for retries */ - if (is_ssl) { - apr_table_set(backend->connection->notes, "proxy-request-hostname", - uri->hostname); + if ((status = ap_proxy_http_request(p, r, backend, worker, + conf, uri, locurl, server_portstr)) != OK) { + if ((status == HTTP_SERVICE_UNAVAILABLE) && worker->ping_timeout_set) { + backend->close = 1; + ap_log_error(APLOG_MARK, APLOG_INFO, status, r->server, + "proxy: HTTP: 100-Continue failed to %pI (%s)", + worker->cp->addr, worker->hostname); + retry++; + continue; + } else { + break; + } + } - } - /* Step Four: Send the Request */ - if ((status = ap_proxy_http_request(p, r, backend, backend->connection, - conf, uri, url, server_portstr)) != OK) - goto cleanup; + /* Step Five: Receive the Response... Fall thru to cleanup */ + status = ap_proxy_http_process_response(p, r, backend, worker, + conf, server_portstr); - /* Step Five: Receive the Response */ - if ((status = ap_proxy_http_process_response(p, r, backend, - backend->connection, - conf, server_portstr)) != OK) - goto cleanup; + break; + } /* Step Six: Clean Up */ - cleanup: if (backend) { if (status != OK)