2
0

HTTPClient.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // Copyright (c) Kuba Szczodrzyński 2021-12-21.
  2. #include "HTTPClient.h"
  3. #include "TCPSocket.h"
  4. using namespace bell;
  5. void HTTPClient::HTTPResponse::close() {
  6. socket = nullptr;
  7. if (buf)
  8. free(buf);
  9. buf = nullptr;
  10. bufPtr = nullptr;
  11. }
  12. HTTPClient::HTTPResponse::~HTTPResponse() {
  13. this->close();
  14. }
  15. HTTPResponse_t HTTPClient::execute(const struct HTTPRequest &request) {
  16. auto response = std::make_unique<HTTPResponse>();
  17. response->dumpFs = request.dumpFs;
  18. response->dumpRawFs = request.dumpRawFs;
  19. return HTTPClient::executeImpl(request, std::move(response));
  20. }
  21. HTTPResponse_t HTTPClient::executeImpl(const struct HTTPRequest &request, HTTPResponse_t response) {
  22. const char *url = request.url.c_str();
  23. if (response->isRedirect) {
  24. url = response->location.c_str();
  25. }
  26. bool https = url[4] == 's';
  27. uint16_t port = https ? 443 : 80;
  28. auto *hostname = url + (https ? 8 : 7);
  29. auto *hostnameEnd = strchr(hostname, ':');
  30. auto *path = strchr(hostname, '/');
  31. if (hostnameEnd == nullptr) {
  32. hostnameEnd = path;
  33. } else {
  34. port = strtol(hostnameEnd + 1, nullptr, 10);
  35. }
  36. auto hostnameStr = std::string(hostname, (const char *)hostnameEnd);
  37. if (https) {
  38. response->socket = std::make_shared<TLSSocket>();
  39. } else {
  40. response->socket = std::make_shared<TCPSocket>();
  41. }
  42. response->socket->open(hostnameStr, port);
  43. const char *endl = "\r\n";
  44. std::stringstream stream;
  45. switch (request.method) {
  46. case HTTPMethod::GET:
  47. stream << "GET ";
  48. break;
  49. case HTTPMethod::POST:
  50. stream << "POST ";
  51. break;
  52. }
  53. stream << path << " HTTP/1.1" << endl;
  54. stream << "Host: " << hostnameStr << ":" << port << endl;
  55. stream << "Accept: */*" << endl;
  56. if (request.body != nullptr) {
  57. stream << "Content-Type: " << request.contentType << endl;
  58. stream << "Content-Length: " << strlen(request.body) << endl;
  59. }
  60. for (const auto &header : request.headers) {
  61. stream << header.first << ": " << header.second << endl;
  62. }
  63. stream << endl;
  64. if (request.body != nullptr) {
  65. stream << request.body;
  66. }
  67. std::string data = stream.str();
  68. uint32_t len = response->socket->write((uint8_t *)data.c_str(), data.size());
  69. if (len != data.size()) {
  70. response->close();
  71. BELL_LOG(error, "http", "Writing failed: wrote %d of %d bytes", len, data.size());
  72. return nullptr;
  73. }
  74. response->readHeaders();
  75. if (response->isRedirect && (request.maxRedirects < 0 || response->redirectCount < request.maxRedirects)) {
  76. response->redirectCount++;
  77. response->close(); // close the previous socket
  78. return HTTPClient::executeImpl(request, std::move(response));
  79. }
  80. return response;
  81. }
  82. bool HTTPClient::readHeader(const char *&header, const char *name) {
  83. uint32_t len = strlen(name);
  84. if (strncasecmp(header, name, len) == 0) {
  85. header += len;
  86. while (*header == ' ')
  87. header++;
  88. return true;
  89. }
  90. return false;
  91. }
  92. uint32_t HTTPClient::HTTPResponse::readRaw(char *dst) {
  93. if (!this->socket)
  94. return 0; // socket is already closed, I guess
  95. uint32_t len = this->socket->read((uint8_t *)dst, BUF_SIZE);
  96. if (len == 0 || len == -1) {
  97. isComplete = true;
  98. }
  99. if (dumpRawFs)
  100. dumpRawFs->write(dst, (long)len);
  101. // BELL_LOG(debug, "http", "Read %d bytes", len);
  102. dst[len] = '\0';
  103. return len;
  104. }
  105. void HTTPClient::HTTPResponse::readHeaders() {
  106. uint32_t len;
  107. char *line, *lineEnd;
  108. bool complete = false;
  109. std::string lineBuf;
  110. if (this->buf == nullptr) { // allocate a buffer
  111. this->buf = static_cast<char *>(malloc(BUF_SIZE + 1));
  112. this->bufPtr = this->buf;
  113. }
  114. // reset everything after a redirect
  115. this->statusCode = 0;
  116. this->contentLength = 0;
  117. this->isChunked = false;
  118. this->isGzip = false;
  119. this->isComplete = false;
  120. this->isRedirect = false;
  121. this->isStreaming = false;
  122. do {
  123. len = this->readRaw(this->buf);
  124. line = this->buf;
  125. do {
  126. lineEnd = strstr(line, "\r\n");
  127. if (!lineEnd) {
  128. lineBuf += std::string(line, this->buf + len);
  129. break;
  130. }
  131. lineBuf += std::string(line, lineEnd);
  132. if (lineBuf.empty()) {
  133. complete = true;
  134. // if body is present in buf, move the reading pointer
  135. if (lineEnd + 2 < this->buf + len) {
  136. this->bufPtr = lineEnd + 2;
  137. this->bufRemaining = len - (this->bufPtr - this->buf);
  138. this->isStreaming =
  139. !this->isComplete && !this->contentLength && (len < BUF_SIZE || this->socket->poll() == 0);
  140. }
  141. break;
  142. }
  143. auto *header = lineBuf.c_str();
  144. if (strncmp(header, "HTTP/", 5) == 0) {
  145. header += 9; // skip "1.1 "
  146. this->statusCode = strtol(header, nullptr, 10);
  147. } else if (readHeader(header, "content-type:")) {
  148. this->contentType = std::string(header);
  149. } else if (readHeader(header, "content-length:")) {
  150. this->contentLength = strtol(header, nullptr, 10);
  151. if (!this->contentLength)
  152. this->isComplete = true; // forbid reading of the body
  153. } else if (readHeader(header, "transfer-encoding:")) {
  154. this->isChunked = strncmp(header, "chunked", 7) == 0;
  155. } else if (readHeader(header, "location:")) {
  156. this->isRedirect = true;
  157. this->location = std::string(header);
  158. } else {
  159. auto *colonPtr = strchr((char *)header, ':');
  160. if (colonPtr) {
  161. auto *valuePtr = colonPtr + 1;
  162. while (*valuePtr == ' ')
  163. valuePtr++;
  164. *colonPtr = '\0';
  165. for (auto *p = (char *)header; *p; ++p) // convert header name to lower case
  166. *p = (char)tolower(*p);
  167. this->headers[std::string(header)] = std::string(valuePtr);
  168. }
  169. }
  170. lineBuf.clear();
  171. line = lineEnd + 2; // skip \r\n
  172. } while (true);
  173. } while (!complete && len); // if len == 0, the connection is closed
  174. }
  175. bool HTTPClient::HTTPResponse::skipRaw(uint32_t len, bool dontRead) {
  176. uint32_t skip = 0;
  177. if (len > bufRemaining) {
  178. skip = len - bufRemaining;
  179. len = bufRemaining;
  180. }
  181. bufRemaining -= len;
  182. bufPtr += len;
  183. if (!bufRemaining && !dontRead) { // don't read more data after a chunk's \r\n
  184. if (isComplete || (contentLength && bodyRead >= contentLength && !chunkRemaining)) {
  185. isComplete = true;
  186. return false;
  187. }
  188. bufRemaining = this->readRaw(this->buf);
  189. if (!bufRemaining)
  190. return false; // if len == 0, the connection is closed
  191. bufPtr = this->buf + skip;
  192. bufRemaining -= skip;
  193. if (!contentLength && bufRemaining < BUF_SIZE) {
  194. // no content length set and the TCP buffer is not yielding more data, yet
  195. isStreaming = true;
  196. }
  197. }
  198. return true;
  199. }
  200. uint32_t HTTPClient::HTTPResponse::read(char *dst, uint32_t toRead, bool wait) {
  201. if (isComplete) {
  202. // end of chunked stream was found OR complete body was read
  203. return 0;
  204. }
  205. auto *dstStart = dst ? dst : nullptr;
  206. uint32_t read = 0;
  207. while (toRead) { // this loop ends after original toRead
  208. skipRaw(0); // ensure the buffer contains data, wait if necessary
  209. if (isChunked && !chunkRemaining) {
  210. // chunked responses (either streaming or not)
  211. if (*bufPtr == '0') { // all chunks were read *and emitted*
  212. isComplete = true;
  213. break;
  214. }
  215. auto *endPtr = bufPtr;
  216. if (strchr(bufPtr, '\r') == nullptr) { // buf doesn't contain complete chunk size
  217. auto size = std::string(bufPtr, bufPtr + bufRemaining); // take the rest of the buffer
  218. if (!skipRaw(bufRemaining)) // skip the rest, read another buf
  219. break; // -> no more data
  220. endPtr = strchr(bufPtr, '\r'); // find the end of the actual number
  221. if (endPtr == nullptr) // something's wrong
  222. break; // - give up
  223. size += std::string(bufPtr, endPtr); // append the newly read size
  224. chunkRemaining = std::stoul(size, nullptr, 16); // read the hex size
  225. } else {
  226. chunkRemaining = strtol(bufPtr, &endPtr, 16); // read the hex size
  227. }
  228. if (!skipRaw(endPtr - bufPtr + 2)) // skip the size and \r\n
  229. break; // -> no more data, break out of main loop
  230. } else if (contentLength && !chunkRemaining) {
  231. // normal responses (having content-length)
  232. chunkRemaining = contentLength;
  233. } else if (!chunkRemaining) {
  234. // fallback for non-chunked streams (without content-length)
  235. chunkRemaining = toRead;
  236. }
  237. while (chunkRemaining && toRead) {
  238. uint32_t count = std::min(toRead, std::min(bufRemaining, chunkRemaining));
  239. if (dst) {
  240. memcpy(dst, bufPtr, count);
  241. dst += count; // move the dst pointer
  242. }
  243. read += count; // increment read counter
  244. bodyRead += count; // increment total response size
  245. chunkRemaining -= count; // decrease chunk remaining size
  246. toRead -= count; // decrease local remaining size
  247. if (!skipRaw(count)) { // eat some buffer
  248. toRead = 0; // -> no more data, break out of main loop
  249. break;
  250. }
  251. if (isChunked && !chunkRemaining) { // bufPtr is on the end of chunk
  252. if (!skipRaw(2, isStreaming)) // skip the \r\n for chunked encoding
  253. toRead = 0; // -> no more data, break out of main loop
  254. if (bufRemaining > 1 && bufPtr[0] == '0' && bufPtr[1] == '\r') // this is the last chunk
  255. isComplete = true;
  256. }
  257. }
  258. if (isStreaming && !bufRemaining && !wait) { // stream with no buffer available, just yield the current chunk
  259. break;
  260. }
  261. }
  262. if (!isChunked && contentLength && !chunkRemaining)
  263. isComplete = true; // entire response was read
  264. if (dumpFs && dstStart)
  265. dumpFs->write(dstStart, (long)read);
  266. // BELL_LOG(debug, "http", "Read %d of %d bytes", bodyRead, contentLength);
  267. return read;
  268. }
  269. std::string HTTPClient::HTTPResponse::readToString() {
  270. if (this->contentLength) {
  271. std::string result(this->contentLength, '\0');
  272. auto *data = result.data();
  273. auto len = this->read(data, this->contentLength);
  274. data[len] = '\0';
  275. this->close();
  276. return result;
  277. }
  278. std::string result;
  279. char buffer[BUF_SIZE + 1]; // make space for null-terminator
  280. uint32_t len;
  281. do {
  282. len = this->read(buffer, BUF_SIZE);
  283. buffer[len] = '\0';
  284. result += std::string(buffer);
  285. } while (len);
  286. this->close();
  287. return result;
  288. }