HTTPServer.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. #include "HTTPServer.h"
  2. #include <cstring>
  3. bell::HTTPServer::HTTPServer(int serverPort) { this->serverPort = serverPort; }
  4. unsigned char bell::HTTPServer::h2int(char c) {
  5. if (c >= '0' && c <= '9') {
  6. return ((unsigned char)c - '0');
  7. }
  8. if (c >= 'a' && c <= 'f') {
  9. return ((unsigned char)c - 'a' + 10);
  10. }
  11. if (c >= 'A' && c <= 'F') {
  12. return ((unsigned char)c - 'A' + 10);
  13. }
  14. return (0);
  15. }
  16. std::string bell::HTTPServer::urlDecode(std::string str) {
  17. std::string encodedString = "";
  18. char c;
  19. char code0;
  20. char code1;
  21. for (int i = 0; i < str.length(); i++) {
  22. c = str[i];
  23. if (c == '+') {
  24. encodedString += ' ';
  25. } else if (c == '%') {
  26. i++;
  27. code0 = str[i];
  28. i++;
  29. code1 = str[i];
  30. c = (h2int(code0) << 4) | h2int(code1);
  31. encodedString += c;
  32. } else {
  33. encodedString += c;
  34. }
  35. }
  36. return encodedString;
  37. }
  38. std::vector<std::string> bell::HTTPServer::splitUrl(const std::string &url,
  39. char delimiter) {
  40. std::stringstream ssb(url);
  41. std::string segment;
  42. std::vector<std::string> seglist;
  43. while (std::getline(ssb, segment, delimiter)) {
  44. seglist.push_back(segment);
  45. }
  46. return seglist;
  47. }
  48. void bell::HTTPServer::registerHandler(RequestType requestType,
  49. const std::string &routeUrl,
  50. httpHandler handler,
  51. bool readBodyToStr) {
  52. if (routes.find(routeUrl) == routes.end()) {
  53. routes.insert({routeUrl, std::vector<HTTPRoute>()});
  54. }
  55. this->routes[routeUrl].push_back(HTTPRoute{.requestType = requestType,
  56. .handler = handler,
  57. .readBodyToStr = readBodyToStr});
  58. }
  59. void bell::HTTPServer::listen() {
  60. BELL_LOG(info, "http", "Starting server at port %d", this->serverPort);
  61. // setup address
  62. struct addrinfo hints, *server;
  63. memset(&hints, 0, sizeof hints);
  64. hints.ai_family = AF_INET;
  65. hints.ai_socktype = SOCK_STREAM;
  66. hints.ai_flags = AI_PASSIVE;
  67. getaddrinfo(NULL, std::to_string(serverPort).c_str(), &hints, &server);
  68. int sockfd =
  69. socket(server->ai_family, server->ai_socktype, server->ai_protocol);
  70. struct sockaddr_in clientname;
  71. socklen_t incomingSockSize;
  72. int yes = true;
  73. if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*) &yes, sizeof(int)) < 0) {
  74. throw std::runtime_error("setsockopt failed: " +
  75. std::string(strerror(errno)));
  76. }
  77. if (bind(sockfd, server->ai_addr, server->ai_addrlen) < 0) {
  78. throw std::runtime_error("bind failed on port " +
  79. std::to_string(this->serverPort) + ": " +
  80. std::string(strerror(errno)));
  81. }
  82. if (::listen(sockfd, 5) < 0) {
  83. throw std::runtime_error("listen failed on port " +
  84. std::to_string(this->serverPort) + ": " +
  85. std::string(strerror(errno)));
  86. }
  87. FD_ZERO(&activeFdSet);
  88. FD_SET(sockfd, &activeFdSet);
  89. for (int maxfd = sockfd;;) {
  90. /* Block until input arrives on one or more active sockets. */
  91. readFdSet = activeFdSet;
  92. struct timeval tv = {0, 100000};
  93. if (select(maxfd + 1, &readFdSet, NULL, NULL, &tv) < 0) {
  94. BELL_LOG(error, "http", "Error in select");
  95. perror("select");
  96. // exit(EXIT_FAILURE);
  97. }
  98. /* Service listening socket. */
  99. if (FD_ISSET(sockfd, &readFdSet)) {
  100. int newFd;
  101. incomingSockSize = sizeof(clientname);
  102. newFd = accept(sockfd, (struct sockaddr*)&clientname,
  103. &incomingSockSize);
  104. if (newFd < 0) {
  105. perror("accept");
  106. exit(EXIT_FAILURE);
  107. }
  108. FD_SET(newFd, &activeFdSet);
  109. HTTPConnection conn = { .buffer = std::vector<uint8_t>(128),
  110. .httpMethod = "" };
  111. this->connections.insert({newFd, conn});
  112. }
  113. /* Service other sockets and update set & max */
  114. maxfd = sockfd;
  115. for (auto it = this->connections.cbegin();
  116. it != this->connections.cend() /* not hoisted */;
  117. /* no increment */) {
  118. int fd = (*it).first;
  119. if ((*it).second.toBeClosed) {
  120. close(fd);
  121. FD_CLR(fd, &activeFdSet);
  122. this->connections.erase(
  123. it++); // or "it = m.erase(it)" since C++11
  124. } else {
  125. if (fd != sockfd && FD_ISSET(fd, &readFdSet)) {
  126. /* Data arriving on an already-connected socket. */
  127. readFromClient(fd);
  128. }
  129. if (fd > maxfd) maxfd = fd;
  130. ++it;
  131. }
  132. }
  133. }
  134. }
  135. void bell::HTTPServer::readFromClient(int clientFd) {
  136. HTTPConnection &conn = this->connections[clientFd];
  137. if (conn.headersRead) {
  138. return;
  139. }
  140. conn.fd = clientFd;
  141. int nbytes = recv(clientFd, (char*) &conn.buffer[0], conn.buffer.size(), 0);
  142. if (nbytes < 0) {
  143. BELL_LOG(error, "http", "Error reading from client");
  144. perror("recv");
  145. this->closeConnection(clientFd);
  146. } else if (nbytes == 0) {
  147. this->closeConnection(clientFd);
  148. } else {
  149. // append buffer to partialBuffer
  150. conn.partialBuffer.insert(conn.partialBuffer.end(), conn.buffer.begin(),
  151. conn.buffer.begin() + nbytes);
  152. auto stringifiedBuffer =
  153. std::string(conn.partialBuffer.data(),
  154. conn.partialBuffer.data() + conn.partialBuffer.size());
  155. READBODY:
  156. auto readSize = 0;
  157. while (stringifiedBuffer.find("\r\n") != std::string::npos) {
  158. auto line =
  159. stringifiedBuffer.substr(0, stringifiedBuffer.find("\r\n"));
  160. readSize += stringifiedBuffer.find("\r\n") + 2;
  161. stringifiedBuffer = stringifiedBuffer.substr(
  162. stringifiedBuffer.find("\r\n") + 2, stringifiedBuffer.size());
  163. if (line.find("GET ") != std::string::npos ||
  164. line.find("POST ") != std::string::npos ||
  165. line.find("OPTIONS ") != std::string::npos) {
  166. conn.httpMethod = line;
  167. }
  168. if (line.find("Content-Length: ") != std::string::npos) {
  169. conn.contentLength =
  170. std::stoi(line.substr(16, line.size() - 1));
  171. }
  172. // detect hostname for captive portal
  173. if (line.find("Host: connectivitycheck.gstatic.com") !=
  174. std::string::npos) {
  175. conn.isCaptivePortal = true;
  176. BELL_LOG(info, "http", "Captive portal request detected");
  177. }
  178. if (line.size() == 0) {
  179. if (conn.contentLength != 0) {
  180. // conn.isReadingBody = true;
  181. // remove readSize bytes from partialBuffer
  182. conn.partialBuffer.erase(conn.partialBuffer.begin(),
  183. conn.partialBuffer.begin() +
  184. readSize);
  185. findAndHandleRoute(conn);
  186. // goto READBODY;
  187. } else {
  188. if (!conn.isCaptivePortal) {
  189. findAndHandleRoute(conn);
  190. } else {
  191. this->redirectCaptivePortal(clientFd);
  192. }
  193. }
  194. }
  195. }
  196. }
  197. }
  198. void bell::HTTPServer::closeConnection(int connection) {
  199. this->connections[connection].toBeClosed = true;
  200. }
  201. void bell::HTTPServer::writeResponseEvents(int connFd) {
  202. std::lock_guard lock(this->responseMutex);
  203. std::stringstream stream;
  204. stream << "HTTP/1.1 200 OK\r\n";
  205. stream << "Server: bell-http\r\n";
  206. stream << "Connection: keep-alive\r\n";
  207. stream << "Content-type: text/event-stream\r\n";
  208. stream << "Cache-Control: no-cache\r\n";
  209. stream << "Access-Control-Allow-Origin: *\r\n";
  210. stream << "Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, "
  211. "OPTIONS\r\n";
  212. stream << "Access-Control-Allow-Headers: Origin, Content-Type, "
  213. "X-Auth-Token\r\n";
  214. stream << "\r\n";
  215. auto responseStr = stream.str();
  216. write(connFd, responseStr.c_str(), responseStr.size());
  217. this->connections[connFd].isEventConnection = true;
  218. }
  219. void bell::HTTPServer::writeResponse(const HTTPResponse &response) {
  220. std::lock_guard lock(this->responseMutex);
  221. auto fileSize = response.body.size();
  222. if (response.responseReader != nullptr) {
  223. fileSize = response.responseReader->getTotalSize();
  224. }
  225. std::stringstream stream;
  226. stream << "HTTP/1.1 " << response.status << " OK\r\n";
  227. stream << "Server: bell-http\r\n";
  228. stream << "Connection: close\r\n";
  229. stream << "Content-type: " << response.contentType << "\r\n";
  230. if (response.useGzip) {
  231. stream << "Content-encoding: gzip"
  232. << "\r\n";
  233. }
  234. stream << "Access-Control-Allow-Origin: *\r\n";
  235. stream << "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS\r\n";
  236. stream << "Access-Control-Expose-Headers: Location\r\n";
  237. stream << "Access-Control-Allow-Headers: Origin, Content-Type, "
  238. "X-Auth-Token\r\n";
  239. // go over every item in request->extraHeaders
  240. for (auto &header : response.extraHeaders) {
  241. stream << header << "\r\n";
  242. }
  243. stream << "\r\n";
  244. if (response.body.size() > 0) {
  245. stream << response.body;
  246. }
  247. auto responseStr = stream.str();
  248. write(response.connectionFd, responseStr.c_str(), responseStr.size());
  249. if (response.responseReader != nullptr) {
  250. size_t read;
  251. do {
  252. read = response.responseReader->read(responseBuffer.data(),
  253. responseBuffer.size());
  254. if (read > 0) {
  255. write(response.connectionFd, responseBuffer.data(), read);
  256. }
  257. } while (read > 0);
  258. }
  259. this->closeConnection(response.connectionFd);
  260. }
  261. void bell::HTTPServer::respond(const HTTPResponse &response) {
  262. writeResponse(response);
  263. }
  264. void bell::HTTPServer::redirectCaptivePortal(int connectionFd) {
  265. std::lock_guard lock(this->responseMutex);
  266. std::stringstream stream;
  267. stream << "HTTP/1.1 302 Found\r\n";
  268. stream << "Server: bell-http\r\n";
  269. stream << "Connection: close\r\n";
  270. stream << "Location: http://euphonium.audio\r\n\r\n";
  271. stream << "Content-Length: 9\r\n";
  272. stream << "302 Found";
  273. auto responseStr = stream.str();
  274. write(connectionFd, responseStr.c_str(), responseStr.size());
  275. this->closeConnection(connectionFd);
  276. }
  277. void bell::HTTPServer::redirectTo(const std::string &url, int connectionFd) {
  278. std::lock_guard lock(this->responseMutex);
  279. std::stringstream stream;
  280. stream << "HTTP/1.1 301 Moved Permanently\r\n";
  281. stream << "Server: bell-http\r\n";
  282. stream << "Connection: close\r\n";
  283. stream << "Location: " << url << "\r\n\r\n";
  284. auto responseStr = stream.str();
  285. write(connectionFd, responseStr.c_str(), responseStr.size());
  286. this->closeConnection(connectionFd);
  287. }
  288. void bell::HTTPServer::publishEvent(std::string eventName,
  289. std::string eventData) {
  290. std::lock_guard lock(this->responseMutex);
  291. BELL_LOG(info, "http", "Publishing event");
  292. std::stringstream stream;
  293. stream << "event: " << eventName << "\n";
  294. stream << "data: " << eventData << "\n\n";
  295. auto responseStr = stream.str();
  296. // Reply to all event-connections
  297. for (auto it = this->connections.cbegin(); it != this->connections.cend();
  298. ++it) {
  299. if ((*it).second.isEventConnection) {
  300. write(it->first, responseStr.c_str(), responseStr.size());
  301. }
  302. }
  303. }
  304. std::map<std::string, std::string>
  305. bell::HTTPServer::parseQueryString(const std::string &queryString) {
  306. std::map<std::string, std::string> query;
  307. auto prefixedString = "&" + queryString;
  308. while (prefixedString.find('&') != std::string::npos) {
  309. auto keyStart = prefixedString.find('&');
  310. auto keyEnd = prefixedString.find('=');
  311. // Find second occurence of "&" in prefixedString
  312. auto valueEnd = prefixedString.find('&', keyStart + 1);
  313. if (valueEnd == std::string::npos) {
  314. valueEnd = prefixedString.size();
  315. }
  316. auto key = prefixedString.substr(keyStart + 1, keyEnd - 1);
  317. auto value = prefixedString.substr(keyEnd + 1, valueEnd - keyEnd - 1);
  318. query[key] = urlDecode(value);
  319. prefixedString = prefixedString.substr(valueEnd);
  320. }
  321. return query;
  322. }
  323. void bell::HTTPServer::findAndHandleRoute(HTTPConnection &conn) {
  324. conn.headersRead = true;
  325. auto connectionFd = conn.fd;
  326. // auto body = conn.partialBuffer;
  327. auto url = conn.httpMethod;
  328. std::map<std::string, std::string> pathParams;
  329. std::map<std::string, std::string> queryParams;
  330. if (url.find("OPTIONS /") != std::string::npos) {
  331. std::stringstream stream;
  332. stream << "HTTP/1.1 200 OK\r\n";
  333. stream << "Server: bell-http\r\n";
  334. stream << "Allow: OPTIONS, GET, HEAD, POST\r\n";
  335. stream << "Connection: close\r\n";
  336. stream << "Access-Control-Allow-Origin: *\r\n";
  337. stream << "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n";
  338. stream << "Access-Control-Allow-Headers: Origin, Content-Type, "
  339. "X-Auth-Token\r\n";
  340. stream << "\r\n";
  341. auto responseStr = stream.str();
  342. write(connectionFd, responseStr.c_str(), responseStr.size());
  343. closeConnection(connectionFd);
  344. return;
  345. }
  346. if (url.find("GET /events") != std::string::npos) {
  347. // Handle SSE endpoint here
  348. writeResponseEvents(connectionFd);
  349. return;
  350. }
  351. for (const auto &routeSet : this->routes) {
  352. for (const auto &route : routeSet.second) {
  353. std::string path = url;
  354. if (url.find("GET ") != std::string::npos &&
  355. route.requestType == RequestType::GET) {
  356. path = path.substr(4);
  357. } else if (url.find("POST ") != std::string::npos &&
  358. route.requestType == RequestType::POST) {
  359. path = path.substr(5);
  360. } else {
  361. continue;
  362. }
  363. path = path.substr(0, path.find(' '));
  364. if (path.find('?') != std::string::npos) {
  365. auto urlEncodedSplit = splitUrl(path, '?');
  366. path = urlEncodedSplit[0];
  367. queryParams = this->parseQueryString(urlEncodedSplit[1]);
  368. }
  369. auto routeSplit = splitUrl(routeSet.first, '/');
  370. auto urlSplit = splitUrl(path, '/');
  371. bool matches = true;
  372. pathParams.clear();
  373. if (routeSplit.size() == urlSplit.size()) {
  374. for (int x = 0; x < routeSplit.size(); x++) {
  375. if (routeSplit[x] != urlSplit[x]) {
  376. if (routeSplit[x][0] == ':') {
  377. pathParams.insert(
  378. {routeSplit[x].substr(1), urlSplit[x]});
  379. } else {
  380. matches = false;
  381. }
  382. }
  383. }
  384. } else {
  385. matches = false;
  386. }
  387. if (routeSplit.back().find('*') != std::string::npos &&
  388. urlSplit[1] == routeSplit[1]) {
  389. matches = true;
  390. for (int x = 1; x <= routeSplit.size() - 2; x++) {
  391. if (urlSplit[x] != routeSplit[x]) {
  392. matches = false;
  393. }
  394. }
  395. }
  396. if (matches) {
  397. auto reader = std::make_unique<RequestBodyReader>(
  398. conn.contentLength, conn.fd, conn.partialBuffer);
  399. auto body = std::string();
  400. if (route.readBodyToStr) {
  401. body.resize(conn.contentLength);
  402. auto read = 0;
  403. while (read < conn.contentLength) {
  404. auto readBytes = reader->read(
  405. body.data() + read, conn.contentLength - read);
  406. read += readBytes;
  407. }
  408. body.resize(read);
  409. if (body.find('&') != std::string::npos) {
  410. queryParams = this->parseQueryString(body);
  411. }
  412. }
  413. std::unique_ptr<HTTPRequest> req =
  414. std::make_unique<HTTPRequest>();
  415. req->queryParams = queryParams;
  416. req->urlParams = pathParams;
  417. req->url = path;
  418. req->body = body;
  419. req->connection = connectionFd;
  420. req->handlerId = 0;
  421. req->responseReader = std::move(reader);
  422. req->contentLength = conn.contentLength;
  423. route.handler(std::move(req));
  424. return;
  425. }
  426. }
  427. }
  428. writeResponse(HTTPResponse{
  429. .connectionFd = connectionFd,
  430. .status = 404,
  431. .body = "Not found",
  432. });
  433. }