2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
42#include "web/http/client/Request.h"
44#include "core/file/FileReader.h"
45#include "core/socket/stream/SocketConnection.h"
46#include "web/http/MimeTypes.h"
47#include "web/http/TransferEncoding.h"
48#include "web/http/client/SocketContext.h"
49#include "web/http/client/SocketContextUpgradeFactorySelector.h"
53#include "commands/EndCommand.h"
54#include "commands/SendFileCommand.h"
55#include "commands/SendFragmentCommand.h"
56#include "commands/SendHeaderCommand.h"
57#include "commands/SseCommand.h"
58#include "commands/UpgradeCommand.h"
60#ifndef DOXYGEN_SHOULD_SKIP_THIS
62#include "log/Logger.h"
63#include "web/http/http_utils.h"
68#include <system_error>
73#define to_hex_str(int_val) (static_cast<std::ostringstream const&>(std::ostringstream() << std::uppercase << std::hex << int_val)).str()
75namespace web::http::client {
81 set("X-Powered-By", "snode.c");
103 set("X-Powered-By", "snode.c");
119 set("Host", hostFieldValue
, true);
125 const std::map<std::string, std::string>::iterator it =
headers.find(field);
128 set(field
, it->second.append(
", ").append(value)
);
137 if (!value.empty()) {
139 headers.insert_or_assign(field, value);
141 headers.insert({field, value});
153 headers.erase(
"Transfer-Encoding");
157 headers.erase(
"Content-Length");
183 for (
const auto& [field, value] : headers) {
191 headers.insert({
"Content-Type", type});
203 for (
const auto& [name, value] : cookies) {
217 if (!value.empty()) {
219 trailer.insert_or_assign(field, value);
221 trailer.insert({field, value});
223 if (!
headers.contains(
"Trailer")) {
226 headers[
"Trailer"].append(
"," + field);
236 auto fieldElement =
headers.find(field);
238 return fieldElement !=
headers.end() ? fieldElement->second :
"";
257 void Request::
upgrade(
const std::shared_ptr<
Response>& response,
const std::function<
void(
const std::string&)>& status) {
263 if (response !=
nullptr) {
268 if (socketContextUpgradeFactory !=
nullptr) {
269 name = socketContextUpgradeFactory
->name();
271 LOG(DEBUG) << connectionName <<
" HTTP upgrade: SocketContextUpgradeFactory create success for: " << name;
276 if (socketContextUpgrade !=
nullptr) {
277 LOG(DEBUG) << connectionName <<
" HTTP upgrade: SocketContextUpgrade create success for: " << name;
281 LOG(DEBUG) << connectionName <<
" HTTP upgrade: SocketContextUpgrade create failed for: " << name;
286 LOG(DEBUG) << connectionName
287 <<
" HTTP upgrade: SocketContextUpgradeFactory not supported by server: " <<
header("upgrade");
292 LOG(DEBUG) << connectionName <<
" HTTP upgrade: No upgrade requested";
297 LOG(ERROR) << connectionName <<
" HTTP upgrade: Response has gone away";
302 LOG(ERROR) << connectionName <<
" HTTP upgrade: Unexpected disconnect";
324 delete requestCommand;
350 std::size_t chunkLen,
351 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::shared_ptr<
Response>&)>& onResponseReceived,
352 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::string&)>& onResponseParseError) {
359 newRequest->
set("Content-Type", "application/octet-stream", false);
378 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::shared_ptr<
Response>&)>& onResponseReceived,
379 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::string&)>& onResponseParseError) {
380 if (!chunk.empty()) {
381 set("Content-Type", "text/html; charset=utf-8", false);
384 return send(chunk.data()
, chunk.size()
, onResponseReceived
, onResponseParseError
);
388 const std::string& url,
389 const std::string& protocols,
390 const std::function<
void(
bool)>& onUpgradeInitiate,
391 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::shared_ptr<
Response>&,
bool)>& onResponseReceived,
392 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::string&)>& onResponseParseError) {
396 newRequest->
url = url;
402 [onResponseReceived](
const std::shared_ptr<
Request>& request,
const std::shared_ptr<
Response>& response) {
403 if (request !=
nullptr) {
406 LOG(DEBUG) << connectionName <<
" HTTP upgrade: Response to upgrade request: " << request->
method <<
" "
407 << request->
url <<
" "
416 request->
upgrade(response
, [request, response, connectionName, &onResponseReceived](
const std::string& name) {
417 LOG(DEBUG) << connectionName <<
" HTTP upgrade: bootstrap " << (!name.empty() ?
"success" :
"failed");
418 LOG(DEBUG) <<
" Protocol selected: " << name;
419 LOG(DEBUG) <<
" requested: " << request->
header("upgrade");
420 LOG(DEBUG) <<
" Subprotocol selected: " << response->
get("Sec-WebSocket-Protocol");
421 LOG(DEBUG) <<
" requested: " << request->
header("Sec-WebSocket-Protocol");
423 onResponseReceived(request, response, !name.empty());
427 onResponseParseError
));
436 const std::function<std::size_t()>& onServerSentEvent,
437 const std::function<
void()>& onOpen,
438 const std::function<
void()>& onError) {
442 newRequest->
url = url;
446 newRequest->
set("Connection", "keep-alive", true);
447 newRequest->
set("Accept", "text/event-stream", true);
448 newRequest->
set("Cache-Control", "no-cache", true);
452 [masterRequest =
this->masterRequest, onServerSentEvent, onOpen, onError](
const std::shared_ptr<
Request>& request,
453 const std::shared_ptr<
Response>& response) {
454 if (!masterRequest.expired()) {
467 [masterRequest =
this->masterRequest](
const std::shared_ptr<
Request>& request,
const std::string& status) {
468 if (!masterRequest.expired()) {
470 <<
" error in response: " << status;
482 const std::string& file,
483 const std::function<
void(
int)>& onStatus,
484 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::shared_ptr<
Response>&)>& onResponseReceived,
485 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::string&)>& onResponseParseError) {
525 const std::function<
void(
const std::shared_ptr<
Request>&,
const std::string&)>& onResponseParseError) {
556 delete requestCommand;
560 if (atomar && !error) {
570 std::string absolutFileName = file;
572 if (std::filesystem::exists(absolutFileName)) {
574 absolutFileName = std::filesystem::canonical(absolutFileName);
576 if (std::filesystem::is_regular_file(absolutFileName, ec) && !ec) {
587 set("Transfer-Encoding", "chunked");
589 set("Content-Length", std::to_string(std::filesystem::file_size(absolutFileName) +
contentLength)
);
614 set("Connection", "Upgrade", true);
615 set("Upgrade", protocols
, true);
620 if (socketContextUpgradeFactory !=
nullptr) {
621 LOG(DEBUG) << connectionName <<
" HTTP: "
622 <<
"SocketContextUpgradeFactory create success: " << socketContextUpgradeFactory
->name();
623 LOG(DEBUG) << connectionName <<
" HTTP: Initiating upgrade: " <<
method <<
" " << url
627 LOG(DEBUG) << connectionName <<
" HTTP: "
628 <<
"SocketContextUpgradeFactory create failed: " << protocols;
629 LOG(DEBUG) << connectionName <<
" HTTP: Not initiating upgrade " <<
method <<
" " << url
633 LOG(DEBUG) << connectionName <<
" HTTP: Upgrade request:\n"
641 std::vector<
char>()
);
643 onStatus(socketContextUpgradeFactory !=
nullptr);
645 if (socketContextUpgradeFactory !=
nullptr) {
665 const std::string httpVersion =
"HTTP/" + std::to_string(
httpMajor) +
"." + std::to_string(
httpMinor);
667 std::string queryString;
670 for (
auto& [key, value] :
queries) {
673 queryString.pop_back();
683 for (
const auto& [field, value] :
headers) {
687 for (
const auto& [name, value] :
cookies) {
730 for (
auto& [field, value] :
trailer) {
static FileReader * open(const std::string &path, const std::function< void(int)> &callback)
void sendToPeer(const std::string &data) const
const std::string & getConnectionName() const
SocketConnection * getSocketConnection() const
void switchSocketContext(SocketContext *newSocketContext)
void sendToPeer(const char *chunk, std::size_t chunkLen) const final
void shutdownWrite(bool forceClose=false)
bool streamToPeer(core::pipe::Source *source) const
static std::string contentType(const std::string &file)
core::socket::stream::SocketContext * create(core::socket::stream::SocketConnection *socketConnection) final
virtual std::string name()=0
bool send(const std::string &chunk, const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
bool executeUpgrade(const std::string &url, const std::string &protocols, const std::function< void(bool)> &onStatus)
bool send(const char *chunk, std::size_t chunkLen, const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
MasterRequest & sendHeader()
void onSourceData(const char *chunk, std::size_t chunkLen) override
void onSourceConnect(core::pipe::Source *source) override
bool requestEventSource(const std::string &url, const std::function< std::size_t()> &onServerSentEvent, const std::function< void()> &onOpen, const std::function< void()> &onError)
MasterRequest(MasterRequest &&) noexcept
std::function< void(const std::shared_ptr< Request > &, const std::string &message)> onResponseParseError
void requestPrepared(const std::shared_ptr< MasterRequest > &request)
void deliverResponseParseError(const std::shared_ptr< MasterRequest > &request, const std::string &message)
std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> onResponseReceived
void deliverResponse(const std::shared_ptr< MasterRequest > &request, const std::shared_ptr< Response > &response)
void onSourceError(int errnum) override
bool upgrade(const std::string &url, const std::string &protocols, const std::function< void(bool)> &onUpgradeInitiate, const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &, bool)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
bool executeSendFragment(const char *chunk, std::size_t chunkLen)
bool sendFile(const std::string &file, const std::function< void(int errnum)> &onStatus, const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
std::size_t contentLengthSent
std::list< RequestCommand * > requestCommands
void onSourceEof() override
~MasterRequest() override
MasterRequest(SocketContext *socketContext, const std::string &hostFieldValue)
MasterRequest & sendFragment(const std::string &data)
bool end(const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
MasterRequest & sendFragment(const char *chunk, std::size_t chunkLen)
bool executeSendFile(const std::string &file, const std::function< void(int)> &onStatus)
bool initiate(const std::shared_ptr< MasterRequest > &request)
virtual bool execute(const std::shared_ptr< MasterRequest > &request)=0
const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> onResponseReceived
const std::function< void(const std::shared_ptr< Request > &, const std::string &)> onResponseParseError
CiStringMap< std::string > headers
Request & set(const std::string &field, const std::string &value, bool overwrite=true)
TransferEncoding transferEncoding
void setMasterRequest(const std::shared_ptr< MasterRequest > &masterRequest)
const CiStringMap< std::string > & getCookies() const
Request(Request &&request) noexcept
ConnectionState connectionState
CiStringMap< std::string > cookies
Request & cookie(const std::string &name, const std::string &value)
CiStringMap< std::string > trailer
const CiStringMap< std::string > & getQueries() const
Request & query(const std::string &key, const std::string &value)
Request(SocketContext *socketContext, const std::string &host)
std::weak_ptr< MasterRequest > masterRequest
Request & setTrailer(const std::string &field, const std::string &value, bool overwrite=true)
Request & cookie(const std::map< std::string, std::string > &cookies)
SocketContext * getSocketContext() const
std::string header(const std::string &field) const
CiStringMap< std::string > queries
Request & set(const std::map< std::string, std::string > &headers, bool overwrite=true)
std::size_t contentLength
web::http::client::SocketContext * socketContext
std::string hostFieldValue
const CiStringMap< std::string > & getTrailer() const
Request & append(const std::string &field, const std::string &value)
Request & host(const std::string &hostFieldValue)
std::shared_ptr< MasterRequest > getMasterRequest() const
Request & type(const std::string &type)
const CiStringMap< std::string > & getHeaders() const
void upgrade(const std::shared_ptr< Response > &response, const std::function< void(const std::string &)> &status)
CiStringMap< std::string > headers
CiStringMap< CookieOptions > cookies
const std::string & get(const std::string &key, int i=0) const
SocketContextUpgradeFactory * select(Request &req, Response &res) override
static SocketContextUpgradeFactorySelector * instance()
SocketContextUpgradeFactory * select(const std::string &protocols, Request &req)
void checkRefCount() final
void setSseEventReceiver(const std::function< std::size_t()> &onServerSentEvent)
void requestPrepared(const std::shared_ptr< MasterRequest > &request)
void requestDelivered(bool success)
EndCommand(const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
SendFileCommand(const std::string &file, const std::function< void(int)> &onStatus, const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
SendFragmentCommand(const char *chunk, std::size_t chunkLen)
SseCommand(const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
UpgradeCommand(const std::string &url, const std::string &protocols, const std::function< void(bool)> &onUpgradeInitiate, const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
std::string to_http_date(struct tm *tm)
std::string toString(const std::string &version, const std::string &statusCode, const std::string &reason, const web::http::CiStringMap< std::string > &header, const web::http::CiStringMap< web::http::CookieOptions > &cookies, const std::vector< char > &body)
std::string url_encode(const std::string &text)
std::string file_mod_http_date(const std::string &filePath)
std::string toString(const std::string &method, const std::string &url, const std::string &version, const web::http::CiStringMap< std::string > &queries, const web::http::CiStringMap< std::string > &header, const web::http::CiStringMap< std::string > &trailer, const web::http::CiStringMap< std::string > &cookies, const std::vector< char > &body)
bool ciEquals(const std::string &str1, const std::string &str2)
bool ciContains(const std::string &str1, const std::string &str2)
#define to_hex_str(int_val)