Commit 1795d3ea authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

nghttp: Add include file to clarify used objects

parent b933ee8e
......@@ -72,7 +72,7 @@ if HAVE_LIBXML2
HTML_PARSER_OBJECTS += HtmlParser.cc
endif # HAVE_LIBXML2
nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \
nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc nghttp.h \
${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES} \
ssl.cc ssl.h
......
......@@ -22,17 +22,13 @@
* 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 "nghttp2_config.h"
#include "nghttp.h"
#include <sys/types.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 <signal.h>
#include <getopt.h>
#include <cassert>
......@@ -40,40 +36,22 @@
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <string>
#include <set>
#include <iomanip>
#include <fstream>
#include <map>
#include <vector>
#include <sstream>
#include <tuple>
#include <chrono>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/conf.h>
#include <ev.h>
#include <nghttp2/nghttp2.h>
#ifdef HAVE_JANSSON
#include <jansson.h>
#endif // HAVE_JANSSON
#include "http-parser/http_parser.h"
#include "app_helper.h"
#include "HtmlParser.h"
#include "util.h"
#include "base64.h"
#include "http2.h"
#include "nghttp2_gzip.h"
#include "ssl.h"
#include "ringbuf.h"
#ifndef O_BINARY
#define O_BINARY (0)
......@@ -92,36 +70,7 @@ enum {
ANCHOR_ID_LOWEST = 9,
};
namespace {
struct Config {
Headers headers;
std::string certfile;
std::string keyfile;
std::string datafile;
std::string harfile;
nghttp2_option *http2_option;
size_t output_upper_thres;
size_t padding;
ssize_t peer_max_concurrent_streams;
ssize_t header_table_size;
int32_t weight;
int multiply;
// milliseconds
ev_tstamp timeout;
int window_bits;
int connection_window_bits;
int verbose;
bool null_out;
bool remote_name;
bool get_assets;
bool stat;
bool upgrade;
bool continuation;
bool no_content_length;
bool no_dep;
bool dep_idle;
Config()
Config::Config()
: output_upper_thres(1024 * 1024), padding(0),
peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
header_table_size(-1), weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1),
......@@ -132,11 +81,9 @@ struct Config {
nghttp2_option_new(&http2_option);
nghttp2_option_set_peer_max_concurrent_streams(http2_option,
peer_max_concurrent_streams);
}
}
~Config() { nghttp2_option_del(http2_option); }
};
} // namespace
Config::~Config() { nghttp2_option_del(http2_option); }
namespace {
Config config;
......@@ -150,23 +97,6 @@ void print_protocol_nego_error() {
}
} // namespace
enum StatStage {
STAT_INITIAL,
STAT_ON_REQUEST,
STAT_ON_RESPONSE,
STAT_ON_COMPLETE
};
namespace {
struct RequestStat {
std::chrono::steady_clock::time_point on_request_time;
std::chrono::steady_clock::time_point on_response_time;
std::chrono::steady_clock::time_point on_complete_time;
StatStage stage;
RequestStat() : stage(STAT_INITIAL) {}
};
} // namespace
namespace {
std::string strip_fragment(const char *raw_uri) {
const char *end;
......@@ -191,49 +121,10 @@ std::string numeric_name(addrinfo *addr) {
}
} // namespace
namespace {
struct Request;
} // namespace
namespace {
struct Dependency {
std::vector<std::vector<Request *>> deps;
};
} // namespace
namespace {
struct Request {
Headers res_nva;
Headers req_nva;
// URI without fragment
std::string uri;
http_parser_url u;
std::shared_ptr<Dependency> dep;
nghttp2_priority_spec pri_spec;
RequestStat stat;
int64_t data_length;
int64_t data_offset;
// Number of bytes received from server
int64_t response_len;
nghttp2_gzip *inflater;
HtmlParser *html_parser;
const nghttp2_data_provider *data_prd;
int32_t stream_id;
int status;
// Recursion level: 0: first entity, 1: entity linked from first entity
int level;
// RequestPriority value defined in HtmlParser.h
int pri;
int res_hdidx[http2::HD_MAXIDX];
// used for incoming PUSH_PROMISE
int req_hdidx[http2::HD_MAXIDX];
bool expect_final_response;
// For pushed request, |uri| is empty and |u| is zero-cleared.
Request(const std::string &uri, const http_parser_url &u,
Request::Request(const std::string &uri, const http_parser_url &u,
const nghttp2_data_provider *data_prd, int64_t data_length,
const nghttp2_priority_spec &pri_spec,
std::shared_ptr<Dependency> dep, int pri = 0, int level = 0)
std::shared_ptr<Dependency> dep, int pri, int level)
: uri(uri), u(u), dep(std::move(dep)), pri_spec(pri_spec),
data_length(data_length), data_offset(0), response_len(0),
inflater(nullptr), html_parser(nullptr), data_prd(data_prd),
......@@ -241,32 +132,30 @@ struct Request {
expect_final_response(false) {
http2::init_hdidx(res_hdidx);
http2::init_hdidx(req_hdidx);
}
}
~Request() {
Request::~Request() {
nghttp2_gzip_inflate_del(inflater);
delete html_parser;
}
}
void init_inflater() {
void Request::init_inflater() {
int rv;
rv = nghttp2_gzip_inflate_new(&inflater);
assert(rv == 0);
}
}
void init_html_parser() { html_parser = new HtmlParser(uri); }
void Request::init_html_parser() { html_parser = new HtmlParser(uri); }
int update_html_parser(const uint8_t *data, size_t len, int fin) {
int Request::update_html_parser(const uint8_t *data, size_t len, int fin) {
if (!html_parser) {
return 0;
}
int rv;
rv = html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
return html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
fin);
return rv;
}
}
std::string make_reqpath() const {
std::string Request::make_reqpath() const {
std::string path = util::has_uri_field(u, UF_PATH)
? util::get_uri_field(uri.c_str(), u, UF_PATH)
: "/";
......@@ -276,18 +165,18 @@ struct Request {
u.field_data[UF_QUERY].len);
}
return path;
}
}
int32_t find_dep_stream_id(int start) {
int32_t Request::find_dep_stream_id(int start) {
for (auto i = start; i >= 0; --i) {
for (auto req : dep->deps[i]) {
return req->stream_id;
}
}
return -1;
}
}
nghttp2_priority_spec resolve_dep(int32_t pri) {
nghttp2_priority_spec Request::resolve_dep(int32_t pri) {
nghttp2_priority_spec pri_spec;
int exclusive = 0;
int32_t stream_id = -1;
......@@ -314,8 +203,7 @@ struct Request {
anchor_id = ANCHOR_ID_LOWEST;
break;
}
nghttp2_priority_spec_init(&pri_spec, anchor_id, NGHTTP2_DEFAULT_WEIGHT,
0);
nghttp2_priority_spec_init(&pri_spec, anchor_id, NGHTTP2_DEFAULT_WEIGHT, 0);
return pri_spec;
}
......@@ -349,18 +237,18 @@ struct Request {
exclusive);
return pri_spec;
}
}
bool is_ipv6_literal_addr() const {
bool Request::is_ipv6_literal_addr() const {
if (util::has_uri_field(u, UF_HOST)) {
return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':',
u.field_data[UF_HOST].len);
} else {
return false;
}
}
}
bool response_pseudo_header_allowed(int token) const {
bool Request::response_pseudo_header_allowed(int token) const {
if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') {
return false;
}
......@@ -370,9 +258,9 @@ struct Request {
default:
return false;
}
}
}
bool push_request_pseudo_header_allowed(int token) const {
bool Request::push_request_pseudo_header_allowed(int token) const {
if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') {
return false;
}
......@@ -385,157 +273,183 @@ struct Request {
default:
return false;
}
}
}
Headers::value_type *get_res_header(int token) {
Headers::value_type *Request::get_res_header(int token) {
auto idx = res_hdidx[token];
if (idx == -1) {
return nullptr;
}
return &res_nva[idx];
}
}
Headers::value_type *get_req_header(int token) {
Headers::value_type *Request::get_req_header(int token) {
auto idx = req_hdidx[token];
if (idx == -1) {
return nullptr;
}
return &req_nva[idx];
}
}
void record_request_time() {
void Request::record_request_time() {
stat.stage = STAT_ON_REQUEST;
stat.on_request_time = get_time();
}
}
void record_response_time() {
void Request::record_response_time() {
stat.stage = STAT_ON_RESPONSE;
stat.on_response_time = get_time();
}
}
void record_complete_time() {
void Request::record_complete_time() {
stat.stage = STAT_ON_COMPLETE;
stat.on_complete_time = get_time();
}
namespace {
int htp_msg_begincb(http_parser *htp) {
if (config.verbose) {
print_timer();
std::cout << " HTTP Upgrade response" << std::endl;
}
};
return 0;
}
} // namespace
namespace {
struct SessionStat {
// The point in time when download was started.
std::chrono::system_clock::time_point started_system_time;
// The point of time when download was started.
std::chrono::steady_clock::time_point on_started_time;
// The point of time when DNS resolution was completed.
std::chrono::steady_clock::time_point on_dns_complete_time;
// The point of time when connection was established or SSL/TLS
// handshake was completed.
std::chrono::steady_clock::time_point on_connect_time;
// The point of time when HTTP/2 commnucation was started.
std::chrono::steady_clock::time_point on_handshake_time;
int htp_statuscb(http_parser *htp, const char *at, size_t length) {
auto client = static_cast<HttpClient *>(htp->data);
client->upgrade_response_status_code = htp->status_code;
return 0;
}
} // namespace
namespace {
int htp_msg_completecb(http_parser *htp) {
auto client = static_cast<HttpClient *>(htp->data);
client->upgrade_response_complete = true;
return 0;
}
} // namespace
namespace {
http_parser_settings htp_hooks = {
htp_msg_begincb, // http_cb on_message_begin;
nullptr, // http_data_cb on_url;
htp_statuscb, // http_data_cb on_status;
nullptr, // http_data_cb on_header_field;
nullptr, // http_data_cb on_header_value;
nullptr, // http_cb on_headers_complete;
nullptr, // http_data_cb on_body;
htp_msg_completecb // http_cb on_message_complete;
};
} // namespace
namespace {
size_t populate_settings(nghttp2_settings_entry *iv) {
size_t niv = 2;
int submit_request(HttpClient *client, const Headers &headers, Request *req) {
auto path = req->make_reqpath();
auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
{":path", path},
{":scheme", scheme},
{":authority", client->hostport},
{"accept", "*/*"},
{"accept-encoding", "gzip, deflate"},
{"user-agent", "nghttp2/" NGHTTP2_VERSION}};
if (config.continuation) {
for (size_t i = 0; i < 6; ++i) {
build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
std::string(4096, '-'));
}
}
auto num_initial_headers = build_headers.size();
if (!config.no_content_length && req->data_prd) {
build_headers.emplace_back("content-length", util::utos(req->data_length));
}
for (auto &kv : headers) {
size_t i;
for (i = 0; i < num_initial_headers; ++i) {
if (kv.name == build_headers[i].name) {
build_headers[i].value = kv.value;
break;
}
}
if (i < num_initial_headers) {
continue;
}
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 100;
build_headers.emplace_back(kv.name, kv.value, kv.no_index);
}
iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
if (config.window_bits != -1) {
iv[1].value = (1 << config.window_bits) - 1;
} else {
iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
auto nva = std::vector<nghttp2_nv>();
nva.reserve(build_headers.size());
for (auto &kv : build_headers) {
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
}
if (config.header_table_size >= 0) {
iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iv[niv].value = config.header_table_size;
++niv;
auto stream_id =
nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
nva.size(), req->data_prd, req);
if (stream_id < 0) {
std::cerr << "[ERROR] nghttp2_submit_request() returned error: "
<< nghttp2_strerror(stream_id) << std::endl;
return -1;
}
return niv;
}
} // namespace
namespace {
extern http_parser_settings htp_hooks;
} // namespace
req->stream_id = stream_id;
client->on_request(req);
namespace {
void readcb(struct ev_loop *loop, ev_io *w, int revents);
} // namespace
req->req_nva = std::move(build_headers);
namespace {
void writecb(struct ev_loop *loop, ev_io *w, int revents);
return 0;
}
} // namespace
namespace {
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents);
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
if (client->do_read() != 0) {
client->disconnect();
}
}
} // namespace
namespace {
struct HttpClient;
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
auto rv = client->do_write();
if (rv == HttpClient::ERR_CONNECT_FAIL) {
client->on_connect_fail();
return;
}
if (rv != 0) {
client->disconnect();
}
}
} // namespace
namespace {
int submit_request(HttpClient *client, const Headers &headers, Request *req);
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
std::cerr << "[ERROR] Timeout" << std::endl;
client->disconnect();
}
} // namespace
namespace {
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents);
} // namespace
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
ev_timer_stop(loop, w);
enum client_state { STATE_IDLE, STATE_CONNECTED };
nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
namespace {
struct HttpClient {
std::vector<std::unique_ptr<Request>> reqvec;
// Insert path already added in reqvec to prevent multiple request
// for 1 resource.
std::set<std::string> path_cache;
std::string scheme;
std::string host;
std::string hostport;
// Used for parse the HTTP upgrade response from server
std::unique_ptr<http_parser> htp;
SessionStat stat;
ev_io wev;
ev_io rev;
ev_timer wt;
ev_timer rt;
ev_timer settings_timer;
std::function<int(HttpClient &)> readfn, writefn;
std::function<int(HttpClient &, const uint8_t *, size_t)> on_readfn;
std::function<int(HttpClient &)> on_writefn;
nghttp2_session *session;
const nghttp2_session_callbacks *callbacks;
struct ev_loop *loop;
SSL_CTX *ssl_ctx;
SSL *ssl;
addrinfo *addrs;
addrinfo *next_addr;
addrinfo *cur_addr;
// The number of completed requests, including failed ones.
size_t complete;
// The length of settings_payload
size_t settings_payloadlen;
client_state state;
// The HTTP status code of the response message of HTTP Upgrade.
unsigned int upgrade_response_status_code;
int fd;
// true if the response message of HTTP Upgrade request is fully
// received. It is not relevant the upgrade succeeds, or not.
bool upgrade_response_complete;
RingBuf<65536> wb;
// SETTINGS payload sent as token68 in HTTP Upgrade
uint8_t settings_payload[128];
enum { ERR_CONNECT_FAIL = -100 };
HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop,
SSL_CTX *ssl_ctx)
client->signal_write();
}
} // namespace
HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
struct ev_loop *loop, SSL_CTX *ssl_ctx)
: session(nullptr), callbacks(callbacks), loop(loop), ssl_ctx(ssl_ctx),
ssl(nullptr), addrs(nullptr), next_addr(nullptr), cur_addr(nullptr),
complete(0), settings_payloadlen(0), state(STATE_IDLE),
......@@ -556,9 +470,9 @@ struct HttpClient {
ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.);
settings_timer.data = this;
}
}
~HttpClient() {
HttpClient::~HttpClient() {
disconnect();
if (addrs) {
......@@ -566,11 +480,13 @@ struct HttpClient {
addrs = nullptr;
next_addr = nullptr;
}
}
}
bool need_upgrade() const { return config.upgrade && scheme == "http"; }
bool HttpClient::need_upgrade() const {
return config.upgrade && scheme == "http";
}
int resolve_host(const std::string &host, uint16_t port) {
int HttpClient::resolve_host(const std::string &host, uint16_t port) {
int rv;
addrinfo hints;
this->host = host;
......@@ -591,9 +507,9 @@ struct HttpClient {
}
next_addr = addrs;
return 0;
}
}
int initiate_connection() {
int HttpClient::initiate_connection() {
int rv;
cur_addr = nullptr;
......@@ -670,9 +586,9 @@ struct HttpClient {
ev_timer_again(loop, &wt);
return 0;
}
}
void disconnect() {
void HttpClient::disconnect() {
state = STATE_IDLE;
ev_timer_stop(loop, &settings_timer);
......@@ -699,9 +615,9 @@ struct HttpClient {
close(fd);
fd = -1;
}
}
}
int read_clear() {
int HttpClient::read_clear() {
ev_timer_again(loop, &rt);
uint8_t buf[8192];
......@@ -727,9 +643,9 @@ struct HttpClient {
}
return 0;
}
}
int write_clear() {
int HttpClient::write_clear() {
ev_timer_again(loop, &rt);
for (;;) {
......@@ -765,11 +681,11 @@ struct HttpClient {
ev_timer_stop(loop, &wt);
return 0;
}
}
int noop() { return 0; }
int HttpClient::noop() { return 0; }
void on_connect_fail() {
void HttpClient::on_connect_fail() {
if (state == STATE_IDLE) {
std::cerr << "[ERROR] Could not connect to the address "
<< numeric_name(cur_addr) << std::endl;
......@@ -782,9 +698,9 @@ struct HttpClient {
<< std::endl;
}
}
}
}
int connected() {
int HttpClient::connected() {
if (!util::check_socket_connected(fd)) {
return ERR_CONNECT_FAIL;
}
......@@ -826,17 +742,40 @@ struct HttpClient {
}
return 0;
}
namespace {
size_t populate_settings(nghttp2_settings_entry *iv) {
size_t niv = 2;
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = 100;
iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
if (config.window_bits != -1) {
iv[1].value = (1 << config.window_bits) - 1;
} else {
iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
}
int on_upgrade_connect() {
if (config.header_table_size >= 0) {
iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
iv[niv].value = config.header_table_size;
++niv;
}
return niv;
}
} // namespace
int HttpClient::on_upgrade_connect() {
ssize_t rv;
record_handshake_time();
assert(!reqvec.empty());
nghttp2_settings_entry iv[32];
size_t niv = populate_settings(iv);
assert(sizeof(settings_payload) >= 8 * niv);
rv = nghttp2_pack_settings_payload(settings_payload,
sizeof(settings_payload), iv, niv);
rv = nghttp2_pack_settings_payload(settings_payload, sizeof(settings_payload),
iv, niv);
if (rv < 0) {
return -1;
}
......@@ -877,9 +816,9 @@ struct HttpClient {
signal_write();
return 0;
}
}
int on_upgrade_read(const uint8_t *data, size_t len) {
int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) {
int rv;
auto nread = http_parser_execute(htp.get(), &htp_hooks,
......@@ -933,12 +872,12 @@ struct HttpClient {
}
return 0;
}
}
int do_read() { return readfn(*this); }
int do_write() { return writefn(*this); }
int HttpClient::do_read() { return readfn(*this); }
int HttpClient::do_write() { return writefn(*this); }
int on_connect() {
int HttpClient::on_connect() {
int rv;
if (ssl) {
......@@ -988,8 +927,8 @@ struct HttpClient {
if (!reqvec[0]->data_prd) {
stream_user_data = reqvec[0].get();
}
rv = nghttp2_session_upgrade(session, settings_payload,
settings_payloadlen, stream_user_data);
rv = nghttp2_session_upgrade(session, settings_payload, settings_payloadlen,
stream_user_data);
if (rv != 0) {
std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
<< nghttp2_strerror(rv) << std::endl;
......@@ -1019,8 +958,8 @@ struct HttpClient {
nghttp2_priority_spec pri_spec;
int32_t dep_stream_id = 0;
for (auto stream_id : {ANCHOR_ID_HIGH, ANCHOR_ID_MEDIUM, ANCHOR_ID_LOW,
ANCHOR_ID_LOWEST}) {
for (auto stream_id :
{ANCHOR_ID_HIGH, ANCHOR_ID_MEDIUM, ANCHOR_ID_LOW, ANCHOR_ID_LOWEST}) {
nghttp2_priority_spec_init(&pri_spec, dep_stream_id,
NGHTTP2_DEFAULT_WEIGHT, 0);
......@@ -1083,9 +1022,9 @@ struct HttpClient {
signal_write();
return 0;
}
}
int on_read(const uint8_t *data, size_t len) {
int HttpClient::on_read(const uint8_t *data, size_t len) {
auto rv = nghttp2_session_mem_recv(session, data, len);
if (rv < 0) {
std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: "
......@@ -1103,9 +1042,9 @@ struct HttpClient {
signal_write();
return 0;
}
}
int on_write() {
int HttpClient::on_write() {
auto rv = nghttp2_session_send(session);
if (rv != 0) {
std::cerr << "[ERROR] nghttp2_session_send() returned error: "
......@@ -1119,9 +1058,9 @@ struct HttpClient {
}
return 0;
}
}
int tls_handshake() {
int HttpClient::tls_handshake() {
ev_timer_again(loop, &rt);
ERR_clear_error();
......@@ -1159,9 +1098,9 @@ struct HttpClient {
}
return 0;
}
}
int read_tls() {
int HttpClient::read_tls() {
ev_timer_again(loop, &rt);
ERR_clear_error();
......@@ -1191,9 +1130,9 @@ struct HttpClient {
return -1;
}
}
}
}
int write_tls() {
int HttpClient::write_tls() {
ev_timer_again(loop, &rt);
ERR_clear_error();
......@@ -1241,17 +1180,19 @@ struct HttpClient {
ev_timer_stop(loop, &wt);
return 0;
}
}
void HttpClient::signal_write() { ev_io_start(loop, &wev); }
void signal_write() { ev_io_start(loop, &wev); }
bool HttpClient::all_requests_processed() const {
return complete == reqvec.size();
}
bool all_requests_processed() const { return complete == reqvec.size(); }
void update_hostport() {
void HttpClient::update_hostport() {
if (reqvec.empty()) {
return;
}
scheme =
util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA);
scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA);
std::stringstream ss;
if (reqvec[0]->is_ipv6_literal_addr()) {
ss << "[";
......@@ -1266,12 +1207,14 @@ struct HttpClient {
ss << ":" << reqvec[0]->u.port;
}
hostport = ss.str();
}
bool add_request(const std::string &uri,
const nghttp2_data_provider *data_prd, int64_t data_length,
}
bool HttpClient::add_request(const std::string &uri,
const nghttp2_data_provider *data_prd,
int64_t data_length,
const nghttp2_priority_spec &pri_spec,
std::shared_ptr<Dependency> dep, int pri = 0,
int level = 0) {
std::shared_ptr<Dependency> dep, int pri,
int level) {
http_parser_url u;
memset(&u, 0, sizeof(u));
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
......@@ -1288,20 +1231,24 @@ struct HttpClient {
reqvec.push_back(util::make_unique<Request>(
uri, u, data_prd, data_length, pri_spec, std::move(dep), pri, level));
return true;
}
}
void record_handshake_time() { stat.on_handshake_time = get_time(); }
void HttpClient::record_handshake_time() {
stat.on_handshake_time = get_time();
}
void record_started_time() {
void HttpClient::record_started_time() {
stat.started_system_time = std::chrono::system_clock::now();
stat.on_started_time = get_time();
}
}
void record_dns_complete_time() { stat.on_dns_complete_time = get_time(); }
void HttpClient::record_dns_complete_time() {
stat.on_dns_complete_time = get_time();
}
void record_connect_time() { stat.on_connect_time = get_time(); }
void HttpClient::record_connect_time() { stat.on_connect_time = get_time(); }
void on_request(Request *req) {
void HttpClient::on_request(Request *req) {
req->record_request_time();
if (req->pri == 0 && req->dep) {
......@@ -1335,10 +1282,10 @@ struct HttpClient {
if (itr == std::end(req->dep->deps)) {
req->dep->deps.push_back(std::vector<Request *>{req});
}
}
}
#ifdef HAVE_JANSSON
void output_har(FILE *outfile) {
void HttpClient::output_har(FILE *outfile) {
static auto PAGE_ID = "page_0";
auto root = json_object();
......@@ -1504,110 +1451,8 @@ struct HttpClient {
json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2));
json_decref(root);
}
#endif // HAVE_JANSSON
};
} // namespace
namespace {
int htp_msg_begincb(http_parser *htp) {
if (config.verbose) {
print_timer();
std::cout << " HTTP Upgrade response" << std::endl;
}
return 0;
}
} // namespace
namespace {
int htp_statuscb(http_parser *htp, const char *at, size_t length) {
auto client = static_cast<HttpClient *>(htp->data);
client->upgrade_response_status_code = htp->status_code;
return 0;
}
} // namespace
namespace {
int htp_msg_completecb(http_parser *htp) {
auto client = static_cast<HttpClient *>(htp->data);
client->upgrade_response_complete = true;
return 0;
}
} // namespace
namespace {
http_parser_settings htp_hooks = {
htp_msg_begincb, // http_cb on_message_begin;
nullptr, // http_data_cb on_url;
htp_statuscb, // http_data_cb on_status;
nullptr, // http_data_cb on_header_field;
nullptr, // http_data_cb on_header_value;
nullptr, // http_cb on_headers_complete;
nullptr, // http_data_cb on_body;
htp_msg_completecb // http_cb on_message_complete;
};
} // namespace
namespace {
int submit_request(HttpClient *client, const Headers &headers, Request *req) {
auto path = req->make_reqpath();
auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
{":path", path},
{":scheme", scheme},
{":authority", client->hostport},
{"accept", "*/*"},
{"accept-encoding", "gzip, deflate"},
{"user-agent", "nghttp2/" NGHTTP2_VERSION}};
if (config.continuation) {
for (size_t i = 0; i < 6; ++i) {
build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
std::string(4096, '-'));
}
}
auto num_initial_headers = build_headers.size();
if (!config.no_content_length && req->data_prd) {
build_headers.emplace_back("content-length", util::utos(req->data_length));
}
for (auto &kv : headers) {
size_t i;
for (i = 0; i < num_initial_headers; ++i) {
if (kv.name == build_headers[i].name) {
build_headers[i].value = kv.value;
break;
}
}
if (i < num_initial_headers) {
continue;
}
build_headers.emplace_back(kv.name, kv.value, kv.no_index);
}
auto nva = std::vector<nghttp2_nv>();
nva.reserve(build_headers.size());
for (auto &kv : build_headers) {
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
}
auto stream_id =
nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
nva.size(), req->data_prd, req);
if (stream_id < 0) {
std::cerr << "[ERROR] nghttp2_submit_request() returned error: "
<< nghttp2_strerror(stream_id) << std::endl;
return -1;
}
req->stream_id = stream_id;
client->on_request(req);
req->req_nva = std::move(build_headers);
return 0;
}
} // namespace
#endif // HAVE_JANSSON
namespace {
void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
......@@ -1642,7 +1487,7 @@ void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
} // namespace
namespace {
HttpClient *get_session(void *user_data) {
HttpClient *get_client(void *user_data) {
return static_cast<HttpClient *>(user_data);
}
} // namespace
......@@ -1651,7 +1496,7 @@ namespace {
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 client = get_session(user_data);
auto client = get_client(user_data);
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, stream_id));
......@@ -1709,17 +1554,6 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
}
} // namespace
namespace {
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
ev_timer_stop(loop, w);
nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
client->signal_write();
}
} // namespace
namespace {
ssize_t select_padding_callback(nghttp2_session *session,
const nghttp2_frame *frame, size_t max_payload,
......@@ -1783,7 +1617,7 @@ void check_response_header(nghttp2_session *session, Request *req) {
namespace {
int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) {
auto client = get_session(user_data);
auto client = get_client(user_data);
switch (frame->hd.type) {
case NGHTTP2_PUSH_PROMISE: {
auto stream_id = frame->push_promise.promised_stream_id;
......@@ -1894,7 +1728,7 @@ int on_frame_recv_callback2(nghttp2_session *session,
verbose_on_frame_recv_callback(session, frame, user_data);
}
auto client = get_session(user_data);
auto client = get_client(user_data);
switch (frame->hd.type) {
case NGHTTP2_HEADERS: {
auto req = static_cast<Request *>(
......@@ -1984,7 +1818,7 @@ int on_frame_recv_callback2(nghttp2_session *session,
namespace {
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data) {
auto client = get_session(user_data);
auto client = get_client(user_data);
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, stream_id));
......@@ -2057,37 +1891,6 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
}
} // namespace
namespace {
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
if (client->do_read() != 0) {
client->disconnect();
}
}
} // namespace
namespace {
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
auto rv = client->do_write();
if (rv == HttpClient::ERR_CONNECT_FAIL) {
client->on_connect_fail();
return;
}
if (rv != 0) {
client->disconnect();
}
}
} // namespace
namespace {
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto client = static_cast<HttpClient *>(w->data);
std::cerr << "[ERROR] Timeout" << std::endl;
client->disconnect();
}
} // namespace
namespace {
int communicate(
const std::string &scheme, const std::string &host, uint16_t port,
......
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 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.
*/
#ifndef NGHTTP_H
#define NGHTTP_H
#include "nghttp2_config.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string>
#include <vector>
#include <set>
#include <chrono>
#include <openssl/ssl.h>
#include <ev.h>
#include <nghttp2/nghttp2.h>
#include "http-parser/http_parser.h"
#include "ringbuf.h"
#include "http2.h"
#include "nghttp2_gzip.h"
namespace nghttp2 {
class HtmlParser;
struct Config {
Config();
~Config();
Headers headers;
std::string certfile;
std::string keyfile;
std::string datafile;
std::string harfile;
nghttp2_option *http2_option;
size_t output_upper_thres;
size_t padding;
ssize_t peer_max_concurrent_streams;
ssize_t header_table_size;
int32_t weight;
int multiply;
// milliseconds
ev_tstamp timeout;
int window_bits;
int connection_window_bits;
int verbose;
bool null_out;
bool remote_name;
bool get_assets;
bool stat;
bool upgrade;
bool continuation;
bool no_content_length;
bool no_dep;
bool dep_idle;
};
enum StatStage {
STAT_INITIAL,
STAT_ON_REQUEST,
STAT_ON_RESPONSE,
STAT_ON_COMPLETE
};
struct RequestStat {
std::chrono::steady_clock::time_point on_request_time;
std::chrono::steady_clock::time_point on_response_time;
std::chrono::steady_clock::time_point on_complete_time;
StatStage stage;
RequestStat() : stage(STAT_INITIAL) {}
};
struct Request;
struct Dependency {
std::vector<std::vector<Request *>> deps;
};
struct Request {
// For pushed request, |uri| is empty and |u| is zero-cleared.
Request(const std::string &uri, const http_parser_url &u,
const nghttp2_data_provider *data_prd, int64_t data_length,
const nghttp2_priority_spec &pri_spec,
std::shared_ptr<Dependency> dep, int pri = 0, int level = 0);
~Request();
void init_inflater();
void init_html_parser();
int update_html_parser(const uint8_t *data, size_t len, int fin);
std::string make_reqpath() const;
int32_t find_dep_stream_id(int start);
nghttp2_priority_spec resolve_dep(int32_t pri);
bool is_ipv6_literal_addr() const;
bool response_pseudo_header_allowed(int token) const;
bool push_request_pseudo_header_allowed(int token) const;
Headers::value_type *get_res_header(int token);
Headers::value_type *get_req_header(int token);
void record_request_time();
void record_response_time();
void record_complete_time();
Headers res_nva;
Headers req_nva;
// URI without fragment
std::string uri;
http_parser_url u;
std::shared_ptr<Dependency> dep;
nghttp2_priority_spec pri_spec;
RequestStat stat;
int64_t data_length;
int64_t data_offset;
// Number of bytes received from server
int64_t response_len;
nghttp2_gzip *inflater;
HtmlParser *html_parser;
const nghttp2_data_provider *data_prd;
int32_t stream_id;
int status;
// Recursion level: 0: first entity, 1: entity linked from first entity
int level;
// RequestPriority value defined in HtmlParser.h
int pri;
int res_hdidx[http2::HD_MAXIDX];
// used for incoming PUSH_PROMISE
int req_hdidx[http2::HD_MAXIDX];
bool expect_final_response;
};
struct SessionStat {
// The point in time when download was started.
std::chrono::system_clock::time_point started_system_time;
// The point of time when download was started.
std::chrono::steady_clock::time_point on_started_time;
// The point of time when DNS resolution was completed.
std::chrono::steady_clock::time_point on_dns_complete_time;
// The point of time when connection was established or SSL/TLS
// handshake was completed.
std::chrono::steady_clock::time_point on_connect_time;
// The point of time when HTTP/2 commnucation was started.
std::chrono::steady_clock::time_point on_handshake_time;
};
enum client_state { STATE_IDLE, STATE_CONNECTED };
struct HttpClient {
HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop,
SSL_CTX *ssl_ctx);
~HttpClient();
bool need_upgrade() const;
int resolve_host(const std::string &host, uint16_t port);
int initiate_connection();
void disconnect();
void on_connect_fail();
int noop();
int read_clear();
int write_clear();
int connected();
int tls_handshake();
int read_tls();
int write_tls();
int do_read();
int do_write();
int on_upgrade_connect();
int on_upgrade_read(const uint8_t *data, size_t len);
int on_read(const uint8_t *data, size_t len);
int on_write();
int on_connect();
void on_request(Request *req);
void signal_write();
bool all_requests_processed() const;
void update_hostport();
bool add_request(const std::string &uri,
const nghttp2_data_provider *data_prd, int64_t data_length,
const nghttp2_priority_spec &pri_spec,
std::shared_ptr<Dependency> dep, int pri = 0, int level = 0);
void record_handshake_time();
void record_started_time();
void record_dns_complete_time();
void record_connect_time();
#ifdef HAVE_JANSSON
void output_har(FILE *outfile);
#endif // HAVE_JANSSON
std::vector<std::unique_ptr<Request>> reqvec;
// Insert path already added in reqvec to prevent multiple request
// for 1 resource.
std::set<std::string> path_cache;
std::string scheme;
std::string host;
std::string hostport;
// Used for parse the HTTP upgrade response from server
std::unique_ptr<http_parser> htp;
SessionStat stat;
ev_io wev;
ev_io rev;
ev_timer wt;
ev_timer rt;
ev_timer settings_timer;
std::function<int(HttpClient &)> readfn, writefn;
std::function<int(HttpClient &, const uint8_t *, size_t)> on_readfn;
std::function<int(HttpClient &)> on_writefn;
nghttp2_session *session;
const nghttp2_session_callbacks *callbacks;
struct ev_loop *loop;
SSL_CTX *ssl_ctx;
SSL *ssl;
addrinfo *addrs;
addrinfo *next_addr;
addrinfo *cur_addr;
// The number of completed requests, including failed ones.
size_t complete;
// The length of settings_payload
size_t settings_payloadlen;
client_state state;
// The HTTP status code of the response message of HTTP Upgrade.
unsigned int upgrade_response_status_code;
int fd;
// true if the response message of HTTP Upgrade request is fully
// received. It is not relevant the upgrade succeeds, or not.
bool upgrade_response_complete;
RingBuf<65536> wb;
// SETTINGS payload sent as token68 in HTTP Upgrade
uint8_t settings_payload[128];
enum { ERR_CONNECT_FAIL = -100 };
};
} // namespace nghttp2
#endif // NGHTTP_H
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment