A Discrete-Event Network Simulator
API
http-request.h
Go to the documentation of this file.
1 //
2 // HTTPRequest library from https://github.com/elnormous/HTTPRequest
3 // Adopted for NS-3 by miralem.mehic@ieee.org
4 //
5 
6 #ifndef HTTPREQUEST_HPP
7 #define HTTPREQUEST_HPP
8 
9 #include <cctype>
10 #include <cstddef>
11 #include <cstdint>
12 #include <algorithm>
13 #include <functional>
14 #include <map>
15 #include <memory>
16 #include <stdexcept>
17 #include <string>
18 #include <system_error>
19 #include <type_traits>
20 #include <vector>
21 
22 #include <sys/socket.h>
23 #include <netinet/in.h>
24 #include <netdb.h>
25 #include <unistd.h>
26 #include <errno.h>
27 
28 namespace http
29 {
30  class RequestError final: public std::logic_error
31  {
32  public:
33  explicit RequestError(const char* str): std::logic_error(str) {}
34  explicit RequestError(const std::string& str): std::logic_error(str) {}
35  };
36 
37  class ResponseError final: public std::runtime_error
38  {
39  public:
40  explicit ResponseError(const char* str): std::runtime_error(str) {}
41  explicit ResponseError(const std::string& str): std::runtime_error(str) {}
42  };
43 
44  enum class InternetProtocol: std::uint8_t
45  {
46  V4,
47  V6
48  };
49 
50  inline std::string urlEncode(const std::string& str)
51  {
52  constexpr char hexChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
53 
54  std::string result;
55 
56  for (auto i = str.begin(); i != str.end(); ++i)
57  {
58  const std::uint8_t cp = *i & 0xFF;
59 
60  if ((cp >= 0x30 && cp <= 0x39) || // 0-9
61  (cp >= 0x41 && cp <= 0x5A) || // A-Z
62  (cp >= 0x61 && cp <= 0x7A) || // a-z
63  cp == 0x2D || cp == 0x2E || cp == 0x5F) // - . _
64  result += static_cast<char>(cp);
65  else if (cp <= 0x7F) // length = 1
66  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
67  else if ((cp >> 5) == 0x06) // length = 2
68  {
69  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
70  if (++i == str.end()) break;
71  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
72  }
73  else if ((cp >> 4) == 0x0E) // length = 3
74  {
75  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
76  if (++i == str.end()) break;
77  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
78  if (++i == str.end()) break;
79  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
80  }
81  else if ((cp >> 3) == 0x1E) // length = 4
82  {
83  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
84  if (++i == str.end()) break;
85  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
86  if (++i == str.end()) break;
87  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
88  if (++i == str.end()) break;
89  result += std::string("%") + hexChars[(*i & 0xF0) >> 4] + hexChars[*i & 0x0F];
90  }
91  }
92 
93  return result;
94  }
95 
96  struct Response final
97  {
98  enum Status
99  {
100  Continue = 100,
102  Processing = 102,
103  EarlyHints = 103,
104 
105  Ok = 200,
106  Created = 201,
107  Accepted = 202,
109  NoContent = 204,
112  MultiStatus = 207,
114  ImUsed = 226,
115 
118  Found = 302,
119  SeeOther = 303,
120  NotModified = 304,
121  UseProxy = 305,
124 
125  BadRequest = 400,
128  Forbidden = 403,
129  NotFound = 404,
134  Conflict = 409,
135  Gone = 410,
139  UriTooLong = 414,
143  ImaTeapot = 418,
146  Locked = 423,
148  TooEarly = 425,
154 
157  BadGateway = 502,
164  NotExtended = 510,
166  };
167 
168  int status = 0;
169  std::vector<std::string> headers;
170  std::vector<std::uint8_t> body;
171  };
172 
173  class Request final
174  {
175  public:
176  explicit Request(const std::string& url,
178  internetProtocol(protocol)
179  {
180  const auto schemeEndPosition = url.find("://");
181 
182  if (schemeEndPosition != std::string::npos)
183  {
184  scheme = url.substr(0, schemeEndPosition);
185  path = url.substr(schemeEndPosition + 3);
186  }
187  else
188  {
189  scheme = "http";
190  path = url;
191  }
192 
193  const auto fragmentPosition = path.find('#');
194 
195  // remove the fragment part
196  if (fragmentPosition != std::string::npos)
197  path.resize(fragmentPosition);
198 
199  const auto pathPosition = path.find('/');
200 
201  if (pathPosition == std::string::npos)
202  {
203  domain = path;
204  path = "/";
205  }
206  else
207  {
208  domain = path.substr(0, pathPosition);
209  path = path.substr(pathPosition);
210  }
211 
212  const auto portPosition = domain.find(':');
213 
214  if (portPosition != std::string::npos)
215  {
216  port = domain.substr(portPosition + 1);
217  domain.resize(portPosition);
218  }
219  else
220  port = "80";
221  }
222 
223  Response send(const std::string& method,
224  const std::map<std::string, std::string>& parameters,
225  const std::vector<std::string>& headers = {})
226  {
227  std::string body;
228  bool first = true;
229 
230  for (const auto& parameter : parameters)
231  {
232  if (!first) body += "&";
233  first = false;
234 
235  body += urlEncode(parameter.first) + "=" + urlEncode(parameter.second);
236  }
237 
238  return send(method, body, headers);
239  }
240 
241  Response send(const std::string& method = "GET",
242  const std::string& body = "",
243  const std::vector<std::string>& headers = {})
244  {
245  return send(method,
246  std::vector<uint8_t>(body.begin(), body.end()),
247  headers);
248  }
249 
250  Response send(const std::string& method,
251  const std::vector<uint8_t>& body,
252  const std::vector<std::string>& headers)
253  {
254  if (scheme != "http")
255  throw RequestError("Only HTTP scheme is supported");
256 
257  addrinfo hints = {};
258  hints.ai_family = getAddressFamily(internetProtocol);
259  hints.ai_socktype = SOCK_STREAM;
260 
261  addrinfo* info;
262  if (getaddrinfo(domain.c_str(), port.c_str(), &hints, &info) != 0)
263  throw std::system_error(getLastError(), std::system_category(), "Failed to get address info of " + domain);
264 
265  std::unique_ptr<addrinfo, decltype(&freeaddrinfo)> addressInfo(info, freeaddrinfo);
266 
267  std::string headerData = method + " " + path + " HTTP/1.1\r\n";
268 
269  for (const std::string& header : headers)
270  headerData += header + "\r\n";
271 
272  headerData += "Host: " + domain + "\r\n"
273  "Content-Length: " + std::to_string(body.size()) + "\r\n"
274  "\r\n";
275 
276  std::vector<uint8_t> requestData(headerData.begin(), headerData.end());
277  requestData.insert(requestData.end(), body.begin(), body.end());
278 
279  Socket socket(internetProtocol);
280 
281  // take the first address from the list
282  socket.connect(addressInfo->ai_addr, static_cast<socklen_t>(addressInfo->ai_addrlen));
283 
284  auto remaining = requestData.size();
285  auto sendData = requestData.data();
286 
287  // send the request
288  while (remaining > 0)
289  {
290  const auto size = socket.send(sendData, remaining, noSignal);
291  remaining -= size;
292  sendData += size;
293  }
294 
295  std::uint8_t tempBuffer[4096];
296  constexpr std::uint8_t crlf[] = {'\r', '\n'};
297  Response response;
298  std::vector<std::uint8_t> responseData;
299  bool firstLine = true;
300  bool parsedHeaders = false;
301  bool contentLengthReceived = false;
302  unsigned long contentLength = 0;
303  bool chunkedResponse = false;
304  std::size_t expectedChunkSize = 0;
305  bool removeCrlfAfterChunk = false;
306 
307  // read the response
308  for (;;)
309  {
310  const auto size = socket.recv(tempBuffer, sizeof(tempBuffer), noSignal);
311 
312  if (size == 0)
313  break; // disconnected
314 
315  responseData.insert(responseData.end(), tempBuffer, tempBuffer + size);
316 
317  if (!parsedHeaders)
318  for (;;)
319  {
320  const auto i = std::search(responseData.begin(), responseData.end(), std::begin(crlf), std::end(crlf));
321 
322  // didn't find a newline
323  if (i == responseData.end()) break;
324 
325  const std::string line(responseData.begin(), i);
326  responseData.erase(responseData.begin(), i + 2);
327 
328  // empty line indicates the end of the header section
329  if (line.empty())
330  {
331  parsedHeaders = true;
332  break;
333  }
334  else if (firstLine) // first line
335  {
336  firstLine = false;
337 
338  std::string::size_type lastPos = 0;
339  const auto length = line.length();
340  std::vector<std::string> parts;
341 
342  // tokenize first line
343  while (lastPos < length + 1)
344  {
345  auto pos = line.find(' ', lastPos);
346  if (pos == std::string::npos) pos = length;
347 
348  if (pos != lastPos)
349  parts.emplace_back(line.data() + lastPos,
350  static_cast<std::vector<std::string>::size_type>(pos) - lastPos);
351 
352  lastPos = pos + 1;
353  }
354 
355  if (parts.size() >= 2)
356  response.status = std::stoi(parts[1]);
357  }
358  else // headers
359  {
360  response.headers.push_back(line);
361 
362  const auto pos = line.find(':');
363 
364  if (pos != std::string::npos)
365  {
366  std::string headerName = line.substr(0, pos);
367  std::string headerValue = line.substr(pos + 1);
368 
369  // ltrim
370  headerValue.erase(headerValue.begin(),
371  std::find_if(headerValue.begin(), headerValue.end(),
372  [](int c) {return !std::isspace(c);}));
373 
374  // rtrim
375  headerValue.erase(std::find_if(headerValue.rbegin(), headerValue.rend(),
376  [](int c) {return !std::isspace(c);}).base(),
377  headerValue.end());
378 
379  if (headerName == "Content-Length")
380  {
381  contentLength = std::stoul(headerValue);
382  contentLengthReceived = true;
383  response.body.reserve(contentLength);
384  }
385  else if (headerName == "Transfer-Encoding")
386  {
387  if (headerValue == "chunked")
388  chunkedResponse = true;
389  else
390  throw ResponseError("Unsupported transfer encoding: " + headerValue);
391  }
392  }
393  }
394  }
395 
396  if (parsedHeaders)
397  {
398  // Content-Length must be ignored if Transfer-Encoding is received
399  if (chunkedResponse)
400  {
401  bool dataReceived = false;
402  for (;;)
403  {
404  if (expectedChunkSize > 0)
405  {
406  const auto toWrite = std::min(expectedChunkSize, responseData.size());
407  response.body.insert(response.body.end(), responseData.begin(), responseData.begin() + static_cast<ptrdiff_t>(toWrite));
408  responseData.erase(responseData.begin(), responseData.begin() + static_cast<ptrdiff_t>(toWrite));
409  expectedChunkSize -= toWrite;
410 
411  if (expectedChunkSize == 0) removeCrlfAfterChunk = true;
412  if (responseData.empty()) break;
413  }
414  else
415  {
416  if (removeCrlfAfterChunk)
417  {
418  if (responseData.size() >= 2)
419  {
420  removeCrlfAfterChunk = false;
421  responseData.erase(responseData.begin(), responseData.begin() + 2);
422  }
423  else break;
424  }
425 
426  const auto i = std::search(responseData.begin(), responseData.end(), std::begin(crlf), std::end(crlf));
427 
428  if (i == responseData.end()) break;
429 
430  const std::string line(responseData.begin(), i);
431  responseData.erase(responseData.begin(), i + 2);
432 
433  expectedChunkSize = std::stoul(line, nullptr, 16);
434 
435  if (expectedChunkSize == 0)
436  {
437  dataReceived = true;
438  break;
439  }
440  }
441  }
442 
443  if (dataReceived)
444  break;
445  }
446  else
447  {
448  response.body.insert(response.body.end(), responseData.begin(), responseData.end());
449  responseData.clear();
450 
451  // got the whole content
452  if (contentLengthReceived && response.body.size() >= contentLength)
453  break;
454  }
455  }
456  }
457 
458  return response;
459  }
460 
461  private:
462 
464  std::string scheme;
465  std::string domain;
466  std::string port;
467  std::string path;
468  };
469 }
470 
471 #endif
#define min(a, b)
Definition: 80211b.c:42
RequestError(const std::string &str)
Definition: http-request.h:34
RequestError(const char *str)
Definition: http-request.h:33
Response send(const std::string &method="GET", const std::string &body="", const std::vector< std::string > &headers={})
Definition: http-request.h:241
std::string path
Definition: http-request.h:467
InternetProtocol internetProtocol
Definition: http-request.h:463
std::string port
Definition: http-request.h:466
std::string domain
Definition: http-request.h:465
Response send(const std::string &method, const std::vector< uint8_t > &body, const std::vector< std::string > &headers)
Definition: http-request.h:250
std::string scheme
Definition: http-request.h:464
Request(const std::string &url, InternetProtocol protocol=InternetProtocol::V4)
Definition: http-request.h:176
Response send(const std::string &method, const std::map< std::string, std::string > &parameters, const std::vector< std::string > &headers={})
Definition: http-request.h:223
ResponseError(const std::string &str)
Definition: http-request.h:41
ResponseError(const char *str)
Definition: http-request.h:40
Definition: first.py:1
InternetProtocol
Definition: http-request.h:45
std::string urlEncode(const std::string &str)
Definition: http-request.h:50
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:25255
@ RequestHeaderFieldsTooLarge
Definition: http-request.h:152
@ ProxyAuthenticationRequired
Definition: http-request.h:132
@ NonAuthoritativeInformation
Definition: http-request.h:108
@ UnavailableForLegalReasons
Definition: http-request.h:153
@ NetworkAuthenticationRequired
Definition: http-request.h:165
std::vector< std::uint8_t > body
Definition: http-request.h:170
std::vector< std::string > headers
Definition: http-request.h:169