blob: 3db89a4cafeb0d3e78af8097c6552d23e603491a [file] [log] [blame]
// 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/tools/quic/quic_simple_server_session.h"
#include <utility>
#include "base/logging.h"
#include "base/stl_util.h"
#include "net/quic/proto/cached_network_parameters.pb.h"
#include "net/quic/quic_connection.h"
#include "net/quic/quic_flags.h"
#include "net/quic/quic_spdy_session.h"
#include "net/quic/reliable_quic_stream.h"
#include "net/tools/quic/quic_simple_server_stream.h"
#include "url/gurl.h"
using std::string;
namespace net {
QuicSimpleServerSession::QuicSimpleServerSession(
const QuicConfig& config,
QuicConnection* connection,
QuicServerSessionBase::Visitor* visitor,
QuicServerSessionBase::Helper* helper,
const QuicCryptoServerConfig* crypto_config,
QuicCompressedCertsCache* compressed_certs_cache)
: QuicServerSessionBase(config,
connection,
visitor,
helper,
crypto_config,
compressed_certs_cache),
highest_promised_stream_id_(0) {}
QuicSimpleServerSession::~QuicSimpleServerSession() {}
QuicCryptoServerStreamBase*
QuicSimpleServerSession::CreateQuicCryptoServerStream(
const QuicCryptoServerConfig* crypto_config,
QuicCompressedCertsCache* compressed_certs_cache) {
return new QuicCryptoServerStream(crypto_config, compressed_certs_cache,
FLAGS_enable_quic_stateless_reject_support,
this);
}
void QuicSimpleServerSession::StreamDraining(QuicStreamId id) {
QuicSpdySession::StreamDraining(id);
if (!IsIncomingStream(id)) {
HandlePromisedPushRequests();
}
}
void QuicSimpleServerSession::OnStreamFrame(const QuicStreamFrame& frame) {
if (!IsIncomingStream(frame.stream_id)) {
LOG(WARNING) << "Client shouldn't send data on server push stream";
connection()->CloseConnection(
QUIC_INVALID_STREAM_ID, "Client sent data on server push stream",
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
QuicSpdySession::OnStreamFrame(frame);
}
void QuicSimpleServerSession::PromisePushResources(
const string& request_url,
const std::list<QuicInMemoryCache::ServerPushInfo>& resources,
QuicStreamId original_stream_id,
const SpdyHeaderBlock& original_request_headers) {
if (!server_push_enabled()) {
return;
}
for (QuicInMemoryCache::ServerPushInfo resource : resources) {
SpdyHeaderBlock headers = SynthesizePushRequestHeaders(
request_url, resource, original_request_headers);
highest_promised_stream_id_ += 2;
SendPushPromise(original_stream_id, highest_promised_stream_id_,
headers.Clone());
promised_streams_.push_back(PromisedStreamInfo(
std::move(headers), highest_promised_stream_id_, resource.priority));
}
// Procese promised push request as many as possible.
HandlePromisedPushRequests();
}
QuicSpdyStream* QuicSimpleServerSession::CreateIncomingDynamicStream(
QuicStreamId id) {
if (!ShouldCreateIncomingDynamicStream(id)) {
return nullptr;
}
QuicSpdyStream* stream = new QuicSimpleServerStream(id, this);
ActivateStream(stream);
return stream;
}
QuicSimpleServerStream* QuicSimpleServerSession::CreateOutgoingDynamicStream(
SpdyPriority priority) {
if (!ShouldCreateOutgoingDynamicStream()) {
return nullptr;
}
QuicSimpleServerStream* stream =
new QuicSimpleServerStream(GetNextOutgoingStreamId(), this);
stream->SetPriority(priority);
ActivateStream(stream);
return stream;
}
void QuicSimpleServerSession::CloseStreamInner(QuicStreamId stream_id,
bool locally_reset) {
QuicSpdySession::CloseStreamInner(stream_id, locally_reset);
HandlePromisedPushRequests();
}
void QuicSimpleServerSession::HandleFrameOnNonexistentOutgoingStream(
QuicStreamId stream_id) {
// If this stream is a promised but not created stream (stream_id within the
// range of next_outgoing_stream_id_ and highes_promised_stream_id_),
// connection shouldn't be closed.
// Otherwise behave in the same way as base class.
if (stream_id > highest_promised_stream_id_) {
QuicSession::HandleFrameOnNonexistentOutgoingStream(stream_id);
}
}
void QuicSimpleServerSession::HandleRstOnValidNonexistentStream(
const QuicRstStreamFrame& frame) {
QuicSession::HandleRstOnValidNonexistentStream(frame);
if (!IsClosedStream(frame.stream_id)) {
// If a nonexistent stream is not a closed stream and still valid, it must
// be a locally preserved stream. Resetting this kind of stream means
// cancelling the promised server push.
// Since PromisedStreamInfo are queued in sequence, the corresponding
// index for it in promised_streams_ can be calculated.
DCHECK(frame.stream_id >= next_outgoing_stream_id());
size_t index = (frame.stream_id - next_outgoing_stream_id()) / 2;
DCHECK(index <= promised_streams_.size());
promised_streams_[index].is_cancelled = true;
connection()->SendRstStream(frame.stream_id, QUIC_RST_ACKNOWLEDGEMENT, 0);
}
}
SpdyHeaderBlock QuicSimpleServerSession::SynthesizePushRequestHeaders(
string request_url,
QuicInMemoryCache::ServerPushInfo resource,
const SpdyHeaderBlock& original_request_headers) {
GURL push_request_url = resource.request_url;
string path = push_request_url.path();
SpdyHeaderBlock spdy_headers = original_request_headers.Clone();
// :authority could be different from original request.
spdy_headers.ReplaceOrAppendHeader(":authority", push_request_url.host());
spdy_headers.ReplaceOrAppendHeader(":path", path);
// Push request always use GET.
spdy_headers.ReplaceOrAppendHeader(":method", "GET");
spdy_headers.ReplaceOrAppendHeader("referer", request_url);
spdy_headers.ReplaceOrAppendHeader(":scheme", push_request_url.scheme());
// It is not possible to push a response to a request that includes a request
// body.
spdy_headers.ReplaceOrAppendHeader("content-length", "0");
// Remove "host" field as push request is a directly generated HTTP2 request
// which should use ":authority" instead of "host".
spdy_headers.erase("host");
return spdy_headers;
}
void QuicSimpleServerSession::SendPushPromise(QuicStreamId original_stream_id,
QuicStreamId promised_stream_id,
SpdyHeaderBlock headers) {
DVLOG(1) << "stream " << original_stream_id
<< " send PUSH_PROMISE for promised stream " << promised_stream_id;
headers_stream()->WritePushPromise(original_stream_id, promised_stream_id,
std::move(headers), nullptr);
}
void QuicSimpleServerSession::HandlePromisedPushRequests() {
while (!promised_streams_.empty() && ShouldCreateOutgoingDynamicStream()) {
PromisedStreamInfo& promised_info = promised_streams_.front();
DCHECK_EQ(next_outgoing_stream_id(), promised_info.stream_id);
if (promised_info.is_cancelled) {
// This stream has been reset by client. Skip this stream id.
promised_streams_.pop_front();
GetNextOutgoingStreamId();
return;
}
QuicSimpleServerStream* promised_stream =
static_cast<QuicSimpleServerStream*>(
CreateOutgoingDynamicStream(promised_info.priority));
DCHECK(promised_stream != nullptr);
DCHECK_EQ(promised_info.stream_id, promised_stream->id());
DVLOG(1) << "created server push stream " << promised_stream->id();
SpdyHeaderBlock request_headers(std::move(promised_info.request_headers));
promised_streams_.pop_front();
promised_stream->PushResponse(std::move(request_headers));
}
}
} // namespace net