blob: e39c4fb9fdcf709c117488843c9c74c922c21352 [file] [log] [blame]
// Copyright 2015 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 "services/http_server/connection.h"
#include "base/bind.h"
#include "base/format_macros.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
namespace http_server {
namespace {
const char* GetHttpReasonPhrase(uint32_t code_in) {
switch (code_in) {
#define HTTP_STATUS(label, code, reason) \
case code: \
return reason;
#include "services/http_server/http_status_code_list.h"
#undef HTTP_STATUS
default:
NOTREACHED() << "unknown HTTP status code " << code_in;
}
return "";
}
} // namespace
Connection::Connection(mojo::TCPConnectedSocketPtr conn,
mojo::ScopedDataPipeProducerHandle sender,
mojo::ScopedDataPipeConsumerHandle receiver,
const Callback& callback)
: connection_(conn.Pass()),
sender_(sender.Pass()),
receiver_(receiver.Pass()),
request_waiter_(
receiver_.get(),
MOJO_HANDLE_SIGNAL_READABLE,
base::Bind(&Connection::OnRequestDataReady, base::Unretained(this))),
content_length_(0),
handle_request_callback_(callback),
response_offset_(0) {
}
Connection::~Connection() {
}
void Connection::SendResponse(HttpResponsePtr response) {
std::string http_reason_phrase(GetHttpReasonPhrase(response->status_code));
// TODO: should we send http/1.0 for http/1.0.requests?
base::StringAppendF(&response_, "HTTP/1.1 %d %s\r\n", response->status_code,
http_reason_phrase.c_str());
base::StringAppendF(&response_, "Connection: close\r\n");
content_length_ = response->content_length;
if (content_length_) {
base::StringAppendF(&response_, "Content-Length: %" PRIuS "\r\n",
static_cast<size_t>(content_length_));
}
base::StringAppendF(&response_, "Content-Type: %s\r\n",
response->content_type.data());
for (auto it = response->custom_headers.begin();
it != response->custom_headers.end(); ++it) {
const std::string& header_name = it.GetKey();
const std::string& header_value = it.GetValue();
DCHECK(header_value.find_first_of("\n\r") == std::string::npos)
<< "Malformed header value.";
base::StringAppendF(&response_, "%s: %s\r\n", header_name.c_str(),
header_value.c_str());
}
base::StringAppendF(&response_, "\r\n");
content_ = response->body.Pass();
WriteMore();
}
void Connection::OnRequestDataReady(MojoResult result) {
uint32_t num_bytes = 0;
result =
ReadDataRaw(receiver_.get(), NULL, &num_bytes, MOJO_READ_DATA_FLAG_QUERY);
if (!num_bytes)
return;
scoped_ptr<uint8_t[]> buffer(new uint8_t[num_bytes]);
result = ReadDataRaw(receiver_.get(), buffer.get(), &num_bytes,
MOJO_READ_DATA_FLAG_ALL_OR_NONE);
request_parser_.ProcessChunk(
base::StringPiece(reinterpret_cast<char*>(buffer.get()), num_bytes));
if (request_parser_.ParseRequest() == HttpRequestParser::ACCEPTED) {
handle_request_callback_.Run(this, request_parser_.GetRequest());
}
}
void Connection::WriteMore() {
uint32_t response_bytes_available =
static_cast<uint32_t>(response_.size() - response_offset_);
if (response_bytes_available) {
MojoResult result =
WriteDataRaw(sender_.get(), &response_[response_offset_],
&response_bytes_available, 0);
if (result == MOJO_RESULT_SHOULD_WAIT) {
sender_waiter_.reset(new mojo::AsyncWaiter(
sender_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::Bind(&Connection::OnSenderReady, base::Unretained(this))));
return;
} else if (result != MOJO_RESULT_OK) {
LOG(ERROR) << "Error writing to pipe " << result;
delete this;
return;
}
response_offset_ += response_bytes_available;
}
if (response_offset_ != response_.size()) {
// We have more data left in response_. Write more asynchronously.
sender_waiter_.reset(new mojo::AsyncWaiter(
sender_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::Bind(&Connection::OnSenderReady, base::Unretained(this))));
return;
}
// response_ is all sent, and there's no more data so we're done.
if (!content_length_) {
delete this;
return;
}
// Copy data from the handler's pipe to response_.
const uint32_t kMaxChunkSize = 1024 * 1024;
uint32_t num_bytes_available = 0;
MojoResult result = ReadDataRaw(content_.get(), NULL, &num_bytes_available,
MOJO_READ_DATA_FLAG_QUERY);
if (result == MOJO_RESULT_SHOULD_WAIT) {
// Producer isn't ready yet. Wait for it.
response_receiver_waiter_.reset(new mojo::AsyncWaiter(
content_.get(), MOJO_HANDLE_SIGNAL_READABLE,
base::Bind(&Connection::OnResponseDataReady, base::Unretained(this))));
return;
}
DCHECK_EQ(result, MOJO_RESULT_OK);
num_bytes_available = std::min(num_bytes_available, kMaxChunkSize);
response_.resize(num_bytes_available);
response_offset_ = 0;
content_length_ -= num_bytes_available;
result = ReadDataRaw(content_.get(), &response_[0], &num_bytes_available,
MOJO_READ_DATA_FLAG_ALL_OR_NONE);
DCHECK_EQ(result, MOJO_RESULT_OK);
sender_waiter_.reset(new mojo::AsyncWaiter(
sender_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::Bind(&Connection::OnSenderReady, base::Unretained(this))));
}
void Connection::OnResponseDataReady(MojoResult result) {
if (result != MOJO_RESULT_OK) {
LOG(ERROR) << "Error waiting to read data " << result;
delete this;
return;
}
WriteMore();
}
void Connection::OnSenderReady(MojoResult result) {
if (result != MOJO_RESULT_OK) {
LOG(ERROR) << "Error waiting to write data " << result;
delete this;
return;
}
WriteMore();
}
} // namespace http_server