httpclientlite.h 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /*
  2. * httpclientlite.hpp
  3. * ===========================================================================================
  4. *
  5. * The MIT License
  6. *
  7. * Copyright (c) 2016 Christian C. Sachs
  8. * Copyright (c) 2021 Maxim G.
  9. *
  10. * Permission is hereby granted, free of charge, to any person obtaining a copy
  11. * of this software and associated documentation files (the "Software"), to deal
  12. * in the Software without restriction, including without limitation the rights
  13. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14. * copies of the Software, and to permit persons to whom the Software is
  15. * furnished to do so, subject to the following conditions:
  16. *
  17. * The above copyright notice and this permission notice shall be included in all
  18. * copies or substantial portions of the Software.
  19. *
  20. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  22. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  23. * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  24. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  25. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  26. * SOFTWARE.
  27. */
  28. #pragma once
  29. #if defined (__linux__)
  30. # define PLATFORM_LINUX
  31. #elif defined (_WIN32) || defined (_WIN64)
  32. # define PLATFORM_WINDOWS
  33. #else
  34. /* TODO:
  35. * - Added Apple OS */
  36. /* warning: Unknown OS */
  37. #endif
  38. #include <iostream>
  39. #include <string>
  40. #include <map>
  41. #include <vector>
  42. #include <cstring>
  43. #include <sstream>
  44. #include <sys/types.h>
  45. #if defined (PLATFORM_WINDOWS)
  46. # include <WinSock2.h>
  47. # include <WS2tcpip.h>
  48. typedef SOCKET socktype_t;
  49. typedef int socklen_t;
  50. # pragma comment(lib, "ws2_32.lib")
  51. #elif defined (PLATFORM_LINUX)
  52. # include <unistd.h>
  53. # include <sys/socket.h>
  54. # include <netdb.h>
  55. # define INVALID_SOCKET -1
  56. # define closesocket(__sock) close(__sock)
  57. typedef int socktype_t;
  58. #endif /* PLATFORM_WINDOWS or PLATFORM_LINUX */
  59. const std::string content_type = "Content-Type: text/plain; version=0.0.4; charset=utf-8";
  60. namespace jdl {
  61. void init_socket() {
  62. #if defined (PLATFORM_WINDOWS)
  63. WSADATA wsa_data;
  64. WSAStartup(MAKEWORD(2, 2), &wsa_data);
  65. #endif /* PLATFORM_WINDOWS */
  66. }
  67. void deinit_socket() {
  68. #if defined (PLATFORM_WINDOWS)
  69. WSACleanup();
  70. #endif /* PLATFORM_WINDOWS */
  71. }
  72. class tokenizer {
  73. public:
  74. inline tokenizer(std::string &str) : str(str), position(0){}
  75. inline std::string next(std::string search, bool returnTail = false) {
  76. size_t hit = str.find(search, position);
  77. if (hit == std::string::npos) {
  78. if (returnTail) {
  79. return tail();
  80. } else {
  81. return "";
  82. }
  83. }
  84. size_t oldPosition = position;
  85. position = hit + search.length();
  86. return str.substr(oldPosition, hit - oldPosition);
  87. }
  88. inline std::string tail() {
  89. size_t oldPosition = position;
  90. position = str.length();
  91. return str.substr(oldPosition);
  92. }
  93. private:
  94. std::string str;
  95. std::size_t position;
  96. };
  97. typedef std::map<std::string, std::string> stringMap;
  98. struct URI {
  99. inline void parseParameters() {
  100. tokenizer qt(querystring);
  101. do {
  102. std::string key = qt.next("=");
  103. if (key == "")
  104. break;
  105. parameters[key] = qt.next("&", true);
  106. } while (true);
  107. }
  108. inline URI(std::string input, bool shouldParseParameters = false) {
  109. tokenizer t = tokenizer(input);
  110. protocol = t.next("://");
  111. std::string hostPortString = t.next("/");
  112. tokenizer hostPort(hostPortString);
  113. host = hostPort.next(hostPortString[0] == '[' ? "]:" : ":", true);
  114. if (host[0] == '[')
  115. host = host.substr(1, host.size() - 1);
  116. port = hostPort.tail();
  117. if (port.empty())
  118. port = "80";
  119. address = t.next("?", true);
  120. querystring = t.next("#", true);
  121. hash = t.tail();
  122. if (shouldParseParameters) {
  123. parseParameters();
  124. }
  125. }
  126. std::string protocol, host, port, address, querystring, hash;
  127. stringMap parameters;
  128. };
  129. struct HTTPResponse {
  130. bool success;
  131. std::string protocol;
  132. std::string response;
  133. std::string responseString;
  134. stringMap header;
  135. std::string body;
  136. inline HTTPResponse() : success(true){}
  137. inline static HTTPResponse fail() {
  138. HTTPResponse result;
  139. result.success = false;
  140. return result;
  141. }
  142. };
  143. struct HTTPClient {
  144. typedef enum {
  145. m_options = 0,
  146. m_get,
  147. m_head,
  148. m_post,
  149. m_put,
  150. m_delete,
  151. m_trace,
  152. m_connect
  153. } HTTPMethod;
  154. inline static const char *method2string(HTTPMethod method) {
  155. const char *methods[] = {"OPTIONS", "GET", "HEAD", "POST", "PUT",
  156. "DELETE", "TRACE", "CONNECT", nullptr};
  157. return methods[method];
  158. }
  159. inline static socktype_t connectToURI(const URI& uri) {
  160. struct addrinfo hints, *result, *rp;
  161. memset(&hints, 0, sizeof(addrinfo));
  162. hints.ai_family = AF_UNSPEC;
  163. hints.ai_socktype = SOCK_STREAM;
  164. int getaddrinfo_result =
  165. getaddrinfo(uri.host.c_str(), uri.port.c_str(), &hints, &result);
  166. if (getaddrinfo_result != 0)
  167. return -1;
  168. socktype_t fd = INVALID_SOCKET;
  169. for (rp = result; rp != nullptr; rp = rp->ai_next) {
  170. fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
  171. if (fd == INVALID_SOCKET) {
  172. continue;
  173. }
  174. int connect_result = connect(fd, rp->ai_addr, static_cast<socklen_t>(rp->ai_addrlen));
  175. if (connect_result == -1) {
  176. // successfully created a socket, but connection failed. close it!
  177. closesocket(fd);
  178. fd = INVALID_SOCKET;
  179. continue;
  180. }
  181. break;
  182. }
  183. freeaddrinfo(result);
  184. return fd;
  185. }
  186. inline static std::string bufferedRead(socktype_t fd) {
  187. size_t initial_factor = 4, buffer_increment_size = 8192, buffer_size = 0,
  188. bytes_read = 0;
  189. std::string buffer;
  190. buffer.resize(initial_factor * buffer_increment_size);
  191. // do {
  192. bytes_read = recv(fd, ((char*)buffer.c_str()) + buffer_size,
  193. static_cast<socklen_t>(buffer.size() - buffer_size), 0);
  194. buffer_size += bytes_read;
  195. // if (bytes_read > 0 &&
  196. // (buffer.size() - buffer_size) < buffer_increment_size) {
  197. // buffer.resize(buffer.size() + buffer_increment_size);
  198. // }
  199. // } while (bytes_read > 0);
  200. buffer.resize(buffer_size);
  201. return buffer;
  202. }
  203. #define HTTP_NEWLINE "\r\n"
  204. #define HTTP_SPACE " "
  205. #define HTTP_HEADER_SEPARATOR ": "
  206. inline static HTTPResponse request(HTTPMethod method, const URI& uri, const std::string& body = "") {
  207. socktype_t fd = connectToURI(uri);
  208. if (fd < 0)
  209. return HTTPResponse::fail();
  210. // string request = string(method2string(method)) + string(" /") +
  211. // uri.address + ((uri.querystring == "") ? "" : "?") +
  212. // uri.querystring + " HTTP/1.1" HTTP_NEWLINE "Host: " +
  213. // uri.host + HTTP_NEWLINE
  214. // "Accept: */*" HTTP_NEWLINE
  215. // "Connection: close" HTTP_NEWLINE HTTP_NEWLINE;
  216. std::string request = std::string(method2string(method)) + std::string(" /") +
  217. uri.address + ((uri.querystring == "") ? "" : "?") + uri.querystring + " HTTP/1.1" + HTTP_NEWLINE +
  218. "Host: " + uri.host + ":" + uri.port + HTTP_NEWLINE +
  219. "Accept: */*" + HTTP_NEWLINE +
  220. content_type + HTTP_NEWLINE +
  221. "Content-Length: " + std::to_string(body.size()) + HTTP_NEWLINE + HTTP_NEWLINE +
  222. body;
  223. /*int bytes_written = */send(fd, request.c_str(), static_cast<socklen_t>(request.size()), 0);
  224. std::string buffer = bufferedRead(fd);
  225. closesocket(fd);
  226. HTTPResponse result;
  227. tokenizer bt(buffer);
  228. result.protocol = bt.next(HTTP_SPACE);
  229. result.response = bt.next(HTTP_SPACE);
  230. result.responseString = bt.next(HTTP_NEWLINE);
  231. std::string header = bt.next(HTTP_NEWLINE HTTP_NEWLINE);
  232. result.body = bt.tail();
  233. tokenizer ht(header);
  234. do {
  235. std::string key = ht.next(HTTP_HEADER_SEPARATOR);
  236. if (key == "")
  237. break;
  238. result.header[key] = ht.next(HTTP_NEWLINE, true);
  239. } while (true);
  240. return result;
  241. }
  242. };
  243. } /* jdl:: */