blob: 55b9bc7c256f7faebab439c070f0cb01c43b6491 [file] [log] [blame]
// Copyright 2014 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVTOOLS_GOMA_CLIENT_HTTP_H_
#define DEVTOOLS_GOMA_CLIENT_HTTP_H_
#include <atomic>
#include <deque>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#ifndef _WIN32
#include <arpa/inet.h>
#include <sys/socket.h>
#else
#include "socket_helper_win.h"
#endif
#include <json/json.h>
#include "absl/base/thread_annotations.h"
#include "absl/strings/string_view.h"
#include "basictypes.h"
#include "base/compiler_specific.h"
#include "compress_util.h"
#include "gtest/gtest_prod.h"
MSVC_PUSH_DISABLE_WARNING_FOR_PROTO()
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
MSVC_POP_WARNING()
#include "http_util.h"
#include "lockhelper.h"
#include "luci_context.h"
#include "oauth2.h"
#include "tls_engine.h"
#include "worker_thread_manager.h"
using std::string;
namespace devtools_goma {
class Descriptor;
class Histogram;
class HttpRequest;
class HttpResponse;
class HttpRPCStats;
class OAuth2AccessTokenRefreshTask;
class OneshotClosure;
class SocketFactory;
// HttpClient is a HTTP client. It sends HttpRequest on Descriptor
// generated by SocketFactory and TLSEngineFactory, and receives
// the response in HttpResponse.
class HttpClient {
public:
struct Options {
Options();
string dest_host_name;
int dest_port;
string proxy_host_name;
int proxy_port;
string extra_params;
string authorization;
string cookie;
bool capture_response_header;
string url_path_prefix;
string http_host_name;
bool use_ssl;
string ssl_extra_cert;
string ssl_extra_cert_data;
int ssl_crl_max_valid_duration;
double socket_read_timeout_sec;
int min_retry_backoff_ms;
int max_retry_backoff_ms;
OAuth2Config oauth2_config;
string gce_service_account;
string service_account_json_filename;
LuciContextAuth luci_context_auth;
bool fail_fast;
int network_error_margin;
int network_error_threshold_percent;
// Allows throttling if this is true.
bool allow_throttle;
bool reuse_connection;
bool InitFromURL(absl::string_view url);
string SocketHost() const;
int SocketPort() const;
string RequestURL(absl::string_view path) const;
string Host() const;
string DebugString() const;
void ClearAuthConfig();
};
// Status is used for each HTTP transaction.
// Caller can specify
// - timeout_should_be_http_error
// - timeouts.
// The other fields are filled by HttpClient.
// Once it is passed to HttpClient, caller should not access
// all fields, except finished, until finished becomes true.
struct Status {
enum State {
// Running state. If failed in some step, State would be kept as-is.
// Then, caller of HttpClient can know where HttpClient failed.
INIT,
PENDING,
SENDING_REQUEST,
REQUEST_SENT,
RECEIVING_RESPONSE,
RESPONSE_RECEIVED,
};
static absl::string_view StateName(State state);
Status();
// HACK: to provide copy constructor of std::atomic<bool>.
struct AtomicBool {
std::atomic<bool> value;
AtomicBool(bool b) : value(b) {} // NOLINT
AtomicBool(const AtomicBool& b) : value(b.value.load()) {}
AtomicBool& operator=(const AtomicBool& b) {
value = b.value.load();
return *this;
}
AtomicBool& operator=(bool b) {
value = b;
return *this;
}
operator bool() const {
return value.load();
}
};
State state;
// If true, timeout is treated as http error (default).
bool timeout_should_be_http_error;
std::deque<int> timeout_secs;
// Whether connect() was successful for this request.
bool connect_success;
// Whether RPC was finished or not.
AtomicBool finished;
// Result of RPC for CallWithAsync. OK=success, or error code.
int err;
string err_message;
// Become false if http is disabled with failnow().
bool enabled;
int http_return_code;
string response_header;
// size of message on http (maybe compressed).
size_t req_size;
size_t resp_size;
// size of serialized message (not compressed).
// for now, it only for proto messages on HttpRPC.
// TODO: set this for compressed test message or so.
size_t raw_req_size;
size_t raw_resp_size;
// in milliseconds.
int throttle_time;
int pending_time;
int req_build_time;
int req_send_time;
int wait_time;
int resp_recv_time;
int resp_parse_time;
int num_retry;
int num_throttled;
int num_connect_failed;
string trace_id;
string master_trace_id; // master request in multi http rpc.
string DebugString() const;
};
enum ConnectionCloseState {
NO_CLOSE,
NORMAL_CLOSE,
ERROR_CLOSE,
};
// NetworkErrorMonitor can be attached to HttpClient.
// When network error is detected, or network is recovered,
// corresponding method will be called.
// These methods will be called with under mu_ is locked
// to be called in serial.
class NetworkErrorMonitor {
public:
virtual ~NetworkErrorMonitor() {}
// Called when http request was not succeeded.
virtual void OnNetworkErrorDetected() = 0;
// Called when http request was succeeded after network error started.
virtual void OnNetworkRecovered() = 0;
};
// Request is a request of HTTP transaction.
class Request {
public:
Request();
virtual ~Request();
void Init(const string& method, const string& path,
const Options& options);
void SetMethod(const string& method);
void SetRequestPath(const string& path);
const string& request_path() const { return request_path_; }
void SetHost(const string& host);
void SetContentType(const string& content_type);
void SetAuthorization(const string& authorization);
void SetCookie(const string& cookie);
void AddHeader(const string& key, const string& value);
// Clone returns clone of this Request.
virtual std::unique_ptr<Request> Clone() const = 0;
// Returns stream of the request message.
virtual std::unique_ptr<google::protobuf::io::ZeroCopyInputStream>
NewStream() const = 0;
protected:
// CreateHeader creates a header line.
static string CreateHeader(absl::string_view key, absl::string_view value);
// BuildHeader creates HTTP request message with additional headers.
// If content_length >= 0, set Content-Length: header.
// If content_length < 0, header should include
// "Transfer-Encoding: chunked" and NewStream should provide
// chunked-body.
string BuildHeader(
const std::vector<string>& headers, int content_length) const;
private:
string method_;
string request_path_;
string host_;
string content_type_;
string authorization_;
string cookie_;
std::vector<string> headers_;
DISALLOW_ASSIGN(Request);
};
// Response is a response of HTTP transaction.
class Response {
public:
// Body receives http response body.
// Body parses Transfer-Encoding (i.e. chunked),
// and Content-Encoding (e.g. deflate).
class Body {
public:
enum class State {
Error = -1,
Ok = 0,
Incomplete = 1,
};
Body() = default;
virtual ~Body() = default;
// Next obtains a buffer into which data can be written.
// Any data written into this buffer will be parsed accoding to
// Tarnsfer-Encoding and Content-Encoding.
// Ownership of buffer remains to the Body, and the buffer remains
// valid until some other method of Body is called or
// Body is destroyed.
// Different from ZeroCopyOutputStream, *body_size never be 0.
virtual void Next(char** buf, int* buf_size) = 0;
// Process processes data stored in the buffer returned by the
// last Next call at most data_size bytes.
// Data in the buffer after data_size bytes will be ignored,
// and may be reused in the next Next call (or not).
// If data_size == 0, it means EOF.
// If data_size is negative, it means error and must return
// State::Error.
virtual State Process(int data_size) = 0;
// Returns the total number of bytes written.
virtual size_t ByteCount() const = 0;
};
Response();
virtual ~Response();
bool HasHeader() const;
absl::string_view Header() const;
// HttpClient will use the following methods to receive HTTP response.
void SetRequestPath(const string& path);
void SetTraceId(const string& trace_id);
void Reset();
// Buffer returns a buffer pointer and buffer's size.
// Received data should be filled in buf[0..buf_size), and call
// Recv with number data received in the buffer.
void Buffer(char** buf, int* buf_size);
// Recv receives r bytes in the buffer specified by Buffer().
// Returns true if all HTTP response is received so ready to parse.
// Returns false if more data is needed to parse response.
bool Recv(int r);
// Parse parses a HTTP response message.
void Parse();
// Number of bytes already received.
size_t len() const { return len_; }
// Maximum buffer size at the moment.
// HttpResponse grows buffer size in Buffer if necessary.
size_t buffer_size() const { return buffer_.size(); }
// status_code reports HTTP status code.
int status_code() const { return status_code_; }
// result reports transaction results. OK or FAIL.
int result() const { return result_; }
const string& err_message() const { return err_message_; }
// represents whether response has 'Connection: close' header.
bool HasConnectionClose() const;
protected:
// ParseBody parses body.
// if error occured, updates result_, err_message_.
virtual void ParseBody() = 0;
// called to initialize body_.
// subclass must own Body. Body should be valid until next NewBody
// call or Response is destroyed.
virtual Body* NewBody(
size_t content_length, bool is_chunked,
EncodingType encoding_type) = 0;
int result_;
string err_message_;
string trace_id_;
private:
// BodyRecv receives r bytes in body.
// Returns true if no more data needed.
// Returns false if need more data.
bool BodyRecv(int r);
string request_path_;
string buffer_; // whole buffer
size_t len_ = 0UL; // received length in buffer_
size_t body_offset_ = 0UL; // position to start response body in buffer_
// body becomes non nullptr when start receiving response body.
Body *body_ = nullptr;
int status_code_ = 0;
DISALLOW_COPY_AND_ASSIGN(Response);
};
static std::unique_ptr<SocketFactory> NewSocketFactoryFromOptions(
const Options& options);
static std::unique_ptr<TLSEngineFactory> NewTLSEngineFactoryFromOptions(
const Options& options);
// HttpClient is a http client to a specific server.
// Takes ownership of socket_factory and tls_engine_factory.
// It doesn't take ownership of wm.
HttpClient(std::unique_ptr<SocketFactory> socket_factory,
std::unique_ptr<TLSEngineFactory> tls_engine_factory,
const Options& options,
WorkerThreadManager* wm);
~HttpClient();
// Initializes Request for method and path.
void InitHttpRequest(
Request* req, const string& method, const string& path) const;
// Do performs a HTTP transaction.
// Caller have ownership of req, resp and status.
// This is synchronous call.
void Do(const Request* req, Response* resp, Status* status);
// DoAsync initiates a HTTP transaction.
// Caller have ownership of req, resp and status, until callback is called
// (if callback is not NULL) or status->finished becomes true (if callback
// is NULL).
void DoAsync(const Request* req, Response* resp,
Status* status,
OneshotClosure* callback);
// Wait waits for a HTTP transaction initiated by DoAsync with callback=NULL.
void Wait(Status* status);
// Shutdown the client. all on-the-fly requests will fail.
void Shutdown();
bool shutting_down() const;
// ramp_up return [0, 100].
// ramp_up == 0 means 0% of requests could be sent.
// ramp_up == 100 means 100% of requests could be sent.
// when !enabled(), it returns 0.
// when enabled_from_ == 0, it returns 100.
int ramp_up() const;
// IsHealthyRecently returns false if more than given percentage
// (via options_.network_error_threshold_percent) of http requests in
// last 3 seconds having status code other than 200.
bool IsHealthyRecently();
string GetHealthStatusMessage() const;
// Prefer to use IsHealthyRecently instead of IsHealthy to judge
// network is healthy or not. HTTP status could be temporarily unhealthy,
// but we prefer to ignore the case.
bool IsHealthy() const;
// Get email address to login with oauth2.
string GetAccount();
bool GetOAuth2Config(OAuth2Config* config) const;
bool SetOAuth2Config(const OAuth2Config& config);
string DebugString() const;
void DumpToJson(Json::Value* json) const;
void DumpStatsToProto(HttpRPCStats* stats) const;
// options used to construct this client.
// Note that oauth2_config might have been updated and differ from this one.
// Use GetOAuth2Config above.
const Options& options() const { return options_; }
// Calculate next backoff msec.
// prev_backoff_msec must be positive.
static int BackoffMsec(
const Options& option, int prev_backoff_msec, bool in_error);
// public for HttpRPC ping.
void IncNumActive();
void DecNumActive();
// Provided for test that checks socket_pool status.
// A test should wait all in-flight tasks land.
void WaitNoActive();
int UpdateHealthStatusMessageForPing(
const Status& status, int round_trip_time);
// NetworkErrorStartedTime return a time network error started.
// Returns 0 if no error occurred recently.
// The time will be set on fatal http error (302, 401, 403) and when
// no socket in socket pool is available to connect to the host.
// The time will be cleared when HttpClient get 2xx response.
time_t NetworkErrorStartedTime() const;
// Takes the ownership.
void SetMonitor(std::unique_ptr<NetworkErrorMonitor> monitor);
static const char kGomaLength[];
private:
class Task;
friend class Task;
struct TrafficStat {
TrafficStat();
int read_byte;
int write_byte;
int query;
int http_err;
};
typedef std::deque<TrafficStat> TrafficHistory;
// NetworkErrorStatus checks the network error is continued
// from the previous error or not.
// Thread-unsafe, must be guarded by mutex.
class NetworkErrorStatus {
public:
explicit NetworkErrorStatus(int margin)
: error_recover_margin_(margin),
error_started_time_(0),
error_until_(0) {}
// Returns the network error started time.
// 0 if network is not in the error state.
time_t NetworkErrorStartedTime() const { return error_started_time_; }
time_t NetworkErrorUntil() const { return error_until_; }
// Call this when the network access was error.
// Returns true if a new network error is detected.
// This will convert level trigger to edge trigger.
bool OnNetworkErrorDetected(time_t now);
// Call this when network access was not error.
// Even this called, we keep the error until |error_until_|.
// Returns true if the network is really recovered.
// This will convert level trigger to edge trigger.
bool OnNetworkRecovered(time_t now);
private:
const int error_recover_margin_;
// 0 if network is not in the error state. Otherwise, time when the network
// error has started.
time_t error_started_time_;
// Even we get the 2xx http status, we consider the network is still
// in the error state until this time.
time_t error_until_;
};
// |may_retry| is provided for initial ping.
Descriptor* NewDescriptor();
void ReleaseDescriptor(Descriptor* d, ConnectionCloseState close_state);
double EstimatedRecvTime(size_t bytes);
string GetOAuth2Authorization() const;
bool ShouldRefreshOAuth2AccessToken() const;
void RunAfterOAuth2AccessTokenGetReady(
WorkerThreadManager::ThreadId thread_id,
OneshotClosure* callback);
void UpdateBackoffUnlocked(bool in_error) EXCLUSIVE_LOCKS_REQUIRED(mu_);
// Returns time to wait in the queue. If returns 0, no need to wait.
int TryStart();
void IncNumPending();
void DecNumPending();
// Returns milliseconds time to wait in the queue on error.
int GetRandomizeBackoffTimeInMs();
// return true if shutting_down or disabled.
bool failnow() const;
void IncReadByte(int n);
void IncWriteByte(int n);
void UpdateStats(const Status& status);
void UpdateTrafficHistory();
void NetworkErrorDetectedUnlocked() EXCLUSIVE_LOCKS_REQUIRED(mu_);
void NetworkRecoveredUnlocked() EXCLUSIVE_LOCKS_REQUIRED(mu_);
void UpdateStatusCodeHistoryUnlocked() EXCLUSIVE_LOCKS_REQUIRED(mu_);
void AddStatusCodeHistoryUnlocked(int status_code)
EXCLUSIVE_LOCKS_REQUIRED(mu_);
const Options options_;
std::unique_ptr<TLSEngineFactory> tls_engine_factory_;
std::unique_ptr<SocketFactory> socket_pool_;
std::unique_ptr<OAuth2AccessTokenRefreshTask> oauth_refresh_task_;
WorkerThreadManager* wm_;
mutable Lock mu_;
ConditionVariable cond_; // signaled when num_active_ is 0.
string health_status_ GUARDED_BY(mu_);
bool shutting_down_ GUARDED_BY(mu_);
std::deque<std::pair<time_t, int>> recent_http_status_code_ GUARDED_BY(mu_);
size_t bad_status_num_in_recent_http_ GUARDED_BY(mu_);
std::unique_ptr<NetworkErrorMonitor> monitor_ GUARDED_BY(mu_);
// Checking network error state. When we get fatal http error
// defined in IsFatalNetworkErrorCode(), or when no socket in socket pool is
// available to connect to the host, we consider the network error.
// When we get 2xx HTTP responses for specified duration, we consider
// the network is recovered.
// For the other error, this does not care.
NetworkErrorStatus network_error_status_ GUARDED_BY(mu_);;
int num_query_ GUARDED_BY(mu_);
int num_active_ GUARDED_BY(mu_);
int total_pending_ GUARDED_BY(mu_);
int peak_pending_ GUARDED_BY(mu_);
int num_pending_ GUARDED_BY(mu_);
int num_http_retry_ GUARDED_BY(mu_);
int num_http_timeout_ GUARDED_BY(mu_);
int num_http_error_ GUARDED_BY(mu_);
size_t total_write_byte_ GUARDED_BY(mu_);
size_t total_read_byte_ GUARDED_BY(mu_);
size_t num_writable_ GUARDED_BY(mu_);
size_t num_readable_ GUARDED_BY(mu_);
std::unique_ptr<Histogram> read_size_ GUARDED_BY(mu_);
std::unique_ptr<Histogram> write_size_ GUARDED_BY(mu_);
size_t total_resp_byte_ GUARDED_BY(mu_);
long total_resp_time_ GUARDED_BY(mu_); // msec.
int ping_http_return_code_ GUARDED_BY(mu_);
int ping_round_trip_time_ms_ GUARDED_BY(mu_);
std::map<int, int> num_http_status_code_ GUARDED_BY(mu_);
TrafficHistory traffic_history_ GUARDED_BY(mu_);
PeriodicClosureId traffic_history_closure_id_ GUARDED_BY(mu_);
int retry_backoff_ms_;
// if enabled_from_ > 0,
// t < enabled_from, then it will be disabled,
// enabled_from <= t, then it is in ramp up period
// where t=time().
// if enabled_from_ == 0, it is enabled (without checking time()).
time_t enabled_from_ GUARDED_BY(mu_);
int num_network_error_ GUARDED_BY(mu_);
int num_network_recovered_ GUARDED_BY(mu_);
FRIEND_TEST(NetworkErrorStatus, BasicTest);
DISALLOW_COPY_AND_ASSIGN(HttpClient);
};
// HttpRequest is a request of HTTP transaction.
class HttpRequest : public HttpClient::Request {
public:
HttpRequest();
~HttpRequest() override;
// TODO: set body stream producer instead of string.
void SetBody(const string& body);
std::unique_ptr<google::protobuf::io::ZeroCopyInputStream>
NewStream() const override;
std::unique_ptr<HttpClient::Request> Clone() const override {
return std::unique_ptr<HttpClient::Request>(new HttpRequest(*this));
}
private:
string body_;
DISALLOW_ASSIGN(HttpRequest);
};
// HttpResponse is a response of HTTP transaction.
class HttpResponse : public HttpClient::Response {
public:
class Body : public HttpClient::Response::Body {
public:
Body(size_t content_length, bool is_chunked, EncodingType encoding_type);
~Body() override = default;
void Next(char** buf, int* buf_size) override;
State Process(int data_size) override;
size_t ByteCount() const override { return len_; }
std::unique_ptr<google::protobuf::io::ZeroCopyInputStream>
ParsedStream() const;
private:
const size_t content_length_;
std::unique_ptr<HttpChunkParser> chunk_parser_;
const EncodingType encoding_type_;
// buffer_ holds receiving data.
// each char[] has kNetworkBufSize.
// it uses std::unique_ptr<char[]> to avoid relocation of backing array.
// [0, len_) is processed data, chunks_ would point several areas
// in this region.
// [len_, end) is in last char[] in buffer_
// returned by Next to receive body data.
std::vector<std::unique_ptr<char[]>> buffer_;
size_t len_ = 0;
std::vector<absl::string_view> chunks_;
};
HttpResponse();
~HttpResponse() override;
const string& parsed_body() const { return parsed_body_; }
HttpClient::Response::Body* NewBody(
size_t content_length, bool is_chunked,
EncodingType encoding_type) override;
protected:
// ParseBody parses body.
// if error occured, updates result_, err_message_.
void ParseBody() override;
private:
std::unique_ptr<Body> response_body_;
string parsed_body_; // dechunked and uncompressed
DISALLOW_COPY_AND_ASSIGN(HttpResponse);
};
// StringInputStream is helper for ArrayInputStream.
// It owns input string, so no need for caller to own the string
// along with input stream.
class StringInputStream : public google::protobuf::io::ZeroCopyInputStream {
public:
explicit StringInputStream(string data);
~StringInputStream() override = default;
bool Next(const void** data, int* size) override {
return stream_->Next(data, size);
}
void BackUp(int count) override { stream_->BackUp(count); }
bool Skip(int count) override { return stream_->Skip(count); }
google::protobuf::int64 ByteCount() const override {
return stream_->ByteCount();
}
private:
const string data_;
std::unique_ptr<google::protobuf::io::ArrayInputStream> stream_;
};
// ChainedInputStream is similar with ContatinatingInputStream,
// but it owns all underlying input streams.
class ChainedInputStream
: public google::protobuf::io::ZeroCopyInputStream {
public:
explicit ChainedInputStream(
std::vector<
std::unique_ptr<google::protobuf::io::ZeroCopyInputStream>> s);
~ChainedInputStream() override = default;
bool Next(const void** data, int* size) override {
return stream_->Next(data, size);
}
void BackUp(int count) override { stream_->BackUp(count); }
bool Skip(int count) override { return stream_->Skip(count); }
google::protobuf::int64 ByteCount() const override {
return stream_->ByteCount();
}
private:
std::vector<std::unique_ptr<google::protobuf::io::ZeroCopyInputStream>>
streams_;
std::vector<google::protobuf::io::ZeroCopyInputStream*> streams_array_;
std::unique_ptr<google::protobuf::io::ConcatenatingInputStream> stream_;
};
} // namespace devtools_goma
#endif // DEVTOOLS_GOMA_CLIENT_HTTP_H_