├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── examples ├── handlers.cpp ├── handlers.hpp ├── main.cpp ├── my_server.cpp └── my_server.hpp ├── makefile ├── server ├── route.cpp ├── route.hpp ├── server.cpp └── server.hpp ├── static ├── 404.html ├── home.html ├── home.png ├── login.html ├── logincss.html └── upload_form.html ├── template └── colors.html └── utils ├── include.hpp ├── request.cpp ├── request.hpp ├── response.cpp ├── response.hpp ├── template_parser.cpp ├── template_parser.hpp ├── utilities.cpp └── utilities.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | ###C++### 2 | 3 | # Prerequisites 4 | *.d 5 | 6 | # Compiled Object files 7 | *.slo 8 | *.lo 9 | *.o 10 | *.obj 11 | 12 | # Precompiled Headers 13 | *.gch 14 | *.pch 15 | 16 | # Compiled Dynamic libraries 17 | *.so 18 | *.dylib 19 | *.dll 20 | 21 | # Fortran module files 22 | *.mod 23 | *.smod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | 36 | 37 | ###OSX### 38 | 39 | .DS_Store 40 | .AppleDouble 41 | .LSOverride 42 | 43 | # Icon must end with two \r 44 | Icon 45 | 46 | 47 | # Thumbnails 48 | ._* 49 | 50 | # Files that might appear on external disk 51 | .Spotlight-V100 52 | .Trashes 53 | 54 | # Directories potentially created on remote AFP share 55 | .AppleDB 56 | .AppleDesktop 57 | Network Trash Folder 58 | Temporary Items 59 | .apdisk 60 | 61 | 62 | ###Linux### 63 | 64 | *~ 65 | 66 | # KDE directory preferences 67 | .directory 68 | 69 | 70 | ###Other### 71 | build 72 | 73 | 74 | # Editors 75 | .vscode 76 | 77 | ###APHTTP### 78 | 79 | # Compiled Template 80 | compiled.cpp 81 | 82 | # Template cache 83 | .template 84 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - "eval \"${MATRIX_EVAL}\"" 3 | language: cpp 4 | matrix: 5 | include: 6 | - 7 | env: 8 | - "MATRIX_EVAL=\"TESTENV=lint && STYLE=LLVM\"" 9 | os: osx 10 | script: 11 | - "find . -name *.h -exec bash -c 'cmp <(clang-format --style=LLVM $0) $0' {} \\;" 12 | - "find . -name *.hpp -exec bash -c 'cmp <(clang-format --style=LLVM $0) $0' {} \\;" 13 | - "find . -name *.c -exec bash -c 'cmp <(clang-format --style=LLVM $0) $0' {} \\;" 14 | - "find . -name *.cpp -exec bash -c 'cmp <(clang-format --style=LLVM $0) $0' {} \\;" 15 | - 16 | before_install: 17 | - "sudo apt-get update" 18 | - "sudo apt-get install -y g++ make" 19 | env: 20 | - "MATRIX_EVAL=\"TESTENV=build && CC=gcc && CXX=g++\"" 21 | os: linux 22 | script: 23 | - make 24 | - 25 | env: 26 | - "MATRIX_EVAL=\"TESTENV=build && CC=gcc && CXX=g++\"" 27 | os: osx 28 | script: 29 | - make 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AP HTTP 2 | === 3 | [![Travis (.org)](https://travis-ci.com/UTAP/APHTTP.svg)](https://travis-ci.com/UTAP/APHTTP) 4 | [![code style: LLVM](https://img.shields.io/badge/code_style-LLVM-brightgreen.svg)](https://llvm.org/docs/CodingStandards.html) 5 | [![Release](https://img.shields.io/github/release/UTAP/APHTTP.svg)](https://github.com/UTAP/APHTTP/releases/latest) 6 | [![Wiki](https://img.shields.io/badge/GitHub-Wiki-yellowgreen.svg)](https://github.com/UTAP/APHTTP/wiki) 7 | 8 | **AP HTTP::_server_** is a simple web application server-side blocking framework for C++ based on simplified versions of [W++](http://konteck.github.io/wpp/), [HappyHTTP](http://scumways.com/happyhttp/happyhttp.html), and [cpp-netlib](http://cpp-netlib.org/). 9 | -------------------------------------------------------------------------------- /examples/handlers.cpp: -------------------------------------------------------------------------------- 1 | #include "handlers.hpp" 2 | 3 | using namespace std; 4 | 5 | Response *RandomNumberHandler::callback(Request *req) { 6 | Response *res = new Response; 7 | res->setHeader("Content-Type", "text/html"); 8 | string body; 9 | body += ""; 10 | body += ""; 11 | body += ""; 12 | body += "

AP HTTP

"; 13 | body += "

"; 14 | body += "a random number in [1, 10] is: "; 15 | body += to_string(rand() % 10 + 1); 16 | body += "

"; 17 | body += "

"; 18 | body += "SeddionId: "; 19 | body += req->getSessionId(); 20 | body += "

