blob: 4e1ae8f3ab5bd68e21852cd138be8ac30399f9d5 [file] [log] [blame]
Alex Vakulenko039da312015-02-03 08:58:55 -08001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <libwebserv/connection.h>
6
7#include <algorithm>
8#include <vector>
9
10#include <base/bind.h>
11#include <base/location.h>
12#include <base/logging.h>
13#include <base/task_runner.h>
14#include <chromeos/http/http_request.h>
15#include <libwebserv/request.h>
16#include <libwebserv/request_handler_interface.h>
17#include <libwebserv/response.h>
18#include <libwebserv/server.h>
19#include <microhttpd.h>
20
21namespace libwebserv {
22
23// Helper class to provide static callback methods to microhttpd library,
24// with the ability to access private methods of Connection class.
25class ConnectionHelper {
26 public:
27 static int PostDataIterator(void* cls,
28 MHD_ValueKind kind,
29 const char* key,
30 const char* filename,
31 const char* content_type,
32 const char* transfer_encoding,
33 const char* data,
34 uint64_t off,
35 size_t size) {
36 Connection* server_connection = reinterpret_cast<Connection*>(cls);
37 if (!server_connection->ProcessPostData(
38 key, filename, content_type, transfer_encoding, data, off, size)) {
39 return MHD_NO;
40 }
41 return MHD_YES;
42 }
43};
44
45// Helper class to provide static callback methods to microhttpd library,
46// with the ability to access private methods of Request class.
47class RequestHelper {
48 public:
49 static int ValueCallback(void* cls,
50 MHD_ValueKind kind,
51 const char* key,
52 const char* value) {
53 auto self = reinterpret_cast<Request*>(cls);
54 std::string data;
55 if (value)
56 data = value;
57 if (kind == MHD_HEADER_KIND) {
58 self->headers_.emplace(Request::GetCanonicalHeaderName(key), data);
59 } else if (kind == MHD_COOKIE_KIND) {
60 // TODO(avakulenko): add support for cookies...
61 } else if (kind == MHD_POSTDATA_KIND) {
62 self->post_data_.emplace(key, data);
63 } else if (kind == MHD_GET_ARGUMENT_KIND) {
64 self->get_data_.emplace(key, data);
65 }
66 return MHD_YES;
67 }
68};
69
70Connection::Connection(const scoped_refptr<base::TaskRunner>& task_runner,
71 MHD_Connection* connection,
72 RequestHandlerInterface* handler)
73 : task_runner_(task_runner),
74 raw_connection_(connection),
75 handler_(handler) {
76}
77
78Connection::~Connection() {
79 if (post_processor_)
80 MHD_destroy_post_processor(post_processor_);
81}
82
83scoped_refptr<Connection> Connection::Create(Server* server,
84 const std::string& url,
85 const std::string& method,
86 MHD_Connection* connection,
87 RequestHandlerInterface* handler) {
88 scoped_refptr<Connection> result(
89 new Connection(server->task_runner_, connection, handler));
90 VLOG(1) << "Incoming HTTP connection (" << result.get() << ")."
91 << " Method='" << method << "', URL='" << url << "'";
92 result->post_processor_ = MHD_create_post_processor(
93 connection, 1024, &ConnectionHelper::PostDataIterator, result.get());
94 result->request_ = Request::Create(url, method);
95 result->response_ = Response::Create(result);
96 return result;
97}
98
99bool Connection::BeginRequestData() {
100 MHD_get_connection_values(raw_connection_, MHD_HEADER_KIND,
101 &RequestHelper::ValueCallback, request_.get());
102 MHD_get_connection_values(raw_connection_, MHD_COOKIE_KIND,
103 &RequestHelper::ValueCallback, request_.get());
104 MHD_get_connection_values(raw_connection_, MHD_POSTDATA_KIND,
105 &RequestHelper::ValueCallback, request_.get());
106 MHD_get_connection_values(raw_connection_, MHD_GET_ARGUMENT_KIND,
107 &RequestHelper::ValueCallback, request_.get());
108 return true;
109}
110
111bool Connection::AddRequestData(const void* data, size_t size) {
112 if (!post_processor_)
113 return request_->AddRawRequestData(data, size);
114 return MHD_post_process(post_processor_,
115 static_cast<const char*>(data), size) == MHD_YES;
116}
117
118void Connection::EndRequestData() {
119 if (state_ == State::kIdle) {
120 state_ = State::kRequestSent;
121 // libmicrohttpd calls handlers on its own thread.
122 // Redirect this to the main IO thread of the server.
123 task_runner_->PostTask(
124 FROM_HERE,
125 base::Bind(&RequestHandlerInterface::HandleRequest,
126 base::Unretained(handler_), base::Passed(&request_),
127 base::Passed(&response_)));
128 } else if (state_ == State::kResponseReceived) {
129 VLOG(1) << "Sending HTTP response for connection (" << this
130 << "): " << response_status_code_
131 << ", data size = " << response_data_.size();
132 MHD_Response* resp = MHD_create_response_from_buffer(
133 response_data_.size(), response_data_.data(), MHD_RESPMEM_PERSISTENT);
134 for (const auto& pair : response_headers_) {
135 MHD_add_response_header(resp, pair.first.c_str(), pair.second.c_str());
136 }
137 CHECK_EQ(MHD_YES,
138 MHD_queue_response(raw_connection_, response_status_code_, resp))
139 << "Failed to queue response";
140 MHD_destroy_response(resp); // |resp| is ref-counted.
141 state_ = State::kDone;
142 }
143}
144
145bool Connection::ProcessPostData(const char* key,
146 const char* filename,
147 const char* content_type,
148 const char* transfer_encoding,
149 const char* data,
150 uint64_t off,
151 size_t size) {
152 if (off == 0)
153 return request_->AddPostFieldData(key, filename, content_type,
154 transfer_encoding, data, size);
155 return request_->AppendPostFieldData(key, data, size);
156}
157
158} // namespace libwebserv