SNode.C
Loading...
Searching...
No Matches
SocketContext.cpp
Go to the documentation of this file.
1/*
2 * SNode.C - A Slim Toolkit for Network Communication
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2020, 2021, 2022, 2023, 2024, 2025
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#include "web/http/client/SocketContext.h" // IWYU pragma: export
43
44#include "core/EventReceiver.h"
45#include "core/socket/SocketAddress.h"
46#include "core/socket/stream/SocketConnection.h"
47#include "web/http/CookieOptions.h"
48
49#ifndef DOXYGEN_SHOULD_SKIP_THIS
50
51#include "log/Logger.h"
52
53#include <utility>
54
55#endif /* DOXYGEN_SHOULD_SKIP_THIS */
56
57// #define TRUE 1
58// #define FALSE 0
59
60namespace web::http::client {
61
62 SocketContext::SocketContext(core::socket::stream::SocketConnection* socketConnection,
63 const std::function<void(const std::shared_ptr<Request>&)>& onRequestBegin,
64 const std::function<void(const std::shared_ptr<Request>&)>& onRequestEnd,
65 bool pipelinedRequests)
66 : Super(socketConnection)
67 , onRequestBegin(onRequestBegin)
68 , onRequestEnd(onRequestEnd)
69 , pipelinedRequests(pipelinedRequests)
70 , masterRequest(std::make_shared<Request>(this,
73 , parser(
74 this,
75 [this]() {
77 },
78 [this](web::http::client::Response&& response) {
79 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Response parsed: HTTP/" << response.httpMajor << "."
80 << response.httpMinor << " " << response.statusCode << " " << response.reason;
81
82 deliverResponse(std::move(response));
83 },
84 [this](int status, const std::string& reason) {
86 }) {
88 }
89
91 if (!deliveredRequests.empty()) {
92 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Responses missed";
93 for (const Request& request : deliveredRequests) {
94 LOG(DEBUG) << " " << request.method << " " << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
95 }
96 }
97
98 if (!pendingRequests.empty()) {
99 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Requests ignored";
100 for (const Request& request : pendingRequests) {
101 LOG(DEBUG) << " " << request.method << " " << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
102 }
103 }
104 }
105
106 void SocketContext::requestPrepared(Request&& request) {
107 const std::string requestLine = std::string(request.method)
108 .append(" ")
109 .append(request.url)
110 .append(" HTTP/")
111 .append(std::to_string(request.httpMajor))
112 .append(".")
113 .append(std::to_string(request.httpMinor));
114
117 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Request accepted: " << requestLine;
118
119 flags = (flags & ~Flags::HTTP11) | ((request.httpMajor == 1 && request.httpMinor == 1) ? Flags::HTTP11 : Flags::NONE);
120 flags = (flags & ~Flags::HTTP10) | ((request.httpMajor == 1 && request.httpMinor == 0) ? Flags::HTTP10 : Flags::NONE);
122 (web::http::ciContains(request.header("Connection"), "keep-alive") ? Flags::KEEPALIVE : Flags::NONE);
123 flags = (flags & ~Flags::CLOSE) | (web::http::ciContains(request.header("Connection"), "close") ? Flags::CLOSE : Flags::NONE);
124
125 if (!currentRequest) {
126 initiateRequest(request);
127 } else {
128 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request queued: " << requestLine;
129
130 pendingRequests.emplace_back(std::move(request));
131 }
132 } else {
133 LOG(WARNING) << getSocketConnection()->getConnectionName() << " HTTP: Request rejected: " << requestLine;
134 }
135 }
136
137 void SocketContext::initiateRequest(Request& request) {
138 currentRequest = std::make_shared<Request>(std::move(request));
139
140 const std::string requestLine = std::string(currentRequest->method)
141 .append(" ")
142 .append(currentRequest->url)
143 .append(" HTTP/")
144 .append(std::to_string(currentRequest->httpMajor))
145 .append(".")
146 .append(std::to_string(currentRequest->httpMinor));
147
148 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request start: " << requestLine;
149
151 LOG(WARNING) << getSocketConnection()->getConnectionName() << " HTTP: Request delivering failed: " << requestLine;
152
153 core::EventReceiver::atNextTick([this, masterRequest = static_cast<std::weak_ptr<Request>>(masterRequest)]() {
154 if (!masterRequest.expired()) {
155 if (!pendingRequests.empty()) {
156 Request& request = pendingRequests.front();
157
158 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request dequeued: " << request.method << " "
159 << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
160
161 initiateRequest(request);
162 pendingRequests.pop_front();
163 } else {
164 currentRequest = nullptr;
165 }
166 }
167 });
168 }
169 }
170
171 void SocketContext::requestDelivered(Request&& request, bool success) {
172 const std::string requestLine = std::string(request.method)
173 .append(" ")
174 .append(request.url)
175 .append(" HTTP/")
176 .append(std::to_string(request.httpMajor))
177 .append(".")
178 .append(std::to_string(request.httpMinor));
179
180 if (success) {
181 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request delivered: " << requestLine;
182
183 deliveredRequests.emplace_back(std::move(request));
184
185 if (pipelinedRequests) {
186 core::EventReceiver::atNextTick([this, masterRequest = static_cast<std::weak_ptr<Request>>(masterRequest)]() {
187 if (!masterRequest.expired()) {
188 if (!pendingRequests.empty()) {
189 Request& request = pendingRequests.front();
190
191 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request dequeued: " << request.method << " "
192 << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
193
194 initiateRequest(request);
195 pendingRequests.pop_front();
196 } else {
197 currentRequest = nullptr;
198 }
199 }
200 });
201 }
202 } else {
203 LOG(WARNING) << getSocketConnection()->getConnectionName() << " HTTP: Request deliver failed: " << requestLine;
204
206 }
207 }
208
210 if (!deliveredRequests.empty()) {
211 const std::string requestLine = std::string(deliveredRequests.front().method)
212 .append(" ")
213 .append(deliveredRequests.front().url)
214 .append(" HTTP/")
215 .append(std::to_string(deliveredRequests.front().httpMajor))
216 .append(".")
217 .append(std::to_string(deliveredRequests.front().httpMinor));
218
219 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Response start: " << requestLine;
220 } else {
221 LOG(ERROR) << getSocketConnection()->getConnectionName() << " HTTP: Response without delivered request";
222
223 shutdownWrite(true);
224 }
225 }
226
227 void SocketContext::deliverResponse(Response&& response) {
228 const std::shared_ptr<Request> request = std::make_shared<Request>(std::move(deliveredRequests.front()));
229 deliveredRequests.pop_front();
230
231 const std::string requestLine = std::string(request->method)
232 .append(" ")
233 .append(request->url)
234 .append(" HTTP/")
235 .append(std::to_string(request->httpMajor))
236 .append(".")
237 .append(std::to_string(request->httpMinor));
238
239 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Response received: " << requestLine;
240
241 const bool httpClose =
244 ((response.httpMajor == 0 && response.httpMinor == 9) || (response.httpMajor == 1 && response.httpMinor == 0)));
245
246 request->deliverResponse(request, std::make_shared<Response>(std::move(response)));
247
248 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Response completed: " << requestLine;
249
250 responseDelivered(httpClose);
251 }
252
253 void SocketContext::deliverResponseParseError(int status, const std::string& reason) {
254 const std::shared_ptr<Request> request = std::make_shared<Request>(std::move(deliveredRequests.front()));
255 deliveredRequests.pop_front();
256
257 LOG(WARNING) << getSocketConnection()->getConnectionName() << " HTTP: Response parse error: " << reason << " (" << status << ") "
258 << std::string(request->method)
259 .append(" ")
260 .append(request->url)
261 .append(" HTTP/")
262 .append(std::to_string(request->httpMajor))
263 .append(".")
264 .append(std::to_string(request->httpMinor));
265
266 request->deliverResponseParseError(request, reason);
267
268 shutdownWrite(true);
269 }
270
271 void SocketContext::responseDelivered(bool httpClose) {
272 if (httpClose) {
273 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Connection = Close";
274 } else {
275 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Connection = Keep-Alive";
276
277 if (!pipelinedRequests) {
278 core::EventReceiver::atNextTick([this, masterRequest = static_cast<std::weak_ptr<Request>>(masterRequest)]() {
279 if (!masterRequest.expired()) {
280 if (!pendingRequests.empty()) {
281 Request& request = pendingRequests.front();
282
283 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Initiating request: " << request.method
284 << " " << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
285
286 initiateRequest(request);
287
288 pendingRequests.pop_front();
289 } else {
290 currentRequest = nullptr;
291 }
292 }
293 });
294 }
295 }
296 }
297
299 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Connected";
300
302 }
303
305 std::size_t consumed = 0;
306
307 if (!deliveredRequests.empty()) {
308 consumed = parser.parse();
309 }
310
311 return consumed;
312 }
313
315 if (!deliveredRequests.empty()) {
316 const Request& request = deliveredRequests.front();
317 if (request.httpMajor == 1 && request.httpMinor == 0) {
319 }
320 }
321
323
324 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Disconnected";
325 }
326
327 bool SocketContext::onSignal([[maybe_unused]] int signum) {
328 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Received signal " << signum;
329
330 return true;
331 }
332
334 // Do nothing in case of an write error
335 }
336
337} // namespace web::http::client
static void atNextTick(const std::function< void(void)> &callBack)
virtual std::string toString(bool expanded=true) const =0
virtual std::string getEndpoint(const std::string_view &format={}) const
const std::string & getConnectionName() const
virtual const core::socket::SocketAddress & getRemoteAddress() const =0
SocketConnection * getSocketConnection() const
SocketContext(core::socket::stream::SocketConnection *socketConnection)
void shutdownWrite(bool forceClose=false)
std::size_t parse()
Definition Parser.cpp:92
std::string header(const std::string &field)
Definition Request.cpp:678
bool initiate(const std::shared_ptr< Request > &request)
Definition Request.cpp:451
void setMasterRequest(const std::shared_ptr< Request > &masterRequest)
Definition Request.cpp:113
void deliverResponse(const std::shared_ptr< Request > &request, const std::shared_ptr< Response > &response)
Definition Request.cpp:620
void deliverResponseParseError(const std::shared_ptr< Request > &request, const std::string &message)
Definition Request.cpp:624
ResponseParser(core::socket::stream::SocketContext *socketContext, const std::function< void()> &onResponseStart, const std::function< void(Response &&)> &onResponseParsed, const std::function< void(int, const std::string &)> &onResponseParseError)
ConnectionState connectionState
Definition Response.h:74
SocketContext(core::socket::stream::SocketConnection *socketConnection, const std::function< void(const std::shared_ptr< Request > &)> &onRequestBegin, const std::function< void(const std::shared_ptr< Request > &)> &onRequestEnd, bool pipelinedRequests)
void requestPrepared(Request &&request)
std::list< Request > deliveredRequests
void requestDelivered(Request &&request, bool success)
void deliverResponseParseError(int status, const std::string &reason)
std::shared_ptr< Request > currentRequest
std::size_t onReceivedFromPeer() override
std::function< void(const std::shared_ptr< Request > &)> onRequestBegin
void deliverResponse(Response &&response)
void onWriteError(int errnum) override
bool onSignal(int signum) override
void initiateRequest(Request &request)
std::function< void(const std::shared_ptr< Request > &)> onRequestEnd
void responseDelivered(bool httpClose)
std::list< Request > pendingRequests
std::shared_ptr< Request > masterRequest
bool ciContains(const std::string &str1, const std::string &str2)