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

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: 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..76122234df3a422323cafff2c9c68bf663a4c42d 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -75,6 +75,13 @@ namespace net {
namespace {
+// Array of all network error codes. Needed for histograms.
mmenke 2014/07/22 14:57:41 Not needed. See GetAllErrorCodesForUma (declared
jonnyr 2014/07/23 08:43:04 Done.
+const int kAllNetErrorCodes[] = {
+#define NET_ERROR(label, value) - (value),
+#include "net/base/net_error_list.h"
+#undef NET_ERROR
+};
+
void ProcessAlternateProtocol(
HttpNetworkSession* session,
const HttpResponseHeaders& headers,
@@ -142,6 +149,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 +788,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 +1012,20 @@ 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 +1087,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 +1115,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 +1258,22 @@ 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)
mmenke 2014/07/22 14:57:41 Use braces when the body of an if takes more than
jonnyr 2014/07/23 08:43:04 Done.
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.HttpRetry.Cause.MainFrame",
+ -error,
+ base::CustomHistogram::ArrayToCustomRanges(
+ kAllNetErrorCodes, arraysize(kAllNetErrorCodes)));
mmenke 2014/07/22 14:57:41 Should indent lines that continue statements by 4
jonnyr 2014/07/23 08:43:04 Done.
+ else
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.HttpRetry.Cause.Subresource",
+ -error,
+ base::CustomHistogram::ArrayToCustomRanges(
+ kAllNetErrorCodes, arraysize(kAllNetErrorCodes)));
+}
+
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 +1468,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 +1511,90 @@ HttpResponseHeaders* HttpNetworkTransaction::GetResponseHeaders() const {
}
bool HttpNetworkTransaction::ShouldResendRequest() const {
- bool connection_is_proven = stream_->IsConnectionReused();
- bool has_received_headers = GetResponseHeaders() != NULL;
+ if (request_->method != "GET" || retry_attempt_ > 3)
mmenke 2014/07/22 14:57:41 The 3 should be a constant at the top of this file
mmenke 2014/07/22 14:57:41 Not retrying on POSTs when we received a connectio
jonnyr 2014/07/23 08:43:04 Done.
jonnyr 2014/07/23 08:43:04 Done.
jonnyr 2014/07/23 08:43:04 Done.
jonnyr 2014/07/23 08:43:04 Done.
+ 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/22 14:57:41 Your should have some sort of time check here. If
+ // Do not retry automatically if resource is larger than 1 Mb until byte
+ // range support is enabled.
+ if (response_.headers->GetContentLength() > 1000000)
+ return false;
-void HttpNetworkTransaction::ResetConnectionAndRequestForResend() {
- if (stream_.get()) {
- stream_->Close(true);
- stream_.reset();
+ // If etag or last-modified is set we can always retry since the responce
mmenke 2014/07/22 14:57:41 response
jonnyr 2014/07/23 08:43:04 Done.
+ // can be verified.
+ if (response_.headers->HasHeader("etag") ||
+ response_.headers->HasHeader("Last-Modified"))
+ return true;
mmenke 2014/07/22 14:57:41 Use braces when the condition of an if statement t
jonnyr 2014/07/23 08:43:04 Done.
+
+ if (response_.headers->HasHeaderValue("cache-control", "no-cache") ||
+ response_.headers->HasHeaderValue("pragma", "no-cache"))
+ return false;
mmenke 2014/07/22 14:57:41 Not a big fan of having all this cache logic dupli
jonnyr 2014/07/23 08:43:04 I did not like it either. I also missed must-reval
mmenke 2014/07/23 15:08:40 I'm not at all familiar with caching semantics or
+
+ TimeDelta max_age_value;
+ if (response_.headers->GetMaxAgeValue(&max_age_value))
+ return max_age_value.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.
+ Time date_value;
+ if (!response_.headers->GetDateValue(&date_value))
+ return false;
+
+ Time expires_value;
+ if (response_.headers->GetExpiresValue(&expires_value)) {
+ max_age_value = expires_value - date_value;
+ if (max_age_value.InSeconds() > 60)
+ return true;
+ }
+ return false;
}
+ return true;
+}
+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())
+ 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