"; 21 | body += ""; 22 | body += ""; 23 | res->setBody(body); 24 | return res; 25 | } 26 | 27 | Response *LoginHandler::callback(Request *req) { 28 | string username = req->getBodyParam("username"); 29 | string password = req->getBodyParam("password"); 30 | if (username == "root") 31 | throw Server::Exception("Remote root access has been disabled."); 32 | cout << "username: " << username << ",\tpassword: " << password << endl; 33 | Response *res = Response::redirect("/rand"); 34 | res->setSessionId("SID"); 35 | return res; 36 | } 37 | 38 | Response *UploadHandler::callback(Request *req) { 39 | string name = req->getBodyParam("file_name"); 40 | string file = req->getBodyParam("file"); 41 | cout << name << " (" << file.size() << "B):\n" << file << endl; 42 | Response *res = Response::redirect("/"); 43 | return res; 44 | } 45 | 46 | ColorHandler::ColorHandler(string filePath) : TemplateHandler(filePath) {} 47 | 48 | map ColorHandler::handle(Request *req) { 49 | map context; 50 | string newName = "I am " + req->getQueryParam("name"); 51 | context["name"] = newName; 52 | context["color"] = req->getQueryParam("color"); 53 | return context; 54 | } 55 | -------------------------------------------------------------------------------- /examples/handlers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _MY_HANDLERS_ 2 | #define _MY_HANDLERS_ 3 | 4 | #include "../server/server.hpp" 5 | #include // for rand and srand 6 | #include // for time 7 | #include 8 | 9 | class RandomNumberHandler : public RequestHandler { 10 | public: 11 | Response *callback(Request *); 12 | }; 13 | 14 | class LoginHandler : public RequestHandler { 15 | public: 16 | Response *callback(Request *); 17 | }; 18 | 19 | class UploadHandler : public RequestHandler { 20 | public: 21 | Response *callback(Request *); 22 | }; 23 | 24 | class ColorHandler : public TemplateHandler { 25 | public: 26 | ColorHandler(std::string filePath); 27 | std::map handle(Request *req); 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /examples/main.cpp: -------------------------------------------------------------------------------- 1 | #include "handlers.hpp" 2 | #include "my_server.hpp" 3 | #include // for rand and srand 4 | #include // for time 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main(int argc, char **argv) { 10 | srand(time(NULL)); // for rand 11 | try { 12 | MyServer server(argc > 1 ? atoi(argv[1]) : 5000); 13 | server.setNotFoundErrPage("static/404.html"); 14 | server.get("/login", new ShowPage("static/logincss.html")); 15 | server.post("/login", new LoginHandler()); 16 | server.get("/up", new ShowPage("static/upload_form.html")); 17 | server.post("/up", new UploadHandler()); 18 | server.get("/rand", new RandomNumberHandler()); 19 | server.get("/home.png", new ShowImage("static/home.png")); 20 | server.get("/", new ShowPage("static/home.html")); 21 | server.get("/colors", new ColorHandler("template/colors.html")); 22 | server.run(); 23 | } catch (const Server::Exception& e) { 24 | cerr << e.getMessage() << endl; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/my_server.cpp: -------------------------------------------------------------------------------- 1 | #include "my_server.hpp" 2 | 3 | MyServer::MyServer(int port) : Server(port) {} 4 | -------------------------------------------------------------------------------- /examples/my_server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __MY_SERVER__ 2 | #define __MY_SERVER__ 3 | 4 | #include "../server/server.hpp" 5 | 6 | class MyServer : public Server { 7 | public: 8 | MyServer(int port = 5000); 9 | }; 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CC=g++ 2 | STD=-std=c++11 -Wall -pedantic 3 | CF=$(STD) 4 | BUILD_DIR=build 5 | TEMPLATE_DIR=.template 6 | 7 | ifeq ($(OS),Windows_NT) 8 | LDLIBS += -l Ws2_32 9 | endif 10 | 11 | all: $(BUILD_DIR) myserver.out 12 | 13 | $(BUILD_DIR): 14 | mkdir -p $(BUILD_DIR) 15 | 16 | $(BUILD_DIR)/template_parser.o: utils/template_parser.cpp utils/template_parser.hpp utils/request.cpp utils/request.hpp utils/utilities.hpp utils/utilities.cpp 17 | $(CC) $(CF) -c utils/template_parser.cpp -o $(BUILD_DIR)/template_parser.o 18 | 19 | $(BUILD_DIR)/response.o: utils/response.cpp utils/response.hpp utils/include.hpp 20 | $(CC) $(CF) -c utils/response.cpp -o $(BUILD_DIR)/response.o 21 | 22 | $(BUILD_DIR)/request.o: utils/request.cpp utils/request.hpp utils/include.hpp utils/utilities.hpp 23 | $(CC) $(CF) -c utils/request.cpp -o $(BUILD_DIR)/request.o 24 | 25 | $(BUILD_DIR)/utilities.o: utils/utilities.cpp utils/utilities.hpp 26 | $(CC) $(CF) -c utils/utilities.cpp -o $(BUILD_DIR)/utilities.o 27 | 28 | $(BUILD_DIR)/server.o: server/server.cpp server/server.hpp server/route.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp utils/template_parser.hpp utils/template_parser.cpp 29 | $(CC) $(CF) -c server/server.cpp -o $(BUILD_DIR)/server.o 30 | 31 | $(BUILD_DIR)/route.o: server/route.cpp server/route.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp 32 | $(CC) $(CF) -c server/route.cpp -o $(BUILD_DIR)/route.o 33 | 34 | $(BUILD_DIR)/handlers.o: examples/handlers.cpp server/server.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp 35 | $(CC) $(CF) -c examples/handlers.cpp -o $(BUILD_DIR)/handlers.o 36 | 37 | $(BUILD_DIR)/my_server.o: examples/my_server.cpp server/server.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp 38 | $(CC) $(CF) -c examples/my_server.cpp -o $(BUILD_DIR)/my_server.o 39 | 40 | $(BUILD_DIR)/main.o: examples/main.cpp server/server.hpp utils/utilities.hpp utils/response.hpp utils/request.hpp utils/include.hpp 41 | $(CC) $(CF) -c examples/main.cpp -o $(BUILD_DIR)/main.o 42 | 43 | myserver.out: $(BUILD_DIR)/my_server.o $(BUILD_DIR)/main.o $(BUILD_DIR)/handlers.o $(BUILD_DIR)/response.o $(BUILD_DIR)/request.o $(BUILD_DIR)/utilities.o $(BUILD_DIR)/server.o $(BUILD_DIR)/route.o $(BUILD_DIR)/template_parser.o 44 | $(CC) $(CF) $(BUILD_DIR)/my_server.o $(BUILD_DIR)/main.o $(BUILD_DIR)/handlers.o $(BUILD_DIR)/response.o $(BUILD_DIR)/request.o $(BUILD_DIR)/utilities.o $(BUILD_DIR)/server.o $(BUILD_DIR)/route.o $(BUILD_DIR)/template_parser.o $(LDLIBS) -o myserver.out 45 | 46 | .PHONY: clean 47 | clean: 48 | rm -rf $(BUILD_DIR) $(TEMPLATE_DIR) *.o *.out &> /dev/null 49 | -------------------------------------------------------------------------------- /server/route.cpp: -------------------------------------------------------------------------------- 1 | #include "route.hpp" 2 | #include "server.hpp" 3 | 4 | using namespace std; 5 | 6 | Route::Route(Method _method, string _path) { 7 | method = _method; 8 | path = _path; 9 | } 10 | 11 | void Route::setHandler(RequestHandler *_handler) { handler = _handler; } 12 | 13 | bool Route::isMatch(Method _method, string url) { 14 | return (url == path) && (_method == method); 15 | } 16 | 17 | Response *Route::handle(Request *req) { return handler->callback(req); } 18 | 19 | Route::~Route() { delete handler; } 20 | -------------------------------------------------------------------------------- /server/route.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __ROUTE__ 2 | #define __ROUTE__ 3 | #include "../utils/include.hpp" 4 | #include "../utils/request.hpp" 5 | #include "../utils/response.hpp" 6 | #include 7 | 8 | class RequestHandler; 9 | 10 | class Route { 11 | private: 12 | Method method; 13 | std::string path; 14 | RequestHandler *handler; 15 | 16 | public: 17 | Route(Method _method, std::string _path); 18 | ~Route(); 19 | bool isMatch(Method, std::string url); 20 | Response *handle(Request *req); 21 | void setHandler(RequestHandler *_handler); 22 | }; 23 | #endif 24 | -------------------------------------------------------------------------------- /server/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "../utils/utilities.hpp" 17 | 18 | 19 | #ifdef _WIN32 20 | #ifndef _WIN32_WINNT 21 | #define _WIN32_WINNT 0x0501 //win xp 22 | #endif 23 | #include 24 | #include 25 | #else 26 | //POSIX sockets 27 | #include 28 | #include 29 | #include //close() 30 | #endif 31 | 32 | #ifdef _WIN32 33 | #define ISVALIDSOCKET(s) ((s) != INVALID_SOCKET) 34 | #define CLOSESOCKET(s) closesocket(s) 35 | #define GETSOCKETERRNO() (WSAGetLastError()) 36 | #else 37 | #define ISVALIDSOCKET(s) ((s) >= 0) 38 | #define CLOSESOCKET(s) close(s) 39 | #define GETSOCKETERRNO() (errno) 40 | #endif 41 | 42 | static const char* getSocketError() { 43 | #ifdef _WIN32 44 | static char message[256]; 45 | message[0] = '\0'; 46 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, 47 | NULL, WSAGetLastError(), 0, (LPSTR)&message, sizeof(message), NULL); 48 | char *newline = strrchr(message, '\n'); 49 | if (newline) *newline = '\0'; 50 | return message; 51 | #else 52 | return strerror(errno); 53 | #endif 54 | } 55 | 56 | using namespace std; 57 | 58 | class NotFoundHandler : public RequestHandler { 59 | string notFoundErrPage; 60 | 61 | public: 62 | NotFoundHandler(string notFoundErrPage = "") 63 | : notFoundErrPage(notFoundErrPage) {} 64 | Response *callback(Request *req) { 65 | Response *res = new Response(404); 66 | if (!notFoundErrPage.empty()) { 67 | res->setHeader("Content-Type", "text/" + getExtension(notFoundErrPage)); 68 | res->setBody(readFile(notFoundErrPage.c_str())); 69 | } 70 | return res; 71 | } 72 | }; 73 | 74 | class ServerErrorHandler { 75 | public: 76 | static Response *callback(string msg) { 77 | Response *res = new Response(500); 78 | res->setHeader("Content-Type", "application/json"); 79 | res->setBody("{ \"code\": \"500\", \"message\": \"" + msg + "\" }\n"); 80 | return res; 81 | } 82 | }; 83 | 84 | void split(string str, string separator, int max, vector &results) { 85 | int i = 0; 86 | size_t found = str.find_first_of(separator); 87 | 88 | while (found != string::npos) { 89 | if (found > 0) 90 | results.push_back(str.substr(0, found)); 91 | str = str.substr(found + 1); 92 | found = str.find_first_of(separator); 93 | 94 | if (max > -1 && ++i == max) 95 | break; 96 | } 97 | if (str.length() > 0) 98 | results.push_back(str); 99 | } 100 | 101 | Request *parseRawReq(char *headersRaw, size_t length) { 102 | Request *req = nullptr; 103 | string boundary; 104 | string lastFieldKey; 105 | string lastFieldValue; 106 | bool shouldBeEmpty; 107 | try { 108 | enum State { REQ, HEADER, BODY, BODY_HEADER, BODY_BODY }; 109 | State state = REQ; 110 | vector headers = split(string(headersRaw), "\r\n", false); 111 | for (size_t i = 0; i < length; i++) { 112 | if (!headersRaw[i]) 113 | throw Server::Exception("Unsupported binary data in request."); 114 | } 115 | size_t realBodySize = 116 | string(headersRaw).size() - 117 | split(string(headersRaw), "\r\n\r\n", false)[0].size() - 118 | string("\r\n\r\n").size(); 119 | for (size_t headerIndex = 0; headerIndex < headers.size(); headerIndex++) { 120 | string line = headers[headerIndex]; 121 | switch (state) { 122 | case REQ: { 123 | vector R = split(line, " ", false); 124 | if (R.size() != 3) { 125 | throw Server::Exception("Invalid header (request line)"); 126 | } 127 | req = new Request(R[0]); 128 | req->setPath(R[1]); 129 | size_t pos = req->getPath().find('?'); 130 | if (pos != string::npos && pos != req->getPath().size() - 1) { 131 | vector Q1 = split(req->getPath().substr(pos + 1), "&", false); 132 | for (vector::size_type q = 0; q < Q1.size(); q++) { 133 | vector Q2 = split(Q1[q], "=", false); 134 | if (Q2.size() == 2) 135 | req->setQueryParam(Q2[0], Q2[1], false); 136 | else 137 | throw Server::Exception("Invalid query"); 138 | } 139 | } 140 | req->setPath(req->getPath().substr(0, pos)); 141 | state = HEADER; 142 | } break; 143 | case HEADER: { 144 | if (line == "") { 145 | state = BODY; 146 | if (req->getHeader("Content-Type") 147 | .substr(0, string("multipart/form-data").size()) == 148 | "multipart/form-data") { 149 | boundary = 150 | req->getHeader("Content-Type") 151 | .substr(req->getHeader("Content-Type").find("boundary=") + 152 | string("boundary=").size()); 153 | } 154 | break; 155 | } 156 | vector R = split(line, ": ", false); 157 | if (R.size() != 2) 158 | throw Server::Exception("Invalid header"); 159 | req->setHeader(R[0], R[1], false); 160 | if (toLowerCase(R[0]) == toLowerCase("Content-Length")) 161 | if (realBodySize != (size_t)atol(R[1].c_str())) 162 | return NULL; 163 | } break; 164 | case BODY: { 165 | if (req->getHeader("Content-Type") == "") { 166 | } else if (req->getHeader("Content-Type") == 167 | "application/x-www-form-urlencoded") { 168 | vector body = split(line, "&", false); 169 | for (size_t i = 0; i < body.size(); i++) { 170 | vector field = split(body[i], "=", false); 171 | if (field.size() == 2) 172 | req->setBodyParam(field[0], field[1], false); 173 | else if (field.size() == 1) 174 | req->setBodyParam(field[0], "", false); 175 | else 176 | throw Server::Exception("Invalid body"); 177 | } 178 | } else if (req->getHeader("Content-Type") 179 | .substr(0, string("multipart/form-data").size()) == 180 | "multipart/form-data") { 181 | if (line == "--" + boundary || line == "--" + boundary + "--") { 182 | lastFieldKey = ""; 183 | lastFieldValue = ""; 184 | shouldBeEmpty = false; 185 | state = BODY_HEADER; 186 | } 187 | } else { 188 | throw Server::Exception("Unsupported body type: " + 189 | req->getHeader("Content-Type")); 190 | } 191 | } break; 192 | case BODY_HEADER: { 193 | if (line == "") { 194 | state = BODY_BODY; 195 | break; 196 | } 197 | vector R = split(line, ": ", false); 198 | if (R.size() != 2) 199 | throw Server::Exception("Invalid header"); 200 | if (toLowerCase(R[0]) == toLowerCase("Content-Disposition")) { 201 | vector A = split(R[1], "; ", false); 202 | for (size_t i = 0; i < A.size(); i++) { 203 | vector attr = split(A[i], "=", false); 204 | if (attr.size() == 2) { 205 | if (toLowerCase(attr[0]) == toLowerCase("name")) { 206 | lastFieldKey = attr[1].substr(1, attr[1].size() - 2); 207 | } 208 | } else if (attr.size() == 1) { 209 | } else 210 | throw Server::Exception("Invalid body attribute"); 211 | } 212 | } else if (toLowerCase(R[0]) == toLowerCase("Content-Type")) { 213 | if (toLowerCase(R[1]) == toLowerCase("application/octet-stream")) 214 | shouldBeEmpty = true; 215 | else if (toLowerCase(R[1].substr(0, R[1].find("/"))) != 216 | toLowerCase("text")) 217 | throw Server::Exception("Unsupported file type: " + R[1]); 218 | } 219 | } break; 220 | case BODY_BODY: { 221 | if (line == "--" + boundary || line == "--" + boundary + "--") { 222 | req->setBodyParam(lastFieldKey, 223 | lastFieldValue.substr(string("\r\n").size()), 224 | false); 225 | lastFieldKey = ""; 226 | lastFieldValue = ""; 227 | state = BODY_HEADER; 228 | shouldBeEmpty = false; 229 | } else if (shouldBeEmpty && !line.empty()) 230 | throw Server::Exception("Unsupported file type: " + 231 | string("application/octet-stream")); 232 | else 233 | lastFieldValue += "\r\n" + line; 234 | } break; 235 | } 236 | } 237 | } catch (const Server::Exception&) { 238 | throw; 239 | } catch (...) { 240 | throw Server::Exception("Error on parsing request"); 241 | } 242 | return req; 243 | } 244 | 245 | Server::Server(int _port) : port(_port) { 246 | #ifdef _WIN32 247 | WSADATA wsa_data; 248 | int initializeResult = WSAStartup(MAKEWORD(2, 2), &wsa_data); 249 | if (initializeResult != 0) { 250 | throw Exception("Error: WinSock WSAStartup failed: " + string(getSocketError())); 251 | } 252 | #endif 253 | 254 | notFoundHandler = new NotFoundHandler(); 255 | 256 | sc = socket(AF_INET, SOCK_STREAM, 0); 257 | int sc_option = 1; 258 | 259 | #ifdef _WIN32 260 | setsockopt(sc, SOL_SOCKET, SO_REUSEADDR, (char*)&sc_option, sizeof(sc_option)); 261 | #else 262 | setsockopt(sc, SOL_SOCKET, SO_REUSEADDR, &sc_option, sizeof(sc_option)); 263 | #endif 264 | if (!ISVALIDSOCKET(sc)) 265 | throw Exception("Error on opening socket: " + string(getSocketError())); 266 | 267 | struct sockaddr_in serv_addr; 268 | serv_addr.sin_family = AF_INET; 269 | serv_addr.sin_addr.s_addr = INADDR_ANY; 270 | serv_addr.sin_port = htons(port); 271 | 272 | if (::bind(sc, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) { 273 | throw Exception("Error on binding: " + string(getSocketError())); 274 | } 275 | } 276 | 277 | void Server::get(string path, RequestHandler *handler) { 278 | Route *route = new Route(GET, path); 279 | route->setHandler(handler); 280 | routes.push_back(route); 281 | } 282 | 283 | void Server::post(string path, RequestHandler *handler) { 284 | Route *route = new Route(POST, path); 285 | route->setHandler(handler); 286 | routes.push_back(route); 287 | } 288 | 289 | void Server::run() { 290 | ::listen(sc, 10); 291 | 292 | struct sockaddr_in cli_addr; 293 | socklen_t clilen; 294 | clilen = sizeof(cli_addr); 295 | SOCKET newsc; 296 | 297 | while (true) { 298 | newsc = ::accept(sc, (struct sockaddr *)&cli_addr, &clilen); 299 | if (!ISVALIDSOCKET(newsc)) 300 | throw Exception("Error on accept: " + string(getSocketError())); 301 | Response *res = NULL; 302 | try { 303 | char* data = new char[BUFSIZE + 1]; 304 | size_t recv_len, recv_total_len = 0; 305 | Request *req = NULL; 306 | while (!req) { 307 | recv_len = 308 | recv(newsc, data + recv_total_len, BUFSIZE - recv_total_len, 0); 309 | if (recv_len > 0) { 310 | recv_total_len += recv_len; 311 | data[recv_total_len >= 0 ? recv_total_len : 0] = 0; 312 | req = parseRawReq(data, recv_total_len); 313 | } else 314 | break; 315 | } 316 | delete[] data; 317 | if (!recv_total_len) { 318 | CLOSESOCKET(newsc); 319 | continue; 320 | } 321 | req->log(); 322 | size_t i = 0; 323 | for (; i < routes.size(); i++) { 324 | if (routes[i]->isMatch(req->getMethod(), req->getPath())) { 325 | res = routes[i]->handle(req); 326 | break; 327 | } 328 | } 329 | if (i == routes.size() && notFoundHandler) { 330 | res = notFoundHandler->callback(req); 331 | } 332 | delete req; 333 | } catch (const Exception& exc) { 334 | delete res; 335 | res = ServerErrorHandler::callback(exc.getMessage()); 336 | } 337 | int si; 338 | res->log(); 339 | string res_data = res->print(si); 340 | delete res; 341 | int wr = send(newsc, res_data.c_str(), si, 0); 342 | if (wr != si) 343 | throw Exception("Send error: " + string(getSocketError())); 344 | CLOSESOCKET(newsc); 345 | } 346 | } 347 | 348 | Server::~Server() { 349 | if (sc >= 0) 350 | CLOSESOCKET(sc); 351 | delete notFoundHandler; 352 | for (size_t i = 0; i < routes.size(); ++i) 353 | delete routes[i]; 354 | 355 | #ifdef _WIN32 356 | WSACleanup(); 357 | #endif 358 | } 359 | 360 | Server::Exception::Exception(const string msg) { message = msg; } 361 | 362 | string Server::Exception::getMessage() const { return message; } 363 | 364 | ShowFile::ShowFile(string _filePath, string _fileType) { 365 | filePath = _filePath; 366 | fileType = _fileType; 367 | } 368 | 369 | Response *ShowFile::callback(Request *req) { 370 | Response *res = new Response; 371 | res->setHeader("Content-Type", fileType); 372 | res->setBody(readFile(filePath.c_str())); 373 | return res; 374 | } 375 | 376 | ShowPage::ShowPage(string filePath) 377 | : ShowFile(filePath, "text/" + getExtension(filePath)) {} 378 | 379 | ShowImage::ShowImage(string filePath) 380 | : ShowFile(filePath, "image/" + getExtension(filePath)) {} 381 | 382 | void Server::setNotFoundErrPage(std::string notFoundErrPage) { 383 | delete notFoundHandler; 384 | notFoundHandler = new NotFoundHandler(notFoundErrPage); 385 | } 386 | 387 | RequestHandler::~RequestHandler() {} 388 | 389 | TemplateHandler::TemplateHandler(string _filePath) { 390 | filePath = _filePath; 391 | parser = new TemplateParser(filePath); 392 | } 393 | 394 | Response *TemplateHandler::callback(Request *req) { 395 | map context; 396 | context = this->handle(req); 397 | Response *res = new Response; 398 | res->setHeader("Content-Type", "text/html"); 399 | res->setBody(parser->getHtml(context)); 400 | return res; 401 | } 402 | 403 | map TemplateHandler::handle(Request *req) { 404 | map context; 405 | return context; 406 | } 407 | -------------------------------------------------------------------------------- /server/server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __SERVER__ 2 | #define __SERVER__ 3 | #include "../utils/include.hpp" 4 | #include "../utils/request.hpp" 5 | #include "../utils/response.hpp" 6 | #include "../utils/template_parser.hpp" 7 | #include "route.hpp" 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef _WIN32 13 | typedef unsigned SOCKET; 14 | #else 15 | typedef int SOCKET; 16 | #endif 17 | 18 | class TemplateParser; 19 | 20 | class RequestHandler { 21 | public: 22 | virtual ~RequestHandler(); 23 | virtual Response *callback(Request *req) = 0; 24 | }; 25 | 26 | class ShowFile : public RequestHandler { 27 | std::string filePath; 28 | std::string fileType; 29 | 30 | public: 31 | ShowFile(std::string filePath, std::string fileType); 32 | Response *callback(Request *req); 33 | }; 34 | 35 | class ShowPage : public ShowFile { 36 | 37 | public: 38 | ShowPage(std::string _filePath); 39 | }; 40 | 41 | class ShowImage : public ShowFile { 42 | 43 | public: 44 | ShowImage(std::string _filePath); 45 | }; 46 | 47 | class TemplateHandler : public RequestHandler { 48 | std::string filePath; 49 | TemplateParser *parser; 50 | 51 | public: 52 | TemplateHandler(std::string _filePath); 53 | Response *callback(Request *req); 54 | virtual std::map handle(Request *req); 55 | }; 56 | 57 | class Server { 58 | public: 59 | Server(int port = 5000); 60 | ~Server(); 61 | void run(); 62 | void get(std::string path, RequestHandler *handler); 63 | void post(std::string path, RequestHandler *handler); 64 | void setNotFoundErrPage(std::string); 65 | 66 | class Exception : public std::exception { 67 | public: 68 | Exception() {} 69 | Exception(const std::string); 70 | std::string getMessage() const; 71 | 72 | private: 73 | std::string message; 74 | }; 75 | 76 | private: 77 | SOCKET sc; 78 | int port; 79 | std::vector routes; 80 | RequestHandler *notFoundHandler; 81 | }; 82 | #endif 83 | -------------------------------------------------------------------------------- /static/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

AP HTTP

5 |

Err: 404

6 |

Requested page was not found!

7 | 8 | 9 | -------------------------------------------------------------------------------- /static/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

AP HTTP

5 | 6 |
7 | Go to login page 8 |
9 | Go to upload page 10 |
11 | Go to colors page 12 | 13 | 14 | -------------------------------------------------------------------------------- /static/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UTAP/APHTTP/7328d3e2debf8db62cdd8b43b25545bbce068ec2/static/home.png -------------------------------------------------------------------------------- /static/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

AP HTTP

5 |

Login

6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /static/logincss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

AP HTTP

5 |
6 |
7 |

Login

8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /static/upload_form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

AP HTTP

5 |

Upload

6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /template/colors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | Choose color : 14 | 15 | Red 16 | Navy 17 | Green 18 | 19 |
20 | 21 | Your name : 22 | 23 | 24 | 25 |
26 | 27 | <% 28 | if(context["color"] == "red") 29 | { 30 | %> 31 |
32 | <% 33 | cout << context["name"]; 34 | %> 35 |
36 | <% 37 | } 38 | %> 39 | <% 40 | if(context["color"] == "navy") 41 | { 42 | %> 43 |
44 | <% 45 | cout << context["name"]; 46 | %> 47 |
48 | <% 49 | } 50 | %> 51 | <% 52 | if(context["color"] == "green") 53 | { 54 | %> 55 |
56 | <% 57 | cout << context["name"]; 58 | %> 59 |
60 | <% 61 | } 62 | %> 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /utils/include.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __INCLUDE__ 2 | #define __INCLUDE__ 3 | 4 | #define BUFSIZE 4145152 5 | enum Method { GET, POST }; 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /utils/request.cpp: -------------------------------------------------------------------------------- 1 | #include "request.hpp" 2 | #include "../utils/utilities.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | Request::Request(string _method) { 11 | if (_method == "GET") 12 | method = GET; 13 | if (_method == "POST") 14 | method = POST; 15 | } 16 | 17 | string Request::getQueryParam(string key) { return urlDecode(query[key]); } 18 | 19 | string Request::getBodyParam(string key) { return urlDecode(body[key]); } 20 | 21 | string Request::getHeader(string key) { return urlDecode(headers[key]); } 22 | 23 | string Request::getPath() { return path; } 24 | 25 | void Request::setPath(string _path) { path = _path; } 26 | 27 | Method Request::getMethod() { return method; } 28 | 29 | void Request::setMethod(Method _method) { method = _method; } 30 | 31 | void Request::setQueryParam(string key, string value, bool encode) { 32 | query[key] = encode ? urlEncode(value) : value; 33 | } 34 | 35 | void Request::setBodyParam(string key, string value, bool encode) { 36 | body[key] = encode ? urlEncode(value) : value; 37 | } 38 | 39 | void Request::setHeader(string key, string value, bool encode) { 40 | headers[key] = encode ? urlEncode(value) : value; 41 | } 42 | 43 | string Request::getBody() { 44 | string bs = ""; 45 | for (auto it = body.begin(); !body.empty() && it != body.end(); it++) 46 | bs += it->first + "=" + it->second + "&"; 47 | return bs; 48 | } 49 | 50 | string Request::getSessionId() { 51 | string cookie = getHeader("cookie"); 52 | if (cookie == "") 53 | return ""; 54 | vector v = split(cookie, ";"); 55 | for (string kv : v) { 56 | vector k = split(kv, "="); 57 | if (k[0] == "sessionId") 58 | return k[1]; 59 | } 60 | return ""; 61 | } 62 | 63 | void Request::log() { 64 | const string NC = "\033[0;39m"; 65 | const string K = "\033[1m"; 66 | const string H = "\033[33;1m"; 67 | string log = ""; 68 | log += H + string("------- Request --------") + NC + string("\n"); 69 | log += 70 | K + string("Method:\t") + NC + (method ? "POST" : "GET") + string("\n"); 71 | log += K + string("Path:\t") + NC + path + string("\n"); 72 | log += K + string("Headers:") + NC + string("\n"); 73 | for (auto it = headers.begin(); !headers.empty() && it != headers.end(); it++) 74 | log += " " + urlDecode(it->first) + ": " + urlDecode(it->second) + 75 | string("\n"); 76 | log += "[ " + K + string("SessionId:\t") + NC + this->getSessionId() + " ]" + 77 | string("\n"); 78 | log += K + string("Query:") + NC + string("\n"); 79 | for (auto it = query.begin(); !query.empty() && it != query.end(); it++) 80 | log += " " + urlDecode(it->first) + ": " + urlDecode(it->second) + 81 | string("\n"); 82 | log += K + string("Body:") + NC + string("\n"); 83 | for (auto it = body.begin(); !body.empty() && it != body.end(); it++) 84 | log += " " + urlDecode(it->first) + ": " + urlDecode(it->second) + 85 | string("\n"); 86 | log += H + string("------------------------") + NC + string("\n"); 87 | cerr << log << endl; 88 | } 89 | 90 | cimap Request::getHeaders() { 91 | vector res; 92 | for (map::iterator i = headers.begin(); 93 | !headers.empty() && i != headers.end(); i++) { 94 | res.push_back(i->first); 95 | res.push_back(i->second); 96 | } 97 | return headers; 98 | } 99 | 100 | string Request::getQueryString() { 101 | if (query.empty()) 102 | return ""; 103 | string res = "?"; 104 | for (map::iterator i = query.begin(); 105 | !query.empty() && i != query.end(); i++) { 106 | res += i->first; 107 | res += "="; 108 | res += i->second; 109 | res += "&"; 110 | } 111 | return res; 112 | } 113 | 114 | string Request::getHeadersString() { 115 | string headerString = ""; 116 | for (auto it = headers.begin(); !headers.empty() && it != headers.end(); it++) 117 | headerString += it->first + "=" + it->second + "&"; 118 | return headerString; 119 | } 120 | 121 | void Request::setHeaders(string _headers) { 122 | headers = getCimapFromString(_headers); 123 | } 124 | 125 | void Request::setQuery(std::string _query) { 126 | _query = _query.substr(1); 127 | query = getCimapFromString(_query); 128 | } 129 | 130 | void Request::setBody(std::string _body) { body = getCimapFromString(_body); } 131 | 132 | void Request::serializeToFile(Request *req, string filePath) { 133 | string reqString = to_string(req->getMethod()); 134 | reqString += "\n"; 135 | reqString += req->getPath(); 136 | reqString += "\n"; 137 | reqString += req->getHeadersString(); 138 | reqString += "\n"; 139 | reqString += req->getBody(); 140 | reqString += "\n"; 141 | reqString += req->getQueryString(); 142 | writeToFile(reqString, filePath); 143 | } 144 | 145 | void Request::deserializeFromFile(Request *req, string filePath) { 146 | vector fields = tokenize(readFile(filePath), '\n'); 147 | switch (fields.size()) { 148 | case 5: 149 | req->setQuery(fields[4]); 150 | case 4: 151 | req->setBody(fields[3]); 152 | case 3: 153 | req->setHeaders(fields[2]); 154 | case 2: 155 | req->setPath(fields[1]); 156 | case 1: 157 | req->setMethod(stoi(fields[0]) == GET ? GET : POST); 158 | } 159 | } -------------------------------------------------------------------------------- /utils/request.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __REQUEST__ 2 | #define __REQUEST__ 3 | #include "../utils/include.hpp" 4 | #include "../utils/utilities.hpp" 5 | #include 6 | 7 | class Request { 8 | public: 9 | Request(std::string method = "GET"); 10 | std::string getPath(); 11 | void setPath(std::string); 12 | Method getMethod(); 13 | void setMethod(Method); 14 | std::string getQueryParam(std::string key); 15 | void setQueryParam(std::string key, std::string value, bool encode = true); 16 | std::string getBodyParam(std::string key); 17 | void setBodyParam(std::string key, std::string value, bool encode = true); 18 | std::string getHeader(std::string key); 19 | void setHeader(std::string key, std::string value, bool encode = true); 20 | std::string getBody(); 21 | std::string getSessionId(); 22 | void setSessionId(std::string); 23 | std::string getQueryString(); 24 | cimap getHeaders(); 25 | std::string getHeadersString(); 26 | void setHeaders(std::string); 27 | void setQuery(std::string); 28 | void setBody(std::string); 29 | void log(); 30 | static void serializeToFile(Request *req, std::string filePath); 31 | static void deserializeFromFile(Request *req, std::string filePath); 32 | 33 | private: 34 | std::string path; 35 | Method method; 36 | cimap headers; 37 | cimap query; 38 | cimap body; 39 | }; 40 | #endif 41 | -------------------------------------------------------------------------------- /utils/response.cpp: -------------------------------------------------------------------------------- 1 | #include "response.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | map getHttpPhrases() { 9 | map httpPhrase; 10 | httpPhrase[200] = "OK"; 11 | httpPhrase[303] = "See Other"; 12 | httpPhrase[404] = "Not Found"; 13 | return httpPhrase; 14 | } 15 | 16 | map httpPhrase = getHttpPhrases(); 17 | 18 | Response::Response(int code) { 19 | this->code = code; 20 | this->phrase = httpPhrase[code]; 21 | this->headers["Content-Type"] = "text/plain"; 22 | } 23 | 24 | int Response::getStatusCode() { return code; } 25 | 26 | string Response::getStatusPhrase() { return phrase; } 27 | 28 | void Response::setStatus(int _code, string _phrase) { 29 | phrase = _phrase; 30 | code = _code; 31 | } 32 | 33 | void Response::setStatus(int _code) { setStatus(_code, httpPhrase[_code]); } 34 | 35 | string Response::print(int &size) { 36 | string header = ""; 37 | header += "HTTP/1.0 " + to_string(code) + " " + phrase + "\r\n"; 38 | header += "Server: " + SERVER_NAME + " \r\n"; 39 | header += "Content-Length: " + to_string(body.size()) + "\r\n"; 40 | for (auto it = headers.begin(); !headers.empty() && it != headers.end(); it++) 41 | header += it->first + ": " + it->second + "\r\n"; 42 | header += "\r\n"; 43 | size = header.size() + body.size(); 44 | return header + body; 45 | } 46 | 47 | void Response::log(bool showBody) { 48 | const string NC = "\033[0;39m"; 49 | const string K = "\033[1m"; 50 | const string H = "\033[34;1m"; 51 | const string G = "\033[32m"; 52 | const string R = "\033[31m"; 53 | string log = ""; 54 | log += H + string("------- Response -------") + NC + string("\n"); 55 | log += K + string("Status:\t") + NC + (code == 200 ? G : R) + 56 | to_string(code) + " " + phrase + NC + string("\n"); 57 | log += K + string("Headers:") + NC + string("\n"); 58 | for (auto it = headers.begin(); !headers.empty() && it != headers.end(); it++) 59 | log += " " + urlDecode(it->first) + ": " + urlDecode(it->second) + 60 | string("\n"); 61 | if (showBody) 62 | log += K + string("Body:\n") + NC + body + string("\n"); 63 | log += H + string("------------------------") + NC + string("\n"); 64 | cerr << log << endl; 65 | } 66 | 67 | void Response::setHeader(string name, string value) { headers[name] = value; } 68 | 69 | void Response::setBody(string _body) { body = _body; } 70 | 71 | string Response::getHeader(string name) { return ""; } 72 | 73 | void Response::setSessionId(string sessionId) { 74 | setHeader("set-cookie", "sessionId=" + sessionId + ";"); 75 | } 76 | 77 | Response *Response::redirect(string url) { 78 | Response *res = new Response(303); 79 | res->setHeader("Location", url); 80 | return res; 81 | } 82 | -------------------------------------------------------------------------------- /utils/response.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __RESPONSE__ 2 | #define __RESPONSE__ 3 | #include "../utils/include.hpp" 4 | #include "../utils/utilities.hpp" 5 | #include 6 | 7 | const std::string SERVER_NAME = "AP HTTP Server"; 8 | 9 | class Response { 10 | public: 11 | Response(int code = 200); 12 | std::string print(int &); 13 | void log(bool showBody = false); 14 | void setHeader(std::string name, std::string value); 15 | void setBody(std::string _body); 16 | void setStatus(int code, std::string phrase); 17 | void setStatus(int code); 18 | int getStatusCode(); 19 | std::string getStatusPhrase(); 20 | std::string getHeader(std::string name); 21 | void setSessionId(std::string sessionId); 22 | static Response *redirect(std::string url); 23 | 24 | private: 25 | int code; 26 | 27 | std::string phrase; 28 | std::string body; 29 | cimap headers; 30 | }; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /utils/template_parser.cpp: -------------------------------------------------------------------------------- 1 | #include "template_parser.hpp" 2 | #include 3 | using namespace std; 4 | 5 | static std::string mkdirNoErrors(const std::string& dirName) { 6 | //do not error if dir already exists (-p flag on linux) 7 | #ifdef _WIN32 8 | return ("(if not exist \"" + dirName + "\" " + SysCmd::mkdir + "\"" + dirName + "\")"); 9 | #else 10 | return (SysCmd::mkdir + "-p \"" + dirName + "\""); 11 | #endif 12 | } 13 | 14 | int TemplateParser::lastParserNum = 0; 15 | const std::string localTemplate(const int parserNum) { 16 | return "local" + std::to_string(parserNum) + ".html"; 17 | } 18 | 19 | TemplateParser::TemplateParser(string _filePath) { 20 | filePath = _filePath; 21 | variableCount = 0; 22 | programName = to_string(TemplateParser::lastParserNum) + SysCmd::fileExtention; 23 | parserNum = TemplateParser::lastParserNum++; 24 | code = ""; 25 | parseTemplate(); 26 | makeExecutableTemplate(); 27 | } 28 | 29 | string TemplateParser::getHtml(map _context) { 30 | TemplateUtils::writeMapToFile(outputFolder + "/" + mapFile, &_context); 31 | return runGeneratedCode(); 32 | } 33 | 34 | void TemplateParser::parseTemplate() { 35 | string unparsedTemplate = readFile(filePath); 36 | int parsePointer = 0; 37 | while (parsePointer < (signed int) unparsedTemplate.size()) { 38 | int begin = findBeginOfCodeBlock(parsePointer, unparsedTemplate); 39 | int end = findEndOfCodeBlock(parsePointer, unparsedTemplate); 40 | if (begin < 0) 41 | break; 42 | appendHTMLToCode(parsePointer, begin, unparsedTemplate); 43 | appendCodeBlockToCode(begin, end, unparsedTemplate); 44 | parsePointer = end + endCodeBlockTag.size(); 45 | } 46 | appendHTMLToCode(parsePointer, unparsedTemplate.size(), unparsedTemplate); 47 | } 48 | 49 | int TemplateParser::findBeginOfCodeBlock(int startPosition, 50 | string &unparsedTemplate) { 51 | return findSubStrPosition(unparsedTemplate, beginCodeBlockTag, startPosition); 52 | } 53 | 54 | int TemplateParser::findEndOfCodeBlock(int startPosition, 55 | string &unparsedTemplate) { 56 | return findSubStrPosition(unparsedTemplate, endCodeBlockTag, startPosition); 57 | } 58 | 59 | void TemplateParser::appendHTMLToCode(int begin, int end, 60 | string const &unparsedTemplate) { 61 | code += "\nstring __variable" + to_string(variableCount) + ";"; 62 | code += "\n__variable" + to_string(variableCount) + 63 | " = __unparsedTemplate__.substr("; 64 | code += to_string(begin) + ", " + to_string(end - begin) + ");"; 65 | code += "\ncout << __variable" + to_string(variableCount) + ";"; 66 | variableCount++; 67 | } 68 | 69 | void TemplateParser::appendCodeBlockToCode(int begin, int end, 70 | string &unparsedTemplate) { 71 | if (end <= begin || begin < 0) 72 | throw Server::Exception("Can not parse template " + filePath); 73 | int codeBlockSize = end - begin - beginCodeBlockTag.size(); 74 | code += 75 | unparsedTemplate.substr(begin + beginCodeBlockTag.size(), codeBlockSize); 76 | } 77 | 78 | void TemplateParser::makeExecutableTemplate() { 79 | generateCode(); 80 | compileCode(); 81 | makeLocalTemplate(); 82 | } 83 | 84 | void TemplateParser::makeLocalTemplate() { 85 | string templateContent = readFile(filePath); 86 | if (writeToFile(templateContent, 87 | outputFolder + "/" + localTemplate(parserNum)) < 0) 88 | throw Server::Exception("Can not write template to local " + outputFolder + 89 | "folder"); 90 | } 91 | 92 | void TemplateParser::generateCode() { 93 | addReadFromTemplateToCode(); 94 | addContextMapToCode(); 95 | addIncludesToCode(); 96 | addReturnToCode(); 97 | } 98 | 99 | void TemplateParser::compileCode() { 100 | if (writeToFile(code, toCompileFile) < 0) 101 | throw Server::Exception("Can not write generated template code!"); 102 | 103 | string cmd = mkdirNoErrors(outputFolder) + " && " + cc + " " + toCompileFile + 104 | " " + utilitiesPath + " -o " + outputFolder + SysCmd::slash + programName + 105 | "&& " + SysCmd::rm + toCompileFile; 106 | string error = "Can not compile template " + filePath; 107 | TemplateUtils::runSystemCommand(cmd, error); 108 | } 109 | 110 | string TemplateParser::runGeneratedCode() { 111 | 112 | string cmd = 113 | SysCmd::programStart + outputFolder + SysCmd::slash + programName + " " + " > " + staticTemplate; 114 | string error = "Error in running template " + filePath; 115 | TemplateUtils::runSystemCommand(cmd, error); 116 | 117 | string html = readFile(staticTemplate); 118 | 119 | cmd = SysCmd::rm + staticTemplate; 120 | error = "Error in deleting static template for " + filePath; 121 | TemplateUtils::runSystemCommand(cmd, error); 122 | 123 | return html; 124 | } 125 | 126 | void TemplateParser::addIncludesToCode() { 127 | string include = "#include \n"; 128 | include += "#include \n"; 129 | include += "#include \n"; 130 | include += "#include \n"; 131 | include += "#include \"" + utilitiesHeaderPath + "\"\n"; 132 | include += "using namespace std;\n"; 133 | code = include + "int main(int argc, char const *argv[])\n{\n" + code + "\n"; 134 | } 135 | 136 | void TemplateParser::addReadFromTemplateToCode() { 137 | code = "string __unparsedTemplate__ = readFile(\"" + outputFolder + "/" + 138 | localTemplate(parserNum) + "\");\n" + code; 139 | } 140 | 141 | void TemplateParser::addReturnToCode() { code += "return 0;\n}\n"; } 142 | 143 | void TemplateParser::addContextMapToCode() { 144 | string mapCode = "std::map context;\n"; 145 | // `mapFile` should be changed if we want to handle requests 146 | // in a multi-thread non-blocking way 147 | mapCode += 148 | "readMapFromFile(\"" + outputFolder + "/" + mapFile + "\", &context);\n"; 149 | code = mapCode + code; 150 | } 151 | 152 | TemplateParser::~TemplateParser() { 153 | deleteExecutable(); 154 | deleteLocalTemplate(); 155 | } 156 | 157 | void TemplateParser::deleteExecutable() { 158 | string cmd = SysCmd::rm + outputFolder + SysCmd::slash + programName; 159 | string error = "Error in deleting executable file at " + outputFolder + "/" + 160 | programName; 161 | TemplateUtils::runSystemCommand(cmd, error); 162 | } 163 | 164 | void TemplateParser::deleteLocalTemplate() { 165 | string cmd = SysCmd::rm + outputFolder + SysCmd::slash + localTemplate(parserNum); 166 | string error = "Error in deleting local template at " + outputFolder + "/" + 167 | localTemplate(parserNum); 168 | TemplateUtils::runSystemCommand(cmd, error); 169 | } 170 | 171 | void TemplateParser::TemplateUtils::runSystemCommand(string command, 172 | string error) { 173 | int ret = system(command.c_str()); 174 | #ifdef _WIN32 175 | if(ret != 0) { 176 | throw Server::Exception(error); 177 | } 178 | #else 179 | if (WEXITSTATUS(ret) != EXIT_SUCCESS) { 180 | throw Server::Exception(error); 181 | } 182 | #endif 183 | } 184 | 185 | int TemplateParser::TemplateUtils::writeMapToFile( 186 | std::string fname, std::map *m) { 187 | int count = 0; 188 | if (m->empty()) 189 | return 0; 190 | 191 | FILE *fp = fopen(fname.c_str(), "w"); 192 | if (!fp) 193 | return -errno; 194 | 195 | for (std::map::iterator it = m->begin(); 196 | it != m->end(); it++) { 197 | fprintf(fp, "%s=%s\n", it->first.c_str(), it->second.c_str()); 198 | count++; 199 | } 200 | 201 | fclose(fp); 202 | return count; 203 | } -------------------------------------------------------------------------------- /utils/template_parser.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __TEMPLATE_PARSER__ 2 | #define __TEMPLATE_PARSER__ 3 | #include "../server/server.hpp" 4 | #include "../utils/request.hpp" 5 | #include "../utils/utilities.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace SysCmd { 12 | #ifdef _WIN32 13 | const std::string rm = "del "; 14 | const std::string programStart = ""; 15 | const std::string mkdir = "mkdir "; 16 | const std::string slash = "\\"; 17 | const std::string fileExtention = ".exe"; 18 | #else 19 | const std::string rm = "rm -f "; 20 | const std::string programStart = "./"; 21 | const std::string mkdir = "mkdir "; 22 | const std::string slash = "/"; 23 | const std::string fileExtention = ".o"; 24 | #endif 25 | } 26 | 27 | const std::string beginCodeBlockTag = "<%"; 28 | const std::string endCodeBlockTag = "%>"; 29 | const std::string utilitiesHeaderPath = "utils/utilities.hpp"; 30 | const std::string utilitiesPath = "utils/utilities.cpp"; 31 | const std::string cc = "g++ -std=c++11 -Wall -pedantic"; 32 | const std::string compileDirectory = "templateCompile"; 33 | const std::string toCompileFile = "compiled.cpp"; 34 | const std::string staticTemplate = "staticTemplate.html"; 35 | const std::string outputFolder = ".template"; 36 | const std::string mapFile = "map.txt"; 37 | const std::string localTemplate(const int parserNum); 38 | 39 | class TemplateParser { 40 | private: 41 | static int lastParserNum; 42 | int parserNum; 43 | std::string filePath; 44 | std::string code; 45 | std::map context; 46 | int variableCount; 47 | std::string html; 48 | std::string programName; 49 | 50 | void parseTemplate(); 51 | int findBeginOfCodeBlock(int startPosition, std::string &unparsedTemplate); 52 | int findEndOfCodeBlock(int startPosition, std::string &unparsedTemplate); 53 | void appendHTMLToCode(int begin, int end, std::string const &html); 54 | void appendCodeBlockToCode(int begin, int end, std::string &unparsedTemplate); 55 | void generateCode(); 56 | void addIncludesToCode(); 57 | void addReadFromTemplateToCode(); 58 | void addReturnToCode(); 59 | void addContextMapToCode(); 60 | std::string runGeneratedCode(); 61 | void makeExecutableTemplate(); 62 | void makeLocalTemplate(); 63 | void compileCode(); 64 | void deleteExecutable(); 65 | void deleteLocalTemplate(); 66 | class TemplateUtils { 67 | public: 68 | static void runSystemCommand(std::string command, std::string errorMessage); 69 | static int writeMapToFile(std::string fname, 70 | std::map *m); 71 | }; 72 | 73 | public: 74 | TemplateParser(std::string _filePath); 75 | ~TemplateParser(); 76 | std::string getHtml(std::map _context); 77 | }; 78 | 79 | #endif -------------------------------------------------------------------------------- /utils/utilities.cpp: -------------------------------------------------------------------------------- 1 | #include "utilities.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | char easyToLowerCase(char in) { 12 | if (in <= 'Z' && in >= 'A') 13 | return in - ('Z' - 'z'); 14 | return in; 15 | } 16 | 17 | string toLowerCase(string s) { 18 | transform(s.begin(), s.end(), s.begin(), easyToLowerCase); 19 | return s; 20 | } 21 | 22 | bool comp::operator()(const string &lhs, const string &rhs) const { 23 | return toLowerCase(lhs) < toLowerCase(rhs); 24 | } 25 | 26 | string readFile(const char *filename) { 27 | ifstream infile; 28 | infile.open(filename, infile.binary); 29 | if (!infile.is_open()) return string(); 30 | 31 | infile.seekg(0, infile.end); 32 | size_t length = infile.tellg(); 33 | infile.seekg(0, infile.beg); 34 | 35 | if (length > BUFFER_SIZE) 36 | length = BUFFER_SIZE; 37 | char* buffer = new char[length + 1]; 38 | 39 | infile.read(buffer, length); 40 | 41 | string s(buffer, length); 42 | delete[] buffer; 43 | return s; 44 | } 45 | 46 | string readFile(string filename) { return readFile(filename.c_str()); } 47 | 48 | vector split(string s, string delimiter, bool trim) { 49 | vector tokens; 50 | if (trim) 51 | s.erase(remove(s.begin(), s.end(), ' '), s.end()); 52 | size_t pos = 0; 53 | string token; 54 | while ((pos = s.find(delimiter)) != string::npos) { 55 | token = s.substr(0, pos); 56 | tokens.push_back(token); 57 | s.erase(0, pos + delimiter.length()); 58 | } 59 | tokens.push_back(s); 60 | return tokens; 61 | } 62 | 63 | void printVector(vector v) { 64 | for (string s : v) 65 | cout << s << endl; 66 | } 67 | 68 | string urlEncode(string const &str) { 69 | char encode_buf[4]; 70 | string result; 71 | encode_buf[0] = '%'; 72 | result.reserve(str.size()); 73 | 74 | // character selection for this algorithm is based on the following url: 75 | // http://www.blooberry.com/indexdot/html/topics/urlencoding.htm 76 | 77 | for (size_t pos = 0; pos < str.size(); ++pos) { 78 | switch (str[pos]) { 79 | default: 80 | if (str[pos] >= 32 && str[pos] < 127) { 81 | // character does not need to be escaped 82 | result += str[pos]; 83 | break; 84 | } 85 | // else pass through to next case 86 | case '$': 87 | case '&': 88 | case '+': 89 | case ',': 90 | case '/': 91 | case ':': 92 | case ';': 93 | case '=': 94 | case '?': 95 | case '@': 96 | case '"': 97 | case '<': 98 | case '>': 99 | case '#': 100 | case '%': 101 | case '{': 102 | case '}': 103 | case '|': 104 | case '\\': 105 | case '^': 106 | case '~': 107 | case '[': 108 | case ']': 109 | case '`': 110 | // the character needs to be encoded 111 | sprintf(encode_buf + 1, "%02X", str[pos]); 112 | result += encode_buf; 113 | break; 114 | } 115 | }; 116 | return result; 117 | } 118 | 119 | string urlDecode(string const &str) { 120 | char decode_buf[3]; 121 | string result; 122 | result.reserve(str.size()); 123 | 124 | for (size_t pos = 0; pos < str.size(); ++pos) { 125 | switch (str[pos]) { 126 | case '+': 127 | // convert to space character 128 | result += ' '; 129 | break; 130 | case '%': 131 | // decode hexidecimal value 132 | if (pos + 2 < str.size()) { 133 | decode_buf[0] = str[++pos]; 134 | decode_buf[1] = str[++pos]; 135 | decode_buf[2] = '\0'; 136 | result += static_cast(strtol(decode_buf, nullptr, 16)); 137 | } else { 138 | // recover from error by not decoding character 139 | result += '%'; 140 | } 141 | break; 142 | default: 143 | // character does not need to be escaped 144 | result += str[pos]; 145 | } 146 | } 147 | return result; 148 | } 149 | 150 | string getExtension(string filePath) { 151 | size_t pos = filePath.find_last_of("."); 152 | return filePath.substr(pos != string::npos ? pos + 1 : filePath.size()); 153 | } 154 | 155 | vector tokenize(const string &cnt, char delimiter) { 156 | vector res; 157 | istringstream is(cnt); 158 | string part; 159 | while (getline(is, part, delimiter)) 160 | res.push_back(part); 161 | return res; 162 | } 163 | 164 | void replaceAll(std::string &str, const std::string &from, 165 | const std::string &to) { 166 | if (from.empty()) 167 | return; 168 | size_t start_pos = 0; 169 | while ((start_pos = str.find(from, start_pos)) != std::string::npos) { 170 | str.replace(start_pos, from.length(), to); 171 | start_pos += to.length(); // In case 'to' contains 'from', like replacing 172 | // 'x' with 'yx' 173 | } 174 | } 175 | 176 | int findSubStrPosition(std::string &str, std::string const &subStr, 177 | int const &pos) { 178 | size_t found = str.find(subStr, pos); 179 | if (found == string::npos) 180 | return -1; 181 | return found; 182 | } 183 | 184 | int writeObjectToFile(const char *object, int size, 185 | std::string const &filePath) { 186 | ofstream file; 187 | file.open(filePath, fstream::binary); 188 | if (!file.is_open()) 189 | return -1; 190 | file.write(object, size); 191 | file.close(); 192 | return sizeof(object); 193 | } 194 | 195 | int writeToFile(std::string const &str, std::string const &filePath) { 196 | return writeObjectToFile(str.c_str(), str.length(), filePath); 197 | } 198 | 199 | cimap getCimapFromString(std::string str) { 200 | cimap m; 201 | vector tokenized = tokenize(str, '&'); 202 | for (auto token : tokenized) { 203 | vector keyValue = tokenize(token, '='); 204 | if (keyValue.size() != 2) 205 | continue; 206 | string key = keyValue[0]; 207 | string value = keyValue[1]; 208 | m[key] = value; 209 | } 210 | return m; 211 | } 212 | int readMapFromFile(std::string fname, std::map *m) { 213 | std::ifstream inputStream(fname); 214 | if (!inputStream.is_open()) 215 | return -errno; 216 | 217 | std::string line; 218 | while (std::getline(inputStream, line)) { 219 | auto tokens = tokenize(line, '='); 220 | // KEY VALUE 221 | (*m)[tokens[0]] = tokens[(tokens.size() < 2) ? 0 : 1]; 222 | } 223 | 224 | inputStream.close(); 225 | return (*m).size(); 226 | } -------------------------------------------------------------------------------- /utils/utilities.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __UTILILITES__ 2 | #define __UTILILITES__ 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define BUFFER_SIZE 4145152 9 | 10 | struct comp { 11 | bool operator()(const std::string &lhs, const std::string &rhs) const; 12 | }; 13 | 14 | typedef std::map 15 | cimap; // Case-Insensitive map 16 | 17 | std::string readFile(const char *filename); 18 | std::string readFile(std::string filename); 19 | std::string getExtension(std::string filePath); 20 | void printVector(std::vector); 21 | std::vector split(std::string s, std::string d, bool trim = true); 22 | 23 | std::string urlEncode(std::string const &); 24 | std::string urlDecode(std::string const &); 25 | 26 | std::string toLowerCase(std::string); 27 | 28 | std::vector tokenize(std::string const &, char delimiter); 29 | void replaceAll(std::string &str, const std::string &from, 30 | const std::string &to); 31 | 32 | int findSubStrPosition(std::string &str, std::string const &subStr, 33 | int const &pos); 34 | int writeObjectToFile(const char *object, int sizem, 35 | std::string const &filePath); 36 | int writeToFile(std::string const &str, std::string const &filePath); 37 | int readMapFromFile(std::string fname, std::map *m); 38 | 39 | cimap getCimapFromString(std::string); 40 | #endif 41 | --------------------------------------------------------------------------------