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/stream/SocketConnection.h"
46#include "web/http/CookieOptions.h"
47
48#ifndef DOXYGEN_SHOULD_SKIP_THIS
49
50#include "log/Logger.h"
51
52#include <utility>
53
54#endif /* DOXYGEN_SHOULD_SKIP_THIS */
55
56// #define TRUE 1
57// #define FALSE 0
58
59namespace web::http::client {
60
61 SocketContext::SocketContext(core::socket::stream::SocketConnection* socketConnection,
62 const std::function<void(const std::shared_ptr<Request>&)>& onRequestBegin,
63 const std::function<void(const std::shared_ptr<Request>&)>& onRequestEnd,
64 bool pipelinedRequests)
65 : Super(socketConnection)
66 , onRequestBegin(onRequestBegin)
67 , onRequestEnd(onRequestEnd)
68 , pipelinedRequests(pipelinedRequests)
69 , masterRequest(std::make_shared<Request>(this, getSocketConnection()->getConfiguredServer()))
70 , parser(
71 this,
72 [this]() {
74 },
75 [this](web::http::client::Response&& response) {
76 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Response parsed: HTTP/" << response.httpMajor << "."
77 << response.httpMinor << " " << response.statusCode << " " << response.reason;
78
79 deliverResponse(std::move(response));
80 },
81 [this](int status, const std::string& reason) {
83 }) {
85 }
86
88 if (!deliveredRequests.empty()) {
89 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Responses missed";
90 for (const Request& request : deliveredRequests) {
91 LOG(DEBUG) << " " << request.method << " " << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
92 }
93 }
94
95 if (!pendingRequests.empty()) {
96 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Requests ignored";
97 for (const Request& request : pendingRequests) {
98 LOG(DEBUG) << " " << request.method << " " << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
99 }
100 }
101 }
102
103 void SocketContext::requestPrepared(Request&& request) {
104 const std::string requestLine = std::string(request.method)
105 .append(" ")
106 .append(request.url)
107 .append(" HTTP/")
108 .append(std::to_string(request.httpMajor))
109 .append(".")
110 .append(std::to_string(request.httpMinor));
111
114 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Request accepted: " << requestLine;
115
116 flags = (flags & ~Flags::HTTP11) | ((request.httpMajor == 1 && request.httpMinor == 1) ? Flags::HTTP11 : Flags::NONE);
117 flags = (flags & ~Flags::HTTP10) | ((request.httpMajor == 1 && request.httpMinor == 0) ? Flags::HTTP10 : Flags::NONE);
119 (web::http::ciContains(request.header("Connection"), "keep-alive") ? Flags::KEEPALIVE : Flags::NONE);
120 flags = (flags & ~Flags::CLOSE) | (web::http::ciContains(request.header("Connection"), "close") ? Flags::CLOSE : Flags::NONE);
121
122 if (!currentRequest) {
123 initiateRequest(request);
124 } else {
125 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request queued: " << requestLine;
126
127 pendingRequests.emplace_back(std::move(request));
128 }
129 } else {
130 LOG(WARNING) << getSocketConnection()->getConnectionName() << " HTTP: Request rejected: " << requestLine;
131 }
132 }
133
134 void SocketContext::initiateRequest(Request& request) {
135 currentRequest = std::make_shared<Request>(std::move(request));
136
137 const std::string requestLine = std::string(currentRequest->method)
138 .append(" ")
139 .append(currentRequest->url)
140 .append(" HTTP/")
141 .append(std::to_string(currentRequest->httpMajor))
142 .append(".")
143 .append(std::to_string(currentRequest->httpMinor));
144
145 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request delivering: " << requestLine;
146
148 LOG(WARNING) << getSocketConnection()->getConnectionName() << " HTTP: Request delivering failed: " << requestLine;
149
150 core::EventReceiver::atNextTick([this, masterRequest = static_cast<std::weak_ptr<Request>>(masterRequest)]() {
151 if (!masterRequest.expired()) {
152 if (!pendingRequests.empty()) {
153 Request& request = pendingRequests.front();
154
155 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request dequeued: " << request.method << " "
156 << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
157
158 initiateRequest(request);
159 pendingRequests.pop_front();
160 } else {
161 currentRequest = nullptr;
162 }
163 }
164 });
165 }
166 }
167
168 void SocketContext::requestDelivered(Request&& request, bool success) {
169 const std::string requestLine = std::string(request.method)
170 .append(" ")
171 .append(request.url)
172 .append(" HTTP/")
173 .append(std::to_string(request.httpMajor))
174 .append(".")
175 .append(std::to_string(request.httpMinor));
176
177 if (success) {
178 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request delivered: " << requestLine;
179
180 deliveredRequests.emplace_back(std::move(request));
181
182 if (pipelinedRequests) {
183 core::EventReceiver::atNextTick([this, masterRequest = static_cast<std::weak_ptr<Request>>(masterRequest)]() {
184 if (!masterRequest.expired()) {
185 if (!pendingRequests.empty()) {
186 Request& request = pendingRequests.front();
187
188 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request dequeued: " << request.method << " "
189 << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
190
191 initiateRequest(request);
192 pendingRequests.pop_front();
193 } else {
194 currentRequest = nullptr;
195 }
196 }
197 });
198 }
199 } else {
200 LOG(WARNING) << getSocketConnection()->getConnectionName() << " HTTP: Request delivering failed: " << requestLine;
201
203 }
204 }
205
207 if (!deliveredRequests.empty()) {
208 const std::string requestLine = std::string(deliveredRequests.front().method)
209 .append(" ")
210 .append(deliveredRequests.front().url)
211 .append(" HTTP/")
212 .append(std::to_string(deliveredRequests.front().httpMajor))
213 .append(".")
214 .append(std::to_string(deliveredRequests.front().httpMinor));
215
216 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Response started: " << requestLine;
217 } else {
218 LOG(ERROR) << getSocketConnection()->getConnectionName() << " HTTP: Response without delivered request";
219 shutdownWrite(true);
220 }
221 }
222
223 void SocketContext::deliverResponse(Response&& response) {
224 const std::shared_ptr<Request> request = std::make_shared<Request>(std::move(deliveredRequests.front()));
225 deliveredRequests.pop_front();
226
227 const std::string requestLine = std::string(request->method)
228 .append(" ")
229 .append(request->url)
230 .append(" HTTP/")
231 .append(std::to_string(request->httpMajor))
232 .append(".")
233 .append(std::to_string(request->httpMinor));
234
235 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Response ready: " << requestLine;
236
237 const bool httpClose =
240 ((response.httpMajor == 0 && response.httpMinor == 9) || (response.httpMajor == 1 && response.httpMinor == 0)));
241
242 request->deliverResponse(request, std::make_shared<Response>(std::move(response)));
243
244 responseDelivered(httpClose);
245
246 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: Response completed: " << requestLine;
247 }
248
249 void SocketContext::deliverResponseParseError(int status, const std::string& reason) {
250 const std::shared_ptr<Request> request = std::make_shared<Request>(std::move(deliveredRequests.front()));
251 deliveredRequests.pop_front();
252
253 LOG(WARNING) << getSocketConnection()->getConnectionName() << " HTTP: Response parse error: " << reason << " (" << status << ") "
254 << std::string(request->method)
255 .append(" ")
256 .append(request->url)
257 .append(" HTTP/")
258 .append(std::to_string(request->httpMajor))
259 .append(".")
260 .append(std::to_string(request->httpMinor));
261
262 request->deliverResponseParseError(request, reason);
263
264 shutdownWrite(true);
265 }
266
267 void SocketContext::responseDelivered(bool httpClose) {
268 if (httpClose) {
269 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Connection = Close";
270 } else {
271 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Connection = Keep-Alive";
272
273 if (!pipelinedRequests) {
274 core::EventReceiver::atNextTick([this, masterRequest = static_cast<std::weak_ptr<Request>>(masterRequest)]() {
275 if (!masterRequest.expired()) {
276 if (!pendingRequests.empty()) {
277 Request& request = pendingRequests.front();
278
279 LOG(DEBUG) << getSocketConnection()->getConnectionName() << " HTTP: Request dequeued: " << request.method << " "
280 << request.url << " HTTP/" << request.httpMajor << "." << request.httpMinor;
281
282 initiateRequest(request);
283 pendingRequests.pop_front();
284 } else {
285 currentRequest = nullptr;
286 }
287 }
288 });
289 }
290 }
291 }
292
294 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: connected";
295
297 }
298
300 std::size_t consumed = 0;
301
302 if (!deliveredRequests.empty()) {
303 consumed = parser.parse();
304 }
305
306 return consumed;
307 }
308
310 if (!deliveredRequests.empty()) {
311 const Request& request = deliveredRequests.front();
312 if (request.httpMajor == 1 && request.httpMinor == 0) {
314 }
315 }
316
318
319 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: disconnected";
320 }
321
322 bool SocketContext::onSignal([[maybe_unused]] int signum) {
323 LOG(INFO) << getSocketConnection()->getConnectionName() << " HTTP: received signal " << signum;
324
325 return true;
326 }
327
329 // Do nothing in case of an write error
330 }
331
332} // namespace web::http::client
static void atNextTick(const std::function< void(void)> &callBack)
const std::string & getConfiguredServer() const
const std::string & getConnectionName() const
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:638
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:580
void deliverResponseParseError(const std::shared_ptr< Request > &request, const std::string &message)
Definition Request.cpp:584
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)