| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/third_party/quiche/src/quic/tools/quic_simple_server_stream.h" |
| |
| #include <list> |
| #include <utility> |
| |
| #include "absl/strings/match.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/string_view.h" |
| #include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h" |
| #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h" |
| #include "net/third_party/quiche/src/quic/core/quic_utils.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h" |
| #include "net/third_party/quiche/src/quic/tools/quic_simple_server_session.h" |
| #include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" |
| |
| using spdy::Http2HeaderBlock; |
| |
| namespace quic { |
| |
| QuicSimpleServerStream::QuicSimpleServerStream( |
| QuicStreamId id, |
| QuicSpdySession* session, |
| StreamType type, |
| QuicSimpleServerBackend* quic_simple_server_backend) |
| : QuicSpdyServerStreamBase(id, session, type), |
| content_length_(-1), |
| generate_bytes_length_(0), |
| quic_simple_server_backend_(quic_simple_server_backend) { |
| DCHECK(quic_simple_server_backend_); |
| } |
| |
| QuicSimpleServerStream::QuicSimpleServerStream( |
| PendingStream* pending, |
| QuicSpdySession* session, |
| StreamType type, |
| QuicSimpleServerBackend* quic_simple_server_backend) |
| : QuicSpdyServerStreamBase(pending, session, type), |
| content_length_(-1), |
| generate_bytes_length_(0), |
| quic_simple_server_backend_(quic_simple_server_backend) { |
| DCHECK(quic_simple_server_backend_); |
| } |
| |
| QuicSimpleServerStream::~QuicSimpleServerStream() { |
| quic_simple_server_backend_->CloseBackendResponseStream(this); |
| } |
| |
| void QuicSimpleServerStream::OnInitialHeadersComplete( |
| bool fin, |
| size_t frame_len, |
| const QuicHeaderList& header_list) { |
| QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); |
| if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_, |
| &request_headers_)) { |
| QUIC_DVLOG(1) << "Invalid headers"; |
| SendErrorResponse(); |
| } |
| ConsumeHeaderList(); |
| if (!fin) { |
| // CONNECT and other CONNECT-like methods (such as CONNECT-UDP) require |
| // sending the response right after parsing the headers even though the FIN |
| // bit has not been received on the request stream. |
| auto it = request_headers_.find(":method"); |
| if (it != request_headers_.end() && |
| absl::StartsWith(it->second, "CONNECT")) { |
| SendResponse(); |
| } |
| } |
| } |
| |
| void QuicSimpleServerStream::OnTrailingHeadersComplete( |
| bool /*fin*/, |
| size_t /*frame_len*/, |
| const QuicHeaderList& /*header_list*/) { |
| QUIC_BUG << "Server does not support receiving Trailers."; |
| SendErrorResponse(); |
| } |
| |
| void QuicSimpleServerStream::OnBodyAvailable() { |
| while (HasBytesToRead()) { |
| struct iovec iov; |
| if (GetReadableRegions(&iov, 1) == 0) { |
| // No more data to read. |
| break; |
| } |
| QUIC_DVLOG(1) << "Stream " << id() << " processed " << iov.iov_len |
| << " bytes."; |
| body_.append(static_cast<char*>(iov.iov_base), iov.iov_len); |
| |
| if (content_length_ >= 0 && |
| body_.size() > static_cast<uint64_t>(content_length_)) { |
| QUIC_DVLOG(1) << "Body size (" << body_.size() << ") > content length (" |
| << content_length_ << ")."; |
| SendErrorResponse(); |
| return; |
| } |
| MarkConsumed(iov.iov_len); |
| } |
| if (!sequencer()->IsClosed()) { |
| sequencer()->SetUnblocked(); |
| return; |
| } |
| |
| // If the sequencer is closed, then all the body, including the fin, has been |
| // consumed. |
| OnFinRead(); |
| |
| if (write_side_closed() || fin_buffered()) { |
| return; |
| } |
| |
| SendResponse(); |
| } |
| |
| void QuicSimpleServerStream::PushResponse( |
| Http2HeaderBlock push_request_headers) { |
| if (QuicUtils::IsClientInitiatedStreamId(session()->transport_version(), |
| id())) { |
| QUIC_BUG << "Client initiated stream shouldn't be used as promised stream."; |
| return; |
| } |
| // Change the stream state to emulate a client request. |
| request_headers_ = std::move(push_request_headers); |
| content_length_ = 0; |
| QUIC_DVLOG(1) << "Stream " << id() |
| << " ready to receive server push response."; |
| DCHECK(reading_stopped()); |
| |
| // Directly send response based on the emulated request_headers_. |
| SendResponse(); |
| } |
| |
| void QuicSimpleServerStream::SendResponse() { |
| if (request_headers_.empty()) { |
| QUIC_DVLOG(1) << "Request headers empty."; |
| SendErrorResponse(); |
| return; |
| } |
| |
| if (content_length_ > 0 && |
| static_cast<uint64_t>(content_length_) != body_.size()) { |
| QUIC_DVLOG(1) << "Content length (" << content_length_ << ") != body size (" |
| << body_.size() << ")."; |
| SendErrorResponse(); |
| return; |
| } |
| |
| if (!QuicContainsKey(request_headers_, ":authority") || |
| !QuicContainsKey(request_headers_, ":path")) { |
| QUIC_DVLOG(1) << "Request headers do not contain :authority or :path."; |
| SendErrorResponse(); |
| return; |
| } |
| |
| if (quic_simple_server_backend_ == nullptr) { |
| QUIC_DVLOG(1) << "Backend is missing."; |
| SendErrorResponse(); |
| return; |
| } |
| |
| // Fetch the response from the backend interface and wait for callback once |
| // response is ready |
| quic_simple_server_backend_->FetchResponseFromBackend(request_headers_, body_, |
| this); |
| } |
| |
| QuicConnectionId QuicSimpleServerStream::connection_id() const { |
| return spdy_session()->connection_id(); |
| } |
| |
| QuicStreamId QuicSimpleServerStream::stream_id() const { |
| return id(); |
| } |
| |
| std::string QuicSimpleServerStream::peer_host() const { |
| return spdy_session()->peer_address().host().ToString(); |
| } |
| |
| void QuicSimpleServerStream::OnResponseBackendComplete( |
| const QuicBackendResponse* response, |
| std::list<QuicBackendResponse::ServerPushInfo> resources) { |
| if (response == nullptr) { |
| QUIC_DVLOG(1) << "Response not found in cache."; |
| SendNotFoundResponse(); |
| return; |
| } |
| |
| if (response->response_type() == QuicBackendResponse::CLOSE_CONNECTION) { |
| QUIC_DVLOG(1) << "Special response: closing connection."; |
| OnUnrecoverableError(QUIC_NO_ERROR, "Toy server forcing close"); |
| return; |
| } |
| |
| if (response->response_type() == QuicBackendResponse::IGNORE_REQUEST) { |
| QUIC_DVLOG(1) << "Special response: ignoring request."; |
| return; |
| } |
| |
| if (response->response_type() == QuicBackendResponse::BACKEND_ERR_RESPONSE) { |
| QUIC_DVLOG(1) << "Quic Proxy: Backend connection error."; |
| /*502 Bad Gateway |
| The server was acting as a gateway or proxy and received an |
| invalid response from the upstream server.*/ |
| SendErrorResponse(502); |
| return; |
| } |
| |
| // Examing response status, if it was not pure integer as typical h2 |
| // response status, send error response. Notice that |
| // QuicHttpResponseCache push urls are strictly authority + path only, |
| // scheme is not included (see |QuicHttpResponseCache::GetKey()|). |
| std::string request_url = request_headers_[":authority"].as_string() + |
| request_headers_[":path"].as_string(); |
| int response_code; |
| const Http2HeaderBlock& response_headers = response->headers(); |
| if (!ParseHeaderStatusCode(response_headers, &response_code)) { |
| auto status = response_headers.find(":status"); |
| if (status == response_headers.end()) { |
| QUIC_LOG(WARNING) |
| << ":status not present in response from cache for request " |
| << request_url; |
| } else { |
| QUIC_LOG(WARNING) << "Illegal (non-integer) response :status from cache: " |
| << status->second << " for request " << request_url; |
| } |
| SendErrorResponse(); |
| return; |
| } |
| |
| if (QuicUtils::IsServerInitiatedStreamId(session()->transport_version(), |
| id())) { |
| // A server initiated stream is only used for a server push response, |
| // and only 200 and 30X response codes are supported for server push. |
| // This behavior mirrors the HTTP/2 implementation. |
| bool is_redirection = response_code / 100 == 3; |
| if (response_code != 200 && !is_redirection) { |
| QUIC_LOG(WARNING) << "Response to server push request " << request_url |
| << " result in response code " << response_code; |
| Reset(QUIC_STREAM_CANCELLED); |
| return; |
| } |
| } |
| |
| if (!resources.empty()) { |
| QUIC_DVLOG(1) << "Stream " << id() << " found " << resources.size() |
| << " push resources."; |
| QuicSimpleServerSession* session = |
| static_cast<QuicSimpleServerSession*>(spdy_session()); |
| session->PromisePushResources(request_url, resources, id(), precedence(), |
| request_headers_); |
| } |
| |
| if (response->response_type() == QuicBackendResponse::INCOMPLETE_RESPONSE) { |
| QUIC_DVLOG(1) |
| << "Stream " << id() |
| << " sending an incomplete response, i.e. no trailer, no fin."; |
| SendIncompleteResponse(response->headers().Clone(), response->body()); |
| return; |
| } |
| |
| if (response->response_type() == QuicBackendResponse::GENERATE_BYTES) { |
| QUIC_DVLOG(1) << "Stream " << id() << " sending a generate bytes response."; |
| std::string path = request_headers_[":path"].as_string().substr(1); |
| if (!absl::SimpleAtoi(path, &generate_bytes_length_)) { |
| QUIC_LOG(ERROR) << "Path is not a number."; |
| SendNotFoundResponse(); |
| return; |
| } |
| Http2HeaderBlock headers = response->headers().Clone(); |
| headers["content-length"] = |
| quiche::QuicheTextUtils::Uint64ToString(generate_bytes_length_); |
| |
| WriteHeaders(std::move(headers), false, nullptr); |
| |
| WriteGeneratedBytes(); |
| |
| return; |
| } |
| |
| QUIC_DVLOG(1) << "Stream " << id() << " sending response."; |
| SendHeadersAndBodyAndTrailers(response->headers().Clone(), response->body(), |
| response->trailers().Clone()); |
| } |
| |
| void QuicSimpleServerStream::OnCanWrite() { |
| QuicSpdyStream::OnCanWrite(); |
| WriteGeneratedBytes(); |
| } |
| |
| void QuicSimpleServerStream::WriteGeneratedBytes() { |
| static size_t kChunkSize = 1024; |
| while (!HasBufferedData() && generate_bytes_length_ > 0) { |
| size_t len = std::min<size_t>(kChunkSize, generate_bytes_length_); |
| std::string data(len, 'a'); |
| generate_bytes_length_ -= len; |
| bool fin = generate_bytes_length_ == 0; |
| WriteOrBufferBody(data, fin); |
| } |
| } |
| |
| void QuicSimpleServerStream::SendNotFoundResponse() { |
| QUIC_DVLOG(1) << "Stream " << id() << " sending not found response."; |
| Http2HeaderBlock headers; |
| headers[":status"] = "404"; |
| headers["content-length"] = |
| quiche::QuicheTextUtils::Uint64ToString(strlen(kNotFoundResponseBody)); |
| SendHeadersAndBody(std::move(headers), kNotFoundResponseBody); |
| } |
| |
| void QuicSimpleServerStream::SendErrorResponse() { |
| SendErrorResponse(0); |
| } |
| |
| void QuicSimpleServerStream::SendErrorResponse(int resp_code) { |
| QUIC_DVLOG(1) << "Stream " << id() << " sending error response."; |
| Http2HeaderBlock headers; |
| if (resp_code <= 0) { |
| headers[":status"] = "500"; |
| } else { |
| headers[":status"] = quiche::QuicheTextUtils::Uint64ToString(resp_code); |
| } |
| headers["content-length"] = |
| quiche::QuicheTextUtils::Uint64ToString(strlen(kErrorResponseBody)); |
| SendHeadersAndBody(std::move(headers), kErrorResponseBody); |
| } |
| |
| void QuicSimpleServerStream::SendIncompleteResponse( |
| Http2HeaderBlock response_headers, |
| absl::string_view body) { |
| QUIC_DLOG(INFO) << "Stream " << id() << " writing headers (fin = false) : " |
| << response_headers.DebugString(); |
| WriteHeaders(std::move(response_headers), /*fin=*/false, nullptr); |
| |
| QUIC_DLOG(INFO) << "Stream " << id() |
| << " writing body (fin = false) with size: " << body.size(); |
| if (!body.empty()) { |
| WriteOrBufferBody(body, /*fin=*/false); |
| } |
| } |
| |
| void QuicSimpleServerStream::SendHeadersAndBody( |
| Http2HeaderBlock response_headers, |
| absl::string_view body) { |
| SendHeadersAndBodyAndTrailers(std::move(response_headers), body, |
| Http2HeaderBlock()); |
| } |
| |
| void QuicSimpleServerStream::SendHeadersAndBodyAndTrailers( |
| Http2HeaderBlock response_headers, |
| absl::string_view body, |
| Http2HeaderBlock response_trailers) { |
| // Send the headers, with a FIN if there's nothing else to send. |
| bool send_fin = (body.empty() && response_trailers.empty()); |
| QUIC_DLOG(INFO) << "Stream " << id() << " writing headers (fin = " << send_fin |
| << ") : " << response_headers.DebugString(); |
| WriteHeaders(std::move(response_headers), send_fin, nullptr); |
| if (send_fin) { |
| // Nothing else to send. |
| return; |
| } |
| |
| // Send the body, with a FIN if there's no trailers to send. |
| send_fin = response_trailers.empty(); |
| QUIC_DLOG(INFO) << "Stream " << id() << " writing body (fin = " << send_fin |
| << ") with size: " << body.size(); |
| if (!body.empty() || send_fin) { |
| WriteOrBufferBody(body, send_fin); |
| } |
| if (send_fin) { |
| // Nothing else to send. |
| return; |
| } |
| |
| // Send the trailers. A FIN is always sent with trailers. |
| QUIC_DLOG(INFO) << "Stream " << id() << " writing trailers (fin = true): " |
| << response_trailers.DebugString(); |
| WriteTrailers(std::move(response_trailers), nullptr); |
| } |
| |
| const char* const QuicSimpleServerStream::kErrorResponseBody = "bad"; |
| const char* const QuicSimpleServerStream::kNotFoundResponseBody = |
| "file not found"; |
| |
| } // namespace quic |