You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
358 lines
13 KiB
358 lines
13 KiB
// Copyright 2014 The Chromium OS 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 <brillo/http/http_request.h>
|
|
|
|
#include <base/bind.h>
|
|
#include <base/callback.h>
|
|
#include <base/logging.h>
|
|
#include <brillo/http/http_form_data.h>
|
|
#include <brillo/map_utils.h>
|
|
#include <brillo/mime_utils.h>
|
|
#include <brillo/streams/memory_stream.h>
|
|
#include <brillo/strings/string_utils.h>
|
|
|
|
namespace brillo {
|
|
namespace http {
|
|
|
|
// request_type
|
|
const char request_type::kOptions[] = "OPTIONS";
|
|
const char request_type::kGet[] = "GET";
|
|
const char request_type::kHead[] = "HEAD";
|
|
const char request_type::kPost[] = "POST";
|
|
const char request_type::kPut[] = "PUT";
|
|
const char request_type::kPatch[] = "PATCH";
|
|
const char request_type::kDelete[] = "DELETE";
|
|
const char request_type::kTrace[] = "TRACE";
|
|
const char request_type::kConnect[] = "CONNECT";
|
|
const char request_type::kCopy[] = "COPY";
|
|
const char request_type::kMove[] = "MOVE";
|
|
|
|
// request_header
|
|
const char request_header::kAccept[] = "Accept";
|
|
const char request_header::kAcceptCharset[] = "Accept-Charset";
|
|
const char request_header::kAcceptEncoding[] = "Accept-Encoding";
|
|
const char request_header::kAcceptLanguage[] = "Accept-Language";
|
|
const char request_header::kAllow[] = "Allow";
|
|
const char request_header::kAuthorization[] = "Authorization";
|
|
const char request_header::kCacheControl[] = "Cache-Control";
|
|
const char request_header::kConnection[] = "Connection";
|
|
const char request_header::kContentEncoding[] = "Content-Encoding";
|
|
const char request_header::kContentLanguage[] = "Content-Language";
|
|
const char request_header::kContentLength[] = "Content-Length";
|
|
const char request_header::kContentLocation[] = "Content-Location";
|
|
const char request_header::kContentMd5[] = "Content-MD5";
|
|
const char request_header::kContentRange[] = "Content-Range";
|
|
const char request_header::kContentType[] = "Content-Type";
|
|
const char request_header::kCookie[] = "Cookie";
|
|
const char request_header::kDate[] = "Date";
|
|
const char request_header::kExpect[] = "Expect";
|
|
const char request_header::kExpires[] = "Expires";
|
|
const char request_header::kFrom[] = "From";
|
|
const char request_header::kHost[] = "Host";
|
|
const char request_header::kIfMatch[] = "If-Match";
|
|
const char request_header::kIfModifiedSince[] = "If-Modified-Since";
|
|
const char request_header::kIfNoneMatch[] = "If-None-Match";
|
|
const char request_header::kIfRange[] = "If-Range";
|
|
const char request_header::kIfUnmodifiedSince[] = "If-Unmodified-Since";
|
|
const char request_header::kLastModified[] = "Last-Modified";
|
|
const char request_header::kMaxForwards[] = "Max-Forwards";
|
|
const char request_header::kPragma[] = "Pragma";
|
|
const char request_header::kProxyAuthorization[] = "Proxy-Authorization";
|
|
const char request_header::kRange[] = "Range";
|
|
const char request_header::kReferer[] = "Referer";
|
|
const char request_header::kTE[] = "TE";
|
|
const char request_header::kTrailer[] = "Trailer";
|
|
const char request_header::kTransferEncoding[] = "Transfer-Encoding";
|
|
const char request_header::kUpgrade[] = "Upgrade";
|
|
const char request_header::kUserAgent[] = "User-Agent";
|
|
const char request_header::kVia[] = "Via";
|
|
const char request_header::kWarning[] = "Warning";
|
|
|
|
// response_header
|
|
const char response_header::kAcceptRanges[] = "Accept-Ranges";
|
|
const char response_header::kAge[] = "Age";
|
|
const char response_header::kAllow[] = "Allow";
|
|
const char response_header::kCacheControl[] = "Cache-Control";
|
|
const char response_header::kConnection[] = "Connection";
|
|
const char response_header::kContentEncoding[] = "Content-Encoding";
|
|
const char response_header::kContentLanguage[] = "Content-Language";
|
|
const char response_header::kContentLength[] = "Content-Length";
|
|
const char response_header::kContentLocation[] = "Content-Location";
|
|
const char response_header::kContentMd5[] = "Content-MD5";
|
|
const char response_header::kContentRange[] = "Content-Range";
|
|
const char response_header::kContentType[] = "Content-Type";
|
|
const char response_header::kDate[] = "Date";
|
|
const char response_header::kETag[] = "ETag";
|
|
const char response_header::kExpires[] = "Expires";
|
|
const char response_header::kLastModified[] = "Last-Modified";
|
|
const char response_header::kLocation[] = "Location";
|
|
const char response_header::kPragma[] = "Pragma";
|
|
const char response_header::kProxyAuthenticate[] = "Proxy-Authenticate";
|
|
const char response_header::kRetryAfter[] = "Retry-After";
|
|
const char response_header::kServer[] = "Server";
|
|
const char response_header::kSetCookie[] = "Set-Cookie";
|
|
const char response_header::kTrailer[] = "Trailer";
|
|
const char response_header::kTransferEncoding[] = "Transfer-Encoding";
|
|
const char response_header::kUpgrade[] = "Upgrade";
|
|
const char response_header::kVary[] = "Vary";
|
|
const char response_header::kVia[] = "Via";
|
|
const char response_header::kWarning[] = "Warning";
|
|
const char response_header::kWwwAuthenticate[] = "WWW-Authenticate";
|
|
|
|
// ***********************************************************
|
|
// ********************** Request Class **********************
|
|
// ***********************************************************
|
|
Request::Request(const std::string& url,
|
|
const std::string& method,
|
|
std::shared_ptr<Transport> transport)
|
|
: transport_(transport), request_url_(url), method_(method) {
|
|
VLOG(1) << "http::Request created";
|
|
if (!transport_)
|
|
transport_ = http::Transport::CreateDefault();
|
|
}
|
|
|
|
Request::~Request() {
|
|
VLOG(1) << "http::Request destroyed";
|
|
}
|
|
|
|
void Request::AddRange(int64_t bytes) {
|
|
DCHECK(transport_) << "Request already sent";
|
|
if (bytes < 0) {
|
|
ranges_.emplace_back(Request::range_value_omitted, -bytes);
|
|
} else {
|
|
ranges_.emplace_back(bytes, Request::range_value_omitted);
|
|
}
|
|
}
|
|
|
|
void Request::AddRange(uint64_t from_byte, uint64_t to_byte) {
|
|
DCHECK(transport_) << "Request already sent";
|
|
ranges_.emplace_back(from_byte, to_byte);
|
|
}
|
|
|
|
std::unique_ptr<Response> Request::GetResponseAndBlock(
|
|
brillo::ErrorPtr* error) {
|
|
if (!SendRequestIfNeeded(error) || !connection_->FinishRequest(error))
|
|
return std::unique_ptr<Response>();
|
|
std::unique_ptr<Response> response(new Response(connection_));
|
|
connection_.reset();
|
|
transport_.reset(); // Indicate that the response has been received
|
|
return response;
|
|
}
|
|
|
|
RequestID Request::GetResponse(const SuccessCallback& success_callback,
|
|
const ErrorCallback& error_callback) {
|
|
ErrorPtr error;
|
|
if (!SendRequestIfNeeded(&error)) {
|
|
transport_->RunCallbackAsync(
|
|
FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release())));
|
|
return 0;
|
|
}
|
|
RequestID id =
|
|
connection_->FinishRequestAsync(success_callback, error_callback);
|
|
connection_.reset();
|
|
transport_.reset(); // Indicate that the request has been dispatched.
|
|
return id;
|
|
}
|
|
|
|
void Request::SetAccept(const std::string& accept_mime_types) {
|
|
DCHECK(transport_) << "Request already sent";
|
|
accept_ = accept_mime_types;
|
|
}
|
|
|
|
const std::string& Request::GetAccept() const {
|
|
return accept_;
|
|
}
|
|
|
|
void Request::SetContentType(const std::string& contentType) {
|
|
DCHECK(transport_) << "Request already sent";
|
|
content_type_ = contentType;
|
|
}
|
|
|
|
const std::string& Request::GetContentType() const {
|
|
return content_type_;
|
|
}
|
|
|
|
void Request::AddHeader(const std::string& header, const std::string& value) {
|
|
DCHECK(transport_) << "Request already sent";
|
|
headers_.emplace(header, value);
|
|
}
|
|
|
|
void Request::AddHeaders(const HeaderList& headers) {
|
|
DCHECK(transport_) << "Request already sent";
|
|
headers_.insert(headers.begin(), headers.end());
|
|
}
|
|
|
|
bool Request::AddRequestBody(const void* data,
|
|
size_t size,
|
|
brillo::ErrorPtr* error) {
|
|
if (!SendRequestIfNeeded(error))
|
|
return false;
|
|
StreamPtr stream = MemoryStream::OpenCopyOf(data, size, error);
|
|
return stream && connection_->SetRequestData(std::move(stream), error);
|
|
}
|
|
|
|
bool Request::AddRequestBody(StreamPtr stream, brillo::ErrorPtr* error) {
|
|
return SendRequestIfNeeded(error) &&
|
|
connection_->SetRequestData(std::move(stream), error);
|
|
}
|
|
|
|
bool Request::AddRequestBodyAsFormData(std::unique_ptr<FormData> form_data,
|
|
brillo::ErrorPtr* error) {
|
|
AddHeader(request_header::kContentType, form_data->GetContentType());
|
|
if (!SendRequestIfNeeded(error))
|
|
return false;
|
|
return connection_->SetRequestData(form_data->ExtractDataStream(), error);
|
|
}
|
|
|
|
bool Request::AddResponseStream(StreamPtr stream, brillo::ErrorPtr* error) {
|
|
if (!SendRequestIfNeeded(error))
|
|
return false;
|
|
connection_->SetResponseData(std::move(stream));
|
|
return true;
|
|
}
|
|
|
|
const std::string& Request::GetRequestURL() const {
|
|
return request_url_;
|
|
}
|
|
|
|
const std::string& Request::GetRequestMethod() const {
|
|
return method_;
|
|
}
|
|
|
|
void Request::SetReferer(const std::string& referer) {
|
|
DCHECK(transport_) << "Request already sent";
|
|
referer_ = referer;
|
|
}
|
|
|
|
const std::string& Request::GetReferer() const {
|
|
return referer_;
|
|
}
|
|
|
|
void Request::SetUserAgent(const std::string& user_agent) {
|
|
DCHECK(transport_) << "Request already sent";
|
|
user_agent_ = user_agent;
|
|
}
|
|
|
|
const std::string& Request::GetUserAgent() const {
|
|
return user_agent_;
|
|
}
|
|
|
|
bool Request::SendRequestIfNeeded(brillo::ErrorPtr* error) {
|
|
if (transport_) {
|
|
if (!connection_) {
|
|
http::HeaderList headers = brillo::MapToVector(headers_);
|
|
std::vector<std::string> ranges;
|
|
if (method_ != request_type::kHead) {
|
|
ranges.reserve(ranges_.size());
|
|
for (auto p : ranges_) {
|
|
if (p.first != range_value_omitted ||
|
|
p.second != range_value_omitted) {
|
|
std::string range;
|
|
if (p.first != range_value_omitted) {
|
|
range = brillo::string_utils::ToString(p.first);
|
|
}
|
|
range += '-';
|
|
if (p.second != range_value_omitted) {
|
|
range += brillo::string_utils::ToString(p.second);
|
|
}
|
|
ranges.push_back(range);
|
|
}
|
|
}
|
|
}
|
|
if (!ranges.empty())
|
|
headers.emplace_back(
|
|
request_header::kRange,
|
|
"bytes=" + brillo::string_utils::Join(",", ranges));
|
|
|
|
headers.emplace_back(request_header::kAccept, GetAccept());
|
|
if (method_ != request_type::kGet && method_ != request_type::kHead) {
|
|
if (!content_type_.empty())
|
|
headers.emplace_back(request_header::kContentType, content_type_);
|
|
}
|
|
connection_ = transport_->CreateConnection(
|
|
request_url_, method_, headers, user_agent_, referer_, error);
|
|
}
|
|
|
|
if (connection_)
|
|
return true;
|
|
} else {
|
|
brillo::Error::AddTo(error,
|
|
FROM_HERE,
|
|
http::kErrorDomain,
|
|
"response_already_received",
|
|
"HTTP response already received");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ************************************************************
|
|
// ********************** Response Class **********************
|
|
// ************************************************************
|
|
Response::Response(const std::shared_ptr<Connection>& connection)
|
|
: connection_{connection} {
|
|
VLOG(1) << "http::Response created";
|
|
}
|
|
|
|
Response::~Response() {
|
|
VLOG(1) << "http::Response destroyed";
|
|
}
|
|
|
|
bool Response::IsSuccessful() const {
|
|
int code = GetStatusCode();
|
|
return code >= status_code::Continue && code < status_code::BadRequest;
|
|
}
|
|
|
|
int Response::GetStatusCode() const {
|
|
if (!connection_)
|
|
return -1;
|
|
|
|
return connection_->GetResponseStatusCode();
|
|
}
|
|
|
|
std::string Response::GetStatusText() const {
|
|
if (!connection_)
|
|
return std::string();
|
|
|
|
return connection_->GetResponseStatusText();
|
|
}
|
|
|
|
std::string Response::GetContentType() const {
|
|
return GetHeader(response_header::kContentType);
|
|
}
|
|
|
|
StreamPtr Response::ExtractDataStream(ErrorPtr* error) {
|
|
return connection_->ExtractDataStream(error);
|
|
}
|
|
|
|
std::vector<uint8_t> Response::ExtractData() {
|
|
std::vector<uint8_t> data;
|
|
StreamPtr src_stream = connection_->ExtractDataStream(nullptr);
|
|
StreamPtr dest_stream = MemoryStream::CreateRef(&data, nullptr);
|
|
if (src_stream && dest_stream) {
|
|
char buffer[1024];
|
|
size_t read = 0;
|
|
while (src_stream->ReadBlocking(buffer, sizeof(buffer), &read, nullptr) &&
|
|
read > 0) {
|
|
CHECK(dest_stream->WriteAllBlocking(buffer, read, nullptr));
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
std::string Response::ExtractDataAsString() {
|
|
std::vector<uint8_t> data = ExtractData();
|
|
return std::string{data.begin(), data.end()};
|
|
}
|
|
|
|
std::string Response::GetHeader(const std::string& header_name) const {
|
|
if (connection_)
|
|
return connection_->GetResponseHeader(header_name);
|
|
|
|
return std::string();
|
|
}
|
|
|
|
} // namespace http
|
|
} // namespace brillo
|