nghttpx: Complete HTTP request and response

This commit is contained in:
Tatsuhiro Tsujikawa 2021-08-17 18:25:55 +09:00
parent 204b3884d1
commit abe06b4389
8 changed files with 1831 additions and 26 deletions

View File

@ -156,6 +156,7 @@ NGHTTPX_SRCS = \
shrpx_quic_listener.cc shrpx_quic_listener.h \
shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \
shrpx_http3_upstream.cc shrpx_http3_upstream.h \
http3.cc http3.h \
quic.cc quic.h \
buffer.h memchunk.h template.h allocator.h \
xsi_strerror.c xsi_strerror.h

215
src/http3.cc Normal file
View File

@ -0,0 +1,215 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2021 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 "http3.h"
namespace nghttp2 {
namespace http3 {
namespace {
nghttp3_nv make_nv_internal(const std::string &name, const std::string &value,
bool never_index, uint8_t nv_flags) {
uint8_t flags;
flags = nv_flags |
(never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE);
return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
value.size(), flags};
}
} // namespace
namespace {
nghttp3_nv make_nv_internal(const StringRef &name, const StringRef &value,
bool never_index, uint8_t nv_flags) {
uint8_t flags;
flags = nv_flags |
(never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE);
return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
value.size(), flags};
}
} // namespace
nghttp3_nv make_nv(const std::string &name, const std::string &value,
bool never_index) {
return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE);
}
nghttp3_nv make_nv(const StringRef &name, const StringRef &value,
bool never_index) {
return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE);
}
nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool never_index) {
return make_nv_internal(name, value, never_index,
NGHTTP3_NV_FLAG_NO_COPY_NAME |
NGHTTP3_NV_FLAG_NO_COPY_VALUE);
}
nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
bool never_index) {
return make_nv_internal(name, value, never_index,
NGHTTP3_NV_FLAG_NO_COPY_NAME |
NGHTTP3_NV_FLAG_NO_COPY_VALUE);
}
namespace {
void copy_headers_to_nva_internal(std::vector<nghttp3_nv> &nva,
const HeaderRefs &headers, uint8_t nv_flags,
uint32_t flags) {
auto it_forwarded = std::end(headers);
auto it_xff = std::end(headers);
auto it_xfp = std::end(headers);
auto it_via = std::end(headers);
for (auto it = std::begin(headers); it != std::end(headers); ++it) {
auto kv = &(*it);
if (kv->name.empty() || kv->name[0] == ':') {
continue;
}
switch (kv->token) {
case http2::HD_COOKIE:
case http2::HD_CONNECTION:
case http2::HD_HOST:
case http2::HD_HTTP2_SETTINGS:
case http2::HD_KEEP_ALIVE:
case http2::HD_PROXY_CONNECTION:
case http2::HD_SERVER:
case http2::HD_TE:
case http2::HD_TRANSFER_ENCODING:
case http2::HD_UPGRADE:
continue;
case http2::HD_EARLY_DATA:
if (flags & http2::HDOP_STRIP_EARLY_DATA) {
continue;
}
break;
case http2::HD_SEC_WEBSOCKET_ACCEPT:
if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) {
continue;
}
break;
case http2::HD_SEC_WEBSOCKET_KEY:
if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_KEY) {
continue;
}
break;
case http2::HD_FORWARDED:
if (flags & http2::HDOP_STRIP_FORWARDED) {
continue;
}
if (it_forwarded == std::end(headers)) {
it_forwarded = it;
continue;
}
kv = &(*it_forwarded);
it_forwarded = it;
break;
case http2::HD_X_FORWARDED_FOR:
if (flags & http2::HDOP_STRIP_X_FORWARDED_FOR) {
continue;
}
if (it_xff == std::end(headers)) {
it_xff = it;
continue;
}
kv = &(*it_xff);
it_xff = it;
break;
case http2::HD_X_FORWARDED_PROTO:
if (flags & http2::HDOP_STRIP_X_FORWARDED_PROTO) {
continue;
}
if (it_xfp == std::end(headers)) {
it_xfp = it;
continue;
}
kv = &(*it_xfp);
it_xfp = it;
break;
case http2::HD_VIA:
if (flags & http2::HDOP_STRIP_VIA) {
continue;
}
if (it_via == std::end(headers)) {
it_via = it;
continue;
}
kv = &(*it_via);
it_via = it;
break;
}
nva.push_back(
make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags));
}
}
} // namespace
void copy_headers_to_nva(std::vector<nghttp3_nv> &nva,
const HeaderRefs &headers, uint32_t flags) {
copy_headers_to_nva_internal(nva, headers, NGHTTP3_NV_FLAG_NONE, flags);
}
void copy_headers_to_nva_nocopy(std::vector<nghttp3_nv> &nva,
const HeaderRefs &headers, uint32_t flags) {
copy_headers_to_nva_internal(
nva, headers,
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE, flags);
}
int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen) {
if (!nghttp3_check_header_name(name, namelen)) {
return 0;
}
if (!nghttp3_check_header_value(value, valuelen)) {
return 0;
}
return 1;
}
void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen) {
auto end = nva + nvlen;
for (; nva != end; ++nva) {
fprintf(out, "%s: %s\n", nva->name, nva->value);
}
fputc('\n', out);
fflush(out);
}
} // namespace http3
} // namespace nghttp2

