2013-07-22 14:30:40 +02:00
|
|
|
/*
|
2014-03-30 12:09:21 +02:00
|
|
|
* nghttp2 - HTTP/2 C Library
|
2013-07-22 14:30:40 +02:00
|
|
|
*
|
|
|
|
* Copyright (c) 2013 Tatsuhiro Tsujikawa
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
* a copy of this software and associated documentation files (the
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
* the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be
|
|
|
|
* included in all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
#include "HttpServer.h"
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netinet/tcp.h>
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
#include <set>
|
|
|
|
#include <iostream>
|
2014-03-04 15:14:26 +01:00
|
|
|
#include <thread>
|
2014-12-23 16:05:45 +01:00
|
|
|
#include <mutex>
|
|
|
|
#include <deque>
|
2013-07-22 14:30:40 +02:00
|
|
|
|
|
|
|
#include <openssl/err.h>
|
|
|
|
|
|
|
|
#include <zlib.h>
|
|
|
|
|
2013-07-22 15:12:54 +02:00
|
|
|
#include "app_helper.h"
|
2013-08-27 20:14:19 +02:00
|
|
|
#include "http2.h"
|
2013-07-22 14:30:40 +02:00
|
|
|
#include "util.h"
|
2014-06-28 08:28:19 +02:00
|
|
|
#include "ssl.h"
|
2013-07-22 14:30:40 +02:00
|
|
|
|
|
|
|
#ifndef O_BINARY
|
2014-11-27 15:39:04 +01:00
|
|
|
#define O_BINARY (0)
|
2013-07-22 14:30:40 +02:00
|
|
|
#endif // O_BINARY
|
|
|
|
|
|
|
|
namespace nghttp2 {
|
|
|
|
|
|
|
|
namespace {
|
2013-08-25 17:58:06 +02:00
|
|
|
const std::string STATUS_200 = "200";
|
2014-11-02 15:27:38 +01:00
|
|
|
const std::string STATUS_301 = "301";
|
2013-08-25 17:58:06 +02:00
|
|
|
const std::string STATUS_304 = "304";
|
|
|
|
const std::string STATUS_400 = "400";
|
|
|
|
const std::string STATUS_404 = "404";
|
2013-07-22 14:30:40 +02:00
|
|
|
const std::string DEFAULT_HTML = "index.html";
|
|
|
|
const std::string NGHTTPD_SERVER = "nghttpd nghttp2/" NGHTTP2_VERSION;
|
|
|
|
} // namespace
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
namespace {
|
|
|
|
int make_socket_nonblocking(int fd) {
|
|
|
|
int flags;
|
|
|
|
int rv;
|
|
|
|
while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
|
|
|
|
;
|
|
|
|
while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
|
|
|
|
;
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2014-03-21 15:07:20 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void delete_handler(Http2Handler *handler) {
|
2014-03-21 15:07:20 +01:00
|
|
|
handler->remove_self();
|
|
|
|
delete handler;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; }
|
2014-03-21 15:07:20 +01:00
|
|
|
} // namespace
|
|
|
|
|
2014-05-07 16:24:07 +02:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void append_nv(Stream *stream, const std::vector<nghttp2_nv> &nva) {
|
|
|
|
for (auto &nv : nva) {
|
|
|
|
http2::add_header(stream->headers, nv.name, nv.namelen, nv.value,
|
|
|
|
nv.valuelen, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
2014-05-07 16:24:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
Config::Config()
|
2014-12-23 16:05:45 +01:00
|
|
|
: stream_read_timeout(60.), stream_write_timeout(60.),
|
2014-11-27 15:39:04 +01:00
|
|
|
session_option(nullptr), data_ptr(nullptr), padding(0), num_worker(1),
|
|
|
|
header_table_size(-1), port(0), verbose(false), daemon(false),
|
|
|
|
verify_client(false), no_tls(false), error_gzip(false),
|
|
|
|
early_response(false) {
|
2014-09-13 12:50:44 +02:00
|
|
|
nghttp2_option_new(&session_option);
|
|
|
|
nghttp2_option_set_recv_client_preface(session_option, 1);
|
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
Config::~Config() { nghttp2_option_del(session_option); }
|
2014-10-10 15:50:35 +02:00
|
|
|
|
2014-03-21 15:07:20 +01:00
|
|
|
namespace {
|
2014-12-23 16:05:45 +01:00
|
|
|
void stream_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
2014-03-21 15:07:20 +01:00
|
|
|
int rv;
|
2014-12-23 16:05:45 +01:00
|
|
|
auto stream = static_cast<Stream *>(w->data);
|
2014-03-21 15:07:20 +01:00
|
|
|
auto hd = stream->handler;
|
|
|
|
auto config = hd->get_config();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (config->verbose) {
|
2014-03-21 15:07:20 +01:00
|
|
|
print_session_id(hd->session_id());
|
|
|
|
print_timer();
|
|
|
|
std::cout << " timeout stream_id=" << stream->stream_id << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
|
|
|
|
rv = hd->on_write();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv == -1) {
|
2014-03-21 15:07:20 +01:00
|
|
|
delete_handler(hd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void add_stream_read_timeout(Stream *stream) {
|
2014-03-21 15:26:53 +01:00
|
|
|
auto hd = stream->handler;
|
2014-12-23 16:05:45 +01:00
|
|
|
ev_timer_again(hd->get_loop(), &stream->rtimer);
|
2014-03-21 15:07:20 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-03-21 16:36:39 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void add_stream_read_timeout_if_pending(Stream *stream) {
|
2014-03-21 16:36:39 +01:00
|
|
|
auto hd = stream->handler;
|
2014-12-23 16:05:45 +01:00
|
|
|
if (ev_is_active(&stream->rtimer)) {
|
|
|
|
ev_timer_again(hd->get_loop(), &stream->rtimer);
|
2014-03-21 16:36:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-03-21 15:07:20 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void add_stream_write_timeout(Stream *stream) {
|
2014-03-21 15:26:53 +01:00
|
|
|
auto hd = stream->handler;
|
2014-12-23 16:05:45 +01:00
|
|
|
ev_timer_again(hd->get_loop(), &stream->wtimer);
|
2014-03-21 15:07:20 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void remove_stream_read_timeout(Stream *stream) {
|
2014-12-23 16:05:45 +01:00
|
|
|
auto hd = stream->handler;
|
|
|
|
ev_timer_stop(hd->get_loop(), &stream->rtimer);
|
2014-03-21 15:07:20 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void remove_stream_write_timeout(Stream *stream) {
|
2014-12-23 16:05:45 +01:00
|
|
|
auto hd = stream->handler;
|
|
|
|
ev_timer_stop(hd->get_loop(), &stream->wtimer);
|
2014-03-21 15:07:20 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-11-10 14:35:08 +01:00
|
|
|
namespace {
|
|
|
|
std::shared_ptr<std::string> cached_date;
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-12-23 16:05:45 +01:00
|
|
|
void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
2014-11-10 14:35:08 +01:00
|
|
|
cached_date = std::make_shared<std::string>(util::http_date(time(nullptr)));
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-08-22 13:59:50 +02:00
|
|
|
namespace {
|
|
|
|
void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config);
|
|
|
|
} // namespace
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
class Sessions {
|
|
|
|
public:
|
2014-12-23 16:05:45 +01:00
|
|
|
Sessions(struct ev_loop *loop, const Config *config, SSL_CTX *ssl_ctx)
|
|
|
|
: loop_(loop), config_(config), ssl_ctx_(ssl_ctx), callbacks_(nullptr),
|
|
|
|
next_session_id_(1) {
|
2014-08-22 13:59:50 +02:00
|
|
|
nghttp2_session_callbacks_new(&callbacks_);
|
|
|
|
|
|
|
|
fill_callback(callbacks_, config_);
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
~Sessions() {
|
|
|
|
for (auto handler : handlers_) {
|
2013-07-22 14:30:40 +02:00
|
|
|
delete handler;
|
|
|
|
}
|
2014-08-22 13:59:50 +02:00
|
|
|
nghttp2_session_callbacks_del(callbacks_);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
void add_handler(Http2Handler *handler) { handlers_.insert(handler); }
|
|
|
|
void remove_handler(Http2Handler *handler) { handlers_.erase(handler); }
|
|
|
|
SSL_CTX *get_ssl_ctx() const { return ssl_ctx_; }
|
|
|
|
SSL *ssl_session_new(int fd) {
|
2013-07-22 14:30:40 +02:00
|
|
|
SSL *ssl = SSL_new(ssl_ctx_);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!ssl) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cerr << "SSL_new() failed" << std::endl;
|
|
|
|
return nullptr;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (SSL_set_fd(ssl, fd) == 0) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cerr << "SSL_set_fd() failed" << std::endl;
|
|
|
|
SSL_free(ssl);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return ssl;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
const Config *get_config() const { return config_; }
|
2014-12-23 16:05:45 +01:00
|
|
|
struct ev_loop *get_loop() const {
|
|
|
|
return loop_;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
int64_t get_next_session_id() {
|
2014-03-04 15:14:26 +01:00
|
|
|
auto session_id = next_session_id_;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (next_session_id_ == std::numeric_limits<int64_t>::max()) {
|
2014-03-04 15:14:26 +01:00
|
|
|
next_session_id_ = 1;
|
2014-12-09 17:21:12 +01:00
|
|
|
} else {
|
|
|
|
++next_session_id_;
|
2014-03-04 15:14:26 +01:00
|
|
|
}
|
|
|
|
return session_id;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_session_callbacks *get_callbacks() const { return callbacks_; }
|
|
|
|
void accept_connection(int fd) {
|
2014-03-04 15:14:26 +01:00
|
|
|
int val = 1;
|
2014-04-05 13:04:09 +02:00
|
|
|
(void)setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
|
|
|
|
reinterpret_cast<char *>(&val), sizeof(val));
|
2014-03-04 15:14:26 +01:00
|
|
|
SSL *ssl = nullptr;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ssl_ctx_) {
|
2014-03-04 15:14:26 +01:00
|
|
|
ssl = ssl_session_new(fd);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!ssl) {
|
2014-03-04 15:14:26 +01:00
|
|
|
close(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
auto handler =
|
|
|
|
util::make_unique<Http2Handler>(this, fd, ssl, get_next_session_id());
|
2014-03-04 15:14:26 +01:00
|
|
|
handler->setup_bev();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!ssl) {
|
|
|
|
if (handler->on_connect() != 0) {
|
2014-03-04 15:14:26 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
add_handler(handler.release());
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
private:
|
2014-11-27 15:39:04 +01:00
|
|
|
std::set<Http2Handler *> handlers_;
|
2014-12-23 16:05:45 +01:00
|
|
|
struct ev_loop *loop_;
|
2013-07-22 14:30:40 +02:00
|
|
|
const Config *config_;
|
|
|
|
SSL_CTX *ssl_ctx_;
|
2014-08-22 13:59:50 +02:00
|
|
|
nghttp2_session_callbacks *callbacks_;
|
2014-03-04 15:14:26 +01:00
|
|
|
int64_t next_session_id_;
|
2013-07-22 14:30:40 +02:00
|
|
|
};
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
Stream::Stream(Http2Handler *handler, int32_t stream_id)
|
|
|
|
: handler(handler), body_left(0), stream_id(stream_id), file(-1) {
|
|
|
|
auto config = handler->get_config();
|
|
|
|
ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout);
|
|
|
|
ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout);
|
|
|
|
rtimer.data = this;
|
|
|
|
wtimer.data = this;
|
|
|
|
|
|
|
|
headers.reserve(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
Stream::~Stream() {
|
|
|
|
if (file != -1) {
|
|
|
|
close(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto loop = handler->get_loop();
|
|
|
|
ev_timer_stop(loop, &rtimer);
|
|
|
|
ev_timer_stop(loop, &wtimer);
|
|
|
|
}
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void on_session_closed(Http2Handler *hd, int64_t session_id) {
|
|
|
|
if (hd->get_config()->verbose) {
|
2013-07-22 14:30:40 +02:00
|
|
|
print_session_id(session_id);
|
|
|
|
print_timer();
|
|
|
|
std::cout << " closed" << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
namespace {
|
|
|
|
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(w->data);
|
|
|
|
hd->terminate_session(NGHTTP2_SETTINGS_TIMEOUT);
|
|
|
|
hd->on_write();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
|
|
|
int rv;
|
|
|
|
auto handler = static_cast<Http2Handler *>(w->data);
|
|
|
|
|
|
|
|
rv = handler->on_read();
|
|
|
|
if (rv == -1) {
|
|
|
|
delete_handler(handler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
|
|
|
int rv;
|
|
|
|
auto handler = static_cast<Http2Handler *>(w->data);
|
|
|
|
|
|
|
|
rv = handler->on_write();
|
|
|
|
if (rv == -1) {
|
|
|
|
delete_handler(handler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
Http2Handler::Http2Handler(Sessions *sessions, int fd, SSL *ssl,
|
|
|
|
int64_t session_id)
|
|
|
|
: session_id_(session_id), session_(nullptr), sessions_(sessions),
|
2014-12-23 16:05:45 +01:00
|
|
|
ssl_(ssl), data_pending_(nullptr), data_pendinglen_(0), fd_(fd) {
|
|
|
|
ev_timer_init(&settings_timerev_, settings_timeout_cb, 10., 0.);
|
|
|
|
ev_io_init(&wev_, writecb, fd, EV_WRITE);
|
|
|
|
ev_io_init(&rev_, readcb, fd, EV_READ);
|
|
|
|
|
|
|
|
settings_timerev_.data = this;
|
|
|
|
wev_.data = this;
|
|
|
|
rev_.data = this;
|
|
|
|
|
|
|
|
auto loop = sessions_->get_loop();
|
|
|
|
ev_io_start(loop, &rev_);
|
|
|
|
|
|
|
|
if (ssl) {
|
|
|
|
SSL_set_accept_state(ssl);
|
|
|
|
read_ = &Http2Handler::tls_handshake;
|
|
|
|
write_ = &Http2Handler::tls_handshake;
|
|
|
|
} else {
|
|
|
|
read_ = &Http2Handler::read_clear;
|
|
|
|
write_ = &Http2Handler::write_clear;
|
|
|
|
}
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
|
|
|
|
Http2Handler::~Http2Handler() {
|
2013-07-22 14:30:40 +02:00
|
|
|
on_session_closed(this, session_id_);
|
|
|
|
nghttp2_session_del(session_);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ssl_) {
|
2014-01-08 15:32:47 +01:00
|
|
|
SSL_set_shutdown(ssl_, SSL_RECEIVED_SHUTDOWN);
|
2013-07-22 14:30:40 +02:00
|
|
|
SSL_shutdown(ssl_);
|
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
auto loop = sessions_->get_loop();
|
|
|
|
ev_timer_stop(loop, &settings_timerev_);
|
|
|
|
ev_io_stop(loop, &rev_);
|
|
|
|
ev_io_stop(loop, &wev_);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ssl_) {
|
2013-07-22 14:30:40 +02:00
|
|
|
SSL_free(ssl_);
|
|
|
|
}
|
|
|
|
shutdown(fd_, SHUT_WR);
|
|
|
|
close(fd_);
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Handler::remove_self() { sessions_->remove_handler(this); }
|
2013-07-22 14:30:40 +02:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
struct ev_loop *Http2Handler::get_loop() const {
|
|
|
|
return sessions_->get_loop();
|
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
int Http2Handler::setup_bev() { return 0; }
|
|
|
|
|
|
|
|
int Http2Handler::fill_rb() {
|
|
|
|
if (data_pending_) {
|
|
|
|
assert(rb_.wleft() >= data_pendinglen_);
|
|
|
|
|
|
|
|
rb_.write(data_pending_, data_pendinglen_);
|
|
|
|
data_pending_ = nullptr;
|
|
|
|
data_pendinglen_ = 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
const uint8_t *data;
|
|
|
|
auto datalen = nghttp2_session_mem_send(session_, &data);
|
|
|
|
|
|
|
|
if (datalen < 0) {
|
|
|
|
std::cerr << "nghttp2_session_mem_send() returned error: "
|
|
|
|
<< nghttp2_strerror(datalen) << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (datalen == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
auto n = rb_.write(data, datalen);
|
|
|
|
if (n < static_cast<decltype(n)>(datalen)) {
|
|
|
|
data_pending_ = data + n;
|
|
|
|
data_pendinglen_ = datalen - n;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
int Http2Handler::read_clear() {
|
2013-07-22 14:30:40 +02:00
|
|
|
int rv;
|
2014-12-23 16:05:45 +01:00
|
|
|
uint8_t buf[8192];
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
for (;;) {
|
|
|
|
ssize_t nread;
|
|
|
|
while ((nread = read(fd_, buf, sizeof(buf))) == -1 && errno == EINTR)
|
|
|
|
;
|
|
|
|
if (nread == -1) {
|
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (nread == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
rv = nghttp2_session_mem_recv(session_, buf, nread);
|
|
|
|
if (rv < 0) {
|
|
|
|
std::cerr << "nghttp2_session_mem_recv() returned error: "
|
|
|
|
<< nghttp2_strerror(rv) << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
|
|
|
|
return write_(*this);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
int Http2Handler::write_clear() {
|
|
|
|
auto loop = sessions_->get_loop();
|
|
|
|
for (;;) {
|
|
|
|
if (rb_.rleft() > 0) {
|
|
|
|
struct iovec iov[2];
|
|
|
|
auto iovcnt = rb_.riovec(iov);
|
|
|
|
|
|
|
|
ssize_t nwrite;
|
|
|
|
while ((nwrite = writev(fd_, iov, iovcnt)) == -1 && errno == EINTR)
|
|
|
|
;
|
|
|
|
if (nwrite == -1) {
|
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
|
|
ev_io_start(loop, &wev_);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
rb_.drain(nwrite);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (fill_rb() != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (rb_.rleft() == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (rb_.rleft() == 0) {
|
|
|
|
ev_io_stop(loop, &wev_);
|
|
|
|
} else {
|
|
|
|
ev_io_start(loop, &wev_);
|
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (nghttp2_session_want_read(session_) == 0 &&
|
|
|
|
nghttp2_session_want_write(session_) == 0 && rb_.rleft() == 0) {
|
|
|
|
return -1;
|
2014-03-16 11:37:06 +01:00
|
|
|
}
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
int Http2Handler::tls_handshake() {
|
|
|
|
ev_io_stop(sessions_->get_loop(), &wev_);
|
2014-09-18 17:58:32 +02:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
auto rv = SSL_do_handshake(ssl_);
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (rv == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2014-09-18 17:58:32 +02:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (rv < 0) {
|
|
|
|
auto err = SSL_get_error(ssl_, rv);
|
|
|
|
switch (err) {
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
return 0;
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
ev_io_start(sessions_->get_loop(), &wev_);
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
return -1;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (sessions_->get_config()->verbose) {
|
|
|
|
std::cerr << "SSL/TLS handshake completed" << std::endl;
|
|
|
|
}
|
2014-09-18 17:58:32 +02:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (verify_npn_result() != 0) {
|
|
|
|
return -1;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
read_ = &Http2Handler::read_tls;
|
|
|
|
write_ = &Http2Handler::write_tls;
|
|
|
|
|
|
|
|
if (on_connect() != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
int Http2Handler::read_tls() {
|
|
|
|
uint8_t buf[8192];
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
for (;;) {
|
|
|
|
auto rv = SSL_read(ssl_, buf, sizeof(buf));
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (rv == 0) {
|
2014-03-16 11:37:06 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
|
|
|
|
if (rv < 0) {
|
|
|
|
auto err = SSL_get_error(ssl_, rv);
|
|
|
|
switch (err) {
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
goto fin;
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
ev_io_start(sessions_->get_loop(), &wev_);
|
|
|
|
goto fin;
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
|
|
|
|
auto nread = rv;
|
|
|
|
rv = nghttp2_session_mem_recv(session_, buf, nread);
|
|
|
|
if (rv < 0) {
|
|
|
|
std::cerr << "nghttp2_session_mem_recv() returned error: "
|
|
|
|
<< nghttp2_strerror(rv) << std::endl;
|
2014-03-16 11:37:06 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
ev_io_stop(sessions_->get_loop(), &wev_);
|
2014-09-18 17:58:32 +02:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
fin:
|
|
|
|
return write_(*this);
|
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
int Http2Handler::write_tls() {
|
|
|
|
auto loop = sessions_->get_loop();
|
|
|
|
for (;;) {
|
|
|
|
if (rb_.rleft() > 0) {
|
|
|
|
const void *p;
|
|
|
|
size_t len;
|
|
|
|
std::tie(p, len) = rb_.get();
|
2014-02-19 15:27:21 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
auto rv = SSL_write(ssl_, p, len);
|
2013-07-22 14:30:40 +02:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (rv == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2014-02-19 15:27:21 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (rv < 0) {
|
|
|
|
auto err = SSL_get_error(ssl_, rv);
|
|
|
|
switch (err) {
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
ev_io_stop(loop, &wev_);
|
|
|
|
return 0;
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
ev_io_start(sessions_->get_loop(), &wev_);
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rb_.drain(rv);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (fill_rb() != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (rb_.rleft() == 0) {
|
|
|
|
break;
|
|
|
|
}
|
2014-12-23 16:46:04 +01:00
|
|
|
}
|
2014-02-19 15:27:21 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (rb_.rleft() == 0) {
|
|
|
|
ev_io_stop(loop, &wev_);
|
|
|
|
} else {
|
|
|
|
ev_io_start(loop, &wev_);
|
2014-12-23 16:46:04 +01:00
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (nghttp2_session_want_read(session_) == 0 &&
|
|
|
|
nghttp2_session_want_write(session_) == 0 && rb_.rleft() == 0) {
|
|
|
|
return -1;
|
2014-02-19 15:27:21 +01:00
|
|
|
}
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
return 0;
|
2014-09-18 17:58:32 +02:00
|
|
|
}
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
int Http2Handler::on_read() { return read_(*this); }
|
2013-07-22 14:30:40 +02:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
int Http2Handler::on_write() { return write_(*this); }
|
2013-10-27 15:02:39 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Handler::on_connect() {
|
2013-07-22 14:30:40 +02:00
|
|
|
int r;
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-09-13 12:50:44 +02:00
|
|
|
r = nghttp2_session_server_new2(&session_, sessions_->get_callbacks(), this,
|
|
|
|
sessions_->get_config()->session_option);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (r != 0) {
|
2013-07-22 14:30:40 +02:00
|
|
|
return r;
|
|
|
|
}
|
2014-01-09 15:47:21 +01:00
|
|
|
nghttp2_settings_entry entry[4];
|
2014-06-07 09:04:43 +02:00
|
|
|
size_t niv = 1;
|
2014-05-02 16:34:57 +02:00
|
|
|
|
2013-07-28 12:28:41 +02:00
|
|
|
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
2013-08-06 18:23:43 +02:00
|
|
|
entry[0].value = 100;
|
2014-05-02 16:34:57 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (sessions_->get_config()->header_table_size >= 0) {
|
2013-11-05 15:44:20 +01:00
|
|
|
entry[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
|
2013-11-06 15:32:32 +01:00
|
|
|
entry[niv].value = sessions_->get_config()->header_table_size;
|
2013-11-05 15:44:20 +01:00
|
|
|
++niv;
|
|
|
|
}
|
2013-10-25 15:50:24 +02:00
|
|
|
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry, niv);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (r != 0) {
|
2013-07-22 14:30:40 +02:00
|
|
|
return r;
|
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
|
|
|
|
ev_timer_start(sessions_->get_loop(), &settings_timerev_);
|
2013-10-27 15:02:39 +01:00
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
return on_write();
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Handler::verify_npn_result() {
|
2013-07-22 14:30:40 +02:00
|
|
|
const unsigned char *next_proto = nullptr;
|
|
|
|
unsigned int next_proto_len;
|
2014-01-01 15:54:28 +01:00
|
|
|
// Check the negotiated protocol in NPN or ALPN
|
2013-07-22 14:30:40 +02:00
|
|
|
SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len);
|
2014-11-27 15:39:04 +01:00
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
if (next_proto) {
|
|
|
|
if (sessions_->get_config()->verbose) {
|
|
|
|
std::string proto(next_proto, next_proto + next_proto_len);
|
2014-01-01 15:54:28 +01:00
|
|
|
std::cout << "The negotiated protocol: " << proto << std::endl;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (util::check_h2_is_selected(next_proto, next_proto_len)) {
|
2014-01-01 15:54:28 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len);
|
2014-11-27 15:39:04 +01:00
|
|
|
#else // OPENSSL_VERSION_NUMBER < 0x10002000L
|
2014-01-01 15:54:28 +01:00
|
|
|
break;
|
|
|
|
#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (sessions_->get_config()->verbose) {
|
2014-03-30 12:09:21 +02:00
|
|
|
std::cerr << "Client did not advertise HTTP/2 protocol."
|
2014-03-03 15:51:46 +01:00
|
|
|
<< " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
|
|
|
|
<< std::endl;
|
|
|
|
}
|
2014-01-01 15:54:28 +01:00
|
|
|
return -1;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Handler::submit_file_response(const std::string &status,
|
|
|
|
Stream *stream, time_t last_modified,
|
2013-07-22 14:30:40 +02:00
|
|
|
off_t file_length,
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_data_provider *data_prd) {
|
2014-11-10 14:35:08 +01:00
|
|
|
auto date_str = cached_date;
|
2014-11-10 13:23:26 +01:00
|
|
|
std::string content_length = util::utos(file_length);
|
2013-07-22 14:30:40 +02:00
|
|
|
std::string last_modified_str;
|
2014-12-23 16:56:25 +01:00
|
|
|
nghttp2_nv nva[] = {
|
2014-11-27 15:39:04 +01:00
|
|
|
http2::make_nv_ls(":status", status),
|
|
|
|
http2::make_nv_ls("server", NGHTTPD_SERVER),
|
|
|
|
http2::make_nv_ls("content-length", content_length),
|
|
|
|
http2::make_nv_ll("cache-control", "max-age=3600"),
|
|
|
|
http2::make_nv_ls("date", *date_str),
|
2014-12-23 16:56:25 +01:00
|
|
|
http2::make_nv_ll("", ""),
|
2013-07-22 14:30:40 +02:00
|
|
|
};
|
2014-12-23 16:56:25 +01:00
|
|
|
size_t nvlen = 5;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (last_modified != 0) {
|
2013-07-22 14:30:40 +02:00
|
|
|
last_modified_str = util::http_date(last_modified);
|
2014-12-23 16:56:25 +01:00
|
|
|
nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-12-23 16:56:25 +01:00
|
|
|
return nghttp2_submit_response(session_, stream->stream_id, nva, nvlen,
|
|
|
|
data_prd);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
|
|
|
|
const Headers &headers,
|
|
|
|
nghttp2_data_provider *data_prd) {
|
2014-11-10 14:35:08 +01:00
|
|
|
auto date_str = cached_date;
|
2014-11-27 15:39:04 +01:00
|
|
|
auto nva =
|
|
|
|
std::vector<nghttp2_nv>{http2::make_nv_ls(":status", status),
|
|
|
|
http2::make_nv_ls("server", NGHTTPD_SERVER),
|
|
|
|
http2::make_nv_ls("date", *date_str)};
|
|
|
|
for (auto &nv : headers) {
|
2014-11-02 15:33:04 +01:00
|
|
|
nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
|
2013-10-23 16:18:24 +02:00
|
|
|
}
|
2013-12-08 13:19:33 +01:00
|
|
|
int r = nghttp2_submit_response(session_, stream_id, nva.data(), nva.size(),
|
|
|
|
data_prd);
|
2013-07-22 14:30:40 +02:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
|
|
|
|
nghttp2_data_provider *data_prd) {
|
|
|
|
auto nva =
|
|
|
|
std::vector<nghttp2_nv>{http2::make_nv_ls(":status", status),
|
|
|
|
http2::make_nv_ls("server", NGHTTPD_SERVER)};
|
2013-12-08 13:19:33 +01:00
|
|
|
return nghttp2_submit_response(session_, stream_id, nva.data(), nva.size(),
|
|
|
|
data_prd);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Handler::submit_non_final_response(const std::string &status,
|
|
|
|
int32_t stream_id) {
|
|
|
|
auto nva = std::vector<nghttp2_nv>{http2::make_nv_ls(":status", status)};
|
|
|
|
return nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, stream_id, nullptr,
|
|
|
|
nva.data(), nva.size(), nullptr);
|
2014-07-23 16:32:57 +02:00
|
|
|
}
|
|
|
|
|
2014-03-21 15:26:53 +01:00
|
|
|
int Http2Handler::submit_push_promise(Stream *stream,
|
2014-11-27 15:39:04 +01:00
|
|
|
const std::string &push_path) {
|
|
|
|
auto itr =
|
|
|
|
std::lower_bound(std::begin(stream->headers), std::end(stream->headers),
|
|
|
|
Header(":authority", ""));
|
2014-04-03 04:22:11 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (itr == std::end(stream->headers) || (*itr).name != ":authority") {
|
2014-03-21 15:26:53 +01:00
|
|
|
itr = std::lower_bound(std::begin(stream->headers),
|
2014-11-27 15:39:04 +01:00
|
|
|
std::end(stream->headers), Header("host", ""));
|
2013-12-08 16:00:12 +01:00
|
|
|
}
|
2014-04-03 04:22:11 +02:00
|
|
|
|
2013-12-08 16:00:12 +01:00
|
|
|
auto nva = std::vector<nghttp2_nv>{
|
2014-11-27 15:39:04 +01:00
|
|
|
http2::make_nv_ll(":method", "GET"),
|
|
|
|
http2::make_nv_ls(":path", push_path),
|
|
|
|
get_config()->no_tls ? http2::make_nv_ll(":scheme", "http")
|
|
|
|
: http2::make_nv_ll(":scheme", "https"),
|
|
|
|
http2::make_nv_ls(":authority", (*itr).value)};
|
2014-05-07 16:24:07 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
auto promised_stream_id = nghttp2_submit_push_promise(
|
|
|
|
session_, NGHTTP2_FLAG_END_HEADERS, stream->stream_id, nva.data(),
|
|
|
|
nva.size(), nullptr);
|
2014-05-07 16:24:07 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (promised_stream_id < 0) {
|
2014-05-07 16:24:07 +02:00
|
|
|
return promised_stream_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto promised_stream = util::make_unique<Stream>(this, promised_stream_id);
|
|
|
|
|
2014-08-07 14:55:30 +02:00
|
|
|
append_nv(promised_stream.get(), nva);
|
2014-05-07 16:24:07 +02:00
|
|
|
add_stream(promised_stream_id, std::move(promised_stream));
|
|
|
|
|
|
|
|
return 0;
|
2013-12-08 16:00:12 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Handler::submit_rst_stream(Stream *stream, uint32_t error_code) {
|
2014-03-21 15:26:53 +01:00
|
|
|
remove_stream_read_timeout(stream);
|
|
|
|
remove_stream_write_timeout(stream);
|
2014-03-21 15:07:20 +01:00
|
|
|
|
|
|
|
return nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE,
|
2014-03-21 15:26:53 +01:00
|
|
|
stream->stream_id, error_code);
|
2014-03-21 15:07:20 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Handler::add_stream(int32_t stream_id,
|
|
|
|
std::unique_ptr<Stream> stream) {
|
2014-03-21 15:26:53 +01:00
|
|
|
id2stream_[stream_id] = std::move(stream);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Handler::remove_stream(int32_t stream_id) {
|
2014-03-21 15:26:53 +01:00
|
|
|
id2stream_.erase(stream_id);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
Stream *Http2Handler::get_stream(int32_t stream_id) {
|
2014-03-21 15:26:53 +01:00
|
|
|
auto itr = id2stream_.find(stream_id);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (itr == std::end(id2stream_)) {
|
2013-07-22 14:30:40 +02:00
|
|
|
return nullptr;
|
|
|
|
} else {
|
|
|
|
return (*itr).second.get();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int64_t Http2Handler::session_id() const { return session_id_; }
|
2013-07-22 14:30:40 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
Sessions *Http2Handler::get_sessions() const { return sessions_; }
|
2013-07-22 14:30:40 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
const Config *Http2Handler::get_config() const {
|
2013-07-22 14:30:40 +02:00
|
|
|
return sessions_->get_config();
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Handler::remove_settings_timer() {
|
2014-12-23 16:05:45 +01:00
|
|
|
ev_timer_stop(sessions_->get_loop(), &settings_timerev_);
|
2013-10-27 15:02:39 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Handler::terminate_session(uint32_t error_code) {
|
2013-12-25 16:23:07 +01:00
|
|
|
nghttp2_session_terminate_session(session_, error_code);
|
2013-10-27 15:02:39 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
|
|
|
uint8_t *buf, size_t length, uint32_t *data_flags,
|
|
|
|
nghttp2_data_source *source, void *user_data) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(user_data);
|
2014-03-21 16:36:39 +01:00
|
|
|
auto stream = hd->get_stream(stream_id);
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
int fd = source->fd;
|
2014-05-02 16:34:57 +02:00
|
|
|
ssize_t nread;
|
2014-03-21 16:36:39 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
while ((nread = read(fd, buf, length)) == -1 && errno == EINTR)
|
|
|
|
;
|
2014-05-02 16:34:57 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (nread == -1) {
|
2014-07-03 15:44:27 +02:00
|
|
|
remove_stream_read_timeout(stream);
|
|
|
|
remove_stream_write_timeout(stream);
|
2014-05-02 16:34:57 +02:00
|
|
|
|
2014-06-07 09:04:43 +02:00
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
2014-03-21 16:36:39 +01:00
|
|
|
}
|
|
|
|
|
2014-12-23 16:31:21 +01:00
|
|
|
stream->body_left -= nread;
|
|
|
|
if (nread == 0 || stream->body_left <= 0) {
|
2014-04-05 10:59:24 +02:00
|
|
|
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
2014-07-03 15:44:27 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
|
2014-07-03 15:44:27 +02:00
|
|
|
remove_stream_read_timeout(stream);
|
|
|
|
remove_stream_write_timeout(stream);
|
|
|
|
|
|
|
|
hd->submit_rst_stream(stream, NGHTTP2_NO_ERROR);
|
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-03-21 16:36:39 +01:00
|
|
|
|
2014-05-02 16:34:57 +02:00
|
|
|
return nread;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
2014-03-21 15:26:53 +01:00
|
|
|
void prepare_status_response(Stream *stream, Http2Handler *hd,
|
2014-11-27 15:39:04 +01:00
|
|
|
const std::string &status) {
|
2013-07-22 14:30:40 +02:00
|
|
|
int pipefd[2];
|
2014-11-27 15:39:04 +01:00
|
|
|
if (status == STATUS_304 || pipe(pipefd) == -1) {
|
2014-03-21 15:26:53 +01:00
|
|
|
hd->submit_response(status, stream->stream_id, 0);
|
2014-03-04 15:29:30 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::string body;
|
|
|
|
body.reserve(256);
|
|
|
|
body = "<html><head><title>";
|
|
|
|
body += status;
|
|
|
|
body += "</title></head><body><h1>";
|
|
|
|
body += status;
|
|
|
|
body += "</h1><hr><address>";
|
|
|
|
body += NGHTTPD_SERVER;
|
|
|
|
body += " at port ";
|
|
|
|
body += util::utos(hd->get_config()->port);
|
|
|
|
body += "</address>";
|
|
|
|
body += "</body></html>";
|
2014-03-04 15:37:42 +01:00
|
|
|
|
2014-11-02 15:33:04 +01:00
|
|
|
Headers headers;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (hd->get_config()->error_gzip) {
|
2013-07-22 14:30:40 +02:00
|
|
|
gzFile write_fd = gzdopen(pipefd[1], "w");
|
|
|
|
gzwrite(write_fd, body.c_str(), body.size());
|
|
|
|
gzclose(write_fd);
|
2014-03-04 15:37:42 +01:00
|
|
|
headers.emplace_back("content-encoding", "gzip");
|
2014-03-04 15:29:30 +01:00
|
|
|
} else {
|
2014-07-17 16:41:54 +02:00
|
|
|
ssize_t rv;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
while ((rv = write(pipefd[1], body.c_str(), body.size())) == -1 &&
|
|
|
|
errno == EINTR)
|
|
|
|
;
|
2014-07-17 16:41:54 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != static_cast<ssize_t>(body.size())) {
|
2014-05-11 14:51:25 +02:00
|
|
|
std::cerr << "Could not write all response body: " << rv << std::endl;
|
|
|
|
}
|
2014-03-04 15:29:30 +01:00
|
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
|
2014-03-21 15:26:53 +01:00
|
|
|
stream->file = pipefd[0];
|
2014-12-23 16:31:21 +01:00
|
|
|
stream->body_left = body.size();
|
2014-03-04 15:29:30 +01:00
|
|
|
nghttp2_data_provider data_prd;
|
|
|
|
data_prd.source.fd = pipefd[0];
|
|
|
|
data_prd.read_callback = file_read_callback;
|
|
|
|
headers.emplace_back("content-type", "text/html; charset=UTF-8");
|
2014-03-21 15:26:53 +01:00
|
|
|
hd->submit_response(status, stream->stream_id, headers, &data_prd);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-11-02 15:27:38 +01:00
|
|
|
namespace {
|
|
|
|
void prepare_redirect_response(Stream *stream, Http2Handler *hd,
|
2014-11-27 15:39:04 +01:00
|
|
|
const std::string &path,
|
|
|
|
const std::string &status) {
|
2014-11-02 15:27:38 +01:00
|
|
|
auto scheme = http2::get_unique_header(stream->headers, ":scheme");
|
|
|
|
auto authority = http2::get_unique_header(stream->headers, ":authority");
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!authority) {
|
2014-12-24 16:49:36 +01:00
|
|
|
authority = http2::get_unique_header(stream->headers, "host");
|
2014-11-02 15:27:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
auto redirect_url = scheme->value;
|
|
|
|
redirect_url += "://";
|
|
|
|
redirect_url += authority->value;
|
|
|
|
redirect_url += path;
|
|
|
|
|
2014-11-02 15:33:04 +01:00
|
|
|
auto headers = Headers{{"location", redirect_url}};
|
2014-11-02 15:27:38 +01:00
|
|
|
|
|
|
|
hd->submit_response(status, stream->stream_id, headers, nullptr);
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void prepare_response(Stream *stream, Http2Handler *hd,
|
|
|
|
bool allow_push = true) {
|
2013-12-08 16:00:12 +01:00
|
|
|
int rv;
|
2014-11-27 15:39:04 +01:00
|
|
|
auto reqpath =
|
|
|
|
(*std::lower_bound(std::begin(stream->headers), std::end(stream->headers),
|
|
|
|
Header(":path", ""))).value;
|
|
|
|
auto ims =
|
|
|
|
std::lower_bound(std::begin(stream->headers), std::end(stream->headers),
|
|
|
|
Header("if-modified-since", ""));
|
2014-04-03 04:22:11 +02:00
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
time_t last_mod = 0;
|
|
|
|
bool last_mod_found = false;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ims != std::end(stream->headers) && (*ims).name == "if-modified-since") {
|
|
|
|
last_mod_found = true;
|
|
|
|
last_mod = util::parse_http_date((*ims).value);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-11-02 15:27:38 +01:00
|
|
|
auto query_pos = reqpath.find("?");
|
|
|
|
std::string url;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (query_pos != std::string::npos) {
|
2013-07-22 14:30:40 +02:00
|
|
|
// Do not response to this request to allow clients to test timeouts.
|
2014-11-27 15:39:04 +01:00
|
|
|
if (reqpath.find("nghttpd_do_not_respond_to_req=yes", query_pos) !=
|
|
|
|
std::string::npos) {
|
2013-07-22 14:30:40 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-11-02 15:27:38 +01:00
|
|
|
url = reqpath.substr(0, query_pos);
|
|
|
|
} else {
|
|
|
|
url = reqpath;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-11-02 15:27:38 +01:00
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
url = util::percentDecode(url.begin(), url.end());
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!util::check_path(url)) {
|
2014-03-21 15:26:53 +01:00
|
|
|
prepare_status_response(stream, hd, STATUS_404);
|
2013-07-22 14:30:40 +02:00
|
|
|
return;
|
|
|
|
}
|
2013-12-08 16:00:12 +01:00
|
|
|
auto push_itr = hd->get_config()->push.find(url);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (allow_push && push_itr != std::end(hd->get_config()->push)) {
|
|
|
|
for (auto &push_path : (*push_itr).second) {
|
2014-03-21 15:26:53 +01:00
|
|
|
rv = hd->submit_push_promise(stream, push_path);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-12-08 16:00:12 +01:00
|
|
|
std::cerr << "nghttp2_submit_push_promise() returned error: "
|
|
|
|
<< nghttp2_strerror(rv) << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
std::string path = hd->get_config()->htdocs + url;
|
|
|
|
if (path[path.size() - 1] == '/') {
|
2013-07-22 14:30:40 +02:00
|
|
|
path += DEFAULT_HTML;
|
|
|
|
}
|
|
|
|
int file = open(path.c_str(), O_RDONLY | O_BINARY);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (file == -1) {
|
2014-03-21 15:26:53 +01:00
|
|
|
prepare_status_response(stream, hd, STATUS_404);
|
2014-06-07 09:04:43 +02:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct stat buf;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (fstat(file, &buf) == -1) {
|
2014-06-07 09:04:43 +02:00
|
|
|
close(file);
|
|
|
|
prepare_status_response(stream, hd, STATUS_404);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (buf.st_mode & S_IFDIR) {
|
2014-11-08 15:07:40 +01:00
|
|
|
close(file);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (query_pos == std::string::npos) {
|
2014-11-02 15:27:38 +01:00
|
|
|
reqpath += "/";
|
|
|
|
} else {
|
|
|
|
reqpath.insert(query_pos, "/");
|
|
|
|
}
|
|
|
|
|
|
|
|
prepare_redirect_response(stream, hd, reqpath, STATUS_301);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-06-07 09:04:43 +02:00
|
|
|
stream->file = file;
|
2014-12-23 16:31:21 +01:00
|
|
|
stream->body_left = buf.st_size;
|
2014-06-07 09:04:43 +02:00
|
|
|
|
|
|
|
nghttp2_data_provider data_prd;
|
|
|
|
|
|
|
|
data_prd.source.fd = file;
|
|
|
|
data_prd.read_callback = file_read_callback;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (last_mod_found && buf.st_mtime <= last_mod) {
|
2014-06-07 09:04:43 +02:00
|
|
|
prepare_status_response(stream, hd, STATUS_304);
|
|
|
|
|
|
|
|
return;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-06-07 09:04:43 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
hd->submit_file_response(STATUS_200, stream, buf.st_mtime, buf.st_size,
|
|
|
|
&data_prd);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-08-27 20:14:19 +02:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
const char *REQUIRED_HEADERS[] = {":method", ":path", ":scheme", nullptr};
|
2013-08-27 20:14:19 +02:00
|
|
|
} // namespace
|
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
2014-01-16 15:41:13 +01:00
|
|
|
const uint8_t *name, size_t namelen,
|
2014-11-27 15:39:04 +01:00
|
|
|
const uint8_t *value, size_t valuelen, uint8_t flags,
|
|
|
|
void *user_data) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(user_data);
|
|
|
|
if (hd->get_config()->verbose) {
|
2014-01-27 16:20:48 +01:00
|
|
|
print_session_id(hd->session_id());
|
2014-01-16 15:41:13 +01:00
|
|
|
verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
|
2014-04-01 19:10:35 +02:00
|
|
|
flags, user_data);
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.type != NGHTTP2_HEADERS ||
|
|
|
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
auto stream = hd->get_stream(frame->hd.stream_id);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!stream) {
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!http2::check_nv(name, namelen, value, valuelen)) {
|
2014-01-16 18:16:53 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-08-08 13:52:32 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (namelen > 0 && name[0] == ':') {
|
|
|
|
if ((!stream->headers.empty() &&
|
|
|
|
stream->headers.back().name.c_str()[0] != ':') ||
|
|
|
|
!http2::check_http2_request_pseudo_header(name, namelen)) {
|
2014-08-08 13:52:32 +02:00
|
|
|
|
|
|
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
|
|
|
|
NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-12 11:55:08 +02:00
|
|
|
http2::add_header(stream->headers, name, namelen, value, valuelen,
|
|
|
|
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-01-29 13:23:13 +01:00
|
|
|
int on_begin_headers_callback(nghttp2_session *session,
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_frame *frame, void *user_data) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(user_data);
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.type != NGHTTP2_HEADERS ||
|
|
|
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-03-21 15:26:53 +01:00
|
|
|
auto stream = util::make_unique<Stream>(hd, frame->hd.stream_id);
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-03-21 15:26:53 +01:00
|
|
|
add_stream_read_timeout(stream.get());
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-03-21 15:26:53 +01:00
|
|
|
hd->add_stream(frame->hd.stream_id, std::move(stream));
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int hd_on_frame_recv_callback(nghttp2_session *session,
|
|
|
|
const nghttp2_frame *frame, void *user_data) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(user_data);
|
|
|
|
if (hd->get_config()->verbose) {
|
2013-07-22 14:30:40 +02:00
|
|
|
print_session_id(hd->session_id());
|
2013-12-20 15:48:56 +01:00
|
|
|
verbose_on_frame_recv_callback(session, frame, user_data);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
switch (frame->hd.type) {
|
2014-03-21 16:36:39 +01:00
|
|
|
case NGHTTP2_DATA: {
|
2014-01-27 14:13:41 +01:00
|
|
|
// TODO Handle POST
|
2014-03-21 16:36:39 +01:00
|
|
|
auto stream = hd->get_stream(frame->hd.stream_id);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!stream) {
|
2014-03-21 16:36:39 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
2014-03-21 16:36:39 +01:00
|
|
|
remove_stream_read_timeout(stream);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!hd->get_config()->early_response) {
|
2014-07-03 15:44:27 +02:00
|
|
|
prepare_response(stream, hd);
|
|
|
|
}
|
2014-03-21 16:36:39 +01:00
|
|
|
} else {
|
|
|
|
add_stream_read_timeout(stream);
|
2014-01-29 13:23:13 +01:00
|
|
|
}
|
2014-03-21 16:36:39 +01:00
|
|
|
|
2014-01-27 14:13:41 +01:00
|
|
|
break;
|
2014-03-21 16:36:39 +01:00
|
|
|
}
|
2014-05-24 08:02:46 +02:00
|
|
|
case NGHTTP2_HEADERS: {
|
|
|
|
auto stream = hd->get_stream(frame->hd.stream_id);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!stream) {
|
2014-05-24 08:02:46 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-01-29 13:23:13 +01:00
|
|
|
http2::normalize_headers(stream->headers);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!http2::check_http2_request_headers(stream->headers)) {
|
2014-03-21 15:07:20 +01:00
|
|
|
hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
|
2014-01-29 13:23:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
for (size_t i = 0; REQUIRED_HEADERS[i]; ++i) {
|
|
|
|
if (!http2::get_unique_header(stream->headers, REQUIRED_HEADERS[i])) {
|
2014-03-21 15:07:20 +01:00
|
|
|
hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
|
2014-01-29 13:23:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// intermediary translating from HTTP/1 request to HTTP/2 may
|
|
|
|
// not produce :authority header field. In this case, it should
|
|
|
|
// provide host HTTP/1.1 header field.
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!http2::get_unique_header(stream->headers, ":authority") &&
|
|
|
|
!http2::get_unique_header(stream->headers, "host")) {
|
2014-03-21 15:07:20 +01:00
|
|
|
hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
|
2014-01-29 13:23:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-07-03 15:44:27 +02:00
|
|
|
|
2014-07-23 16:32:57 +02:00
|
|
|
auto expect100 = http2::get_header(stream->headers, "expect");
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (expect100 && util::strieq("100-continue", expect100->value.c_str())) {
|
2014-07-23 16:32:57 +02:00
|
|
|
hd->submit_non_final_response("100", frame->hd.stream_id);
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (hd->get_config()->early_response) {
|
2014-07-03 15:44:27 +02:00
|
|
|
prepare_response(stream, hd);
|
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-05-24 08:02:46 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
2014-05-24 08:02:46 +02:00
|
|
|
remove_stream_read_timeout(stream);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!hd->get_config()->early_response) {
|
2014-07-03 15:44:27 +02:00
|
|
|
prepare_response(stream, hd);
|
|
|
|
}
|
2014-05-24 08:02:46 +02:00
|
|
|
} else {
|
|
|
|
add_stream_read_timeout(stream);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-05-24 08:02:46 +02:00
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
break;
|
2014-05-24 08:02:46 +02:00
|
|
|
}
|
2013-10-27 15:02:39 +01:00
|
|
|
case NGHTTP2_SETTINGS:
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
|
2013-10-27 15:02:39 +01:00
|
|
|
hd->remove_settings_timer();
|
|
|
|
}
|
|
|
|
break;
|
2014-01-09 15:47:21 +01:00
|
|
|
case NGHTTP2_PUSH_PROMISE:
|
|
|
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
|
|
|
frame->push_promise.promised_stream_id,
|
|
|
|
NGHTTP2_REFUSED_STREAM);
|
|
|
|
break;
|
2013-07-22 14:30:40 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2013-08-29 14:03:39 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-12-08 16:22:01 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int hd_on_frame_send_callback(nghttp2_session *session,
|
|
|
|
const nghttp2_frame *frame, void *user_data) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(user_data);
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (hd->get_config()->verbose) {
|
2013-07-22 14:30:40 +02:00
|
|
|
print_session_id(hd->session_id());
|
2013-12-20 15:48:56 +01:00
|
|
|
verbose_on_frame_send_callback(session, frame, user_data);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
switch (frame->hd.type) {
|
2014-03-21 15:07:20 +01:00
|
|
|
case NGHTTP2_DATA:
|
|
|
|
case NGHTTP2_HEADERS: {
|
|
|
|
auto stream = hd->get_stream(frame->hd.stream_id);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!stream) {
|
2014-03-21 15:07:20 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
2014-03-21 16:36:39 +01:00
|
|
|
remove_stream_write_timeout(stream);
|
2014-11-27 15:39:04 +01:00
|
|
|
} else if (std::min(nghttp2_session_get_stream_remote_window_size(
|
|
|
|
session, frame->hd.stream_id),
|
|
|
|
nghttp2_session_get_remote_window_size(session)) <= 0) {
|
2014-03-21 16:36:39 +01:00
|
|
|
// If stream is blocked by flow control, enable write timeout.
|
|
|
|
add_stream_read_timeout_if_pending(stream);
|
2014-03-21 15:07:20 +01:00
|
|
|
add_stream_write_timeout(stream);
|
2014-03-21 16:36:39 +01:00
|
|
|
} else {
|
|
|
|
add_stream_read_timeout_if_pending(stream);
|
|
|
|
remove_stream_write_timeout(stream);
|
2014-03-21 15:07:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case NGHTTP2_PUSH_PROMISE: {
|
|
|
|
auto promised_stream_id = frame->push_promise.promised_stream_id;
|
2014-03-21 16:36:39 +01:00
|
|
|
auto promised_stream = hd->get_stream(promised_stream_id);
|
|
|
|
auto stream = hd->get_stream(frame->hd.stream_id);
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!stream || !promised_stream) {
|
2014-03-21 15:07:20 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-03-21 16:36:39 +01:00
|
|
|
add_stream_read_timeout_if_pending(stream);
|
|
|
|
add_stream_write_timeout(stream);
|
|
|
|
|
|
|
|
prepare_response(promised_stream, hd, /*allow_push */ false);
|
2014-03-21 15:07:20 +01:00
|
|
|
}
|
|
|
|
}
|
2013-08-29 14:48:34 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-02-11 07:28:44 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
ssize_t select_padding_callback(nghttp2_session *session,
|
|
|
|
const nghttp2_frame *frame, size_t max_payload,
|
|
|
|
void *user_data) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(user_data);
|
2014-02-15 08:40:32 +01:00
|
|
|
return std::min(max_payload, frame->hd.length + hd->get_config()->padding);
|
2014-02-11 07:28:44 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
|
|
|
int32_t stream_id, const uint8_t *data,
|
|
|
|
size_t len, void *user_data) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(user_data);
|
2014-03-21 15:26:53 +01:00
|
|
|
auto stream = hd->get_stream(stream_id);
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!stream) {
|
2014-03-21 15:07:20 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
// TODO Handle POST
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2014-03-21 15:26:53 +01:00
|
|
|
add_stream_read_timeout(stream);
|
2014-03-21 15:07:20 +01:00
|
|
|
|
2013-08-29 14:18:40 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
|
|
|
uint32_t error_code, void *user_data) {
|
|
|
|
auto hd = static_cast<Http2Handler *>(user_data);
|
2013-07-22 14:30:40 +02:00
|
|
|
hd->remove_stream(stream_id);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (hd->get_config()->verbose) {
|
2013-07-22 14:30:40 +02:00
|
|
|
print_session_id(hd->session_id());
|
|
|
|
print_timer();
|
|
|
|
printf(" stream_id=%d closed\n", stream_id);
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
2013-08-29 15:58:05 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) {
|
|
|
|
nghttp2_session_callbacks_set_on_stream_close_callback(
|
|
|
|
callbacks, on_stream_close_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_recv_callback(
|
|
|
|
callbacks, hd_on_frame_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_send_callback(
|
|
|
|
callbacks, hd_on_frame_send_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (config->verbose) {
|
|
|
|
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
|
|
|
|
callbacks, verbose_on_invalid_frame_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
|
|
|
|
callbacks, on_data_chunk_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
|
|
|
on_header_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_begin_headers_callback(
|
|
|
|
callbacks, on_begin_headers_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (config->padding) {
|
|
|
|
nghttp2_session_callbacks_set_select_padding_callback(
|
|
|
|
callbacks, select_padding_callback);
|
2014-02-11 08:48:27 +01:00
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-03-04 15:14:26 +01:00
|
|
|
struct ClientInfo {
|
|
|
|
int fd;
|
|
|
|
};
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
struct Worker {
|
|
|
|
std::unique_ptr<Sessions> sessions;
|
|
|
|
ev_async w;
|
|
|
|
// protectes q
|
|
|
|
std::mutex m;
|
|
|
|
std::deque<ClientInfo> q;
|
|
|
|
};
|
|
|
|
|
2014-03-04 15:14:26 +01:00
|
|
|
namespace {
|
2014-12-23 16:05:45 +01:00
|
|
|
void worker_acceptcb(struct ev_loop *loop, ev_async *w, int revents) {
|
|
|
|
auto worker = static_cast<Worker *>(w->data);
|
|
|
|
auto &sessions = worker->sessions;
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(worker->m);
|
|
|
|
|
|
|
|
for (auto c : worker->q) {
|
|
|
|
sessions->accept_connection(c.fd);
|
2014-03-04 15:14:26 +01:00
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
|
|
|
|
worker->q.clear();
|
2014-03-04 15:14:26 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-12-23 16:05:45 +01:00
|
|
|
void run_worker(Worker *worker) { ev_run(worker->sessions->get_loop(), 0); }
|
2014-03-04 15:14:26 +01:00
|
|
|
} // namespace
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
class AcceptHandler {
|
2013-07-22 14:30:40 +02:00
|
|
|
public:
|
2014-12-23 16:05:45 +01:00
|
|
|
AcceptHandler(Sessions *sessions, const Config *config)
|
2014-11-27 15:39:04 +01:00
|
|
|
: sessions_(sessions), config_(config), next_worker_(0) {
|
|
|
|
if (config_->num_worker == 1) {
|
2014-03-04 15:14:26 +01:00
|
|
|
return;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
for (size_t i = 0; i < config_->num_worker; ++i) {
|
|
|
|
if (config_->verbose) {
|
2014-03-04 15:14:26 +01:00
|
|
|
std::cerr << "spawning thread #" << i << std::endl;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
auto worker = util::make_unique<Worker>();
|
|
|
|
auto loop = ev_loop_new(0);
|
|
|
|
worker->sessions =
|
|
|
|
util::make_unique<Sessions>(loop, config_, sessions_->get_ssl_ctx());
|
|
|
|
ev_async_init(&worker->w, worker_acceptcb);
|
|
|
|
worker->w.data = worker.get();
|
|
|
|
ev_async_start(loop, &worker->w);
|
|
|
|
|
|
|
|
auto t = std::thread(run_worker, worker.get());
|
2014-03-04 15:14:26 +01:00
|
|
|
t.detach();
|
2014-12-23 16:05:45 +01:00
|
|
|
workers_.push_back(std::move(worker));
|
2014-03-04 15:14:26 +01:00
|
|
|
}
|
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
void accept_connection(int fd) {
|
2014-11-27 15:39:04 +01:00
|
|
|
if (config_->num_worker == 1) {
|
2014-03-04 15:14:26 +01:00
|
|
|
sessions_->accept_connection(fd);
|
|
|
|
return;
|
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
|
2014-03-04 15:14:26 +01:00
|
|
|
// Dispatch client to the one of the worker threads, in a round
|
|
|
|
// robin manner.
|
2014-12-23 16:05:45 +01:00
|
|
|
auto &worker = workers_[next_worker_];
|
2014-11-27 15:39:04 +01:00
|
|
|
if (next_worker_ == config_->num_worker - 1) {
|
2014-03-04 15:14:26 +01:00
|
|
|
next_worker_ = 0;
|
|
|
|
} else {
|
|
|
|
++next_worker_;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(worker->m);
|
|
|
|
worker->q.push_back({fd});
|
|
|
|
}
|
|
|
|
ev_async_send(worker->sessions->get_loop(), &worker->w);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
private:
|
2014-12-23 16:05:45 +01:00
|
|
|
std::vector<std::unique_ptr<Worker>> workers_;
|
2013-07-22 14:30:40 +02:00
|
|
|
Sessions *sessions_;
|
2014-03-04 15:14:26 +01:00
|
|
|
const Config *config_;
|
|
|
|
// In multi threading mode, this points to the next thread that
|
|
|
|
// client will be dispatched.
|
|
|
|
size_t next_worker_;
|
2013-07-22 14:30:40 +02:00
|
|
|
};
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
namespace {
|
|
|
|
void acceptcb(struct ev_loop *loop, ev_io *w, int revents);
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
class ListenEventHandler {
|
|
|
|
public:
|
|
|
|
ListenEventHandler(Sessions *sessions, int fd,
|
|
|
|
std::shared_ptr<AcceptHandler> acceptor)
|
|
|
|
: acceptor_(acceptor), sessions_(sessions), fd_(fd) {
|
|
|
|
ev_io_init(&w_, acceptcb, fd, EV_READ);
|
|
|
|
w_.data = this;
|
|
|
|
ev_io_start(sessions_->get_loop(), &w_);
|
|
|
|
}
|
|
|
|
void accept_connection() {
|
|
|
|
for (;;) {
|
|
|
|
auto fd = accept(fd_, nullptr, nullptr);
|
|
|
|
if (fd == -1) {
|
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
make_socket_nonblocking(fd);
|
|
|
|
acceptor_->accept_connection(fd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
ev_io w_;
|
|
|
|
std::shared_ptr<AcceptHandler> acceptor_;
|
|
|
|
Sessions *sessions_;
|
|
|
|
int fd_;
|
|
|
|
};
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void acceptcb(struct ev_loop *loop, ev_io *w, int revents) {
|
|
|
|
auto handler = static_cast<ListenEventHandler *>(w->data);
|
|
|
|
handler->accept_connection();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
HttpServer::HttpServer(const Config *config) : config_(config) {}
|
2013-07-22 14:30:40 +02:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len,
|
2014-11-27 15:39:04 +01:00
|
|
|
void *arg) {
|
|
|
|
auto next_proto = static_cast<std::vector<unsigned char> *>(arg);
|
2014-11-17 11:01:06 +01:00
|
|
|
*data = next_proto->data();
|
|
|
|
*len = next_proto->size();
|
2013-07-22 14:30:40 +02:00
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
|
2013-07-22 14:30:40 +02:00
|
|
|
// We don't verify the client certificate. Just request it for the
|
|
|
|
// testing purpose.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-12-23 16:05:45 +01:00
|
|
|
int start_listen(struct ev_loop *loop, Sessions *sessions,
|
|
|
|
const Config *config) {
|
2013-07-22 14:30:40 +02:00
|
|
|
addrinfo hints;
|
|
|
|
int r;
|
2014-12-23 16:05:45 +01:00
|
|
|
bool ok = false;
|
2014-05-14 15:39:28 +02:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
auto acceptor = std::make_shared<AcceptHandler>(sessions, config);
|
2014-05-14 15:39:28 +02:00
|
|
|
auto service = util::utos(config->port);
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
memset(&hints, 0, sizeof(addrinfo));
|
|
|
|
hints.ai_family = AF_UNSPEC;
|
|
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
hints.ai_flags = AI_PASSIVE;
|
|
|
|
#ifdef AI_ADDRCONFIG
|
|
|
|
hints.ai_flags |= AI_ADDRCONFIG;
|
|
|
|
#endif // AI_ADDRCONFIG
|
|
|
|
|
|
|
|
addrinfo *res, *rp;
|
2014-05-14 15:39:28 +02:00
|
|
|
r = getaddrinfo(nullptr, service.c_str(), &hints, &res);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (r != 0) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cerr << "getaddrinfo() failed: " << gai_strerror(r) << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
for (rp = res; rp; rp = rp->ai_next) {
|
2013-07-22 14:30:40 +02:00
|
|
|
int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (fd == -1) {
|
2013-07-22 14:30:40 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
int val = 1;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
|
|
|
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
2013-07-22 14:30:40 +02:00
|
|
|
close(fd);
|
|
|
|
continue;
|
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
make_socket_nonblocking(fd);
|
2013-07-22 14:30:40 +02:00
|
|
|
#ifdef IPV6_V6ONLY
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rp->ai_family == AF_INET6) {
|
|
|
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
|
|
|
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
2013-07-22 14:30:40 +02:00
|
|
|
close(fd);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // IPV6_V6ONLY
|
2014-12-23 16:05:45 +01:00
|
|
|
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0 && listen(fd, 1000) == 0) {
|
|
|
|
new ListenEventHandler(sessions, fd, acceptor);
|
2014-04-05 13:04:09 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (config->verbose) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6")
|
2014-11-27 15:39:04 +01:00
|
|
|
<< ": listen on port " << config->port << std::endl;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2014-12-23 16:05:45 +01:00
|
|
|
ok = true;
|
2013-07-22 14:30:40 +02:00
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
std::cerr << strerror(errno) << std::endl;
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
freeaddrinfo(res);
|
2014-12-15 15:17:50 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
if (!ok) {
|
2014-12-15 15:17:50 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-01-01 15:54:28 +01:00
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
|
|
|
|
unsigned char *outlen, const unsigned char *in,
|
|
|
|
unsigned int inlen, void *arg) {
|
|
|
|
auto config = static_cast<HttpServer *>(arg)->get_config();
|
|
|
|
if (config->verbose) {
|
2014-01-01 15:54:28 +01:00
|
|
|
std::cout << "[ALPN] client offers:" << std::endl;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (config->verbose) {
|
|
|
|
for (unsigned int i = 0; i < inlen; i += in [i] + 1) {
|
2014-01-01 15:54:28 +01:00
|
|
|
std::cout << " * ";
|
2014-11-27 15:39:04 +01:00
|
|
|
std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
|
2014-01-01 15:54:28 +01:00
|
|
|
std::cout << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2014-12-15 14:51:34 +01:00
|
|
|
if (!util::select_h2(out, outlen, in, inlen)) {
|
2014-01-01 15:54:28 +01:00
|
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
|
|
}
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int HttpServer::run() {
|
2013-07-22 14:30:40 +02:00
|
|
|
SSL_CTX *ssl_ctx = nullptr;
|
2014-11-17 11:01:06 +01:00
|
|
|
std::vector<unsigned char> next_proto;
|
2014-11-14 15:14:39 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!config_->no_tls) {
|
2013-07-22 14:30:40 +02:00
|
|
|
ssl_ctx = SSL_CTX_new(SSLv23_server_method());
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!ssl_ctx) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
2014-03-03 15:49:13 +01:00
|
|
|
|
|
|
|
SSL_CTX_set_options(ssl_ctx,
|
2014-10-22 16:14:07 +02:00
|
|
|
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
|
2014-11-27 15:39:04 +01:00
|
|
|
SSL_OP_NO_COMPRESSION |
|
|
|
|
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
|
|
|
|
SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_TICKET |
|
|
|
|
SSL_OP_CIPHER_SERVER_PREFERENCE);
|
2013-07-22 14:30:40 +02:00
|
|
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
|
|
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
|
2014-03-03 15:49:13 +01:00
|
|
|
|
2014-06-28 08:28:19 +02:00
|
|
|
SSL_CTX_set_cipher_list(ssl_ctx, ssl::DEFAULT_CIPHER_LIST);
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-03-03 15:49:13 +01:00
|
|
|
const unsigned char sid_ctx[] = "nghttpd";
|
2014-11-27 15:39:04 +01:00
|
|
|
SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1);
|
2014-03-03 15:49:13 +01:00
|
|
|
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER);
|
|
|
|
|
|
|
|
#ifndef OPENSSL_NO_EC
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
// Disabled SSL_CTX_set_ecdh_auto, because computational cost of
|
|
|
|
// chosen curve is much higher than P-256.
|
2014-06-28 08:35:10 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
// #if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
// SSL_CTX_set_ecdh_auto(ssl_ctx, 1);
|
|
|
|
// #else // OPENSSL_VERSION_NUBMER < 0x10002000L
|
2014-03-03 15:49:13 +01:00
|
|
|
// Use P-256, which is sufficiently secure at the time of this
|
|
|
|
// writing.
|
|
|
|
auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ecdh == nullptr) {
|
2014-03-03 15:49:13 +01:00
|
|
|
std::cerr << "EC_KEY_new_by_curv_name failed: "
|
|
|
|
<< ERR_error_string(ERR_get_error(), nullptr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
|
|
|
|
EC_KEY_free(ecdh);
|
2014-06-28 08:35:10 +02:00
|
|
|
// #endif // OPENSSL_VERSION_NUBMER < 0x10002000L
|
2014-03-03 15:49:13 +01:00
|
|
|
|
2014-05-14 16:22:23 +02:00
|
|
|
#endif // OPENSSL_NO_EC
|
2014-03-03 15:49:13 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!config_->dh_param_file.empty()) {
|
2014-06-28 08:43:06 +02:00
|
|
|
// Read DH parameters from file
|
|
|
|
auto bio = BIO_new_file(config_->dh_param_file.c_str(), "r");
|
2014-11-27 15:39:04 +01:00
|
|
|
if (bio == nullptr) {
|
2014-06-28 08:43:06 +02:00
|
|
|
std::cerr << "BIO_new_file() failed: "
|
|
|
|
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (dh == nullptr) {
|
2014-06-28 08:43:06 +02:00
|
|
|
std::cerr << "PEM_read_bio_DHparams() failed: "
|
|
|
|
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_CTX_set_tmp_dh(ssl_ctx, dh);
|
|
|
|
DH_free(dh);
|
|
|
|
BIO_free(bio);
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config_->private_key_file.c_str(),
|
|
|
|
SSL_FILETYPE_PEM) != 1) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
|
|
|
|
config_->cert_file.c_str()) != 1) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cerr << "SSL_CTX_check_private_key failed." << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (config_->verify_client) {
|
|
|
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
|
|
|
|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
2013-07-22 14:30:40 +02:00
|
|
|
verify_callback);
|
|
|
|
}
|
|
|
|
|
2014-11-17 11:01:06 +01:00
|
|
|
next_proto = util::get_default_alpn();
|
2013-07-22 14:30:40 +02:00
|
|
|
|
|
|
|
SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto);
|
2014-01-01 15:54:28 +01:00
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
// ALPN selection callback
|
|
|
|
SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, this);
|
|
|
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
auto loop = EV_DEFAULT;
|
2014-03-16 11:37:06 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
Sessions sessions(loop, config_, ssl_ctx);
|
|
|
|
if (start_listen(loop, &sessions, config_) != 0) {
|
2013-07-22 14:30:40 +02:00
|
|
|
std::cerr << "Could not listen" << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
2014-11-10 14:35:08 +01:00
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
ev_timer refresh_wtc;
|
|
|
|
ev_timer_init(&refresh_wtc, refresh_cb, 1.0, 0.);
|
|
|
|
ev_timer_again(loop, &refresh_wtc);
|
2014-11-10 14:35:08 +01:00
|
|
|
|
|
|
|
cached_date = std::make_shared<std::string>(util::http_date(time(nullptr)));
|
|
|
|
|
2014-12-23 16:05:45 +01:00
|
|
|
ev_run(loop, 0);
|
2013-07-22 14:30:40 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
const Config *HttpServer::get_config() const { return config_; }
|
2014-01-01 15:54:28 +01:00
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
} // namespace nghttp2
|