blob: 71259a67b9c0246dd9d11e60924c319bf18234a0 [file] [log] [blame]
// 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 "libwebserv/connection.h"
#include <algorithm>
#include <vector>
#include <base/bind.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/task_runner.h>
#include <chromeos/http/http_request.h>
#include <microhttpd.h>
#include "libwebserv/request.h"
#include "libwebserv/request_handler_interface.h"
#include "libwebserv/response.h"
#include "libwebserv/server.h"
namespace libwebserv {
// Helper class to provide static callback methods to microhttpd library,
// with the ability to access private methods of Connection class.
class ConnectionHelper {
public:
static int PostDataIterator(void* cls,
MHD_ValueKind kind,
const char* key,
const char* filename,
const char* content_type,
const char* transfer_encoding,
const char* data,
uint64_t off,
size_t size) {
Connection* server_connection = reinterpret_cast<Connection*>(cls);
if (!server_connection->ProcessPostData(
key, filename, content_type, transfer_encoding, data, off, size)) {
return MHD_NO;
}
return MHD_YES;
}
};
// Helper class to provide static callback methods to microhttpd library,
// with the ability to access private methods of Request class.
class RequestHelper {
public:
static int ValueCallback(void* cls,
MHD_ValueKind kind,
const char* key,
const char* value) {
auto self = reinterpret_cast<Request*>(cls);
std::string data;
if (value)
data = value;
if (kind == MHD_HEADER_KIND) {
self->headers_.emplace(Request::GetCanonicalHeaderName(key), data);
} else if (kind == MHD_COOKIE_KIND) {
// TODO(avakulenko): add support for cookies...
} else if (kind == MHD_POSTDATA_KIND) {
self->post_data_.emplace(key, data);
} else if (kind == MHD_GET_ARGUMENT_KIND) {
self->get_data_.emplace(key, data);
}
return MHD_YES;
}
};
Connection::Connection(const scoped_refptr<base::TaskRunner>& task_runner,
MHD_Connection* connection,
RequestHandlerInterface* handler)
: task_runner_(task_runner),
raw_connection_(connection),
handler_(handler) {
}
Connection::~Connection() {
if (post_processor_)
MHD_destroy_post_processor(post_processor_);
}
scoped_refptr<Connection> Connection::Create(Server* server,
const std::string& url,
const std::string& method,
MHD_Connection* connection,
RequestHandlerInterface* handler) {
scoped_refptr<Connection> result(
new Connection(server->task_runner_, connection, handler));
VLOG(1) << "Incoming HTTP connection (" << result.get() << ")."
<< " Method='" << method << "', URL='" << url << "'";
result->post_processor_ = MHD_create_post_processor(
connection, 1024, &ConnectionHelper::PostDataIterator, result.get());
result->request_ = Request::Create(url, method);
result->response_ = Response::Create(result);
return result;
}
bool Connection::BeginRequestData() {
MHD_get_connection_values(raw_connection_, MHD_HEADER_KIND,
&RequestHelper::ValueCallback, request_.get());
MHD_get_connection_values(raw_connection_, MHD_COOKIE_KIND,
&RequestHelper::ValueCallback, request_.get());
MHD_get_connection_values(raw_connection_, MHD_POSTDATA_KIND,
&RequestHelper::ValueCallback, request_.get());
MHD_get_connection_values(raw_connection_, MHD_GET_ARGUMENT_KIND,
&RequestHelper::ValueCallback, request_.get());
return true;
}
bool Connection::AddRequestData(const void* data, size_t size) {
if (!post_processor_)
return request_->AddRawRequestData(data, size);
return MHD_post_process(post_processor_,
static_cast<const char*>(data), size) == MHD_YES;
}
void Connection::EndRequestData() {
if (state_ == State::kIdle) {
state_ = State::kRequestSent;
// libmicrohttpd calls handlers on its own thread.
// Redirect this to the main IO thread of the server.
task_runner_->PostTask(
FROM_HERE,
base::Bind(&RequestHandlerInterface::HandleRequest,
base::Unretained(handler_), base::Passed(&request_),
base::Passed(&response_)));
} else if (state_ == State::kResponseReceived) {
VLOG(1) << "Sending HTTP response for connection (" << this
<< "): " << response_status_code_
<< ", data size = " << response_data_.size();
MHD_Response* resp = MHD_create_response_from_buffer(
response_data_.size(), response_data_.data(), MHD_RESPMEM_PERSISTENT);
for (const auto& pair : response_headers_) {
MHD_add_response_header(resp, pair.first.c_str(), pair.second.c_str());
}
CHECK_EQ(MHD_YES,
MHD_queue_response(raw_connection_, response_status_code_, resp))
<< "Failed to queue response";
MHD_destroy_response(resp); // |resp| is ref-counted.
state_ = State::kDone;
}
}
bool Connection::ProcessPostData(const char* key,
const char* filename,
const char* content_type,
const char* transfer_encoding,
const char* data,
uint64_t off,
size_t size) {
if (off == 0)
return request_->AddPostFieldData(key, filename, content_type,
transfer_encoding, data, size);
return request_->AppendPostFieldData(key, data, size);
}
} // namespace libwebserv