Files
scylladb/http/httpd.hh
Amnon Heiman 427a6165b3 using the routes and request from the http directory
This use the routes and the reqeuest found in the http directory and
move all files but main to the http directory

Signed-off-by: Amnon Heiman <amnon@cloudius-systems.com>
2015-03-08 21:55:57 +02:00

260 lines
9.0 KiB
C++

/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2015 Cloudius Systems
*/
#ifndef APPS_HTTPD_HTTPD_HH_
#define APPS_HTTPD_HTTPD_HH_
#include "http/request_parser.hh"
#include "http/request.hh"
#include "core/reactor.hh"
#include "core/sstring.hh"
#include "core/app-template.hh"
#include "core/circular_buffer.hh"
#include "core/distributed.hh"
#include "core/queue.hh"
#include "core/future-util.hh"
#include "core/scollectd.hh"
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <queue>
#include <bitset>
#include <limits>
#include <cctype>
#include <vector>
#include "reply.hh"
#include "http/routes.hh"
namespace httpd {
class http_server;
class http_stats;
using namespace std::chrono_literals;
class http_stats {
std::vector<scollectd::registration> _regs;
public:
http_stats(http_server& server);
};
class http_server {
std::vector<server_socket> _listeners;
http_stats _stats { *this };
uint64_t _total_connections = 0;
uint64_t _current_connections = 0;
uint64_t _requests_served = 0;
sstring _date = http_date();
timer<> _date_format_timer { [this] {_date = http_date();} };
public:
routes _routes;
http_server() {
_date_format_timer.arm_periodic(1s);
}
future<> listen(ipv4_addr addr) {
listen_options lo;
lo.reuse_address = true;
_listeners.push_back(engine().listen(make_ipv4_address(addr), lo));
do_accepts(_listeners.size() - 1);
return make_ready_future<>();
}
void do_accepts(int which) {
_listeners[which].accept().then(
[this, which] (connected_socket fd, socket_address addr) mutable {
auto conn = new connection(*this, std::move(fd), addr);
conn->process().then_wrapped([this, conn] (auto&& f) {
delete conn;
try {
f.get();
} catch (std::exception& ex) {
std::cout << "request error " << ex.what() << "\n";
}
});
do_accepts(which);
}).then_wrapped([] (auto f) {
try {
f.get();
} catch (std::exception& ex) {
std::cout << "accept failed: " << ex.what() << "\n";
}
});
}
class connection {
http_server& _server;
connected_socket _fd;
input_stream<char> _read_buf;
output_stream<char> _write_buf;
static constexpr size_t limit = 4096;
using tmp_buf = temporary_buffer<char>;
http_request_parser _parser;
std::unique_ptr<request> _req;
std::unique_ptr<reply> _resp;
// null element marks eof
queue<std::unique_ptr<reply>> _replies { 10 };bool _done = false;
public:
connection(http_server& server, connected_socket&& fd,
socket_address addr)
: _server(server), _fd(std::move(fd)), _read_buf(_fd.input()), _write_buf(
_fd.output()) {
++_server._total_connections;
++_server._current_connections;
}
~connection() {
--_server._current_connections;
}
future<> process() {
// Launch read and write "threads" simultaneously:
return when_all(read(), respond()).then(
[] (std::tuple<future<>, future<>> joined) {
// FIXME: notify any exceptions in joined?
return make_ready_future<>();
});
}
future<> read() {
return do_until([this] {return _done;}, [this] {
return read_one();
}).then_wrapped([this] (future<> f) {
// swallow error
// FIXME: count it?
return _replies.push_eventually( {});
});
}
future<> read_one() {
_parser.init();
return _read_buf.consume(_parser).then([this] {
if (_parser.eof()) {
_done = true;
return make_ready_future<>();
}
++_server._requests_served;
_req = _parser.get_parsed_request();
return _replies.not_full().then([this] {
_done = generate_reply(std::move(_req));
});
});
}
future<> respond() {
return _replies.pop_eventually().then(
[this] (std::unique_ptr<reply> resp) {
if (!resp) {
// eof
return make_ready_future<>();
}
_resp = std::move(resp);
return start_response().then([this] {
return respond();
});
});
}
future<> start_response() {
_resp->_headers["Server"] = "Seastar httpd";
_resp->_headers["Date"] = _server._date;
_resp->_headers["Content-Length"] = to_sstring(
_resp->_content.size());
return _write_buf.write(_resp->_response_line.begin(),
_resp->_response_line.size()).then([this] {
return write_reply_headers(_resp->_headers.begin());
}).then([this] {
return _write_buf.write("\r\n", 2);
}).then([this] {
return write_body();
}).then([this] {
return _write_buf.flush();
}).then([this] {
_resp.reset();
});
}
future<> write_reply_headers(
std::unordered_map<sstring, sstring>::iterator hi) {
if (hi == _resp->_headers.end()) {
return make_ready_future<>();
}
return _write_buf.write(hi->first.begin(), hi->first.size()).then(
[this] {
return _write_buf.write(": ", 2);
}).then([hi, this] {
return _write_buf.write(hi->second.begin(), hi->second.size());
}).then([this] {
return _write_buf.write("\r\n", 2);
}).then([hi, this] () mutable {
return write_reply_headers(++hi);
});
}
bool generate_reply(std::unique_ptr<request> req) {
auto resp = std::make_unique<reply>();
bool conn_keep_alive = false;
bool conn_close = false;
auto it = req->_headers.find("Connection");
if (it != req->_headers.end()) {
if (it->second == "Keep-Alive") {
conn_keep_alive = true;
} else if (it->second == "Close") {
conn_close = true;
}
}
bool should_close;
// TODO: Handle HTTP/2.0 when it releases
resp->set_version(req->_version);
if (req->_version == "1.0") {
if (conn_keep_alive) {
resp->_headers["Connection"] = "Keep-Alive";
}
should_close = !conn_keep_alive;
} else if (req->_version == "1.1") {
should_close = conn_close;
} else {
// HTTP/0.9 goes here
should_close = true;
}
_server._routes.handle(req->_url, *(req.get()), *(resp.get()));
// Caller guarantees enough room
_replies.push(std::move(resp));
return should_close;
}
future<> write_body() {
return _write_buf.write(_resp->_content.begin(),
_resp->_content.size());
}
};
uint64_t total_connections() const {
return _total_connections;
}
uint64_t current_connections() const {
return _current_connections;
}
uint64_t requests_served() const {
return _requests_served;
}
static sstring http_date() {
auto t = ::time(nullptr);
struct tm tm;
gmtime_r(&t, &tm);
char tmp[100];
strftime(tmp, sizeof(tmp), "%d %b %Y %H:%M:%S GMT", &tm);
return tmp;
}
};
}
#endif /* APPS_HTTPD_HTTPD_HH_ */