Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(13)

Unified Diff: net/http/http_network_transaction.cc

Issue 403393003: HTTP retry support. Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixed according to comments. Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/http/http_network_transaction.cc
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index f4df073b4bba5ad905631653d2cba46820c0daa7..b3f7391a059421382eb600d37713d8e6c38e61a1 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -75,6 +75,8 @@ namespace net {
namespace {
+const int kNumRetries = 3;
+
void ProcessAlternateProtocol(
HttpNetworkSession* session,
const HttpResponseHeaders& headers,
@@ -142,6 +144,9 @@ HttpNetworkTransaction::HttpNetworkTransaction(RequestPriority priority,
request_headers_(),
read_buf_len_(0),
total_received_bytes_(0),
+ retry_attempt_(0),
+ offset_(0),
+ previous_content_length_(0),
next_state_(STATE_NONE),
establishing_tunnel_(false),
websocket_handshake_stream_base_create_helper_(NULL) {
@@ -778,6 +783,8 @@ int HttpNetworkTransaction::DoInitStream() {
int HttpNetworkTransaction::DoInitStreamComplete(int result) {
if (result == OK) {
+ if (offset_)
+ stream_->SetRestartInfo(offset_, hash_, sizeof(hash_));
next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
} else {
if (result < 0)
@@ -1000,6 +1007,21 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) {
DCHECK(response_.headers.get());
+ if ((response_.headers->response_code() == 200 ||
+ response_.headers->response_code() == 206) && offset_) {
+ std::string etag, last_modified;
+ response_.headers->EnumerateHeader(NULL, "last-modified", &last_modified);
+ if (response_.headers->GetHttpVersion() >= HttpVersion(1, 1))
+ response_.headers->EnumerateHeader(NULL, "etag", &etag);
+
+ // Return an error if content was updated on a retry.
+ if ((previous_content_length_ != response_.headers->GetContentLength() &&
+ response_.headers->response_code() == 200) ||
+ previous_etag_ != etag || previous_last_modified_ != last_modified) {
+ return HandleIOError(ERR_RETRY_CONTENT_UPDATED);
+ }
+ }
+
// On a 408 response from the server ("Request Timeout") on a stale socket,
// retry the request.
// Headers can be NULL because of http://crbug.com/384554.
@@ -1061,6 +1083,9 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) {
headers_valid_ = true;
+ if (retry_attempt_ && read_buf_.get())
+ next_state_ = STATE_READ_BODY;
+
if (session_->huffman_aggregator()) {
session_->huffman_aggregator()->AggregateTransactionCharacterCounts(
*request_,
@@ -1086,6 +1111,8 @@ int HttpNetworkTransaction::DoReadBodyComplete(int result) {
bool done = false;
if (result <= 0) {
DCHECK_NE(ERR_IO_PENDING, result);
+ if (result < 0)
+ return HandleIOError(result);
done = true;
}
@@ -1227,6 +1254,21 @@ void HttpNetworkTransaction::LogTransactionMetrics() const {
}
}
+void HttpNetworkTransaction::LogTransactionRetryMetrics(int error) const {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.HttpRetry.Count", retry_attempt_, 1, 5, 5);
+ if (request_->load_flags & LOAD_MAIN_FRAME) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.HttpRetry.Cause.MainFrame",
+ -error,
+ GetAllErrorCodesForUma());
+ } else {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.HttpRetry.Cause.Subresource",
+ -error,
+ GetAllErrorCodesForUma());
+ }
+}
+
int HttpNetworkTransaction::HandleCertificateRequest(int error) {
// There are two paths through which the server can request a certificate
// from us. The first is during the initial handshake, the second is
@@ -1421,6 +1463,8 @@ int HttpNetworkTransaction::HandleIOError(int error) {
if (ShouldResendRequest()) {
net_log_.AddEventWithNetErrorCode(
NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, error);
+ retry_attempt_++;
+ LogTransactionRetryMetrics(error);
ResetConnectionAndRequestForResend();
error = OK;
}
@@ -1462,27 +1506,101 @@ HttpResponseHeaders* HttpNetworkTransaction::GetResponseHeaders() const {
}
bool HttpNetworkTransaction::ShouldResendRequest() const {
- bool connection_is_proven = stream_->IsConnectionReused();
- bool has_received_headers = GetResponseHeaders() != NULL;
+ if (retry_attempt_ > kNumRetries)
+ return false;
- // NOTE: we resend a request only if we reused a keep-alive connection.
- // This automatically prevents an infinite resend loop because we'll run
- // out of the cached keep-alive connections eventually.
- if (connection_is_proven && !has_received_headers)
- return true;
- return false;
-}
+ if (headers_valid_) {
mmenke 2014/07/23 20:17:42 If there a reason the old GetResponseHeaders() !=
jonnyr 2014/07/25 13:41:59 Done.
+ // Do not attempt a retry on anything but GET request if headers are valid.
rvargas (doing something else) 2014/07/24 19:09:47 How do we know that nothing actually reached the s
jonnyr 2014/07/25 13:41:59 Right, this will have to be removed.
+ if (request_->method != "GET")
+ return false;
-void HttpNetworkTransaction::ResetConnectionAndRequestForResend() {
- if (stream_.get()) {
- stream_->Close(true);
- stream_.reset();
+ TimeDelta time_delta = base::TimeTicks::Now() - send_start_time_;
+ if (time_delta.InSeconds() > 30)
+ return true;
+
+ // Do not retry automatically if resource is larger than 1 Mb until byte
+ // range support is enabled. This is temporary.
+ if (response_.headers->GetContentLength() > 1000000)
rvargas (doing something else) 2014/07/24 19:09:47 Seems like this should go before the 30 seconds ru
jonnyr 2014/07/25 13:41:59 Done.
+ return false;
+
+ // If etag or last-modified is set we can always retry since the response
+ // can be verified.
+ if (response_.headers->HasHeader("etag") ||
rvargas (doing something else) 2014/07/24 19:09:47 use HasStrongValidators()
jonnyr 2014/07/25 13:41:59 Done.
+ response_.headers->HasHeader("Last-Modified")) {
+ return true;
+ }
+
+ if (response_.headers->HasHeaderValue("cache-control", "no-cache") ||
rvargas (doing something else) 2014/07/24 19:09:47 why fail if no-cache or must-revalidate?
jonnyr 2014/07/25 13:41:59 What I am trying to avoid is that we glue together
rvargas (doing something else) 2014/07/26 00:30:33 hmm... I don't think it is safe to remove the hash
+ response_.headers->HasHeaderValue("cache-control", "no-store") ||
+ response_.headers->HasHeaderValue("cache-control", "must-revalidate") ||
+ response_.headers->HasHeaderValue("pragma", "no-cache")) {
+ return false;
+ }
+
+ if (response_.headers->GetMaxAgeValue(&time_delta))
+ return time_delta.InSeconds() > 60;
+
+ // If there is no Date header, then assume that the server response was
+ // generated at the time when we received the response, hence do not retry.
rvargas (doing something else) 2014/07/24 19:09:48 why not? If it actually differs, we'll catch that
jonnyr 2014/07/25 13:41:59 The intention is to remove the hash.
+ Time date_value;
+ if (!response_.headers->GetDateValue(&date_value))
+ return false;
+
+ Time expires_value;
+ if (response_.headers->GetExpiresValue(&expires_value)) {
+ time_delta = expires_value - date_value;
+ if (time_delta.InSeconds() > 60)
+ return true;
+ }
rvargas (doing something else) 2014/07/24 19:09:47 Most of this logic should probably be moved to a m
jonnyr 2014/07/25 13:41:59 That I can do. Do you think the overall strategy a
rvargas (doing something else) 2014/07/26 00:30:33 Yeah, I think that avoid merging by using a hash m
+ return false;
}
+ return true;
mmenke 2014/07/23 20:17:42 Out of paranoia, think we should keep the only ret
jonnyr 2014/07/25 13:41:59 Agreed.
+}
+void HttpNetworkTransaction::ResetConnectionAndRequestForResend() {
// We need to clear request_headers_ because it contains the real request
// headers, but we may need to resend the CONNECT request first to recreate
// the SSL tunnel.
request_headers_.Clear();
+ headers_valid_ = false;
+
+ if (stream_) {
+ total_received_bytes_ += stream_->GetTotalReceivedBytes();
+ if (stream_->GetReceivedBodyLength() > offset_) {
+ offset_ = stream_->GetReceivedBodyLength();
+ stream_->GetHash(hash_, sizeof(hash_));
+ }
+ }
+
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ if (offset_ && headers) {
+ previous_content_length_ = headers->GetContentLength();
+ headers->EnumerateHeader(NULL, "last-modified",
+ &previous_last_modified_);
+ if (headers->GetHttpVersion() >= HttpVersion(1, 1))
+ headers->EnumerateHeader(NULL, "etag", &previous_etag_);
+
+ // Disable until we have statistics that show that retrying does not
+ // result in too many ERR_RETRY_HASH_MISMATCH.
+ if (0 && previous_content_length_ &&
+ headers->HasHeaderValue("Accept-Ranges", "bytes")) {
+ // Try resending using a range request if supported.
+ request_headers_.SetHeader(HttpRequestHeaders::kRange,
+ "bytes=" + base::Uint64ToString(offset_) + "-");
+ if (!previous_last_modified_.empty())
+ request_headers_.SetHeader("If-Unmodified-Since",
+ previous_last_modified_);
+ if (!previous_etag_.empty())
rvargas (doing something else) 2014/07/24 19:09:47 I suggest checking this one before last-modified a
jonnyr 2014/07/25 13:41:59 Done.
+ request_headers_.SetHeader("If-Match", previous_etag_);
+ }
+ }
+
+ response_ = HttpResponseInfo();
+ establishing_tunnel_ = false;
+ if (stream_.get()) {
+ stream_->Close(true);
+ stream_.reset();
+ }
next_state_ = STATE_CREATE_STREAM; // Resend the request.
}

Powered by Google App Engine
This is Rietveld 408576698