2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
20#include "web/http/client/Request.h"
22#include "core/file/FileReader.h"
23#include "core/socket/stream/SocketConnection.h"
24#include "web/http/MimeTypes.h"
25#include "web/http/client/SocketContext.h"
26#include "web/http/client/SocketContextUpgradeFactorySelector.h"
30#include "commands/EndCommand.h"
31#include "commands/SendFileCommand.h"
32#include "commands/SendFragmentCommand.h"
33#include "commands/SendHeaderCommand.h"
34#include "commands/UpgradeCommand.h"
36#ifndef DOXYGEN_SHOULD_SKIP_THIS
38#include "log/Logger.h"
39#include "web/http/http_utils.h"
44#include <system_error>
49#define to_hex_str(int_val) (static_cast<std::ostringstream const&>(std::ostringstream() << std::uppercase << std::hex << int_val)).str()
51namespace web::http::
client {
54 : hostFieldValue(host)
55 , socketContext(socketContext) {
56 this->host(hostFieldValue);
82 for (
const RequestCommand* requestCommand : requestCommands) {
83 delete requestCommand;
86 if (!masterRequest.expired() && Sink::isStreaming()) {
87 socketContext->streamEof();
92 this->masterRequest = masterRequest;
104 for (
const RequestCommand* requestCommand : requestCommands) {
105 delete requestCommand;
107 requestCommands.clear();
108 transferEncoding = TransferEncoding::HTTP10;
110 contentLengthSent = 0;
111 connectionState = ConnectionState::Default;
112 onResponseReceived =
nullptr;
113 onResponseParseError =
nullptr;
115 this->host(hostFieldValue);
116 set(
"X-Powered-By",
"snode.c");
120 set(
"Host", hostFieldValue);
126 const std::map<std::string, std::string>::iterator it = headers.find(field);
128 if (it != headers.end()) {
129 set(field, it->second.append(
", ").append(value));
138 if (!value.empty()) {
140 headers.insert_or_assign(field, value);
142 headers.insert({field, value});
145 if (web::http::ciEquals(field,
"Connection")) {
146 if (web::http::ciContains(headers[field],
"close")) {
147 connectionState = ConnectionState::Close;
148 }
else if (web::http::ciContains(headers[field],
"keep-alive")) {
149 connectionState = ConnectionState::Keep;
151 }
else if (web::http::ciEquals(field,
"Content-Length")) {
152 contentLength = std::stoul(value);
153 transferEncoding = TransferEncoding::Identity;
154 headers.erase(
"Transfer-Encoding");
155 }
else if (web::http::ciEquals(field,
"Transfer-Encoding")) {
156 if (web::http::ciContains(headers[field],
"chunked")) {
157 transferEncoding = TransferEncoding::Chunked;
158 headers.erase(
"Content-Length");
160 if (web::http::ciContains(headers[field],
"compressed")) {
162 if (web::http::ciContains(headers[field],
"deflate")) {
164 if (web::http::ciContains(headers[field],
"gzip")) {
166 }
else if (web::http::ciEquals(field,
"Content-Encoding")) {
167 if (web::http::ciContains(headers[field],
"compressed")) {
169 if (web::http::ciContains(headers[field],
"deflate")) {
171 if (web::http::ciContains(headers[field],
"gzip")) {
173 if (web::http::ciContains(headers[field],
"br")) {
177 headers.erase(field);
184 for (
const auto& [field, value] : headers) {
185 set(field, value, overwrite);
192 headers.insert({
"Content-Type", type});
198 cookies.insert({name, value});
204 for (
const auto& [name, value] : cookies) {
212 queries.insert({key, value});
218 if (!value.empty()) {
220 trailer.insert_or_assign(field, value);
222 trailer.insert({field, value});
224 if (!headers.contains(
"Trailer")) {
225 set(
"Trailer", field);
227 headers[
"Trailer"].append(
"," + field);
230 trailer.erase(field);
236 void Request::responseParseError(
const std::shared_ptr<
Request>& request,
const std::string& message) {
237 LOG(WARNING) << request->getSocketContext()->getSocketConnection()->getConnectionName()
238 <<
" HTTP: Response parse error: " << request->method <<
" " << request->url <<
" "
239 <<
"HTTP/" << request->httpMajor <<
"." << request->httpMinor <<
": " << message;
243 std::size_t chunkLen,
244 const std::function<
void(
const std::shared_ptr<Request>&,
const std::shared_ptr<Response>&)>& onResponseReceived,
245 const std::function<
void(
const std::shared_ptr<Request>&,
const std::string&)>& onResponseParseError) {
248 if (!masterRequest.expired()) {
249 this->onResponseReceived = onResponseReceived;
250 this->onResponseParseError = onResponseParseError;
253 set(
"Content-Type",
"application/octet-stream",
false);
257 sendFragment(chunk, chunkLen);
259 requestCommands.push_back(
new commands::EndCommand());
270 const std::function<
void(
const std::shared_ptr<Request>&,
const std::shared_ptr<Response>&)>& onResponseReceived,
271 const std::function<
void(
const std::shared_ptr<Request>&,
const std::string&)>& onResponseParseError) {
272 if (!chunk.empty()) {
273 set(
"Content-Type",
"text/html; charset=utf-8",
false);
276 return send(chunk.data(), chunk.size(), onResponseReceived, onResponseParseError);
280 const std::string& protocols,
281 const std::function<
void(
const std::shared_ptr<Request>&,
const std::shared_ptr<Response>&)>& onResponseReceived,
282 const std::function<
void(
const std::shared_ptr<Request>&,
const std::string&)>& onResponseParseError) {
285 if (!masterRequest.expired()) {
286 this->onResponseReceived = onResponseReceived;
287 this->onResponseParseError = onResponseParseError;
289 requestCommands.push_back(
new commands::UpgradeCommand(url, protocols));
299 void Request::upgrade(
const std::shared_ptr<Response>& response,
const std::function<
void(
const std::string&)>& status) {
302 if (!masterRequest.expired()) {
303 if (response !=
nullptr) {
304 if (web::http::ciContains(response->get(
"connection"),
"Upgrade")) {
305 web::http::
client::SocketContextUpgradeFactory* socketContextUpgradeFactory =
306 web::http::
client::SocketContextUpgradeFactorySelector::instance()->select(*
this, *response);
308 if (socketContextUpgradeFactory !=
nullptr) {
309 name = socketContextUpgradeFactory->name();
311 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
312 <<
" HTTP upgrade: SocketContextUpgradeFactory created successful: " << name;
314 core::socket::
stream::SocketContext* socketContextUpgrade =
315 socketContextUpgradeFactory->create(socketContext->getSocketConnection());
317 if (socketContextUpgrade !=
nullptr) {
318 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
319 <<
" HTTP upgrade: SocketContextUpgrade created successful: " << name;
321 socketContext->switchSocketContext(socketContextUpgrade);
323 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
324 <<
" HTTP upgrade: Create SocketContextUpgrade failed: " << name;
326 socketContext->close();
329 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
330 <<
" HTTP upgrade: SocketContextUpgradeFactory not supported by server: " << header(
"upgrade");
332 socketContext->close();
335 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
336 <<
" HTTP upgrade: Not any protocol supported by server: " << header(
"upgrade");
338 socketContext->close();
341 LOG(ERROR) << socketContext->getSocketConnection()->getConnectionName() <<
" HTTP upgrade: Response has gone away";
343 socketContext->close();
346 LOG(ERROR) << socketContext->getSocketConnection()->getConnectionName() <<
" HTTP upgrade: SocketContext has gone away";
353 const std::function<
void(
int)>& onStatus,
354 const std::function<
void(
const std::shared_ptr<Request>&,
const std::shared_ptr<Response>&)>& onResponseReceived,
355 const std::function<
void(
const std::shared_ptr<Request>&,
const std::string&)>& onResponseParseError) {
358 if (!masterRequest.expired()) {
359 this->onResponseReceived = onResponseReceived;
360 this->onResponseParseError = onResponseParseError;
362 requestCommands.push_back(
new commands::SendFileCommand(file, onStatus));
373 if (!masterRequest.expired()) {
374 requestCommands.push_back(
new commands::SendHeaderCommand());
381 if (!masterRequest.expired()) {
382 contentLength += chunkLen;
384 requestCommands.push_back(
new commands::SendFragmentCommand(chunk, chunkLen));
391 return sendFragment(data.data(), data.size());
394 bool Request::
end(
const std::function<
void(
const std::shared_ptr<Request>&,
const std::shared_ptr<Response>&)>& onResponseReceived,
395 const std::function<
void(
const std::shared_ptr<Request>&,
const std::string&)>& onResponseParseError) {
398 if (!masterRequest.expired()) {
399 this->onResponseReceived = onResponseReceived;
400 this->onResponseParseError = onResponseParseError;
404 requestCommands.push_back(
new commands::EndCommand());
418 for (RequestCommand* requestCommand : requestCommands) {
420 const bool atomarCommand = requestCommand->execute(
this);
422 atomar = atomarCommand;
425 error = requestCommand->getError();
428 delete requestCommand;
430 requestCommands.clear();
432 if (atomar && (!error || contentLengthSent != 0)) {
436 return !error || contentLengthSent != 0;
442 std::string absolutFileName = file;
444 if (std::filesystem::exists(absolutFileName)) {
446 absolutFileName = std::filesystem::canonical(absolutFileName);
448 if (std::filesystem::is_regular_file(absolutFileName, ec) && !ec) {
449 core::file::FileReader::open(absolutFileName)->pipe(
this, [
this, &atomar, &absolutFileName, &onStatus](
int errnum) {
454 if (httpMajor == 1) {
457 set(
"Content-Type", web::http::MimeTypes::contentType(absolutFileName),
false);
458 set(
"Last-Modified", httputils::file_mod_http_date(absolutFileName),
false);
459 if (httpMinor == 1 && contentLength == 0) {
460 set(
"Transfer-Encoding",
"chunked");
462 set(
"Content-Length", std::to_string(std::filesystem::file_size(absolutFileName) + contentLength));
484 set(
"Connection",
"Upgrade",
true);
485 set(
"Upgrade", protocols,
true);
487 web::http::
client::SocketContextUpgradeFactory* socketContextUpgradeFactory =
488 web::http::client::SocketContextUpgradeFactorySelector::instance()->select(protocols, *
this);
490 if (socketContextUpgradeFactory !=
nullptr) {
491 socketContextUpgradeFactory->checkRefCount();
495 socketContext->close();
506 const std::string httpVersion =
"HTTP/" + std::to_string(httpMajor) +
"." + std::to_string(httpMinor);
508 std::string queryString;
509 if (!queries.empty()) {
511 for (
auto& [key, value] : queries) {
512 queryString += httputils::url_encode(key) +
"=" + httputils::url_encode(value) +
"&";
514 queryString.pop_back();
517 socketContext->sendToPeer(method +
" " + url + queryString +
" " + httpVersion +
"\r\n");
518 socketContext->sendToPeer(
"Date: " + httputils::to_http_date() +
"\r\n");
520 if (!headers.contains(
"Transfer-Encoding") && contentLength > 0) {
521 set(
"Content-Length", std::to_string(contentLength));
524 for (
const auto& [field, value] : headers) {
525 socketContext->sendToPeer(std::string(field).append(
":").append(value).append(
"\r\n"));
528 for (
const auto& [name, value] : cookies) {
529 socketContext->sendToPeer(std::string(
"Cookie:").append(name).append(
"=").append(value).append(
"\r\n"));
532 socketContext->sendToPeer(
"\r\n");
538 if (transferEncoding == TransferEncoding::Chunked) {
539 socketContext->sendToPeer(
to_hex_str(chunkLen).append(
"\r\n"));
542 socketContext->sendToPeer(chunk, chunkLen);
543 contentLengthSent += chunkLen;
545 if (transferEncoding == TransferEncoding::Chunked) {
546 socketContext->sendToPeer(
"\r\n");
547 contentLength += chunkLen;
554 socketContext->requestPrepared(std::move(*
this));
558 void Request::deliverResponse(
const std::shared_ptr<
Request>& request,
const std::shared_ptr<Response>& response) {
559 onResponseReceived(request, response);
563 onResponseParseError(request, message);
567 if (!masterRequest.expired()) {
568 if (transferEncoding == TransferEncoding::Chunked) {
569 executeSendFragment(
"", 0);
571 if (!trailer.empty()) {
572 for (
auto& [field, value] : trailer) {
573 socketContext->sendToPeer(std::string(field).append(
":").append(value).append(
"\r\n"));
575 socketContext->sendToPeer(
"\r\n");
579 socketContext->requestDelivered(std::move(*
this), contentLengthSent == contentLength);
584 if (!masterRequest.expired()) {
585 if (socketContext->streamToPeer(source)) {
594 executeSendFragment(chunk, chunkLen);
598 if (!masterRequest.expired()) {
599 socketContext->streamEof();
608 if (!masterRequest.expired()) {
609 socketContext->streamEof();
610 socketContext->close();
617 return headers.contains(field) ? headers[field] :
"";
633 return socketContext;
Request & set(const std::string &field, const std::string &value, bool overwrite=true)
void onSourceEof() override
Request & cookie(const std::string &name, const std::string &value)
Request(Request &&) noexcept
void onSourceError(int errnum) override
std::string header(const std::string &field)
Request & sendFragment(const std::string &data)
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=responseParseError)
Request & query(const std::string &key, const std::string &value)
bool upgrade(const std::string &url, const std::string &protocols, 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=responseParseError)
void onSourceData(const char *chunk, std::size_t chunkLen) override
bool executeSendFragment(const char *chunk, std::size_t chunkLen)
const CiStringMap< std::string > & getQueries() const
const CiStringMap< std::string > & getCookies() const
Request & setTrailer(const std::string &field, const std::string &value, bool overwrite=true)
Request & cookie(const std::map< std::string, std::string > &cookies)
web::http::client::SocketContext * getSocketContext() const
void setMasterRequest(const std::shared_ptr< Request > &masterRequest)
Request & set(const std::map< std::string, std::string > &headers, bool overwrite=true)
Request & append(const std::string &field, const std::string &value)
Request & sendFragment(const char *chunk, std::size_t chunkLen)
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=responseParseError)
const CiStringMap< std::string > & getHeaders() const
Request & host(const std::string &hostFieldValue)
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=responseParseError)
Request & type(const std::string &type)
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=responseParseError)
void deliverResponseParseError(const std::shared_ptr< Request > &request, const std::string &message)
bool executeSendFile(const std::string &file, const std::function< void(int)> &onStatus)
bool executeUpgrade(const std::string &url, const std::string &protocols)
#define to_hex_str(int_val)