static handler_t cgi_handle_fdevent(void *s, void *ctx, int revents) { server *srv = (server *)s; handler_ctx *hctx = ctx; connection *con = hctx->remote_conn; joblist_append(srv, con); if (hctx->fd == -1) { log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "invalid cgi-fd"); return HANDLER_ERROR; } if (revents & FDEVENT_IN) { switch (cgi_demux_response(srv, hctx)) { case FDEVENT_HANDLED_NOT_FINISHED: break; case FDEVENT_HANDLED_FINISHED: /* we are done */ #if 0 log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), "finished"); #endif cgi_connection_close(srv, hctx); /* if we get a IN|HUP and have read everything don't exec the close twice */ return HANDLER_FINISHED; case FDEVENT_HANDLED_ERROR: connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); con->http_status = 500; con->mode = DIRECT; log_error_write(srv, __FILE__, __LINE__, "s", "demuxer failed: "); break; } } if (revents & FDEVENT_OUT) { /* nothing to do */ } /* perhaps this issue is already handled */ if (revents & FDEVENT_HUP) { /* check if we still have a unfinished header package which is a body in reality */ if (con->file_started == 0 && hctx->response_header->used) { con->file_started = 1; http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used); joblist_append(srv, con); } if (con->file_finished == 0) { http_chunk_append_mem(srv, con, NULL, 0); joblist_append(srv, con); } con->file_finished = 1; if (chunkqueue_is_empty(con->write_queue)) { /* there is nothing left to write */ connection_set_state(srv, con, CON_STATE_RESPONSE_END); } else { /* used the write-handler to finish the request on demand */ } # if 0 log_error_write(srv, __FILE__, __LINE__, "sddd", "got HUP from cgi", con->fd, hctx->fd, revents); # endif /* rtsigs didn't liked the close */ cgi_connection_close(srv, hctx); } else if (revents & FDEVENT_ERR) { con->file_finished = 1; /* kill all connections to the cgi process */ cgi_connection_close(srv, hctx); #if 1 log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR"); #endif return HANDLER_ERROR; } return HANDLER_FINISHED; }
static int cgi_demux_response(server *srv, handler_ctx *hctx) { plugin_data *p = hctx->plugin_data; connection *con = hctx->remote_conn; while(1) { int n; int toread; #if defined(__WIN32) buffer_string_prepare_copy(hctx->response, 4 * 1024); #else if (ioctl(con->fd, FIONREAD, &toread) || toread <= 4*1024) { buffer_string_prepare_copy(hctx->response, 4 * 1024); } else { if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT; buffer_string_prepare_copy(hctx->response, toread); } #endif if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) { if (errno == EAGAIN || errno == EINTR) { /* would block, wait for signal */ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); return FDEVENT_HANDLED_NOT_FINISHED; } /* error */ log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd); return FDEVENT_HANDLED_ERROR; } if (n == 0) { /* read finished */ return FDEVENT_HANDLED_FINISHED; } buffer_commit(hctx->response, n); /* split header from body */ if (con->file_started == 0) { int is_header = 0; int is_header_end = 0; size_t last_eol = 0; size_t i, header_len; buffer_append_string_buffer(hctx->response_header, hctx->response); /** * we have to handle a few cases: * * nph: * * HTTP/1.0 200 Ok\n * Header: Value\n * \n * * CGI: * Header: Value\n * Status: 200\n * \n * * and different mixes of \n and \r\n combinations * * Some users also forget about CGI and just send a response and hope * we handle it. No headers, no header-content seperator * */ /* nph (non-parsed headers) */ if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) is_header = 1; header_len = buffer_string_length(hctx->response_header); for (i = 0; !is_header_end && i < header_len; i++) { char c = hctx->response_header->ptr[i]; switch (c) { case ':': /* we found a colon * * looks like we have a normal header */ is_header = 1; break; case '\n': /* EOL */ if (is_header == 0) { /* we got a EOL but we don't seem to got a HTTP header */ is_header_end = 1; break; } /** * check if we saw a \n(\r)?\n sequence */ if (last_eol > 0 && ((i - last_eol == 1) || (i - last_eol == 2 && hctx->response_header->ptr[i - 1] == '\r'))) { is_header_end = 1; break; } last_eol = i; break; } } if (is_header_end) { if (!is_header) { /* no header, but a body */ if (0 != http_chunk_append_buffer(srv, con, hctx->response_header)) { return FDEVENT_HANDLED_ERROR; } } else { const char *bstart; size_t blen; /* the body starts after the EOL */ bstart = hctx->response_header->ptr + i; blen = header_len - i; /** * i still points to the char after the terminating EOL EOL * * put it on the last \n again */ i--; /* string the last \r?\n */ if (i > 0 && (hctx->response_header->ptr[i - 1] == '\r')) { i--; } buffer_string_set_length(hctx->response_header, i); /* parse the response header */ cgi_response_parse(srv, con, p, hctx->response_header); if (con->http_status >= 300 && con->http_status < 400) { /*(con->parsed_response & HTTP_LOCATION)*/ data_string *ds; if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "Location")) && ds->value->ptr[0] == '/') { if (++con->loops_per_request > 5) { log_error_write(srv, __FILE__, __LINE__, "sb", "too many internal loops while processing request:", con->request.orig_uri); con->http_status = 500; /* Internal Server Error */ con->mode = DIRECT; return FDEVENT_HANDLED_FINISHED; } buffer_copy_buffer(con->request.uri, ds->value); if (con->request.content_length) { if (con->request.content_length != con->request_content_queue->bytes_in) { con->keep_alive = 0; } con->request.content_length = 0; chunkqueue_reset(con->request_content_queue); } if (con->http_status != 307 && con->http_status != 308) { /* Note: request body (if any) sent to initial dynamic handler * and is not available to the internal redirect */ con->request.http_method = HTTP_METHOD_GET; } connection_response_reset(srv, con); /*(includes con->http_status = 0)*/ con->mode = DIRECT; return FDEVENT_HANDLED_COMEBACK; } } if (hctx->conf.xsendfile_allow) { data_string *ds; if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile"))) { http_response_xsendfile(srv, con, ds->value, hctx->conf.xsendfile_docroot); return FDEVENT_HANDLED_FINISHED; } } if (blen > 0) { if (0 != http_chunk_append_mem(srv, con, bstart, blen)) { return FDEVENT_HANDLED_ERROR; } } } con->file_started = 1; } else { /*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/ if (header_len > MAX_HTTP_REQUEST_HEADER) { log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path); con->http_status = 502; /* Bad Gateway */ con->mode = DIRECT; return FDEVENT_HANDLED_FINISHED; } } } else { if (0 != http_chunk_append_buffer(srv, con, hctx->response)) { return FDEVENT_HANDLED_ERROR; } if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) && chunkqueue_length(con->write_queue) > 65536 - 4096) { if (!con->is_writable) { /*(defer removal of FDEVENT_IN interest since * connection_state_machine() might be able to send data * immediately, unless !con->is_writable, where * connection_state_machine() might not loop back to call * mod_cgi_handle_subrequest())*/ fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); } break; } } #if 0 log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr); #endif } return FDEVENT_HANDLED_NOT_FINISHED; }
static int cgi_demux_response(server *srv, handler_ctx *hctx) { plugin_data *p = hctx->plugin_data; connection *con = hctx->remote_conn; while(1) { int n; buffer_prepare_copy(hctx->response, 1024); if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) { if (errno == EAGAIN || errno == EINTR) { /* would block, wait for signal */ return FDEVENT_HANDLED_NOT_FINISHED; } /* error */ log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd); return FDEVENT_HANDLED_ERROR; } if (n == 0) { /* read finished */ con->file_finished = 1; /* send final chunk */ http_chunk_append_mem(srv, con, NULL, 0); joblist_append(srv, con); return FDEVENT_HANDLED_FINISHED; } hctx->response->ptr[n] = '\0'; hctx->response->used = n+1; /* split header from body */ if (con->file_started == 0) { char *c; int in_header = 0; int header_end = 0; int cp, eol = EOL_UNSET; size_t used = 0; buffer_append_string_buffer(hctx->response_header, hctx->response); /* nph (non-parsed headers) */ if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) in_header = 1; /* search for the \r\n\r\n or \n\n in the string */ for (c = hctx->response_header->ptr, cp = 0, used = hctx->response_header->used - 1; used; c++, cp++, used--) { if (*c == ':') in_header = 1; else if (*c == '\n') { if (in_header == 0) { /* got a response without a response header */ c = NULL; header_end = 1; break; } if (eol == EOL_UNSET) eol = EOL_N; if (*(c+1) == '\n') { header_end = 1; break; } } else if (used > 1 && *c == '\r' && *(c+1) == '\n') { if (in_header == 0) { /* got a response without a response header */ c = NULL; header_end = 1; break; } if (eol == EOL_UNSET) eol = EOL_RN; if (used > 3 && *(c+2) == '\r' && *(c+3) == '\n') { header_end = 1; break; } /* skip the \n */ c++; cp++; used--; } } if (header_end) { if (c == NULL) { /* no header, but a body */ if (con->request.http_version == HTTP_VERSION_1_1) { con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; } http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used); joblist_append(srv, con); } else { size_t hlen = c - hctx->response_header->ptr + (eol == EOL_RN ? 4 : 2); size_t blen = hctx->response_header->used - hlen - 1; /* a small hack: terminate after at the second \r */ hctx->response_header->used = hlen + 1 - (eol == EOL_RN ? 2 : 1); hctx->response_header->ptr[hlen - (eol == EOL_RN ? 2 : 1)] = '\0'; /* parse the response header */ cgi_response_parse(srv, con, p, hctx->response_header, eol); /* enable chunked-transfer-encoding */ if (con->request.http_version == HTTP_VERSION_1_1 && !(con->parsed_response & HTTP_CONTENT_LENGTH)) { con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; } if ((hctx->response->used != hlen) && blen > 0) { http_chunk_append_mem(srv, con, c + (eol == EOL_RN ? 4: 2), blen + 1); joblist_append(srv, con); } } con->file_started = 1; } } else { http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used); joblist_append(srv, con); } #if 0 log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr); #endif } return FDEVENT_HANDLED_NOT_FINISHED; }
static int cgi_demux_response(server *srv, handler_ctx *hctx) { plugin_data *p = hctx->plugin_data; connection *con = hctx->remote_conn; while(1) { int n; int toread; #if defined(__WIN32) buffer_prepare_copy(hctx->response, 4 * 1024); #else if (ioctl(con->fd, FIONREAD, &toread) || toread == 0 || toread <= 4*1024) { buffer_prepare_copy(hctx->response, 4 * 1024); } else { if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT; buffer_prepare_copy(hctx->response, toread + 1); } #endif if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) { if (errno == EAGAIN || errno == EINTR) { /* would block, wait for signal */ return FDEVENT_HANDLED_NOT_FINISHED; } /* error */ log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd); return FDEVENT_HANDLED_ERROR; } if (n == 0) { /* read finished */ con->file_finished = 1; /* send final chunk */ http_chunk_append_mem(srv, con, NULL, 0); joblist_append(srv, con); return FDEVENT_HANDLED_FINISHED; } hctx->response->ptr[n] = '\0'; hctx->response->used = n+1; /* split header from body */ if (con->file_started == 0) { int is_header = 0; int is_header_end = 0; size_t last_eol = 0; size_t i; buffer_append_string_buffer(hctx->response_header, hctx->response); /** * we have to handle a few cases: * * nph: * * HTTP/1.0 200 Ok\n * Header: Value\n * \n * * CGI: * Header: Value\n * Status: 200\n * \n * * and different mixes of \n and \r\n combinations * * Some users also forget about CGI and just send a response and hope * we handle it. No headers, no header-content seperator * */ /* nph (non-parsed headers) */ if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) is_header = 1; for (i = 0; !is_header_end && i < hctx->response_header->used - 1; i++) { char c = hctx->response_header->ptr[i]; switch (c) { case ':': /* we found a colon * * looks like we have a normal header */ is_header = 1; break; case '\n': /* EOL */ if (is_header == 0) { /* we got a EOL but we don't seem to got a HTTP header */ is_header_end = 1; break; } /** * check if we saw a \n(\r)?\n sequence */ if (last_eol > 0 && ((i - last_eol == 1) || (i - last_eol == 2 && hctx->response_header->ptr[i - 1] == '\r'))) { is_header_end = 1; break; } last_eol = i; break; } } if (is_header_end) { if (!is_header) { /* no header, but a body */ if (con->request.http_version == HTTP_VERSION_1_1) { con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; } http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used); joblist_append(srv, con); } else { const char *bstart; size_t blen; /** * i still points to the char after the terminating EOL EOL * * put it on the last \n again */ i--; /* the body starts after the EOL */ bstart = hctx->response_header->ptr + (i + 1); blen = (hctx->response_header->used - 1) - (i + 1); /* string the last \r?\n */ if (i > 0 && (hctx->response_header->ptr[i - 1] == '\r')) { i--; } hctx->response_header->ptr[i] = '\0'; hctx->response_header->used = i + 1; /* the string + \0 */ /* parse the response header */ cgi_response_parse(srv, con, p, hctx->response_header); /* enable chunked-transfer-encoding */ if (con->request.http_version == HTTP_VERSION_1_1 && !(con->parsed_response & HTTP_CONTENT_LENGTH)) { con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; } if (blen > 0) { http_chunk_append_mem(srv, con, bstart, blen + 1); joblist_append(srv, con); } } con->file_started = 1; } } else { http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used); joblist_append(srv, con); } #if 0 log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr); #endif } return FDEVENT_HANDLED_NOT_FINISHED; }
static int stream_http_chunk_append_mem(server *srv, connection *con, handler_ctx *hctx, size_t len) { /* future: might also write stream to hctx temporary file in compressed file cache */ return http_chunk_append_mem(srv, con, hctx->output->ptr, len); }
static handler_t proxy_handle_fdevent(server *srv, void *ctx, int revents) { handler_ctx *hctx = ctx; connection *con = hctx->remote_conn; plugin_data *p = hctx->plugin_data; if ((revents & FDEVENT_IN) && hctx->state == PROXY_STATE_READ) { if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sd", "proxy: fdevent-in", hctx->state); } switch (proxy_demux_response(srv, hctx)) { case 0: break; case 1: /* we are done */ proxy_connection_close(srv, hctx); joblist_append(srv, con); return HANDLER_FINISHED; case -1: if (con->file_started == 0) { /* nothing has been send out yet, send a 500 */ connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); con->http_status = 500; con->mode = DIRECT; } else { /* response might have been already started, kill the connection */ connection_set_state(srv, con, CON_STATE_ERROR); } joblist_append(srv, con); return HANDLER_FINISHED; } } if (revents & FDEVENT_OUT) { if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sd", "proxy: fdevent-out", hctx->state); } if (hctx->state == PROXY_STATE_CONNECT) { int socket_error; socklen_t socket_error_len = sizeof(socket_error); /* we don't need it anymore */ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); hctx->fde_ndx = -1; /* try to finish the connect() */ if (0 != getsockopt(hctx->fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_len)) { log_error_write(srv, __FILE__, __LINE__, "ss", "getsockopt failed:", strerror(errno)); joblist_append(srv, con); return HANDLER_FINISHED; } if (socket_error != 0) { log_error_write(srv, __FILE__, __LINE__, "ss", "establishing connection failed:", strerror(socket_error), "port:", hctx->host->port); joblist_append(srv, con); return HANDLER_FINISHED; } if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "s", "proxy - connect - delayed success"); } proxy_set_state(srv, hctx, PROXY_STATE_PREPARE_WRITE); } if (hctx->state == PROXY_STATE_PREPARE_WRITE || hctx->state == PROXY_STATE_WRITE) { /* we are allowed to send something out * * 1. after a just finished connect() call * 2. in a unfinished write() call (long POST request) */ return mod_proxy_handle_subrequest(srv, con, p); } else { log_error_write(srv, __FILE__, __LINE__, "sd", "proxy: out", hctx->state); } } /* perhaps this issue is already handled */ if (revents & FDEVENT_HUP) { if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sd", "proxy: fdevent-hup", hctx->state); } if (hctx->state == PROXY_STATE_CONNECT) { /* connect() -> EINPROGRESS -> HUP */ /** * what is proxy is doing if it can't reach the next hop ? * */ if (hctx->host) { hctx->host->is_disabled = 1; hctx->host->disable_ts = srv->cur_ts; log_error_write(srv, __FILE__, __LINE__, "sbdd", "proxy-server disabled:", hctx->host->host, hctx->host->port, hctx->fd); /* disable this server */ hctx->host->is_disabled = 1; hctx->host->disable_ts = srv->cur_ts; proxy_connection_close(srv, hctx); /* reset the enviroment and restart the sub-request */ buffer_reset(con->physical.path); con->mode = DIRECT; joblist_append(srv, con); } else { proxy_connection_close(srv, hctx); joblist_append(srv, con); con->mode = DIRECT; con->http_status = 503; } return HANDLER_FINISHED; } if (!con->file_finished) { http_chunk_append_mem(srv, con, NULL, 0); } con->file_finished = 1; proxy_connection_close(srv, hctx); joblist_append(srv, con); } else if (revents & FDEVENT_ERR) { /* kill all connections to the proxy process */ log_error_write(srv, __FILE__, __LINE__, "sd", "proxy-FDEVENT_ERR, but no HUP", revents); con->file_finished = 1; joblist_append(srv, con); proxy_connection_close(srv, hctx); } return HANDLER_FINISHED; }
static int proxy_demux_response(server *srv, handler_ctx *hctx) { int fin = 0; int b; ssize_t r; plugin_data *p = hctx->plugin_data; connection *con = hctx->remote_conn; int proxy_fd = hctx->fd; /* check how much we have to read */ if (ioctl(hctx->fd, FIONREAD, &b)) { log_error_write(srv, __FILE__, __LINE__, "sd", "ioctl failed: ", proxy_fd); return -1; } if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sd", "proxy - have to read:", b); } if (b > 0) { if (hctx->response->used == 0) { /* avoid too small buffer */ buffer_prepare_append(hctx->response, b + 1); hctx->response->used = 1; } else { buffer_prepare_append(hctx->response, b); } if (-1 == (r = read(hctx->fd, hctx->response->ptr + hctx->response->used - 1, b))) { if (errno == EAGAIN) return 0; log_error_write(srv, __FILE__, __LINE__, "sds", "unexpected end-of-file (perhaps the proxy process died):", proxy_fd, strerror(errno)); return -1; } /* this should be catched by the b > 0 above */ assert(r); hctx->response->used += r; hctx->response->ptr[hctx->response->used - 1] = '\0'; #if 0 log_error_write(srv, __FILE__, __LINE__, "sdsbs", "demux: Response buffer len", hctx->response->used, ":", hctx->response, ":"); #endif if (0 == con->got_response) { con->got_response = 1; buffer_prepare_copy(hctx->response_header, 128); } if (0 == con->file_started) { char *c; /* search for the \r\n\r\n in the string */ if (NULL != (c = buffer_search_string_len(hctx->response, "\r\n\r\n", 4))) { size_t hlen = c - hctx->response->ptr + 4; size_t blen = hctx->response->used - hlen - 1; /* found */ buffer_append_string_len(hctx->response_header, hctx->response->ptr, c - hctx->response->ptr + 4); #if 0 log_error_write(srv, __FILE__, __LINE__, "sb", "Header:", hctx->response_header); #endif /* parse the response header */ proxy_response_parse(srv, con, p, hctx->response_header); /* enable chunked-transfer-encoding */ if (con->request.http_version == HTTP_VERSION_1_1 && !(con->parsed_response & HTTP_CONTENT_LENGTH)) { con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; } con->file_started = 1; if (blen) { http_chunk_append_mem(srv, con, c + 4, blen + 1); } hctx->response->used = 0; joblist_append(srv, con); } } else { http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used); joblist_append(srv, con); hctx->response->used = 0; } } else { /* reading from upstream done */ con->file_finished = 1; http_chunk_append_mem(srv, con, NULL, 0); joblist_append(srv, con); fin = 1; } return fin; }