129
src/http3.h Normal file
View File

@ -0,0 +1,129 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2021 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 HTTP3_H
#define HTTP3_H
#include "nghttp2_config.h"
#include <cstring>
#include <string>
#include <vector>
#include <nghttp3/nghttp3.h>
#include "http2.h"
#include "template.h"
namespace nghttp2 {
namespace http3 {
// Creates nghttp3_nv using |name| and |value| and returns it. The
// returned value only references the data pointer to name.c_str() and
// value.c_str(). If |no_index| is true, nghttp3_nv flags member has
// NGHTTP3_NV_FLAG_NEVER_INDEX flag set.
nghttp3_nv make_nv(const std::string &name, const std::string &value,
bool never_index = false);
nghttp3_nv make_nv(const StringRef &name, const StringRef &value,
bool never_index = false);
nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool never_index = false);
nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
bool never_index = false);
// Create nghttp3_nv from string literal |name| and |value|.
template <size_t N, size_t M>
constexpr nghttp3_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1,
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
}
// Create nghttp3_nv from string literal |name| and c-string |value|.
template <size_t N>
nghttp3_nv make_nv_lc(const char (&name)[N], const char *value) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
NGHTTP3_NV_FLAG_NO_COPY_NAME};
}
template <size_t N>
nghttp3_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
}
// Create nghttp3_nv from string literal |name| and std::string
// |value|.
template <size_t N>
nghttp3_nv make_nv_ls(const char (&name)[N], const std::string &value) {
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
NGHTTP3_NV_FLAG_NO_COPY_NAME};
}
template <size_t N>
nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) {
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
}
template <size_t N>
nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) {
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE};
}
// Appends headers in |headers| to |nv|. |headers| must be indexed
// before this call (its element's token field is assigned). Certain
// headers, including disallowed headers in HTTP/3 spec and headers
// which require special handling (i.e. via), are not copied. |flags|
// is one or more of HeaderBuildOp flags. They tell function that
// certain header fields should not be added.
void copy_headers_to_nva(std::vector<nghttp3_nv> &nva,
const HeaderRefs &headers, uint32_t flags);
// Just like copy_headers_to_nva(), but this adds
// NGHTTP3_NV_FLAG_NO_COPY_NAME and NGHTTP3_NV_FLAG_NO_COPY_VALUE.
void copy_headers_to_nva_nocopy(std::vector<nghttp3_nv> &nva,
const HeaderRefs &headers, uint32_t flags);
// Dumps name/value pairs in |nva| to |out|.
void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen);
// Checks the header name/value pair using nghttp3_check_header_name()
// and nghttp3_check_header_value(). If both function returns nonzero,
// this function returns nonzero.
int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen);
// Dumps name/value pairs in |nva| to |out|.
void dump_nv(FILE *out, const nghttp3_nv *nva, size_t nvlen);
} // namespace http3
} // namespace nghttp2
#endif // HTTP3_H

View File

