--- modules/proxy/mod_proxy_fcgi.c +++ modules/proxy/mod_proxy_fcgi.c @@ -40,6 +40,10 @@ fcgi_backend_t backend_type; } fcgi_dirconf_t; +typedef struct { + int detect_client_disconnect; +} fcgi_serverconf_t; + /* * Canonicalise http-like URLs. * scheme is the scheme for the URL @@ -497,7 +501,7 @@ apr_uint16_t request_id, const char **err, int *bad_request, int *has_responded) { - apr_bucket_brigade *ib, *ob; + apr_bucket_brigade *ib, *ob, *cib; int seen_end_of_headers = 0, done = 0, ignore_body = 0; apr_status_t rv = APR_SUCCESS; int script_error_status = HTTP_OK; @@ -505,7 +509,7 @@ struct iovec vec[2]; ap_fcgi_header header; unsigned char farray[AP_FCGI_HEADER_LEN]; - apr_pollfd_t pfd; + apr_pollfd_t pfd[2]; int header_state = HDR_STATE_READING_HEADERS; char stack_iobuf[AP_IOBUFSIZE]; apr_size_t iobuf_size = AP_IOBUFSIZE; @@ -517,14 +521,25 @@ iobuf = apr_palloc(r->pool, iobuf_size); } - pfd.desc_type = APR_POLL_SOCKET; - pfd.desc.s = conn->sock; - pfd.p = r->pool; - pfd.reqevents = APR_POLLIN | APR_POLLOUT; + pfd[0].desc_type = APR_POLL_SOCKET; + pfd[0].desc.s = conn->sock; + pfd[0].p = r->pool; + pfd[0].reqevents = APR_POLLIN | APR_POLLOUT; + /* Specific pollfd to detect client connection aborts */ + pfd[1].desc_type = APR_POLL_SOCKET; + pfd[1].desc.s = (apr_socket_t*) ap_get_module_config(r->connection->conn_config, + &core_module); + pfd[1].p = r->connection->pool; + pfd[1].reqevents = APR_POLLIN; + ib = apr_brigade_create(r->pool, c->bucket_alloc); ob = apr_brigade_create(r->pool, c->bucket_alloc); + cib = apr_brigade_create(r->connection->pool, c->bucket_alloc); + fcgi_serverconf_t *cfg = ap_get_module_config(r->server->module_config, + &proxy_fcgi_module); + while (! done) { apr_interval_time_t timeout; apr_size_t len; @@ -534,7 +549,7 @@ * cause timeout errors. */ apr_socket_timeout_get(conn->sock, &timeout); - rv = apr_poll(&pfd, 1, &n, timeout); + rv = apr_poll(pfd, 2, &n, timeout); if (rv != APR_SUCCESS) { if (APR_STATUS_IS_EINTR(rv)) { continue; @@ -543,7 +558,7 @@ break; } - if (pfd.rtnevents & APR_POLLOUT) { + if (pfd[0].rtnevents & APR_POLLOUT) { apr_size_t to_send, writebuflen; int last_stdin = 0; char *iobuf_cursor; @@ -608,7 +623,7 @@ } if (last_stdin) { - pfd.reqevents = APR_POLLIN; /* Done with input data */ + pfd[0].reqevents = APR_POLLIN; /* Done with input data */ /* signal EOF (empty FCGI_STDIN) */ ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id, @@ -626,7 +641,36 @@ } } - if (pfd.rtnevents & APR_POLLIN) { + /* + * Client connection aborted before any attempt to flush + * data out has been made. This can happen for long running FCGI + * tasks. According to the FCGI specs, an FCGI_ABORT should be sent + * and the FCGI backend should respond with an FCGI_END_REQUEST, but as of + * today (December 2016) there seems to be no FCGI backend implementation + * that really honor/care about this functionality. Since this module + * does not implement FCGI request multiplexing, it seems more consistent + * to follow this bit of the FCGI_ABORT spec: + * "When a Web server is not multiplexing requests over a transport + * connection, the Web server can abort a request by closing + * the request's transport connection." + */ + if (pfd[1].rtnevents & APR_POLLIN) { + if (cfg->detect_client_disconnect) { + rv = ap_get_brigade(r->connection->input_filters, cib, + AP_MODE_SPECULATIVE, APR_NONBLOCK_READ, 8); + if(rv == APR_EOF) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "EOF detected from the main client connection."); + r->connection->aborted = 1; + break; + } + } else { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "Client disconnect detector disabled!"); + } + } + + if (pfd[0].rtnevents & APR_POLLIN) { apr_size_t readbuflen; apr_uint16_t clen, rid; apr_bucket *b; @@ -855,6 +899,7 @@ apr_brigade_destroy(ib); apr_brigade_destroy(ob); + apr_brigade_destroy(cib); if (script_error_status != HTTP_OK) { ap_die(script_error_status, r); /* send ErrorDocument */ @@ -1052,6 +1097,25 @@ return a; } +static void *fcgi_create_sconf(apr_pool_t *p, server_rec *s) +{ + fcgi_serverconf_t *a; + + a = (fcgi_serverconf_t *) apr_pcalloc(p, sizeof(fcgi_serverconf_t)); + a->detect_client_disconnect = 0; + return a; +} + +static void *fcgi_merge_sconf(apr_pool_t *p, void *basev, void *overridesv) +{ + fcgi_serverconf_t *a, *override; + + override = (fcgi_serverconf_t *) overridesv; + a = (fcgi_serverconf_t *) apr_pcalloc(p, sizeof(fcgi_serverconf_t)); + a->detect_client_disconnect = override->detect_client_disconnect; + return a; +} + static const char *cmd_servertype(cmd_parms *cmd, void *in_dconf, const char *val) { @@ -1071,6 +1135,18 @@ return NULL; } +const char *cmd_client_disconnect(cmd_parms *cmd, void *cfg, const char *arg) +{ + fcgi_serverconf_t *config = ap_get_module_config(cmd->server->module_config, + &proxy_fcgi_module); + if(!strcasecmp(arg, "on")) { + config->detect_client_disconnect = 1; + } else { + config->detect_client_disconnect = 0; + } + return NULL; +} + static void register_hooks(apr_pool_t *p) { proxy_hook_scheme_handler(proxy_fcgi_handler, NULL, NULL, APR_HOOK_FIRST); @@ -1080,6 +1156,9 @@ static const command_rec command_table[] = { AP_INIT_TAKE1("ProxyFCGIBackendType", cmd_servertype, NULL, OR_FILEINFO, "Specify the type of FastCGI server: 'Generic', 'FPM'"), + AP_INIT_TAKE1("ProxyFCGIDetectClientDisconnect", cmd_client_disconnect, + NULL, RSRC_CONF, "Detect when a client connection is dropped" + " and close the connection with the FCGI backend."), { NULL } }; @@ -1087,8 +1166,8 @@ STANDARD20_MODULE_STUFF, fcgi_create_dconf, /* create per-directory config structure */ fcgi_merge_dconf, /* merge per-directory config structures */ - NULL, /* create per-server config structure */ - NULL, /* merge per-server config structures */ + fcgi_create_sconf, /* create per-server config structure */ + fcgi_merge_sconf, /* merge per-server config structures */ command_table, /* command apr_table_t */ register_hooks /* register hooks */ };