2013-07-22 14:30:40 +02:00
|
|
|
/*
|
|
|
|
* nghttp2 - HTTP/2.0 C Library
|
|
|
|
*
|
|
|
|
* 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>
|
|
|
|
|
|
|
|
#include <openssl/err.h>
|
|
|
|
|
|
|
|
#include <zlib.h>
|
|
|
|
|
|
|
|
#include <event.h>
|
|
|
|
#include <event2/bufferevent_ssl.h>
|
|
|
|
#include <event2/listener.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"
|
|
|
|
|
|
|
|
#ifndef O_BINARY
|
|
|
|
# define O_BINARY (0)
|
|
|
|
#endif // O_BINARY
|
|
|
|
|
|
|
|
namespace nghttp2 {
|
|
|
|
|
|
|
|
namespace {
|
2013-08-25 17:58:06 +02:00
|
|
|
const std::string STATUS_200 = "200";
|
|
|
|
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
|
|
|
|
|
|
|
|
Config::Config()
|
2013-12-06 15:17:38 +01:00
|
|
|
: data_ptr(nullptr),
|
2013-07-22 14:30:40 +02:00
|
|
|
on_request_recv_callback(nullptr),
|
2013-12-06 15:17:38 +01:00
|
|
|
output_upper_thres(1024*1024),
|
|
|
|
header_table_size(-1),
|
|
|
|
port(0),
|
|
|
|
verbose(false),
|
|
|
|
daemon(false),
|
2013-07-22 14:30:40 +02:00
|
|
|
verify_client(false),
|
|
|
|
no_tls(false),
|
2013-12-06 15:17:38 +01:00
|
|
|
no_flow_control(false)
|
2013-07-22 14:30:40 +02:00
|
|
|
{}
|
|
|
|
|
|
|
|
Request::Request(int32_t stream_id)
|
|
|
|
: stream_id(stream_id),
|
|
|
|
file(-1)
|
|
|
|
{}
|
|
|
|
|
|
|
|
Request::~Request()
|
|
|
|
{
|
|
|
|
if(file != -1) {
|
|
|
|
close(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Sessions {
|
|
|
|
public:
|
|
|
|
Sessions(event_base *evbase, const Config *config, SSL_CTX *ssl_ctx)
|
|
|
|
: evbase_(evbase),
|
|
|
|
config_(config),
|
|
|
|
ssl_ctx_(ssl_ctx)
|
|
|
|
{}
|
|
|
|
~Sessions()
|
|
|
|
{
|
|
|
|
for(auto handler : handlers_) {
|
|
|
|
delete handler;
|
|
|
|
}
|
|
|
|
SSL_CTX_free(ssl_ctx_);
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
SSL *ssl = SSL_new(ssl_ctx_);
|
|
|
|
if(!ssl) {
|
|
|
|
std::cerr << "SSL_new() failed" << std::endl;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
if(SSL_set_fd(ssl, fd) == 0) {
|
|
|
|
std::cerr << "SSL_set_fd() failed" << std::endl;
|
|
|
|
SSL_free(ssl);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return ssl;
|
|
|
|
}
|
|
|
|
const Config* get_config() const
|
|
|
|
{
|
|
|
|
return config_;
|
|
|
|
}
|
|
|
|
event_base* get_evbase() const
|
|
|
|
{
|
|
|
|
return evbase_;
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
std::set<Http2Handler*> handlers_;
|
|
|
|
event_base *evbase_;
|
|
|
|
const Config *config_;
|
|
|
|
SSL_CTX *ssl_ctx_;
|
|
|
|
};
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void delete_handler(Http2Handler *handler)
|
|
|
|
{
|
|
|
|
handler->remove_self();
|
|
|
|
delete handler;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void print_session_id(int64_t id)
|
|
|
|
{
|
|
|
|
std::cout << "[id=" << id << "] ";
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void on_session_closed(Http2Handler *hd, int64_t session_id)
|
|
|
|
{
|
|
|
|
if(hd->get_config()->verbose) {
|
|
|
|
print_session_id(session_id);
|
|
|
|
print_timer();
|
|
|
|
std::cout << " closed" << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void fill_callback(nghttp2_session_callbacks& callbacks, const Config *config);
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
Http2Handler::Http2Handler(Sessions *sessions,
|
|
|
|
int fd, SSL *ssl, int64_t session_id)
|
2013-12-06 15:17:38 +01:00
|
|
|
: session_id_(session_id),
|
|
|
|
session_(nullptr),
|
|
|
|
sessions_(sessions),
|
|
|
|
bev_(nullptr),
|
|
|
|
ssl_(ssl),
|
|
|
|
settings_timerev_(nullptr),
|
2013-10-27 15:02:39 +01:00
|
|
|
left_connhd_len_(NGHTTP2_CLIENT_CONNECTION_HEADER_LEN),
|
2013-12-06 15:17:38 +01:00
|
|
|
fd_(fd)
|
2013-07-22 14:30:40 +02:00
|
|
|
{}
|
|
|
|
|
|
|
|
Http2Handler::~Http2Handler()
|
|
|
|
{
|
|
|
|
on_session_closed(this, session_id_);
|
2013-10-27 15:02:39 +01:00
|
|
|
if(settings_timerev_) {
|
|
|
|
event_free(settings_timerev_);
|
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
nghttp2_session_del(session_);
|
|
|
|
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_);
|
|
|
|
}
|
|
|
|
if(bev_) {
|
2014-01-01 15:54:28 +01:00
|
|
|
bufferevent_disable(bev_, EV_READ | EV_WRITE);
|
2013-07-22 14:30:40 +02:00
|
|
|
bufferevent_free(bev_);
|
|
|
|
}
|
|
|
|
if(ssl_) {
|
|
|
|
SSL_free(ssl_);
|
|
|
|
}
|
|
|
|
shutdown(fd_, SHUT_WR);
|
|
|
|
close(fd_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Http2Handler::remove_self()
|
|
|
|
{
|
|
|
|
sessions_->remove_handler(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void readcb(bufferevent *bev, void *ptr)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
auto handler = reinterpret_cast<Http2Handler*>(ptr);
|
|
|
|
rv = handler->on_read();
|
|
|
|
if(rv != 0) {
|
|
|
|
delete_handler(handler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void writecb(bufferevent *bev, void *ptr)
|
|
|
|
{
|
|
|
|
if(evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int rv;
|
|
|
|
auto handler = reinterpret_cast<Http2Handler*>(ptr);
|
|
|
|
rv = handler->on_write();
|
|
|
|
if(rv != 0) {
|
|
|
|
delete_handler(handler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void eventcb(bufferevent *bev, short events, void *ptr)
|
|
|
|
{
|
|
|
|
auto handler = reinterpret_cast<Http2Handler*>(ptr);
|
|
|
|
if(events & BEV_EVENT_CONNECTED) {
|
|
|
|
// SSL/TLS handshake completed
|
|
|
|
if(handler->verify_npn_result() != 0) {
|
|
|
|
delete_handler(handler);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(handler->on_connect() != 0) {
|
|
|
|
delete_handler(handler);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if(events & BEV_EVENT_EOF) {
|
|
|
|
delete_handler(handler);
|
|
|
|
return;
|
|
|
|
} else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
|
|
|
|
delete_handler(handler);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void connhd_readcb(bufferevent *bev, void *ptr)
|
|
|
|
{
|
|
|
|
uint8_t data[24];
|
|
|
|
auto handler = reinterpret_cast<Http2Handler*>(ptr);
|
|
|
|
size_t leftlen = handler->get_left_connhd_len();
|
|
|
|
auto input = bufferevent_get_input(bev);
|
|
|
|
int readlen = evbuffer_remove(input, data, leftlen);
|
|
|
|
if(readlen == -1) {
|
|
|
|
delete_handler(handler);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const char *conhead = NGHTTP2_CLIENT_CONNECTION_HEADER;
|
|
|
|
if(memcmp(conhead + NGHTTP2_CLIENT_CONNECTION_HEADER_LEN - leftlen,
|
|
|
|
data, readlen) != 0) {
|
|
|
|
delete_handler(handler);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
leftlen -= readlen;
|
2013-07-26 14:34:39 +02:00
|
|
|
handler->set_left_connhd_len(leftlen);
|
2013-07-22 14:30:40 +02:00
|
|
|
if(leftlen == 0) {
|
|
|
|
bufferevent_setcb(bev, readcb, writecb, eventcb, ptr);
|
|
|
|
// Run on_read to process data left in buffer since they are not
|
|
|
|
// notified further
|
|
|
|
if(handler->on_read() != 0) {
|
|
|
|
delete_handler(handler);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
int Http2Handler::setup_bev()
|
|
|
|
{
|
|
|
|
if(ssl_) {
|
|
|
|
bev_ = bufferevent_openssl_socket_new
|
|
|
|
(sessions_->get_evbase(), fd_, ssl_,
|
|
|
|
BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_DEFER_CALLBACKS);
|
|
|
|
} else {
|
|
|
|
bev_ = bufferevent_socket_new(sessions_->get_evbase(), fd_,
|
|
|
|
BEV_OPT_DEFER_CALLBACKS);
|
|
|
|
}
|
|
|
|
bufferevent_enable(bev_, EV_READ);
|
|
|
|
bufferevent_setcb(bev_, connhd_readcb, writecb, eventcb, this);
|
|
|
|
// TODO set up timeout here
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Handler::on_read()
|
|
|
|
{
|
|
|
|
int rv = 0;
|
|
|
|
if((rv = nghttp2_session_recv(session_)) < 0) {
|
|
|
|
if(rv != NGHTTP2_ERR_EOF) {
|
|
|
|
std::cerr << "nghttp2_session_recv() returned error: "
|
|
|
|
<< nghttp2_strerror(rv) << std::endl;
|
|
|
|
}
|
|
|
|
} else if((rv = nghttp2_session_send(session_)) < 0) {
|
|
|
|
std::cerr << "nghttp2_session_send() returned error: "
|
|
|
|
<< nghttp2_strerror(rv) << std::endl;
|
|
|
|
}
|
|
|
|
if(rv == 0) {
|
|
|
|
if(nghttp2_session_want_read(session_) == 0 &&
|
2013-08-01 13:31:29 +02:00
|
|
|
nghttp2_session_want_write(session_) == 0 &&
|
|
|
|
evbuffer_get_length(bufferevent_get_output(bev_)) == 0) {
|
2013-07-22 14:30:40 +02:00
|
|
|
rv = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Handler::on_write()
|
|
|
|
{
|
|
|
|
int rv = 0;
|
|
|
|
if((rv = nghttp2_session_send(session_)) < 0) {
|
|
|
|
std::cerr << "nghttp2_session_send() returned error: "
|
|
|
|
<< nghttp2_strerror(rv) << std::endl;
|
|
|
|
}
|
|
|
|
if(rv == 0) {
|
|
|
|
if(nghttp2_session_want_read(session_) == 0 &&
|
2013-08-01 13:31:29 +02:00
|
|
|
nghttp2_session_want_write(session_) == 0 &&
|
|
|
|
evbuffer_get_length(bufferevent_get_output(bev_)) == 0) {
|
2013-07-22 14:30:40 +02:00
|
|
|
rv = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2013-10-27 15:02:39 +01:00
|
|
|
namespace {
|
|
|
|
void settings_timeout_cb(evutil_socket_t fd, short what, void *arg)
|
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(arg);
|
2013-12-25 16:23:07 +01:00
|
|
|
hd->terminate_session(NGHTTP2_SETTINGS_TIMEOUT);
|
2013-10-27 15:02:39 +01:00
|
|
|
hd->on_write();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
int Http2Handler::on_connect()
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
nghttp2_session_callbacks callbacks;
|
|
|
|
fill_callback(callbacks, sessions_->get_config());
|
|
|
|
r = nghttp2_session_server_new(&session_, &callbacks, this);
|
|
|
|
if(r != 0) {
|
|
|
|
return r;
|
|
|
|
}
|
2014-01-09 15:47:21 +01:00
|
|
|
nghttp2_settings_entry entry[4];
|
|
|
|
size_t niv = 2;
|
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-01-09 15:47:21 +01:00
|
|
|
entry[1].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
|
|
|
|
entry[1].value = 0;
|
2013-08-18 15:04:09 +02:00
|
|
|
if(sessions_->get_config()->no_flow_control) {
|
2013-07-28 12:28:41 +02:00
|
|
|
entry[niv].settings_id = NGHTTP2_SETTINGS_FLOW_CONTROL_OPTIONS;
|
|
|
|
entry[niv].value = 1;
|
|
|
|
++niv;
|
|
|
|
}
|
2013-11-06 15:32:32 +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);
|
2013-07-22 14:30:40 +02:00
|
|
|
if(r != 0) {
|
|
|
|
return r;
|
|
|
|
}
|
2013-10-27 15:02:39 +01:00
|
|
|
assert(settings_timerev_ == nullptr);
|
|
|
|
settings_timerev_ = evtimer_new(sessions_->get_evbase(), settings_timeout_cb,
|
|
|
|
this);
|
|
|
|
// SETTINGS ACK timeout is 10 seconds for now
|
|
|
|
timeval settings_timeout = { 10, 0 };
|
|
|
|
evtimer_add(settings_timerev_, &settings_timeout);
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
return on_write();
|
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Handler::verify_npn_result()
|
|
|
|
{
|
|
|
|
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-01-01 15:54:28 +01:00
|
|
|
for(int i = 0; i < 2; ++i) {
|
|
|
|
if(next_proto) {
|
|
|
|
std::string proto(next_proto, next_proto+next_proto_len);
|
|
|
|
if(sessions_->get_config()->verbose) {
|
|
|
|
std::cout << "The negotiated protocol: " << proto << std::endl;
|
|
|
|
}
|
|
|
|
if(proto == NGHTTP2_PROTO_VERSION_ID) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len);
|
|
|
|
#else // OPENSSL_VERSION_NUMBER < 0x10002000L
|
|
|
|
break;
|
|
|
|
#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
}
|
2014-01-01 15:54:28 +01:00
|
|
|
std::cerr << "Client did not advertise HTTP/2.0 protocol."
|
|
|
|
<< " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
|
2013-07-22 14:30:40 +02:00
|
|
|
<< std::endl;
|
2014-01-01 15:54:28 +01:00
|
|
|
return -1;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Handler::sendcb(const uint8_t *data, size_t len)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
auto output = bufferevent_get_output(bev_);
|
|
|
|
// Check buffer length and return WOULDBLOCK if it is large enough.
|
|
|
|
if(evbuffer_get_length(output) >
|
|
|
|
sessions_->get_config()->output_upper_thres) {
|
|
|
|
return NGHTTP2_ERR_WOULDBLOCK;
|
|
|
|
}
|
|
|
|
|
|
|
|
rv = evbuffer_add(output, data, len);
|
|
|
|
if(rv == -1) {
|
|
|
|
std::cerr << "evbuffer_add() failed" << std::endl;
|
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
} else {
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Handler::recvcb(uint8_t *buf, size_t len)
|
|
|
|
{
|
|
|
|
auto input = bufferevent_get_input(bev_);
|
|
|
|
int nread = evbuffer_remove(input, buf, len);
|
|
|
|
if(nread == -1) {
|
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
} else if(nread == 0) {
|
|
|
|
return NGHTTP2_ERR_WOULDBLOCK;
|
|
|
|
} else {
|
|
|
|
return nread;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Handler::submit_file_response(const std::string& status,
|
|
|
|
int32_t stream_id,
|
|
|
|
time_t last_modified,
|
|
|
|
off_t file_length,
|
|
|
|
nghttp2_data_provider *data_prd)
|
|
|
|
{
|
|
|
|
std::string date_str = util::http_date(time(0));
|
|
|
|
std::string content_length = util::to_str(file_length);
|
|
|
|
std::string last_modified_str;
|
2013-12-08 13:19:33 +01:00
|
|
|
auto nva = std::vector<nghttp2_nv>{
|
2013-12-08 14:31:43 +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),
|
2013-07-22 14:30:40 +02:00
|
|
|
};
|
|
|
|
if(last_modified != 0) {
|
|
|
|
last_modified_str = util::http_date(last_modified);
|
2013-12-08 14:31:43 +01:00
|
|
|
nva.push_back(http2::make_nv_ls("last-modified", last_modified_str));
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Handler::submit_response
|
|
|
|
(const std::string& status,
|
|
|
|
int32_t stream_id,
|
|
|
|
const std::vector<std::pair<std::string, std::string>>& headers,
|
|
|
|
nghttp2_data_provider *data_prd)
|
|
|
|
{
|
|
|
|
std::string date_str = util::http_date(time(0));
|
2013-12-08 13:19:33 +01:00
|
|
|
auto nva = std::vector<nghttp2_nv>{
|
2013-12-08 14:31:43 +01:00
|
|
|
http2::make_nv_ls(":status", status),
|
|
|
|
http2::make_nv_ls("server", NGHTTPD_SERVER),
|
|
|
|
http2::make_nv_ls("date", date_str)
|
2013-12-08 13:19:33 +01:00
|
|
|
};
|
2013-10-23 16:18:24 +02:00
|
|
|
for(size_t i = 0; i < headers.size(); ++i) {
|
2013-12-08 13:19:33 +01:00
|
|
|
nva.push_back(http2::make_nv(headers[i].first, headers[i].second));
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Handler::submit_response(const std::string& status,
|
|
|
|
int32_t stream_id,
|
|
|
|
nghttp2_data_provider *data_prd)
|
|
|
|
{
|
2013-12-08 13:19:33 +01:00
|
|
|
auto nva = std::vector<nghttp2_nv>{
|
2013-12-08 14:31:43 +01:00
|
|
|
http2::make_nv_ls(":status", status),
|
|
|
|
http2::make_nv_ls("server", NGHTTPD_SERVER)
|
2013-07-22 14:30:40 +02:00
|
|
|
};
|
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
|
|
|
}
|
|
|
|
|
2013-12-08 16:00:12 +01:00
|
|
|
int Http2Handler::submit_push_promise(Request *req,
|
|
|
|
const std::string& push_path)
|
|
|
|
{
|
|
|
|
std::string authority;
|
|
|
|
auto itr = std::lower_bound(std::begin(req->headers),
|
|
|
|
std::end(req->headers),
|
|
|
|
std::make_pair(std::string(":authority"),
|
|
|
|
std::string("")));
|
|
|
|
if(itr == std::end(req->headers) || (*itr).first != ":authority") {
|
|
|
|
itr = std::lower_bound(std::begin(req->headers),
|
|
|
|
std::end(req->headers),
|
|
|
|
std::make_pair(std::string("host"),
|
|
|
|
std::string("")));
|
|
|
|
}
|
|
|
|
auto nva = std::vector<nghttp2_nv>{
|
|
|
|
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).second)
|
|
|
|
};
|
|
|
|
return nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_END_PUSH_PROMISE,
|
|
|
|
req->stream_id, nva.data(), nva.size());
|
|
|
|
}
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
void Http2Handler::add_stream(int32_t stream_id, std::unique_ptr<Request> req)
|
|
|
|
{
|
|
|
|
id2req_[stream_id] = std::move(req);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Http2Handler::remove_stream(int32_t stream_id)
|
|
|
|
{
|
|
|
|
id2req_.erase(stream_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Request* Http2Handler::get_stream(int32_t stream_id)
|
|
|
|
{
|
|
|
|
auto itr = id2req_.find(stream_id);
|
|
|
|
if(itr == std::end(id2req_)) {
|
|
|
|
return nullptr;
|
|
|
|
} else {
|
|
|
|
return (*itr).second.get();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t Http2Handler::session_id() const
|
|
|
|
{
|
|
|
|
return session_id_;
|
|
|
|
}
|
|
|
|
|
|
|
|
Sessions* Http2Handler::get_sessions() const
|
|
|
|
{
|
|
|
|
return sessions_;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Config* Http2Handler::get_config() const
|
|
|
|
{
|
|
|
|
return sessions_->get_config();
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t Http2Handler::get_left_connhd_len() const
|
|
|
|
{
|
|
|
|
return left_connhd_len_;
|
|
|
|
}
|
|
|
|
|
2013-07-26 14:34:39 +02:00
|
|
|
void Http2Handler::set_left_connhd_len(size_t left)
|
|
|
|
{
|
|
|
|
left_connhd_len_ = left;
|
|
|
|
}
|
|
|
|
|
2013-10-27 15:02:39 +01:00
|
|
|
void Http2Handler::remove_settings_timer()
|
|
|
|
{
|
|
|
|
if(settings_timerev_) {
|
|
|
|
evtimer_del(settings_timerev_);
|
|
|
|
event_free(settings_timerev_);
|
|
|
|
settings_timerev_ = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-25 16:23:07 +01:00
|
|
|
void Http2Handler::terminate_session(nghttp2_error_code error_code)
|
2013-10-27 15:02:39 +01:00
|
|
|
{
|
2013-12-25 16:23:07 +01:00
|
|
|
nghttp2_session_terminate_session(session_, error_code);
|
2013-10-27 15:02:39 +01:00
|
|
|
}
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
namespace {
|
|
|
|
ssize_t hd_send_callback(nghttp2_session *session,
|
|
|
|
const uint8_t *data, size_t len, int flags,
|
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
return hd->sendcb(data, len);
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
ssize_t hd_recv_callback(nghttp2_session *session,
|
|
|
|
uint8_t *data, size_t len, int flags, void *user_data)
|
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
return hd->recvcb(data, len);
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
ssize_t file_read_callback
|
|
|
|
(nghttp2_session *session, int32_t stream_id,
|
|
|
|
uint8_t *buf, size_t length, int *eof,
|
|
|
|
nghttp2_data_source *source, void *user_data)
|
|
|
|
{
|
|
|
|
int fd = source->fd;
|
|
|
|
ssize_t r;
|
|
|
|
while((r = read(fd, buf, length)) == -1 && errno == EINTR);
|
|
|
|
if(r == -1) {
|
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
} else {
|
|
|
|
if(r == 0) {
|
|
|
|
*eof = 1;
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
bool check_url(const std::string& url)
|
|
|
|
{
|
|
|
|
// We don't like '\' in url.
|
|
|
|
return !url.empty() && url[0] == '/' &&
|
|
|
|
url.find('\\') == std::string::npos &&
|
|
|
|
url.find("/../") == std::string::npos &&
|
|
|
|
url.find("/./") == std::string::npos &&
|
|
|
|
!util::endsWith(url, "/..") && !util::endsWith(url, "/.");
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void prepare_status_response(Request *req, Http2Handler *hd,
|
|
|
|
const std::string& status)
|
|
|
|
{
|
|
|
|
int pipefd[2];
|
|
|
|
if(status == STATUS_304 || pipe(pipefd) == -1) {
|
|
|
|
hd->submit_response(status, req->stream_id, 0);
|
|
|
|
} else {
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "<html><head><title>" << status << "</title></head><body>"
|
|
|
|
<< "<h1>" << status << "</h1>"
|
|
|
|
<< "<hr>"
|
|
|
|
<< "<address>" << NGHTTPD_SERVER
|
|
|
|
<< " at port " << hd->get_config()->port
|
|
|
|
<< "</address>"
|
|
|
|
<< "</body></html>";
|
|
|
|
std::string body = ss.str();
|
|
|
|
gzFile write_fd = gzdopen(pipefd[1], "w");
|
|
|
|
gzwrite(write_fd, body.c_str(), body.size());
|
|
|
|
gzclose(write_fd);
|
|
|
|
close(pipefd[1]);
|
|
|
|
|
|
|
|
req->file = pipefd[0];
|
|
|
|
nghttp2_data_provider data_prd;
|
|
|
|
data_prd.source.fd = pipefd[0];
|
|
|
|
data_prd.read_callback = file_read_callback;
|
|
|
|
std::vector<std::pair<std::string, std::string>> headers;
|
|
|
|
headers.emplace_back("content-encoding", "gzip");
|
|
|
|
headers.emplace_back("content-type", "text/html; charset=UTF-8");
|
|
|
|
hd->submit_response(status, req->stream_id, headers, &data_prd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2013-12-08 16:00:12 +01:00
|
|
|
void prepare_response(Request *req, Http2Handler *hd, bool allow_push = true)
|
2013-07-22 14:30:40 +02:00
|
|
|
{
|
2013-12-08 16:00:12 +01:00
|
|
|
int rv;
|
2013-08-27 20:14:19 +02:00
|
|
|
auto url = (*std::lower_bound(std::begin(req->headers),
|
|
|
|
std::end(req->headers),
|
|
|
|
std::make_pair(std::string(":path"),
|
|
|
|
std::string()))).second;
|
|
|
|
auto ims = std::lower_bound(std::begin(req->headers),
|
|
|
|
std::end(req->headers),
|
|
|
|
std::make_pair(std::string("if-modified-since"),
|
|
|
|
std::string()));
|
2013-07-22 14:30:40 +02:00
|
|
|
time_t last_mod = 0;
|
|
|
|
bool last_mod_found = false;
|
2013-08-27 20:14:19 +02:00
|
|
|
if(ims != std::end(req->headers) &&
|
|
|
|
(*ims).first == "if-modified-since") {
|
2013-07-22 14:30:40 +02:00
|
|
|
last_mod_found = true;
|
2013-08-27 20:14:19 +02:00
|
|
|
last_mod = util::parse_http_date((*ims).second);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
auto query_pos = url.find("?");
|
|
|
|
if(query_pos != std::string::npos) {
|
|
|
|
// Do not response to this request to allow clients to test timeouts.
|
2013-08-27 20:14:19 +02:00
|
|
|
if(url.find("nghttpd_do_not_respond_to_req=yes",
|
|
|
|
query_pos) != std::string::npos) {
|
2013-07-22 14:30:40 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
url = url.substr(0, query_pos);
|
|
|
|
}
|
|
|
|
url = util::percentDecode(url.begin(), url.end());
|
|
|
|
if(!check_url(url)) {
|
|
|
|
prepare_status_response(req, hd, STATUS_404);
|
|
|
|
return;
|
|
|
|
}
|
2013-12-08 16:00:12 +01:00
|
|
|
auto push_itr = hd->get_config()->push.find(url);
|
2013-12-08 16:04:54 +01:00
|
|
|
if(allow_push && push_itr != std::end(hd->get_config()->push)) {
|
2013-12-08 16:00:12 +01:00
|
|
|
for(auto& push_path : (*push_itr).second) {
|
|
|
|
rv = hd->submit_push_promise(req, push_path);
|
|
|
|
if(rv != 0) {
|
|
|
|
std::cerr << "nghttp2_submit_push_promise() returned error: "
|
|
|
|
<< nghttp2_strerror(rv) << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
std::string path = hd->get_config()->htdocs+url;
|
|
|
|
if(path[path.size()-1] == '/') {
|
|
|
|
path += DEFAULT_HTML;
|
|
|
|
}
|
|
|
|
int file = open(path.c_str(), O_RDONLY | O_BINARY);
|
|
|
|
if(file == -1) {
|
|
|
|
prepare_status_response(req, hd, STATUS_404);
|
|
|
|
} else {
|
|
|
|
struct stat buf;
|
|
|
|
if(fstat(file, &buf) == -1) {
|
|
|
|
close(file);
|
|
|
|
prepare_status_response(req, hd, STATUS_404);
|
|
|
|
} else {
|
|
|
|
req->file = file;
|
|
|
|
nghttp2_data_provider data_prd;
|
|
|
|
data_prd.source.fd = file;
|
|
|
|
data_prd.read_callback = file_read_callback;
|
|
|
|
if(last_mod_found && buf.st_mtime <= last_mod) {
|
|
|
|
prepare_status_response(req, hd, STATUS_304);
|
|
|
|
} else {
|
|
|
|
hd->submit_file_response(STATUS_200, req->stream_id, buf.st_mtime,
|
|
|
|
buf.st_size, &data_prd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2013-12-05 13:54:36 +01:00
|
|
|
void append_nv(Request *req, const std::vector<nghttp2_nv>& nva)
|
2013-07-22 14:30:40 +02:00
|
|
|
{
|
2013-12-05 13:54:36 +01:00
|
|
|
for(auto& nv : nva) {
|
2014-01-16 15:41:13 +01:00
|
|
|
http2::split_add_header(req->headers,
|
|
|
|
nv.name, nv.namelen, nv.value, nv.valuelen);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-08-27 20:14:19 +02:00
|
|
|
namespace {
|
|
|
|
const char *REQUIRED_HEADERS[] = {
|
2013-10-24 16:04:28 +02:00
|
|
|
":method", ":path", ":scheme", nullptr
|
2013-08-27 20:14:19 +02:00
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
namespace {
|
|
|
|
int on_header_callback(nghttp2_session *session,
|
|
|
|
const nghttp2_frame *frame,
|
|
|
|
const uint8_t *name, size_t namelen,
|
|
|
|
const uint8_t *value, size_t valuelen,
|
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
if(hd->get_config()->verbose) {
|
|
|
|
verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
|
|
|
|
user_data);
|
|
|
|
}
|
|
|
|
if(frame->hd.type != NGHTTP2_HEADERS ||
|
|
|
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
auto stream = hd->get_stream(frame->hd.stream_id);
|
|
|
|
if(!stream) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
http2::split_add_header(stream->headers, name, namelen, value, valuelen);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
int on_end_headers_callback(nghttp2_session *session,
|
|
|
|
const nghttp2_frame *frame,
|
|
|
|
nghttp2_error_code error_code,
|
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
if(error_code != NGHTTP2_NO_ERROR) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if(frame->hd.type != NGHTTP2_HEADERS ||
|
|
|
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
auto stream = hd->get_stream(frame->hd.stream_id);
|
|
|
|
if(!stream) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
http2::normalize_headers(stream->headers);
|
|
|
|
if(!http2::check_http2_headers(stream->headers)) {
|
|
|
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
|
|
|
|
NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
for(size_t i = 0; REQUIRED_HEADERS[i]; ++i) {
|
|
|
|
if(!http2::get_unique_header(stream->headers, REQUIRED_HEADERS[i])) {
|
|
|
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
|
|
|
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
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.
|
|
|
|
if(!http2::get_unique_header(stream->headers, ":authority") &&
|
|
|
|
!http2::get_unique_header(stream->headers, "host")) {
|
|
|
|
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
|
|
|
|
NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
namespace {
|
2013-08-29 14:03:39 +02:00
|
|
|
int hd_on_frame_recv_callback
|
2013-09-03 14:24:14 +02:00
|
|
|
(nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
|
2013-07-22 14:30:40 +02:00
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
if(hd->get_config()->verbose) {
|
|
|
|
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
|
|
|
}
|
|
|
|
switch(frame->hd.type) {
|
|
|
|
case NGHTTP2_HEADERS:
|
|
|
|
switch(frame->headers.cat) {
|
2013-07-25 13:53:30 +02:00
|
|
|
case NGHTTP2_HCAT_REQUEST: {
|
2014-01-16 15:41:13 +01:00
|
|
|
auto req = util::make_unique<Request>(frame->hd.stream_id);
|
|
|
|
hd->add_stream(frame->hd.stream_id, std::move(req));
|
2013-07-22 14:30:40 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2013-10-27 15:02:39 +01:00
|
|
|
case NGHTTP2_SETTINGS:
|
|
|
|
if(frame->hd.flags & NGHTTP2_FLAG_ACK) {
|
|
|
|
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-08-29 16:03:21 +02:00
|
|
|
int htdocs_on_request_recv_callback
|
2013-07-22 14:30:40 +02:00
|
|
|
(nghttp2_session *session, int32_t stream_id, void *user_data)
|
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
2013-08-27 20:14:19 +02:00
|
|
|
auto stream = hd->get_stream(stream_id);
|
|
|
|
if(stream) {
|
2013-12-08 16:00:12 +01:00
|
|
|
prepare_response(stream, hd);
|
2013-08-27 20:14:19 +02:00
|
|
|
}
|
2013-08-29 16:03:21 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
2013-12-08 16:22:01 +01:00
|
|
|
int hd_before_frame_send_callback
|
|
|
|
(nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
|
2013-07-22 14:30:40 +02:00
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
2013-12-08 16:00:12 +01:00
|
|
|
if(frame->hd.type == NGHTTP2_PUSH_PROMISE) {
|
2013-12-08 16:22:01 +01:00
|
|
|
auto stream_id = frame->push_promise.promised_stream_id;
|
|
|
|
auto req = util::make_unique<Request>(stream_id);
|
2013-12-08 16:00:12 +01:00
|
|
|
auto nva = http2::sort_nva(frame->push_promise.nva,
|
|
|
|
frame->push_promise.nvlen);
|
|
|
|
append_nv(req.get(), nva);
|
|
|
|
hd->add_stream(stream_id, std::move(req));
|
2013-12-08 16:22:01 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
int hd_on_frame_send_callback
|
|
|
|
(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
if(frame->hd.type == NGHTTP2_PUSH_PROMISE) {
|
|
|
|
auto stream = hd->get_stream(frame->push_promise.promised_stream_id);
|
|
|
|
if(stream) {
|
|
|
|
prepare_response(stream, hd, /*allow_push */ false);
|
|
|
|
}
|
2013-12-08 16:00:12 +01:00
|
|
|
}
|
2013-07-22 14:30:40 +02:00
|
|
|
if(hd->get_config()->verbose) {
|
|
|
|
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
|
|
|
}
|
2013-08-29 14:48:34 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2013-08-29 14:18:40 +02:00
|
|
|
int on_data_chunk_recv_callback
|
2013-07-22 14:30:40 +02:00
|
|
|
(nghttp2_session *session, uint8_t flags, int32_t stream_id,
|
|
|
|
const uint8_t *data, size_t len, void *user_data)
|
|
|
|
{
|
|
|
|
// TODO Handle POST
|
2013-08-29 14:18:40 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2013-08-29 14:22:00 +02:00
|
|
|
int hd_on_data_recv_callback
|
2013-07-22 14:30:40 +02:00
|
|
|
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
// TODO Handle POST
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
if(hd->get_config()->verbose) {
|
|
|
|
print_session_id(hd->session_id());
|
2013-12-20 15:48:56 +01:00
|
|
|
verbose_on_data_recv_callback(session, length, flags, stream_id,
|
|
|
|
user_data);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2013-08-29 14:22:00 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2013-08-29 14:55:04 +02:00
|
|
|
int hd_on_data_send_callback
|
2013-07-22 14:30:40 +02:00
|
|
|
(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id,
|
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
if(hd->get_config()->verbose) {
|
|
|
|
print_session_id(hd->session_id());
|
2013-12-20 15:48:56 +01:00
|
|
|
verbose_on_data_send_callback(session, length, flags, stream_id,
|
|
|
|
user_data);
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
2013-08-29 14:55:04 +02:00
|
|
|
return 0;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2013-08-29 15:58:05 +02:00
|
|
|
int on_stream_close_callback
|
2013-07-22 14:30:40 +02:00
|
|
|
(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code,
|
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
|
|
|
hd->remove_stream(stream_id);
|
|
|
|
if(hd->get_config()->verbose) {
|
|
|
|
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 {
|
|
|
|
void fill_callback(nghttp2_session_callbacks& callbacks, const Config *config)
|
|
|
|
{
|
|
|
|
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
|
|
|
callbacks.send_callback = hd_send_callback;
|
|
|
|
callbacks.recv_callback = hd_recv_callback;
|
|
|
|
callbacks.on_stream_close_callback = on_stream_close_callback;
|
|
|
|
callbacks.on_frame_recv_callback = hd_on_frame_recv_callback;
|
2013-12-08 16:22:01 +01:00
|
|
|
callbacks.before_frame_send_callback = hd_before_frame_send_callback;
|
2013-07-22 14:30:40 +02:00
|
|
|
callbacks.on_frame_send_callback = hd_on_frame_send_callback;
|
|
|
|
callbacks.on_data_recv_callback = hd_on_data_recv_callback;
|
|
|
|
callbacks.on_data_send_callback = hd_on_data_send_callback;
|
|
|
|
if(config->verbose) {
|
2013-12-20 15:48:56 +01:00
|
|
|
callbacks.on_invalid_frame_recv_callback =
|
|
|
|
verbose_on_invalid_frame_recv_callback;
|
2013-07-22 14:30:40 +02:00
|
|
|
callbacks.on_frame_recv_parse_error_callback =
|
2013-12-20 15:48:56 +01:00
|
|
|
verbose_on_frame_recv_parse_error_callback;
|
|
|
|
callbacks.on_unknown_frame_recv_callback =
|
|
|
|
verbose_on_unknown_frame_recv_callback;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
|
|
|
|
callbacks.on_request_recv_callback = config->on_request_recv_callback;
|
2014-01-16 15:41:13 +01:00
|
|
|
callbacks.on_header_callback = on_header_callback;
|
|
|
|
callbacks.on_end_headers_callback = on_end_headers_callback;
|
2013-07-22 14:30:40 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
class ListenEventHandler {
|
|
|
|
public:
|
2013-09-08 16:16:08 +02:00
|
|
|
ListenEventHandler(Sessions *sessions, int64_t *session_id_seed_ptr)
|
2013-07-22 14:30:40 +02:00
|
|
|
: sessions_(sessions),
|
|
|
|
session_id_seed_ptr_(session_id_seed_ptr)
|
|
|
|
{}
|
|
|
|
void accept_connection(int fd, sockaddr *addr, int addrlen)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
int val = 1;
|
|
|
|
SSL *ssl = nullptr;
|
|
|
|
rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
|
|
|
|
reinterpret_cast<char *>(&val), sizeof(val));
|
|
|
|
if(rv == -1) {
|
|
|
|
std::cerr << "Setting option TCP_NODELAY failed: errno="
|
|
|
|
<< errno << std::endl;
|
|
|
|
}
|
|
|
|
if(sessions_->get_ssl_ctx()) {
|
|
|
|
ssl = sessions_->ssl_session_new(fd);
|
|
|
|
if(!ssl) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int64_t session_id = ++(*session_id_seed_ptr_);
|
|
|
|
auto handler = util::make_unique<Http2Handler>(sessions_, fd, ssl,
|
|
|
|
session_id);
|
|
|
|
handler->setup_bev();
|
|
|
|
if(!ssl) {
|
|
|
|
if(handler->on_connect() != 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sessions_->add_handler(handler.release());
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
Sessions *sessions_;
|
|
|
|
int64_t *session_id_seed_ptr_;
|
|
|
|
};
|
|
|
|
|
|
|
|
HttpServer::HttpServer(const Config *config)
|
|
|
|
: config_(config)
|
2013-08-06 16:17:13 +02:00
|
|
|
{}
|
2013-07-22 14:30:40 +02:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len,
|
|
|
|
void *arg)
|
|
|
|
{
|
|
|
|
auto next_proto =
|
|
|
|
reinterpret_cast<std::pair<unsigned char*, size_t>* >(arg);
|
|
|
|
*data = next_proto->first;
|
|
|
|
*len = next_proto->second;
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
|
|
|
|
{
|
|
|
|
// We don't verify the client certificate. Just request it for the
|
|
|
|
// testing purpose.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void evlistener_acceptcb(evconnlistener *listener, int fd,
|
|
|
|
sockaddr *addr, int addrlen, void *arg)
|
|
|
|
{
|
|
|
|
auto handler = reinterpret_cast<ListenEventHandler*>(arg);
|
|
|
|
handler->accept_connection(fd, addr, addrlen);
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void evlistener_errorcb(evconnlistener *listener, void *ptr)
|
|
|
|
{
|
|
|
|
std::cerr << "Accepting incoming connection failed" << std::endl;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
int start_listen(event_base *evbase, Sessions *sessions,
|
|
|
|
int64_t *session_id_seed_ptr)
|
|
|
|
{
|
|
|
|
addrinfo hints;
|
|
|
|
int r;
|
|
|
|
char service[10];
|
|
|
|
snprintf(service, sizeof(service), "%u", sessions->get_config()->port);
|
|
|
|
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;
|
|
|
|
r = getaddrinfo(nullptr, service, &hints, &res);
|
|
|
|
if(r != 0) {
|
|
|
|
std::cerr << "getaddrinfo() failed: " << gai_strerror(r) << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
for(rp = res; rp; rp = rp->ai_next) {
|
|
|
|
int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
|
|
if(fd == -1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
int val = 1;
|
|
|
|
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
|
|
|
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
|
|
|
close(fd);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
evutil_make_socket_nonblocking(fd);
|
|
|
|
#ifdef IPV6_V6ONLY
|
|
|
|
if(rp->ai_family == AF_INET6) {
|
|
|
|
if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
|
|
|
|
static_cast<socklen_t>(sizeof(val))) == -1) {
|
|
|
|
close(fd);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // IPV6_V6ONLY
|
|
|
|
if(bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
|
|
|
auto evlistener = evconnlistener_new
|
|
|
|
(evbase,
|
|
|
|
evlistener_acceptcb,
|
2013-09-08 16:16:08 +02:00
|
|
|
new ListenEventHandler(sessions, session_id_seed_ptr),
|
2013-07-22 14:30:40 +02:00
|
|
|
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
|
2013-12-26 16:02:43 +01:00
|
|
|
-1, fd);
|
2013-07-22 14:30:40 +02:00
|
|
|
evconnlistener_set_error_cb(evlistener, evlistener_errorcb);
|
|
|
|
|
|
|
|
if(sessions->get_config()->verbose) {
|
|
|
|
std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6")
|
|
|
|
<< ": listen on port "
|
|
|
|
<< sessions->get_config()->port << std::endl;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
std::cerr << strerror(errno) << std::endl;
|
|
|
|
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
freeaddrinfo(res);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-01-01 15:54:28 +01:00
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
namespace {
|
|
|
|
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 = reinterpret_cast<HttpServer*>(arg)->get_config();
|
|
|
|
if(config->verbose) {
|
|
|
|
std::cout << "[ALPN] client offers:" << std::endl;
|
|
|
|
}
|
|
|
|
if(config->verbose) {
|
|
|
|
for(unsigned int i = 0; i < inlen; i += in[i]+1) {
|
|
|
|
std::cout << " * ";
|
|
|
|
std::cout.write(reinterpret_cast<const char*>(&in[i+1]), in[i]);
|
|
|
|
std::cout << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(nghttp2_select_next_protocol(const_cast<unsigned char**>(out), outlen,
|
|
|
|
in, inlen) <= 0) {
|
|
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
|
|
}
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
int HttpServer::run()
|
|
|
|
{
|
|
|
|
SSL_CTX *ssl_ctx = nullptr;
|
|
|
|
std::pair<unsigned char*, size_t> next_proto;
|
|
|
|
unsigned char proto_list[255];
|
|
|
|
if(!config_->no_tls) {
|
|
|
|
ssl_ctx = SSL_CTX_new(SSLv23_server_method());
|
|
|
|
if(!ssl_ctx) {
|
|
|
|
std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2);
|
|
|
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
|
|
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
|
2013-07-22 15:06:31 +02:00
|
|
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
2013-07-22 14:30:40 +02:00
|
|
|
if(SSL_CTX_use_PrivateKey_file(ssl_ctx,
|
|
|
|
config_->private_key_file.c_str(),
|
|
|
|
SSL_FILETYPE_PEM) != 1) {
|
|
|
|
std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if(SSL_CTX_use_certificate_chain_file(ssl_ctx,
|
|
|
|
config_->cert_file.c_str()) != 1) {
|
|
|
|
std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if(SSL_CTX_check_private_key(ssl_ctx) != 1) {
|
|
|
|
std::cerr << "SSL_CTX_check_private_key failed." << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if(config_->verify_client) {
|
|
|
|
SSL_CTX_set_verify(ssl_ctx,
|
|
|
|
SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
|
|
|
|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
|
|
verify_callback);
|
|
|
|
}
|
|
|
|
|
2013-10-15 18:19:06 +02:00
|
|
|
proto_list[0] = NGHTTP2_PROTO_VERSION_ID_LEN;
|
2013-07-25 18:38:04 +02:00
|
|
|
memcpy(&proto_list[1], NGHTTP2_PROTO_VERSION_ID,
|
|
|
|
NGHTTP2_PROTO_VERSION_ID_LEN);
|
2013-07-22 14:30:40 +02:00
|
|
|
next_proto.first = proto_list;
|
2013-10-15 18:19:06 +02:00
|
|
|
next_proto.second = proto_list[0] + 1;
|
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
|
|
|
}
|
|
|
|
|
|
|
|
auto evbase = event_base_new();
|
|
|
|
int64_t session_id_seed = 0;
|
|
|
|
Sessions sessions(evbase, config_, ssl_ctx);
|
|
|
|
if(start_listen(evbase, &sessions, &session_id_seed) != 0) {
|
|
|
|
std::cerr << "Could not listen" << std::endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
event_base_loop(evbase, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-01-01 15:54:28 +01:00
|
|
|
const Config* HttpServer::get_config() const
|
|
|
|
{
|
|
|
|
return config_;
|
|
|
|
}
|
|
|
|
|
2013-07-22 14:30:40 +02:00
|
|
|
} // namespace nghttp2
|