@ -113,13 +113,22 @@ template <typename T> struct Pool {
template <typename Memchunk> struct Memchunks {
Memchunks(Pool<Memchunk> *pool)
: pool(pool), head(nullptr), tail(nullptr), len(0) {}
: pool(pool),
head(nullptr),
tail(nullptr),
len(0),
mark(nullptr),
mark_pos(nullptr),
mark_offset(0) {}
Memchunks(const Memchunks &) = delete;
Memchunks(Memchunks &&other) noexcept
: pool{other.pool}, // keep other.pool
head{std::exchange(other.head, nullptr)},
tail{std::exchange(other.tail, nullptr)},
len{std::exchange(other.len, 0)} {}
len{std::exchange(other.len, 0)},
mark{std::exchange(other.mark, nullptr)},
mark_pos{std::exchange(other.mark_pos, nullptr)},
mark_offset{std::exchange(other.mark_offset, 0)} {}
Memchunks &operator=(const Memchunks &) = delete;
Memchunks &operator=(Memchunks &&other) noexcept {
if (this == &other) {
@ -132,6 +141,9 @@ template <typename Memchunk> struct Memchunks {
head = std::exchange(other.head, nullptr);
tail = std::exchange(other.tail, nullptr);
len = std::exchange(other.len, 0);
mark = std::exchange(other.mark, nullptr);
mark_pos = std::exchange(other.mark_pos, nullptr);
mark_offset = std::exchange(other.mark_offset, 0);
return *this;
}
@ -200,6 +212,8 @@ template <typename Memchunk> struct Memchunks {
return len;
}
size_t remove(void *dest, size_t count) {
assert(mark == nullptr);
if (!tail || count == 0) {
return 0;
}
@ -231,6 +245,8 @@ template <typename Memchunk> struct Memchunks {
return first - static_cast<uint8_t *>(dest);
}
size_t remove(Memchunks &dest, size_t count) {
assert(mark == nullptr);
if (!tail || count == 0) {
return 0;
}
@ -262,6 +278,7 @@ template <typename Memchunk> struct Memchunks {
}
size_t remove(Memchunks &dest) {
assert(pool == dest.pool);
assert(mark == nullptr);
if (head == nullptr) {
return 0;
@ -284,6 +301,8 @@ template <typename Memchunk> struct Memchunks {
return n;
}
size_t drain(size_t count) {
assert(mark == nullptr);
auto ndata = count;
auto m = head;
while (m) {
@ -305,6 +324,38 @@ template <typename Memchunk> struct Memchunks {
}
return ndata - count;
}
size_t drain_mark(size_t count) {
auto ndata = count;
auto m = head;
while (m) {
auto next = m->next;
auto n = std::min(count, m->len());
m->pos += n;
count -= n;
len -= n;
mark_offset -= n;
if (m->len() > 0) {
assert(mark != m || m->pos <= mark_pos);
break;
}
if (mark == m) {
assert(m->pos <= mark_pos);
mark = nullptr;
mark_pos = nullptr;
mark_offset = 0;
}
pool->recycle(m);
m = next;
}
head = m;
if (head == nullptr) {
tail = nullptr;
}
return ndata - count;
}
int riovec(struct iovec *iov, int iovcnt) const {
if (!head) {
return 0;
@ -317,7 +368,41 @@ template <typename Memchunk> struct Memchunks {
}
return i;
}
int riovec_mark(struct iovec *iov, int iovcnt) {
if (!head || iovcnt == 0) {
return 0;
}
int i = 0;
Memchunk *m;
if (mark) {
if (mark_pos != mark->last) {
iov[0].iov_base = mark_pos;
iov[0].iov_len = mark->len() - (mark_pos - mark->pos);
mark_pos = mark->last;
mark_offset += iov[0].iov_len;
i = 1;
}
m = mark->next;
} else {
i = 0;
m = head;
}
for (; i < iovcnt && m; ++i, m = m->next) {
iov[i].iov_base = m->pos;
iov[i].iov_len = m->len();
mark = m;
mark_pos = m->last;
mark_offset += m->len();
}
return i;
}
size_t rleft() const { return len; }
size_t rleft_mark() const { return len - mark_offset; }
void reset() {
for (auto m = head; m;) {
auto next = m->next;
@ -325,12 +410,17 @@ template <typename Memchunk> struct Memchunks {
m = next;
}
len = 0;
head = tail = nullptr;
head = tail = mark = nullptr;
mark_pos = nullptr;
mark_offset = 0;
}
Pool<Memchunk> *pool;
Memchunk *head, *tail;
size_t len;
Memchunk *mark;
uint8_t *mark_pos;
size_t mark_offset;
};
// Wrapper around Memchunks to offer "peeking" functionality.

View File

@ -145,7 +145,8 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
accesslog_written_(false),
new_affinity_cookie_(false),
blocked_request_data_eof_(false),
expect_100_continue_(false) {
expect_100_continue_(false),
stop_reading_(false) {
auto &timeoutconf = get_config()->http2.timeout;
@ -164,6 +165,7 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
downstream_wtimer_.data = this;
rcbufs_.reserve(32);
rcbufs3_.reserve(32);
}
Downstream::~Downstream() {
@ -203,6 +205,10 @@ Downstream::~Downstream() {
// explicitly.
dconn_.reset();
for (auto rcbuf : rcbufs3_) {
nghttp3_rcbuf_decref(rcbuf);
}
for (auto rcbuf : rcbufs_) {
nghttp2_rcbuf_decref(rcbuf);
}
@ -1128,6 +1134,11 @@ void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) {
rcbufs_.push_back(rcbuf);
}
void Downstream::add_rcbuf(nghttp3_rcbuf *rcbuf) {
nghttp3_rcbuf_incref(rcbuf);
rcbufs3_.push_back(rcbuf);
}
void Downstream::set_downstream_addr_group(
const std::shared_ptr<DownstreamAddrGroup> &group) {
group_ = group;
@ -1169,4 +1180,8 @@ bool Downstream::get_expect_100_continue() const {
return expect_100_continue_;
}
bool Downstream::get_stop_reading() const { return stop_reading_; }
void Downstream::set_stop_reading(bool f) { stop_reading_ = f; }
} // namespace shrpx

View File

@ -38,6 +38,8 @@
#include <nghttp2/nghttp2.h>
#include <nghttp3/nghttp3.h>
#include "llhttp.h"
#include "shrpx_io_control.h"
@ -488,6 +490,7 @@ public:
BlockAllocator &get_block_allocator();
void add_rcbuf(nghttp2_rcbuf *rcbuf);
void add_rcbuf(nghttp3_rcbuf *rcbuf);
void
set_downstream_addr_group(const std::shared_ptr<DownstreamAddrGroup> &group);
@ -513,6 +516,9 @@ public:
bool get_expect_100_continue() const;
bool get_stop_reading() const;
void set_stop_reading(bool f);
enum {
EVENT_ERROR = 0x1,
EVENT_TIMEOUT = 0x2,
@ -527,6 +533,7 @@ private:
BlockAllocator balloc_;
std::vector<nghttp2_rcbuf *> rcbufs_;
std::vector<nghttp3_rcbuf *> rcbufs3_;
Request req_;
Response resp_;
@ -606,6 +613,7 @@ private:
bool blocked_request_data_eof_;
// true if request contains "expect: 100-continue" header field.
bool expect_100_continue_;
bool stop_reading_;
};
} // namespace shrpx

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,8 @@
#include <nghttp3/nghttp3.h>
#include "shrpx_upstream.h"
#include "shrpx_downstream_queue.h"
#include "shrpx_mruby.h"
#include "quic.h"
#include "network.h"
@ -111,6 +113,31 @@ public:
void reset_timer();
int setup_httpconn();
void add_pending_downstream(std::unique_ptr<Downstream> downstream);
int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data,
size_t datalen);
int acked_stream_data_offset(int64_t stream_id, uint64_t datalen);
int extend_max_stream_data(int64_t stream_id);
void extend_max_remote_streams_bidi(uint64_t max_streams);
int error_reply(Downstream *downstream, unsigned int status_code);
void http_begin_request_headers(int64_t stream_id);
int http_recv_request_header(Downstream *downstream, int32_t token,
nghttp3_rcbuf *name, nghttp3_rcbuf *value,
uint8_t flags);
int http_end_request_headers(Downstream *downstream);
int http_end_stream(Downstream *downstream);
void start_downstream(Downstream *downstream);
void initiate_downstream(Downstream *downstream);
int shutdown_stream(Downstream *downstream, uint64_t app_error_code);
int redirect_to_https(Downstream *downstream);
int http_stream_close(Downstream *downstream, uint64_t app_error_code);
void consume(int64_t stream_id, size_t nconsumed);
void remove_downstream(Downstream *downstream);
int stream_close(int64_t stream_id, uint64_t app_error_code);
void log_response_headers(Downstream *downstream,
const std::vector<nghttp3_nv> &nva) const;
int http_acked_stream_data(Downstream *downstream, size_t datalen);
int http_shutdown_stream_read(int64_t stream_id);
private:
ClientHandler *handler_;
@ -121,6 +148,7 @@ private:
quic::Error last_error_;
uint8_t tls_alert_;
nghttp3_conn *httpconn_;
DownstreamQueue downstream_queue_;
};
} // namespace shrpx