mirror of
https://github.com/drogonframework/drogon.git
synced 2025-07-22 00:00:43 -04:00
Compare commits
9 Commits
c38ca498ee
...
921d8056ae
Author | SHA1 | Date | |
---|---|---|---|
|
921d8056ae | ||
|
356c17c63b | ||
|
bda71e2002 | ||
|
f132339055 | ||
|
88f997819a | ||
|
a2dee1f5fd | ||
|
3512090f62 | ||
|
32dc3b5411 | ||
|
4f6034ab85 |
@ -281,7 +281,9 @@ set(DROGON_SOURCES
|
||||
lib/src/WebSocketConnectionImpl.cc
|
||||
lib/src/WebsocketControllersRouter.cc
|
||||
lib/src/YamlConfigAdapter.cc
|
||||
lib/src/drogon_test.cc)
|
||||
lib/src/drogon_test.cc
|
||||
lib/src/hpack/HPacker.cpp
|
||||
lib/src/hpack/HPackTable.cpp)
|
||||
set(private_headers
|
||||
lib/src/AOPAdvice.h
|
||||
lib/src/CacheFile.h
|
||||
@ -316,7 +318,11 @@ set(private_headers
|
||||
lib/src/ConfigAdapterManager.h
|
||||
lib/src/JsonConfigAdapter.h
|
||||
lib/src/YamlConfigAdapter.h
|
||||
lib/src/ConfigAdapter.h)
|
||||
lib/src/ConfigAdapter.h
|
||||
lib/src/hpack/HPacker.h
|
||||
lib/src/hpack/hpack_huffman_table.h
|
||||
lib/src/hpack/HPackTable.h
|
||||
lib/src/hpack/StaticTable.h)
|
||||
|
||||
if (NOT WIN32)
|
||||
set(DROGON_SOURCES
|
||||
|
@ -16,7 +16,10 @@ int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
{
|
||||
auto client = HttpClient::newHttpClient("https://clehaxze.tw");
|
||||
auto client = HttpClient::newHttpClient("https://clehaxze.tw:8844",
|
||||
nullptr,
|
||||
false,
|
||||
false);
|
||||
client->setSockOptCallback([](int fd) {
|
||||
std::cout << "setSockOptCallback:" << fd << std::endl;
|
||||
#ifdef __linux__
|
||||
|
@ -162,31 +162,27 @@ class DROGON_EXPORT HttpRequest
|
||||
virtual const std::string &getCookie(const std::string &field) const = 0;
|
||||
|
||||
/// Get all headers of the request
|
||||
virtual const std::unordered_map<std::string,
|
||||
std::string,
|
||||
utils::internal::SafeStringHash> &
|
||||
headers() const = 0;
|
||||
virtual const std::
|
||||
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
|
||||
&headers() const = 0;
|
||||
|
||||
/// Get all headers of the request
|
||||
const std::unordered_map<std::string,
|
||||
std::string,
|
||||
utils::internal::SafeStringHash> &
|
||||
getHeaders() const
|
||||
const std::
|
||||
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
|
||||
&getHeaders() const
|
||||
{
|
||||
return headers();
|
||||
}
|
||||
|
||||
/// Get all cookies of the request
|
||||
virtual const std::unordered_map<std::string,
|
||||
std::string,
|
||||
utils::internal::SafeStringHash> &
|
||||
cookies() const = 0;
|
||||
virtual const std::
|
||||
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
|
||||
&cookies() const = 0;
|
||||
|
||||
/// Get all cookies of the request
|
||||
const std::unordered_map<std::string,
|
||||
std::string,
|
||||
utils::internal::SafeStringHash> &
|
||||
getCookies() const
|
||||
const std::
|
||||
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
|
||||
&getCookies() const
|
||||
{
|
||||
return cookies();
|
||||
}
|
||||
@ -305,16 +301,14 @@ class DROGON_EXPORT HttpRequest
|
||||
}
|
||||
|
||||
/// Get parameters of the request.
|
||||
virtual const std::unordered_map<std::string,
|
||||
std::string,
|
||||
utils::internal::SafeStringHash> &
|
||||
parameters() const = 0;
|
||||
virtual const std::
|
||||
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
|
||||
¶meters() const = 0;
|
||||
|
||||
/// Get parameters of the request.
|
||||
const std::unordered_map<std::string,
|
||||
std::string,
|
||||
utils::internal::SafeStringHash> &
|
||||
getParameters() const
|
||||
const std::
|
||||
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
|
||||
&getParameters() const
|
||||
{
|
||||
return parameters();
|
||||
}
|
||||
|
@ -199,16 +199,14 @@ class DROGON_EXPORT HttpResponse
|
||||
virtual void removeHeader(std::string key) = 0;
|
||||
|
||||
/// Get all headers of the response
|
||||
virtual const std::unordered_map<std::string,
|
||||
std::string,
|
||||
utils::internal::SafeStringHash> &
|
||||
headers() const = 0;
|
||||
virtual const std::
|
||||
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
|
||||
&headers() const = 0;
|
||||
|
||||
/// Get all headers of the response
|
||||
const std::unordered_map<std::string,
|
||||
std::string,
|
||||
utils::internal::SafeStringHash> &
|
||||
getHeaders() const
|
||||
const std::
|
||||
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
|
||||
&getHeaders() const
|
||||
{
|
||||
return headers();
|
||||
}
|
||||
@ -237,13 +235,13 @@ class DROGON_EXPORT HttpResponse
|
||||
|
||||
/// Get all cookies.
|
||||
virtual const std::
|
||||
unordered_map<std::string, Cookie, utils::internal::SafeStringHash> &
|
||||
cookies() const = 0;
|
||||
unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
|
||||
&cookies() const = 0;
|
||||
|
||||
/// Get all cookies.
|
||||
const std::
|
||||
unordered_map<std::string, Cookie, utils::internal::SafeStringHash> &
|
||||
getCookies() const
|
||||
unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
|
||||
&getCookies() const
|
||||
{
|
||||
return cookies();
|
||||
}
|
||||
@ -514,8 +512,8 @@ class DROGON_EXPORT HttpResponse
|
||||
* newStreamResponse) returns the callback function. Otherwise a
|
||||
* null function.
|
||||
*/
|
||||
virtual const std::function<std::size_t(char *, std::size_t)> &
|
||||
streamCallback() const = 0;
|
||||
virtual const std::function<std::size_t(char *, std::size_t)>
|
||||
&streamCallback() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the content type associated with the response
|
||||
|
@ -53,6 +53,12 @@ struct ByteStream
|
||||
return ptr[offset++];
|
||||
}
|
||||
|
||||
void skip(size_t n)
|
||||
{
|
||||
assert(offset <= length - n);
|
||||
offset += n;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return length;
|
||||
@ -100,25 +106,6 @@ enum class H2SettingsKey
|
||||
NumEntries
|
||||
};
|
||||
|
||||
// TODO: Either convert this to TMP or remove it
|
||||
// For now it's only for development purposes and make sure
|
||||
// I'm not stupid in designing APIs
|
||||
#ifdef __cpp_concepts
|
||||
template <typename T>
|
||||
concept IsH2Frame = requires(T t) {
|
||||
{
|
||||
t.prettyToString()
|
||||
} -> std::convertible_to<std::string>;
|
||||
{
|
||||
t.serialize()
|
||||
} -> std::convertible_to<trantor::MsgBuffer>;
|
||||
{
|
||||
T::fromBuffer(ByteStream(nullptr, 0))
|
||||
// if returns std::nullopt, it's an error
|
||||
} -> std::convertible_to<std::optional<T>>;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct SettingsFrame
|
||||
{
|
||||
std::vector<std::pair<uint16_t, uint32_t>> settings;
|
||||
@ -129,7 +116,24 @@ struct WindowUpdateFrame
|
||||
uint32_t windowSizeIncrement;
|
||||
};
|
||||
|
||||
using H2Frame = std::variant<SettingsFrame, WindowUpdateFrame>;
|
||||
struct HeadersFrame
|
||||
{
|
||||
uint8_t padLength = 0;
|
||||
bool exclusive = false;
|
||||
uint32_t streamDependency = 0;
|
||||
uint8_t weight = 0;
|
||||
std::vector<uint8_t> headerBlockFragment;
|
||||
};
|
||||
|
||||
struct GoAwayFrame
|
||||
{
|
||||
uint32_t lastStreamId;
|
||||
uint32_t errorCode;
|
||||
std::vector<uint8_t> additionalDebugData;
|
||||
};
|
||||
|
||||
using H2Frame =
|
||||
std::variant<SettingsFrame, WindowUpdateFrame, HeadersFrame, GoAwayFrame>;
|
||||
|
||||
// Print the HEX and ASCII representation of the buffer side by side
|
||||
// 16 bytes per line. Same function as the xdd command in linux.
|
||||
@ -207,12 +211,146 @@ static std::optional<WindowUpdateFrame> parseWindowUpdateFrame(
|
||||
return frame;
|
||||
}
|
||||
|
||||
static std::optional<GoAwayFrame> parseGoAwayFrame(ByteStream &payload)
|
||||
{
|
||||
if (payload.size() < 8)
|
||||
{
|
||||
LOG_ERROR << "Invalid go away frame length";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GoAwayFrame frame;
|
||||
frame.lastStreamId = payload.readU32BE();
|
||||
frame.errorCode = payload.readU32BE();
|
||||
frame.additionalDebugData.resize(payload.remaining());
|
||||
for (size_t i = 0; i < frame.additionalDebugData.size(); ++i)
|
||||
frame.additionalDebugData[i] = payload.readU8();
|
||||
|
||||
LOG_TRACE << "Go away frame: lastStreamId=" << frame.lastStreamId
|
||||
<< " errorCode=" << frame.errorCode
|
||||
<< " additionalDebugData=" << frame.additionalDebugData.size();
|
||||
return frame;
|
||||
}
|
||||
|
||||
static std::optional<HeadersFrame> parseHeadersFrame(ByteStream &payload)
|
||||
{
|
||||
HeadersFrame frame;
|
||||
frame.padLength = payload.readU8();
|
||||
uint32_t streamDependency = payload.readU32BE();
|
||||
frame.exclusive = streamDependency & (1U << 31);
|
||||
frame.streamDependency = streamDependency & ((1U << 31) - 1);
|
||||
frame.weight = payload.readU8();
|
||||
frame.headerBlockFragment.resize(payload.remaining());
|
||||
for (size_t i = 0; i < frame.headerBlockFragment.size(); ++i)
|
||||
frame.headerBlockFragment[i] = payload.readU8();
|
||||
|
||||
// TODO: Handle padding
|
||||
payload.skip(frame.padLength);
|
||||
return frame;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> serializeHeadsFrame(const HeadersFrame &frame)
|
||||
{
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.reserve(6 + frame.headerBlockFragment.size() + frame.padLength);
|
||||
// buffer.push_back(frame.padLength);
|
||||
// uint32_t streamDependency = frame.streamDependency;
|
||||
// if (frame.exclusive)
|
||||
// streamDependency |= 1U << 31;
|
||||
// buffer.push_back(streamDependency >> 24);
|
||||
// buffer.push_back(streamDependency >> 16);
|
||||
// buffer.push_back(streamDependency >> 8);
|
||||
// buffer.push_back(streamDependency);
|
||||
// buffer.push_back(frame.weight);
|
||||
for (size_t i = 0; i < frame.headerBlockFragment.size(); ++i)
|
||||
buffer.push_back(frame.headerBlockFragment[i]);
|
||||
for (size_t i = 0; i < frame.padLength; ++i)
|
||||
buffer.push_back(0x0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> serializeSettingsFrame(const SettingsFrame &frame)
|
||||
{
|
||||
if (frame.settings.size() == 0)
|
||||
return std::vector<uint8_t>();
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.reserve(6 * frame.settings.size());
|
||||
for (auto &[key, value] : frame.settings)
|
||||
{
|
||||
buffer.push_back(key >> 8);
|
||||
buffer.push_back(key);
|
||||
buffer.push_back(value >> 24);
|
||||
buffer.push_back(value >> 16);
|
||||
buffer.push_back(value >> 8);
|
||||
buffer.push_back(value);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> serializeWindowUpdateFrame(
|
||||
const WindowUpdateFrame &frame)
|
||||
{
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.reserve(4);
|
||||
buffer.push_back(frame.windowSizeIncrement >> 24);
|
||||
buffer.push_back(frame.windowSizeIncrement >> 16);
|
||||
buffer.push_back(frame.windowSizeIncrement >> 8);
|
||||
buffer.push_back(frame.windowSizeIncrement);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> serializeFrame(const H2Frame &frame,
|
||||
size_t streamId,
|
||||
uint8_t flags = 0)
|
||||
{
|
||||
std::vector<uint8_t> buffer;
|
||||
uint8_t type;
|
||||
if (std::holds_alternative<HeadersFrame>(frame))
|
||||
{
|
||||
const auto &f = std::get<HeadersFrame>(frame);
|
||||
buffer = serializeHeadsFrame(f);
|
||||
type = (uint8_t)H2FrameType::Headers;
|
||||
}
|
||||
else if (std::holds_alternative<SettingsFrame>(frame))
|
||||
{
|
||||
const auto &f = std::get<SettingsFrame>(frame);
|
||||
buffer = serializeSettingsFrame(f);
|
||||
type = (uint8_t)H2FrameType::Settings;
|
||||
}
|
||||
else if (std::holds_alternative<WindowUpdateFrame>(frame))
|
||||
{
|
||||
const auto &f = std::get<WindowUpdateFrame>(frame);
|
||||
buffer = serializeWindowUpdateFrame(f);
|
||||
type = (uint8_t)H2FrameType::WindowUpdate;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Unsupported frame type";
|
||||
abort();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> full_frame;
|
||||
full_frame.reserve(9 + buffer.size());
|
||||
size_t length = buffer.size();
|
||||
assert(length <= 0xffffff);
|
||||
full_frame.push_back(length >> 16);
|
||||
full_frame.push_back(length >> 8);
|
||||
full_frame.push_back(length);
|
||||
full_frame.push_back(type);
|
||||
full_frame.push_back(flags);
|
||||
full_frame.push_back(streamId >> 24);
|
||||
full_frame.push_back(streamId >> 16);
|
||||
full_frame.push_back(streamId >> 8);
|
||||
full_frame.push_back(streamId);
|
||||
full_frame.insert(full_frame.end(), buffer.begin(), buffer.end());
|
||||
return full_frame;
|
||||
}
|
||||
|
||||
// return streamId, frame, error and should continue parsing
|
||||
// Note that error can orrcur on a stream level or the entire connection
|
||||
// We need to handle both cases. Also it could happen that the TCP stream
|
||||
// just cuts off in the middle of a frame (or header). We need to handle that
|
||||
// too.
|
||||
// std::tuple<std::optional<H2Frame>, size_t, bool>
|
||||
static std::tuple<std::optional<H2Frame>, size_t, bool> parseH2Frame(
|
||||
trantor::MsgBuffer *msg)
|
||||
{
|
||||
@ -254,6 +392,10 @@ static std::tuple<std::optional<H2Frame>, size_t, bool> parseH2Frame(
|
||||
frame = parseSettingsFrame(payload);
|
||||
else if (type == (uint8_t)H2FrameType::WindowUpdate)
|
||||
frame = parseWindowUpdateFrame(payload);
|
||||
else if (type == (uint8_t)H2FrameType::GoAway)
|
||||
frame = parseGoAwayFrame(payload);
|
||||
else if (type == (uint8_t)H2FrameType::Headers)
|
||||
frame = parseHeadersFrame(payload);
|
||||
else
|
||||
{
|
||||
LOG_WARN << "Unsupported H2 frame type: " << (int)type;
|
||||
@ -274,10 +416,87 @@ static std::tuple<std::optional<H2Frame>, size_t, bool> parseH2Frame(
|
||||
return {frame, streamId, false};
|
||||
}
|
||||
|
||||
void drogon::Http2Transport::sendRequestInLoop(const HttpRequestPtr &req,
|
||||
HttpReqCallback &&callback)
|
||||
{
|
||||
if (!serverSettingsReceived)
|
||||
{
|
||||
bufferedRequests.emplace_back(req, std::move(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
// HACK: Acknowledge the settings frame, move this somewhere appropriate
|
||||
LOG_TRACE << "Acknowledge settings frame";
|
||||
SettingsFrame settings;
|
||||
auto sb = serializeFrame(settings, 0, 0);
|
||||
dump_hex_beautiful(sb.data(), sb.size());
|
||||
connPtr->send(sb.data(), sb.size());
|
||||
sb = serializeFrame(settings, 0, 0x1);
|
||||
dump_hex_beautiful(sb.data(), sb.size());
|
||||
connPtr->send(sb.data(), sb.size());
|
||||
|
||||
WindowUpdateFrame windowUpdate;
|
||||
windowUpdate.windowSizeIncrement = 200 * 1024 * 1024; // 200MB
|
||||
auto wu = serializeFrame(windowUpdate, 0);
|
||||
dump_hex_beautiful(wu.data(), wu.size());
|
||||
connPtr->send(wu.data(), wu.size());
|
||||
|
||||
static hpack::HPacker hpack;
|
||||
auto headers = req->headers();
|
||||
HeadersFrame frame;
|
||||
frame.padLength = 0;
|
||||
frame.exclusive = false;
|
||||
frame.streamDependency = 0;
|
||||
frame.weight = 0;
|
||||
frame.headerBlockFragment.resize(1024);
|
||||
|
||||
LOG_TRACE << "Sending HTTP/2 headers: size=" << headers.size();
|
||||
hpack::HPacker::KeyValueVector headersToEncode;
|
||||
const std::array<std::string_view, 2> headersToSkip = {
|
||||
{"host", "connection"}};
|
||||
headersToEncode.emplace_back(":method", req->methodString());
|
||||
headersToEncode.emplace_back(":path", req->path());
|
||||
headersToEncode.emplace_back(":scheme",
|
||||
connPtr->isSSLConnection() ? "https" : "http");
|
||||
headersToEncode.emplace_back(":authority", req->getHeader("host"));
|
||||
for (auto &[key, value] : headers)
|
||||
{
|
||||
if (std::find(headersToSkip.begin(), headersToSkip.end(), key) !=
|
||||
headersToSkip.end())
|
||||
continue;
|
||||
|
||||
headersToEncode.emplace_back(key, value);
|
||||
}
|
||||
|
||||
LOG_TRACE << "Final headers size: " << headersToEncode.size();
|
||||
for (auto &[key, value] : headersToEncode)
|
||||
LOG_TRACE << " " << key << ": " << value;
|
||||
int n = hpack.encode(headersToEncode,
|
||||
frame.headerBlockFragment.data(),
|
||||
frame.headerBlockFragment.size());
|
||||
if (n < 0)
|
||||
{
|
||||
LOG_ERROR << "Failed to encode headers";
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
LOG_TRACE << "Encoded headers size: " << n;
|
||||
frame.headerBlockFragment.resize(n);
|
||||
LOG_TRACE << "Encoded headers:";
|
||||
dump_hex_beautiful(frame.headerBlockFragment.data(),
|
||||
frame.headerBlockFragment.size());
|
||||
LOG_TRACE << "Sending headers frame";
|
||||
auto f = serializeFrame(frame, 1, 0x5);
|
||||
dump_hex_beautiful(f.data(), f.size());
|
||||
connPtr->send(f.data(), f.size());
|
||||
}
|
||||
|
||||
void Http2Transport::onRecvMessage(const trantor::TcpConnectionPtr &,
|
||||
trantor::MsgBuffer *msg)
|
||||
{
|
||||
LOG_TRACE << "HTTP/2 message received:";
|
||||
assert(bytesReceived_ != nullptr);
|
||||
*bytesReceived_ += msg->readableBytes();
|
||||
dump_hex_beautiful(msg->peek(), msg->readableBytes());
|
||||
while (true)
|
||||
{
|
||||
@ -285,14 +504,15 @@ void Http2Transport::onRecvMessage(const trantor::TcpConnectionPtr &,
|
||||
|
||||
if (error && streamId == 0)
|
||||
{
|
||||
abort();
|
||||
LOG_ERROR << "Fatal protocol error happened on stream 0 (global)";
|
||||
errorCallback(ReqResult::BadResponse);
|
||||
}
|
||||
else if (frameOpt.has_value() == false && !error)
|
||||
break;
|
||||
else if (!frameOpt.has_value())
|
||||
{
|
||||
LOG_ERROR << "Failed to parse H2 frame??? Shouldn't happen though";
|
||||
abort();
|
||||
errorCallback(ReqResult::BadResponse);
|
||||
}
|
||||
auto &frame = *frameOpt;
|
||||
|
||||
@ -327,10 +547,30 @@ void Http2Transport::onRecvMessage(const trantor::TcpConnectionPtr &,
|
||||
LOG_TRACE << "Unsupported settings key: " << key;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamId == 0 && !serverSettingsReceived)
|
||||
{
|
||||
serverSettingsReceived = true;
|
||||
for (auto &[req, cb] : bufferedRequests)
|
||||
sendRequestInLoop(req, std::move(cb));
|
||||
bufferedRequests.clear();
|
||||
}
|
||||
}
|
||||
else if (std::holds_alternative<GoAwayFrame>(frame))
|
||||
{
|
||||
LOG_ERROR << "Go away frame received. Die!";
|
||||
auto &f = std::get<GoAwayFrame>(frame);
|
||||
// TODO: Depening on the streamId, we need to kill the entire
|
||||
// connection or just the stream
|
||||
errorCallback(ReqResult::BadResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Remove this once we support all frame types
|
||||
// in that case it'll be a parsing error or bad server
|
||||
LOG_ERROR << "Boom! The client does not understand this frame";
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("HTTP/2 onRecvMessage not implemented");
|
||||
}
|
||||
|
||||
Http2Transport::Http2Transport(trantor::TcpConnectionPtr connPtr,
|
||||
|
@ -1,9 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "HttpTransport.h"
|
||||
// TOOD: Write our own HPACK implementation
|
||||
#include "hpack/HPacker.h"
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
namespace internal
|
||||
{
|
||||
|
||||
// Virtual stream that holds properties for the HTTP/2 stream
|
||||
// Defaults to stream 0 global properties
|
||||
struct H2Stream
|
||||
{
|
||||
size_t maxConcurrentStreams = 100;
|
||||
size_t initialWindowSize = 65535;
|
||||
size_t maxFrameSize = 16384;
|
||||
size_t avaliableWindowSize = 0;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
class Http2Transport : public HttpTransport
|
||||
{
|
||||
private:
|
||||
@ -17,17 +35,17 @@ class Http2Transport : public HttpTransport
|
||||
size_t maxFrameSize = 16384;
|
||||
size_t avaliableWindowSize = 0;
|
||||
|
||||
// Set after server settings are received
|
||||
bool serverSettingsReceived = false;
|
||||
std::vector<std::pair<HttpRequestPtr, HttpReqCallback>> bufferedRequests;
|
||||
|
||||
public:
|
||||
Http2Transport(trantor::TcpConnectionPtr connPtr,
|
||||
size_t *bytesSent,
|
||||
size_t *bytesReceived);
|
||||
|
||||
void sendRequestInLoop(const HttpRequestPtr &req,
|
||||
HttpReqCallback &&callback) override
|
||||
{
|
||||
// throw std::runtime_error("HTTP/2 sendRequestInLoop not implemented");
|
||||
}
|
||||
|
||||
HttpReqCallback &&callback) override;
|
||||
void onRecvMessage(const trantor::TcpConnectionPtr &,
|
||||
trantor::MsgBuffer *) override;
|
||||
|
||||
@ -46,5 +64,8 @@ class Http2Transport : public HttpTransport
|
||||
{
|
||||
throw std::runtime_error("HTTP/2 onError not implemented");
|
||||
}
|
||||
|
||||
protected:
|
||||
void onServerSettingsReceived(){};
|
||||
};
|
||||
} // namespace drogon
|
@ -70,9 +70,8 @@ void Http1xTransport::onRecvMessage(const trantor::TcpConnectionPtr &conn,
|
||||
}
|
||||
if (!responseParser->parseResponse(msg))
|
||||
{
|
||||
// TODO: Make upper layer flush all requests in the buffer
|
||||
onError(ReqResult::BadResponse);
|
||||
*bytesReceived_ += (msgSize - msg->readableBytes());
|
||||
errorCallback(ReqResult::BadResponse);
|
||||
return;
|
||||
}
|
||||
if (responseParser->gotAll())
|
||||
@ -190,6 +189,12 @@ void HttpClientImpl::createTcpClient()
|
||||
return;
|
||||
thisPtr->handleResponse(resp, std::move(reqAndCb), connPtr);
|
||||
});
|
||||
thisPtr->transport_->setErrorCallback([weakPtr](ReqResult result) {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (!thisPtr)
|
||||
return;
|
||||
thisPtr->onError(result);
|
||||
});
|
||||
|
||||
// TODO: respect timeout and pipeliningDepth_
|
||||
while (!thisPtr->requestsBuffer_.empty())
|
||||
@ -533,6 +538,16 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
|
||||
|
||||
if (!tcpClientPtr_)
|
||||
{
|
||||
auto callbackPtr =
|
||||
std::make_shared<drogon::HttpReqCallback>(std::move(callback));
|
||||
requestsBuffer_.push_back(
|
||||
{req,
|
||||
[thisPtr = shared_from_this(),
|
||||
callbackPtr =
|
||||
std::move(callbackPtr)](ReqResult result,
|
||||
const HttpResponsePtr &response) {
|
||||
(*callbackPtr)(result, response);
|
||||
}});
|
||||
if (domain_.empty() || !isDomainName_)
|
||||
{
|
||||
// Valid ip address, no domain, connect directly
|
||||
@ -549,15 +564,6 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
|
||||
return;
|
||||
}
|
||||
|
||||
auto callbackPtr =
|
||||
std::make_shared<drogon::HttpReqCallback>(std::move(callback));
|
||||
requestsBuffer_.push_back(
|
||||
{req,
|
||||
[thisPtr = shared_from_this(),
|
||||
callbackPtr](ReqResult result, const HttpResponsePtr &response) {
|
||||
(*callbackPtr)(result, response);
|
||||
}});
|
||||
|
||||
// A dns query is on going.
|
||||
if (dns_)
|
||||
{
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
@ -21,6 +20,10 @@ class HttpTransport : public trantor::NonCopyable
|
||||
virtual void onRecvMessage(const trantor::TcpConnectionPtr &,
|
||||
trantor::MsgBuffer *) = 0;
|
||||
virtual bool handleConnectionClose() = 0;
|
||||
|
||||
// XXX: Footgun: This MUST be called by the HttpClient. DO NOT
|
||||
// call this within Transport. Client needs to know that error
|
||||
// happened. When in doubt, call error callback.
|
||||
virtual void onError(ReqResult result) = 0;
|
||||
|
||||
virtual size_t requestsInFlight() const = 0;
|
||||
@ -29,13 +32,20 @@ class HttpTransport : public trantor::NonCopyable
|
||||
std::function<void(const HttpResponseImplPtr &,
|
||||
std::pair<HttpRequestPtr, HttpReqCallback> &&,
|
||||
const trantor::TcpConnectionPtr)>;
|
||||
using ErrorCallback = std::function<void(ReqResult)>;
|
||||
|
||||
void setRespCallback(RespCallback cb)
|
||||
{
|
||||
respCallback = std::move(cb);
|
||||
}
|
||||
|
||||
void setErrorCallback(ErrorCallback cb)
|
||||
{
|
||||
errorCallback = std::move(cb);
|
||||
}
|
||||
|
||||
RespCallback respCallback;
|
||||
ErrorCallback errorCallback;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace drogon
|
220
lib/src/hpack/HPackTable.cpp
Normal file
220
lib/src/hpack/HPackTable.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
/* Copyright (c) 2016, Fengping Bao <jamol@live.com>
|
||||
*
|
||||
* 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" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include "HPackTable.h"
|
||||
|
||||
using namespace hpack;
|
||||
|
||||
#include "StaticTable.h"
|
||||
|
||||
HPackTable::HPackTable()
|
||||
{
|
||||
for (int i = 0; i < HPACK_STATIC_TABLE_SIZE; ++i)
|
||||
{
|
||||
std::string key =
|
||||
hpackStaticTable[i].first + hpackStaticTable[i].second;
|
||||
indexMap_.emplace(std::move(key), std::make_pair(-1, i));
|
||||
}
|
||||
}
|
||||
|
||||
bool HPackTable::getIndexedName(int index, std::string &name)
|
||||
{
|
||||
if (index <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (index < HPACK_DYNAMIC_START_INDEX)
|
||||
{
|
||||
name = hpackStaticTable[index - 1].first;
|
||||
}
|
||||
else if (index - HPACK_DYNAMIC_START_INDEX <
|
||||
static_cast<int>(dynamicTable_.size()))
|
||||
{
|
||||
name = dynamicTable_[index - HPACK_DYNAMIC_START_INDEX].first;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HPackTable::getIndexedValue(int index, std::string &value)
|
||||
{
|
||||
if (index <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (index < HPACK_DYNAMIC_START_INDEX)
|
||||
{
|
||||
value = hpackStaticTable[index - 1].second;
|
||||
}
|
||||
else if (index - HPACK_DYNAMIC_START_INDEX <
|
||||
static_cast<int>(dynamicTable_.size()))
|
||||
{
|
||||
value = dynamicTable_[index - HPACK_DYNAMIC_START_INDEX].second;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HPackTable::addHeader(const std::string &name, const std::string &value)
|
||||
{
|
||||
uint32_t entrySize =
|
||||
uint32_t(name.length() + value.length() + TABLE_ENTRY_SIZE_EXTRA);
|
||||
if (entrySize + tableSize_ > limitSize_)
|
||||
{
|
||||
evictTableBySize(entrySize + tableSize_ - limitSize_);
|
||||
}
|
||||
if (entrySize > limitSize_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
dynamicTable_.push_front(std::make_pair(name, value));
|
||||
tableSize_ += entrySize;
|
||||
if (isEncoder_)
|
||||
{
|
||||
std::string key = name + value;
|
||||
updateIndex(key, ++indexSequence_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HPackTable::setMaxSize(size_t maxSize)
|
||||
{
|
||||
maxSize_ = maxSize;
|
||||
if (limitSize_ > maxSize_)
|
||||
{
|
||||
updateLimitSize(maxSize_);
|
||||
}
|
||||
}
|
||||
|
||||
void HPackTable::updateLimitSize(size_t limitSize)
|
||||
{
|
||||
if (tableSize_ > limitSize)
|
||||
{
|
||||
evictTableBySize(tableSize_ - limitSize);
|
||||
}
|
||||
limitSize_ = limitSize;
|
||||
}
|
||||
|
||||
void HPackTable::evictTableBySize(size_t size)
|
||||
{
|
||||
uint32_t evicted = 0;
|
||||
while (evicted < size && !dynamicTable_.empty())
|
||||
{
|
||||
auto &entry = dynamicTable_.back();
|
||||
uint32_t entrySize =
|
||||
uint32_t(entry.first.length() + entry.second.length() +
|
||||
TABLE_ENTRY_SIZE_EXTRA);
|
||||
tableSize_ -= tableSize_ > entrySize ? entrySize : tableSize_;
|
||||
if (isEncoder_)
|
||||
{
|
||||
std::string key = entry.first + entry.second;
|
||||
removeIndex(key);
|
||||
}
|
||||
dynamicTable_.pop_back();
|
||||
evicted += entrySize;
|
||||
}
|
||||
}
|
||||
|
||||
int HPackTable::getDynamicIndex(int idxSeq)
|
||||
{
|
||||
return -1 == idxSeq ? -1 : indexSequence_ - idxSeq;
|
||||
}
|
||||
|
||||
void HPackTable::updateIndex(const std::string &key, int idxSeq)
|
||||
{
|
||||
auto it = indexMap_.find(key);
|
||||
if (it != indexMap_.end())
|
||||
{
|
||||
it->second.first = idxSeq;
|
||||
}
|
||||
else
|
||||
{
|
||||
indexMap_.emplace(key, std::make_pair(idxSeq, -1));
|
||||
}
|
||||
}
|
||||
|
||||
void HPackTable::removeIndex(const std::string &key)
|
||||
{
|
||||
auto it = indexMap_.find(key);
|
||||
if (it != indexMap_.end())
|
||||
{
|
||||
int idx = getDynamicIndex(it->second.first);
|
||||
if (idx == dynamicTable_.size() - 1)
|
||||
{
|
||||
if (it->second.second == -1)
|
||||
{
|
||||
indexMap_.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second.first = -1; // reset dynamic table index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HPackTable::getIndex(const std::string &key, int &indexD, int &indexS)
|
||||
{
|
||||
indexD = -1;
|
||||
indexS = -1;
|
||||
auto it = indexMap_.find(key);
|
||||
if (it != indexMap_.end())
|
||||
{
|
||||
indexD = getDynamicIndex(it->second.first);
|
||||
indexS = it->second.second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int HPackTable::getIndex(const std::string &name,
|
||||
const std::string &value,
|
||||
bool &valueIndexed)
|
||||
{
|
||||
int index = -1, indexD = -1, indexS = -1;
|
||||
valueIndexed = false;
|
||||
|
||||
std::string key = name + value;
|
||||
if (!getIndex(key, indexD, indexS))
|
||||
{
|
||||
getIndex(name, indexD, indexS);
|
||||
}
|
||||
if (indexD != -1 && indexD < static_cast<int>(dynamicTable_.size()) &&
|
||||
name == dynamicTable_[indexD].first)
|
||||
{
|
||||
index = indexD + HPACK_DYNAMIC_START_INDEX;
|
||||
valueIndexed = dynamicTable_[indexD].second == value;
|
||||
}
|
||||
else if (indexS != -1 && indexS < HPACK_STATIC_TABLE_SIZE &&
|
||||
name == hpackStaticTable[indexS].first)
|
||||
{
|
||||
index = indexS + 1;
|
||||
valueIndexed = hpackStaticTable[indexS].second == value;
|
||||
}
|
||||
return index;
|
||||
}
|
93
lib/src/hpack/HPackTable.h
Normal file
93
lib/src/hpack/HPackTable.h
Normal file
@ -0,0 +1,93 @@
|
||||
/* Copyright (c) 2016, Fengping Bao <jamol@live.com>
|
||||
*
|
||||
* 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" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __HPackTable_H__
|
||||
#define __HPackTable_H__
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
|
||||
namespace hpack
|
||||
{
|
||||
|
||||
class HPackTable
|
||||
{
|
||||
public:
|
||||
using KeyValuePair = std::pair<std::string, std::string>;
|
||||
|
||||
public:
|
||||
HPackTable();
|
||||
|
||||
void setMode(bool isEncoder)
|
||||
{
|
||||
isEncoder_ = isEncoder;
|
||||
}
|
||||
|
||||
void setMaxSize(size_t maxSize);
|
||||
void updateLimitSize(size_t limitSize);
|
||||
int getIndex(const std::string &name,
|
||||
const std::string &value,
|
||||
bool &valueIndexed);
|
||||
bool getIndexedName(int index, std::string &name);
|
||||
bool getIndexedValue(int index, std::string &value);
|
||||
bool addHeader(const std::string &name, const std::string &value);
|
||||
|
||||
size_t getMaxSize()
|
||||
{
|
||||
return maxSize_;
|
||||
}
|
||||
|
||||
size_t getLimitSize()
|
||||
{
|
||||
return limitSize_;
|
||||
}
|
||||
|
||||
size_t getTableSize()
|
||||
{
|
||||
return tableSize_;
|
||||
}
|
||||
|
||||
private:
|
||||
int getDynamicIndex(int idxSeq);
|
||||
void updateIndex(const std::string &key, int idxSeq);
|
||||
void removeIndex(const std::string &key);
|
||||
bool getIndex(const std::string &key, int &indexD, int &indexS);
|
||||
void evictTableBySize(size_t size);
|
||||
|
||||
private:
|
||||
using KeyValueQueue = std::deque<KeyValuePair>;
|
||||
using IndexMap = std::map<std::string, std::pair<int, int>>;
|
||||
|
||||
KeyValueQueue dynamicTable_;
|
||||
size_t tableSize_ = 0;
|
||||
size_t limitSize_ = 4096;
|
||||
size_t maxSize_ = 4096;
|
||||
|
||||
bool isEncoder_ = false;
|
||||
int indexSequence_ = 0;
|
||||
IndexMap indexMap_; // <key(header name[ + value]), <dynamic index
|
||||
// sequence, static index>>
|
||||
};
|
||||
|
||||
} // namespace hpack
|
||||
|
||||
#endif /* __HPackTable_H__ */
|
526
lib/src/hpack/HPacker.cpp
Normal file
526
lib/src/hpack/HPacker.cpp
Normal file
@ -0,0 +1,526 @@
|
||||
/* Copyright (c) 2016, Fengping Bao <jamol@live.com>
|
||||
*
|
||||
* 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" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include "HPacker.h"
|
||||
#include "hpack_huffman_table.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h> // for memcpy
|
||||
|
||||
namespace hpack
|
||||
{
|
||||
|
||||
static char *huffDecodeBits(char *dst,
|
||||
uint8_t bits,
|
||||
uint8_t *state,
|
||||
bool *ending)
|
||||
{
|
||||
const auto &entry = huff_decode_table[*state][bits];
|
||||
|
||||
if ((entry.flags & NGHTTP2_HUFF_FAIL) != 0)
|
||||
return nullptr;
|
||||
if ((entry.flags & NGHTTP2_HUFF_SYM) != 0)
|
||||
*dst++ = entry.sym;
|
||||
*state = entry.state;
|
||||
*ending = (entry.flags & NGHTTP2_HUFF_ACCEPTED) != 0;
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
static int huffDecode(const uint8_t *src, size_t len, std::string &str)
|
||||
{
|
||||
uint8_t state = 0;
|
||||
bool ending = false;
|
||||
const uint8_t *src_end = src + len;
|
||||
|
||||
std::vector<char> sbuf;
|
||||
sbuf.resize(2 * len);
|
||||
char *ptr = &sbuf[0];
|
||||
|
||||
for (; src != src_end; ++src)
|
||||
{
|
||||
if ((ptr = huffDecodeBits(ptr, *src >> 4, &state, &ending)) == nullptr)
|
||||
return -1;
|
||||
if ((ptr = huffDecodeBits(ptr, *src & 0xf, &state, &ending)) == nullptr)
|
||||
return -1;
|
||||
}
|
||||
if (!ending)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int slen = int(ptr - &sbuf[0]);
|
||||
str.assign(&sbuf[0], slen);
|
||||
return slen;
|
||||
}
|
||||
|
||||
static int huffEncode(const std::string &str, uint8_t *buf, size_t len)
|
||||
{
|
||||
uint8_t *ptr = buf;
|
||||
// const uint8_t *end = buf + len;
|
||||
const char *src = str.c_str();
|
||||
const char *src_end = src + str.length();
|
||||
|
||||
uint64_t current = 0;
|
||||
uint32_t n = 0;
|
||||
|
||||
for (; src != src_end;)
|
||||
{
|
||||
const auto &sym = huff_sym_table[*src++];
|
||||
uint32_t code = sym.code;
|
||||
uint32_t nbits = sym.nbits;
|
||||
|
||||
current <<= nbits;
|
||||
current |= code;
|
||||
n += nbits;
|
||||
|
||||
while (n >= 8)
|
||||
{
|
||||
n -= 8;
|
||||
*ptr++ = static_cast<uint8_t>(current >> n);
|
||||
}
|
||||
}
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
current <<= (8 - n);
|
||||
current |= (0xFF >> n);
|
||||
*ptr++ = static_cast<uint8_t>(current);
|
||||
}
|
||||
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
static uint32_t huffEncodeLength(const std::string &str)
|
||||
{
|
||||
uint32_t len = 0;
|
||||
for (char c : str)
|
||||
{
|
||||
len += huff_sym_table[c].nbits;
|
||||
}
|
||||
return (len + 7) >> 3;
|
||||
}
|
||||
|
||||
static int encodeInteger(uint8_t N, uint64_t I, uint8_t *buf, size_t len)
|
||||
{
|
||||
uint8_t *ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
if (ptr == end)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
uint8_t NF = (1 << N) - 1;
|
||||
if (I < NF)
|
||||
{
|
||||
*ptr &= NF ^ 0xFF;
|
||||
*ptr |= I;
|
||||
return 1;
|
||||
}
|
||||
*ptr++ |= NF;
|
||||
I -= NF;
|
||||
while (ptr < end && I >= 128)
|
||||
{
|
||||
*ptr++ = I % 128 + 128;
|
||||
I /= 128;
|
||||
}
|
||||
if (ptr == end)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
*ptr++ = static_cast<uint8_t>(I);
|
||||
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
static int encodeString(const std::string &str, uint8_t *buf, size_t len)
|
||||
{
|
||||
uint8_t *ptr = buf;
|
||||
uint8_t *end = buf + len;
|
||||
|
||||
int slen = int(str.length());
|
||||
int hlen = huffEncodeLength(str);
|
||||
if (hlen < slen)
|
||||
{
|
||||
*ptr = 0x80;
|
||||
int ret = encodeInteger(7, hlen, ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
ret = huffEncode(str, ptr, end - ptr);
|
||||
if (ret < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ptr = 0;
|
||||
int ret = encodeInteger(7, slen, ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
if (static_cast<size_t>(end - ptr) < str.length())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
memcpy(ptr, str.c_str(), str.length());
|
||||
ptr += str.length();
|
||||
}
|
||||
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
static int decodeInteger(uint8_t N, const uint8_t *buf, size_t len, uint64_t &I)
|
||||
{
|
||||
if (N > 8)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
const uint8_t *ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
if (ptr == end)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
uint8_t NF = (1 << N) - 1;
|
||||
uint8_t prefix = (*ptr++) & NF;
|
||||
if (prefix < NF)
|
||||
{
|
||||
I = prefix;
|
||||
return 1;
|
||||
}
|
||||
if (ptr == end)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int m = 0;
|
||||
uint64_t u64 = prefix;
|
||||
uint8_t b = 0;
|
||||
do
|
||||
{
|
||||
b = *ptr++;
|
||||
u64 += static_cast<uint64_t>((b & 127) * pow(2, m));
|
||||
m += 7;
|
||||
} while (ptr < end && (b & 128));
|
||||
if (ptr == end && (b & 128))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
I = u64;
|
||||
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
static int decodeString(const uint8_t *buf, size_t len, std::string &str)
|
||||
{
|
||||
const uint8_t *ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
if (ptr == end)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
bool H = !!(*ptr & 0x80);
|
||||
uint64_t slen = 0;
|
||||
int ret = decodeInteger(7, ptr, end - ptr, slen);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
if (slen > static_cast<size_t>(end - ptr))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (H)
|
||||
{
|
||||
if (huffDecode(ptr, static_cast<size_t>(slen), str) < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
str.assign((const char *)ptr, static_cast<size_t>(slen));
|
||||
}
|
||||
ptr += slen;
|
||||
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
enum class PrefixType
|
||||
{
|
||||
INDEXED_HEADER,
|
||||
LITERAL_HEADER_WITH_INDEXING,
|
||||
LITERAL_HEADER_WITHOUT_INDEXING,
|
||||
TABLE_SIZE_UPDATE
|
||||
};
|
||||
|
||||
static int decodePrefix(const uint8_t *buf,
|
||||
size_t len,
|
||||
PrefixType &type,
|
||||
uint64_t &I)
|
||||
{
|
||||
const uint8_t *ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
uint8_t N = 0;
|
||||
if (*ptr & 0x80)
|
||||
{
|
||||
N = 7;
|
||||
type = PrefixType::INDEXED_HEADER;
|
||||
}
|
||||
else if (*ptr & 0x40)
|
||||
{
|
||||
N = 6;
|
||||
type = PrefixType::LITERAL_HEADER_WITH_INDEXING;
|
||||
}
|
||||
else if (*ptr & 0x20)
|
||||
{
|
||||
N = 5;
|
||||
type = PrefixType::TABLE_SIZE_UPDATE;
|
||||
}
|
||||
else
|
||||
{
|
||||
N = 4;
|
||||
type = PrefixType::LITERAL_HEADER_WITHOUT_INDEXING;
|
||||
}
|
||||
int ret = decodeInteger(N, ptr, end - ptr, I);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
HPacker::IndexingType HPacker::getIndexingType(const std::string &name,
|
||||
const std::string &value)
|
||||
{
|
||||
if (query_cb_)
|
||||
{
|
||||
return query_cb_(name, value);
|
||||
}
|
||||
if (name == "cookie" || name == ":authority" || name == "user-agent" ||
|
||||
name == "pragma")
|
||||
{
|
||||
return IndexingType::ALL;
|
||||
}
|
||||
return IndexingType::NONE;
|
||||
}
|
||||
|
||||
int HPacker::encodeSizeUpdate(int sz, uint8_t *buf, size_t len)
|
||||
{
|
||||
uint8_t *ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
*ptr = 0x20;
|
||||
int ret = encodeInteger(5, sz, ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
int HPacker::encodeHeader(const std::string &name,
|
||||
const std::string &value,
|
||||
uint8_t *buf,
|
||||
size_t len)
|
||||
{
|
||||
uint8_t *ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
|
||||
bool valueIndexed = false;
|
||||
int index = table_.getIndex(name, value, valueIndexed);
|
||||
bool addToTable = false;
|
||||
if (index != -1)
|
||||
{
|
||||
uint8_t N = 0;
|
||||
if (valueIndexed)
|
||||
{ // name and value indexed
|
||||
*ptr = 0x80;
|
||||
N = 7;
|
||||
}
|
||||
else
|
||||
{ // name indexed
|
||||
IndexingType idxType = getIndexingType(name, value);
|
||||
if (idxType == IndexingType::ALL)
|
||||
{
|
||||
*ptr = 0x40;
|
||||
N = 6;
|
||||
addToTable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ptr = 0x10;
|
||||
N = 4;
|
||||
}
|
||||
}
|
||||
// encode prefix Bits
|
||||
int ret = encodeInteger(N, index, ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
if (!valueIndexed)
|
||||
{
|
||||
ret = encodeString(value, ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IndexingType idxType = getIndexingType(name, value);
|
||||
if (idxType == IndexingType::ALL)
|
||||
{
|
||||
*ptr++ = 0x40;
|
||||
addToTable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ptr++ = 0x10;
|
||||
}
|
||||
int ret = encodeString(name, ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
ret = encodeString(value, ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
}
|
||||
if (addToTable)
|
||||
{
|
||||
table_.addHeader(name, value);
|
||||
}
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
int HPacker::encode(const KeyValueVector &headers, uint8_t *buf, size_t len)
|
||||
{
|
||||
table_.setMode(true);
|
||||
uint8_t *ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
|
||||
if (updateTableSize_)
|
||||
{
|
||||
updateTableSize_ = false;
|
||||
int ret = encodeSizeUpdate(int(table_.getLimitSize()), ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
}
|
||||
for (const auto &hdr : headers)
|
||||
{
|
||||
int ret = encodeHeader(hdr.first, hdr.second, ptr, end - ptr);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
}
|
||||
return int(ptr - buf);
|
||||
}
|
||||
|
||||
int HPacker::decode(const uint8_t *buf, size_t len, KeyValueVector &headers)
|
||||
{
|
||||
table_.setMode(false);
|
||||
const uint8_t *ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
headers.clear();
|
||||
|
||||
while (ptr < end)
|
||||
{
|
||||
std::string name;
|
||||
std::string value;
|
||||
PrefixType type;
|
||||
uint64_t I = 0;
|
||||
int ret = decodePrefix(ptr, end - ptr, type, I);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
if (PrefixType::INDEXED_HEADER == type)
|
||||
{
|
||||
if (!table_.getIndexedName(int(I), name) ||
|
||||
!table_.getIndexedValue(int(I), value))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (PrefixType::LITERAL_HEADER_WITH_INDEXING == type ||
|
||||
PrefixType::LITERAL_HEADER_WITHOUT_INDEXING == type)
|
||||
{
|
||||
if (0 == I)
|
||||
{
|
||||
ret = decodeString(ptr, end - ptr, name);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
}
|
||||
else if (!table_.getIndexedName(int(I), name))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ret = decodeString(ptr, end - ptr, value);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ptr += ret;
|
||||
if (PrefixType::LITERAL_HEADER_WITH_INDEXING == type)
|
||||
{
|
||||
table_.addHeader(name, value);
|
||||
}
|
||||
}
|
||||
else if (PrefixType::TABLE_SIZE_UPDATE == type)
|
||||
{
|
||||
if (I > table_.getMaxSize())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
table_.updateLimitSize(static_cast<size_t>(I));
|
||||
continue;
|
||||
}
|
||||
headers.emplace_back(std::make_pair(name, value));
|
||||
}
|
||||
return int(len);
|
||||
}
|
||||
|
||||
} // namespace hpack
|
81
lib/src/hpack/HPacker.h
Normal file
81
lib/src/hpack/HPacker.h
Normal file
@ -0,0 +1,81 @@
|
||||
/* Copyright (c) 2016, Fengping Bao <jamol@live.com>
|
||||
*
|
||||
* 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" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __HPacker_H__
|
||||
#define __HPacker_H__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "HPackTable.h"
|
||||
|
||||
namespace hpack
|
||||
{
|
||||
|
||||
class HPacker
|
||||
{
|
||||
public:
|
||||
enum class IndexingType
|
||||
{
|
||||
NONE, // not index
|
||||
NAME, // index name only
|
||||
ALL // index name and value
|
||||
};
|
||||
|
||||
using KeyValuePair = HPackTable::KeyValuePair;
|
||||
using KeyValueVector = std::vector<KeyValuePair>;
|
||||
using IndexingTypeCallback =
|
||||
std::function<IndexingType(const std::string &, const std::string &)>;
|
||||
|
||||
public:
|
||||
int encode(const KeyValueVector &headers, uint8_t *buf, size_t len);
|
||||
int decode(const uint8_t *buf, size_t len, KeyValueVector &headers);
|
||||
|
||||
void setMaxTableSize(size_t maxSize)
|
||||
{
|
||||
table_.setMaxSize(maxSize);
|
||||
}
|
||||
|
||||
void setIndexingTypeCallback(IndexingTypeCallback cb)
|
||||
{
|
||||
query_cb_ = std::move(cb);
|
||||
}
|
||||
|
||||
private:
|
||||
int encodeHeader(const std::string &name,
|
||||
const std::string &value,
|
||||
uint8_t *buf,
|
||||
size_t len);
|
||||
int encodeSizeUpdate(int sz, uint8_t *buf, size_t len);
|
||||
|
||||
IndexingType getIndexingType(const std::string &name,
|
||||
const std::string &value);
|
||||
|
||||
private:
|
||||
HPackTable table_;
|
||||
IndexingTypeCallback query_cb_;
|
||||
bool updateTableSize_ = true;
|
||||
};
|
||||
|
||||
} // namespace hpack
|
||||
|
||||
#endif /* __HPacker_H__ */
|
84
lib/src/hpack/StaticTable.h
Normal file
84
lib/src/hpack/StaticTable.h
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
namespace hpack
|
||||
{
|
||||
|
||||
#define TABLE_ENTRY_SIZE_EXTRA 32
|
||||
#define HPACK_DYNAMIC_START_INDEX 62
|
||||
#define HPACK_STATIC_TABLE_SIZE 61
|
||||
static const HPackTable::KeyValuePair
|
||||
hpackStaticTable[HPACK_STATIC_TABLE_SIZE] = {
|
||||
std::make_pair(":authority", ""),
|
||||
std::make_pair(":method", "GET"),
|
||||
std::make_pair(":method", "POST"),
|
||||
std::make_pair(":path", "/"),
|
||||
std::make_pair(":path", "/index.html"),
|
||||
|
||||
std::make_pair(":scheme", "http"),
|
||||
std::make_pair(":scheme", "https"),
|
||||
std::make_pair(":status", "200"),
|
||||
std::make_pair(":status", "204"),
|
||||
std::make_pair(":status", "206"),
|
||||
|
||||
std::make_pair(":status", "304"),
|
||||
std::make_pair(":status", "400"),
|
||||
std::make_pair(":status", "404"),
|
||||
std::make_pair(":status", "500"),
|
||||
std::make_pair("accept-charset", ""),
|
||||
|
||||
std::make_pair("accept-encoding", "gzip, deflate"),
|
||||
std::make_pair("accept-language", ""),
|
||||
std::make_pair("accept-ranges", ""),
|
||||
std::make_pair("accept", ""),
|
||||
std::make_pair("access-control-allow-origin", ""),
|
||||
|
||||
std::make_pair("age", ""),
|
||||
std::make_pair("allow", ""),
|
||||
std::make_pair("authorization", ""),
|
||||
std::make_pair("cache-control", ""),
|
||||
std::make_pair("content-disposition", ""),
|
||||
|
||||
std::make_pair("content-encoding", ""),
|
||||
std::make_pair("content-language", ""),
|
||||
std::make_pair("content-length", ""),
|
||||
std::make_pair("content-location", ""),
|
||||
std::make_pair("content-range", ""),
|
||||
|
||||
std::make_pair("content-type", ""),
|
||||
std::make_pair("cookie", ""),
|
||||
std::make_pair("date", ""),
|
||||
std::make_pair("etag", ""),
|
||||
std::make_pair("expect", ""),
|
||||
|
||||
std::make_pair("expires", ""),
|
||||
std::make_pair("from", ""),
|
||||
std::make_pair("host", ""),
|
||||
std::make_pair("if-match", ""),
|
||||
std::make_pair("if-modified-since", ""),
|
||||
|
||||
std::make_pair("if-none-match", ""),
|
||||
std::make_pair("if-range", ""),
|
||||
std::make_pair("if-unmodified-since", ""),
|
||||
std::make_pair("last-modified", ""),
|
||||
std::make_pair("link", ""),
|
||||
|
||||
std::make_pair("location", ""),
|
||||
std::make_pair("max-forwards", ""),
|
||||
std::make_pair("proxy-authenticate", ""),
|
||||
std::make_pair("proxy-authorization", ""),
|
||||
std::make_pair("range", ""),
|
||||
|
||||
std::make_pair("referer", ""),
|
||||
std::make_pair("refresh", ""),
|
||||
std::make_pair("retry-after", ""),
|
||||
std::make_pair("server", ""),
|
||||
std::make_pair("set-cookie", ""),
|
||||
|
||||
std::make_pair("strict-transport-security", ""),
|
||||
std::make_pair("transfer-encoding", ""),
|
||||
std::make_pair("user-agent", ""),
|
||||
std::make_pair("vary", ""),
|
||||
std::make_pair("via", ""),
|
||||
|
||||
std::make_pair("www-authenticate", "")};
|
||||
|
||||
} // namespace hpack
|
4993
lib/src/hpack/hpack_huffman_table.h
Normal file
4993
lib/src/hpack/hpack_huffman_table.h
Normal file
File diff suppressed because it is too large
Load Diff
2
trantor
2
trantor
@ -1 +1 @@
|
||||
Subproject commit 26a599857a2fa5d61823ef85012e3d9123d5a675
|
||||
Subproject commit f754e1caa2ce58d2457a2aab107bb6f8cec7f79e
|
Loading…
x
Reference in New Issue
Block a user