HTTPServer.cpp 14 KB

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