static void mainvr_connection_upgrade(liVRequest *vr, liStream *backend_drain, liStream *backend_source) { liConnection* con = li_connection_from_vrequest(vr); LI_FORCE_ASSERT(NULL != con); if (con->response_headers_sent || NULL != con->out.source) { li_connection_error(con); return; } if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "connection upgrade: write response headers"); } con->response_headers_sent = TRUE; con->info.keep_alive = FALSE; li_response_send_headers(vr, con->out.out, NULL, TRUE); con->state = LI_CON_STATE_UPGRADED; vr->response.transfer_encoding = 0; li_connection_update_io_wait(con); li_stream_disconnect_dest(&con->in); con->in.out->is_closed = FALSE; li_stream_connect(&con->in, backend_drain); li_stream_connect(backend_source, &con->out); li_vrequest_reset(con->mainvr, TRUE); if (NULL != con->in.source) { li_chunkqueue_steal_all(con->out.out, backend_drain->out); } con->info.out_queue_length = con->out.out->length; li_stream_notify(&con->out); li_stream_notify(&con->in); }
/* io -> ssl crypted in */ static void stream_crypt_drain_cb(liStream *stream, liStreamEvent event) { liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, crypt_drain); switch (event) { case LI_STREAM_NEW_DATA: if (!stream->out->is_closed && NULL != stream->source) { li_chunkqueue_steal_all(stream->out, stream->source->out); stream->out->is_closed = stream->out->is_closed || stream->source->out->is_closed; li_stream_notify(stream); /* tell plain_source to do SSL_read */ } if (stream->out->is_closed) { li_stream_disconnect(stream); } break; case LI_STREAM_NEW_CQLIMIT: break; case LI_STREAM_CONNECTED_DEST: /* plain_source */ break; case LI_STREAM_CONNECTED_SOURCE: /* io in */ break; case LI_STREAM_DISCONNECTED_DEST: /* plain_source */ if (!stream->out->is_closed || 0 != stream->out->length) { f_abort_gnutls(f); /* didn't read everything */ } break; case LI_STREAM_DISCONNECTED_SOURCE: /* io in disconnect */ if (!stream->out->is_closed) { f_abort_gnutls(f); /* conn aborted */ } break; case LI_STREAM_DESTROY: f_release(f); break; } }
/* ssl (plain) -> app */ static void stream_plain_source_cb(liStream *stream, liStreamEvent event) { liGnuTLSFilter *f = LI_CONTAINER_OF(stream, liGnuTLSFilter, plain_source); switch (event) { case LI_STREAM_NEW_DATA: do_gnutls_read(f); if (f->write_wants_read) do_gnutls_write(f); li_stream_notify(stream); break; case LI_STREAM_NEW_CQLIMIT: break; case LI_STREAM_CONNECTED_DEST: /* app */ break; case LI_STREAM_CONNECTED_SOURCE: /* crypt_drain */ break; case LI_STREAM_DISCONNECTED_DEST: /* app */ if (!stream->out->is_closed || 0 != stream->out->length) { f_abort_gnutls(f); /* didn't read everything */ } break; case LI_STREAM_DISCONNECTED_SOURCE: /* crypt_drain */ if (!stream->out->is_closed) { f_abort_gnutls(f); /* didn't get everything */ } break; case LI_STREAM_DESTROY: f_release(f); break; } }
/* flush current tempfile chunk. ignores out->is_closed. */ static void bod_flush(bod_state *state) { liChunkQueue *out = state->stream.out; if (NULL != out && NULL != state->tempfile && state->write_pos > state->flush_pos) { li_chunkqueue_append_chunkfile(out, state->tempfile, state->flush_pos, state->write_pos - state->flush_pos); state->flush_pos = state->write_pos; li_stream_notify(&state->stream); } }
/* stop buffering, forward everyting */ static void bod_stop(bod_state *state) { bod_close(state); if (NULL != state->stream.source && !state->stream.out->is_closed) { li_chunkqueue_steal_all(state->stream.out, state->stream.source->out); if (state->stream.source->out->is_closed) { state->stream.out->is_closed = TRUE; li_stream_disconnect(&state->stream); } li_stream_notify(&state->stream); } state->vr = NULL; }
static void f_close_with_alert(liGnuTLSFilter *f, int r) { if (f->closing || f->aborted) return; if (GNUTLS_E_SUCCESS != gnutls_alert_send_appropriate(f->session, r)) { f_abort_gnutls(f); return; } /* push alert to io */ li_stream_notify(&f->crypt_source); f->plain_source.out->is_closed = TRUE; f->crypt_drain.out->is_closed = TRUE; f_close_gnutls(f); }
static void stream_http_response_data(liStreamHttpResponse* shr) { if (NULL == shr->stream.source) return; if (!shr->response_headers_finished) { switch (li_http_response_parse(shr->vr, &shr->parse_response_ctx)) { case LI_HANDLER_GO_ON: check_response_header(shr); if (NULL == shr->stream.source) return; break; case LI_HANDLER_ERROR: VR_ERROR(shr->vr, "%s", "Parsing response header failed"); li_vrequest_error(shr->vr); return; case LI_HANDLER_WAIT_FOR_EVENT: if (shr->stream.source->out->is_closed) { VR_ERROR(shr->vr, "%s", "Parsing response header failed (eos)"); li_vrequest_error(shr->vr); } return; default: return; } } if (shr->transfer_encoding_chunked) { if (!li_filter_chunked_decode(shr->vr, shr->stream.out, shr->stream.source->out, &shr->chunked_decode_state)) { if (NULL != shr->vr) { VR_ERROR(shr->vr, "%s", "Decoding chunks failed"); li_vrequest_error(shr->vr); } else { li_stream_reset(&shr->stream); } } if (shr->stream.source->out->is_closed) { li_stream_disconnect(&shr->stream); } } else { li_chunkqueue_steal_all(shr->stream.out, shr->stream.source->out); if (shr->stream.source->out->is_closed) { shr->stream.out->is_closed = TRUE; li_stream_disconnect(&shr->stream); } } li_stream_notify(&shr->stream); }
/* http response header/data -> tcp/ssl */ static void _connection_http_out_cb(liStream *stream, liStreamEvent event) { liConnection *con = LI_CONTAINER_OF(stream, liConnection, out); liChunkQueue *raw_out = stream->out, *out; liVRequest *vr = con->mainvr; switch (event) { case LI_STREAM_NEW_DATA: /* handle below */ break; case LI_STREAM_CONNECTED_SOURCE: /* also handle data immediately */ break; case LI_STREAM_DISCONNECTED_SOURCE: if (!con->out_has_all_data) li_connection_error(con); return; case LI_STREAM_DISCONNECTED_DEST: if (!raw_out->is_closed || 0 != raw_out->length || NULL == con->con_sock.raw_out) { li_connection_error(con); } else { connection_close(con); } return; case LI_STREAM_DESTROY: con->info.resp = NULL; li_job_later(&con->wrk->loop.jobqueue, &con->job_reset); return; default: return; } out = (NULL != stream->source) ? stream->source->out : NULL; /* keep raw_out->is_closed = FALSE for keep-alive requests; instead set con->out_has_all_data = TRUE */ if (LI_CON_STATE_HANDLE_MAINVR <= con->state) { if (NULL == stream->source) { if (LI_CON_STATE_HANDLE_MAINVR == con->state) { /* wait for vrequest to connect the stream as signal that the headers are ready */ return; } } if (!con->response_headers_sent) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "write response headers"); } con->response_headers_sent = TRUE; if (0 == CORE_OPTION(LI_CORE_OPTION_MAX_KEEP_ALIVE_IDLE).number) { con->info.keep_alive = FALSE; } li_response_send_headers(vr, raw_out, out, FALSE); } if (!con->out_has_all_data && !raw_out->is_closed && NULL != out) { if (vr->response.transfer_encoding & LI_HTTP_TRANSFER_ENCODING_CHUNKED) { li_filter_chunked_encode(vr, raw_out, out); } else { li_chunkqueue_steal_all(raw_out, out); } } if (raw_out->is_closed || NULL == out || out->is_closed) { con->out_has_all_data = TRUE; raw_out->is_closed = FALSE; } if (con->out_has_all_data) { if (con->state < LI_CON_STATE_WRITE) { con->state = LI_CON_STATE_WRITE; li_connection_update_io_wait(con); } if (NULL != out) { out = NULL; li_stream_disconnect(stream); } } con->info.out_queue_length = raw_out->length; } li_stream_notify(stream); }
/* tcp/ssl -> http "parser" */ static void _connection_http_in_cb(liStream *stream, liStreamEvent event) { liConnection *con = LI_CONTAINER_OF(stream, liConnection, in); liChunkQueue *raw_in, *in; liVRequest *vr = con->mainvr; switch (event) { case LI_STREAM_NEW_DATA: /* handle below */ break; case LI_STREAM_DISCONNECTED_SOURCE: connection_close(con); return; case LI_STREAM_DESTROY: con->info.req = NULL; li_job_later(&con->wrk->loop.jobqueue, &con->job_reset); return; default: return; } if (NULL == stream->source) return; /* raw_in never gets closed normally - if we receive EOF from the client it means it cancelled the request */ raw_in = stream->source->out; if (raw_in->is_closed) { connection_close(con); return; } /* always close "in" after request body end. reopen it on keep-alive */ in = con->in.out; if (0 == raw_in->length) return; /* no (new) data */ if (LI_CON_STATE_UPGRADED == con->state) { li_chunkqueue_steal_all(in, raw_in); li_stream_notify(stream); return; } if (con->state == LI_CON_STATE_KEEP_ALIVE) { /* stop keep alive timeout watchers */ if (con->keep_alive_data.link) { g_queue_delete_link(&con->wrk->keep_alive_queue, con->keep_alive_data.link); con->keep_alive_data.link = NULL; } con->keep_alive_data.timeout = 0; li_event_stop(&con->keep_alive_data.watcher); con->keep_alive_requests++; /* disable keep alive if limit is reached */ if (con->keep_alive_requests == CORE_OPTION(LI_CORE_OPTION_MAX_KEEP_ALIVE_REQUESTS).number) con->info.keep_alive = FALSE; /* reopen stream for request body */ li_chunkqueue_reset(in); /* reset stuff from keep-alive and record timestamp */ li_vrequest_start(con->mainvr); con->state = LI_CON_STATE_READ_REQUEST_HEADER; /* put back in io timeout queue */ li_connection_update_io_wait(con); } else if (con->state == LI_CON_STATE_REQUEST_START) { con->state = LI_CON_STATE_READ_REQUEST_HEADER; li_connection_update_io_wait(con); } if (con->state == LI_CON_STATE_READ_REQUEST_HEADER) { liHandlerResult res; if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "reading request header"); } res = li_http_request_parse(vr, &con->req_parser_ctx); /* max uri length 8 kilobytes */ /* TODO: check this and similar in request_parse and response_parse */ if (vr->request.uri.raw->len > 8*1024) { VR_INFO(vr, "request uri too large. limit: 8kb, received: %s", li_counter_format(vr->request.uri.raw->len, COUNTER_BYTES, vr->wrk->tmp_str)->str ); con->info.keep_alive = FALSE; vr->response.http_status = 414; /* Request-URI Too Large */ con->state = LI_CON_STATE_WRITE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } switch(res) { case LI_HANDLER_GO_ON: break; /* go on */ case LI_HANDLER_WAIT_FOR_EVENT: return; case LI_HANDLER_ERROR: case LI_HANDLER_COMEBACK: /* unexpected */ /* unparsable header */ if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "parsing header failed"); } con->wrk->stats.requests++; con->info.keep_alive = FALSE; /* set status 400 if not already set to e.g. 413 */ if (vr->response.http_status == 0) vr->response.http_status = 400; con->state = LI_CON_STATE_WRITE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } con->wrk->stats.requests++; /* headers ready */ if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "validating request header"); } if (!li_request_validate_header(con)) { /* set status 400 if not already set */ if (vr->response.http_status == 0) vr->response.http_status = 400; con->state = LI_CON_STATE_WRITE; con->info.keep_alive = FALSE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } /* When does a client ask for 100 Continue? probably not while trying to ddos us * as post content probably goes to a dynamic backend anyway, we don't * care about the rare cases we could determine that we don't want a request at all * before sending it to a backend - so just send the stupid header */ if (con->expect_100_cont) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "send 100 Continue"); } li_chunkqueue_append_mem(con->out.out, CONST_STR_LEN("HTTP/1.1 100 Continue\r\n\r\n")); con->expect_100_cont = FALSE; li_stream_notify(&con->out); } con->state = LI_CON_STATE_HANDLE_MAINVR; li_connection_update_io_wait(con); li_action_enter(vr, con->srv->mainaction); li_vrequest_handle_request_headers(vr); } if (con->state != LI_CON_STATE_READ_REQUEST_HEADER && !in->is_closed) { goffset newbytes = 0; if (-1 == vr->request.content_length) { if (!in->is_closed) { if (!li_filter_chunked_decode(vr, in, raw_in, &con->in_chunked_decode_state)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "failed decoding chunked request body"); } li_connection_error(con); return; } if (in->is_closed) vr->request.content_length = in->bytes_in; newbytes = 1; /* always notify */ } } else { if (in->bytes_in < vr->request.content_length) { newbytes = li_chunkqueue_steal_len(in, raw_in, vr->request.content_length - in->bytes_in); } if (in->bytes_in == vr->request.content_length) { in->is_closed = TRUE; } } if (newbytes > 0 || in->is_closed) { li_stream_notify(&con->in); } } }
static void bod_handle_data(bod_state *state) { liChunkQueue *out = state->stream.out; liChunkQueue *in; if (out->is_closed) { li_stream_disconnect(&state->stream); bod_close(state); return; } in = (state->stream.source != NULL) ? state->stream.source->out : NULL; if (NULL == in) goto out; if (NULL == state->vr) { li_chunkqueue_steal_all(out, in); goto out; } while (in->length > 0) { liChunk *c = li_chunkqueue_first_chunk(in); liChunkIter ci; off_t length, data_len; char *data = NULL; GError *err; assert(UNUSED_CHUNK != c->type); switch (c->type) { case UNUSED_CHUNK: /* shouldn't happen anyway, but stealing it is ok here too */ case FILE_CHUNK: if (state->split_on_file_chunks) { bod_close(state); } else { bod_flush(state); } li_chunkqueue_steal_chunk(out, in); break; case STRING_CHUNK: case MEM_CHUNK: case BUFFER_CHUNK: if (!bod_open(state)) return; length = li_chunk_length(c); ci = li_chunkqueue_iter(in); err = NULL; if (LI_HANDLER_GO_ON != li_chunkiter_read(ci, 0, length, &data, &data_len, &err)) { if (NULL != err) { VR_ERROR(state->vr, "%s", err->message); g_error_free(err); } bod_error(state); return; } while ( data_len > 0 ) { ssize_t r; r = pwrite(state->tempfile->fd, data, data_len, state->write_pos); if (r < 0) { switch (errno) { case EINTR: continue; default: break; } VR_ERROR(state->vr, "pwrite failed: %s", g_strerror(errno)); bod_stop(state); /* write failures are not critical */ return; } data += r; data_len -= r; state->write_pos += r; } li_chunkqueue_skip(in, length); break; } } bod_autoflush(state); out: if (NULL == in || in->is_closed) { out->is_closed = TRUE; bod_close(state); /* close/flush ignores out->is_closed */ li_stream_notify(&state->stream); /* if no flush happened we still notify */ } }