Browse Source

Supported GET routing.

yhirose 13 years ago
parent
commit
107a2b9b5d
2 changed files with 135 additions and 50 deletions
  1. 38 8
      example/sample.cc
  2. 97 42
      httpsvrkit.h

+ 38 - 8
example/sample.cc

@@ -8,22 +8,52 @@
 #include <httpsvrkit.h>
 #include <httpsvrkit.h>
 #include <cstdio>
 #include <cstdio>
 
 
-int main(void)
+using namespace httpsvrkit;
+
+int dump_request(Context& cxt)
 {
 {
-    using namespace httpsvrkit;
+    auto& body = cxt.response.body;
+    char buf[BUFSIZ];
+
+    body += "================================\n";
+
+    sprintf(buf, "Method: %s, URL: %s\n",
+        cxt.request.method.c_str(),
+        cxt.request.url.c_str());
+
+    body += buf;
+
+    //for (const auto& x : cxt.request.headers) {
+    for (auto it = cxt.request.headers.begin(); it != cxt.request.headers.end(); ++it) {
+       const auto& x = *it;
+       sprintf(buf, "%s: %s\n", x.first.c_str(), x.second.c_str());
+       body += buf;
+    }
 
 
+    body += "================================\n";
+
+    return 200;
+}
+
+int main(void)
+{
     Server svr;
     Server svr;
 
 
-    svr.get("/", [](Context& cxt) {
-        cxt.response.body = "<html><head></head><body><ul></ul></body></html>";
+    svr.get("/", [](Context& cxt) -> int {
+        dump_request(cxt);
+        return 200;
     });
     });
 
 
-    svr.post("/item", [](Context& cxt) {
-        cxt.response.body = cxt.request.pattern;
+    svr.post("/item", [](Context& cxt) -> int {
+        dump_request(cxt);
+        cxt.response.body += cxt.request.url;
+        return 200;
     });
     });
 
 
-    svr.get("/item/:name", [](Context& cxt) {
-         cxt.response.body = cxt.request.params.at("name");
+    svr.get("/item/([^/]+)", [](Context& cxt) -> int {
+        dump_request(cxt);
+        cxt.response.body += cxt.request.params[0];
+        return 200;
     });
     });
 
 
     svr.run("localhost", 1234);
     svr.run("localhost", 1234);

+ 97 - 42
httpsvrkit.h

@@ -35,14 +35,17 @@ namespace httpsvrkit
 {
 {
 
 
 typedef std::map<std::string, std::string>      Map;
 typedef std::map<std::string, std::string>      Map;
+typedef std::vector<std::string>                Array;
 typedef std::multimap<std::string, std::string> MultiMap;
 typedef std::multimap<std::string, std::string> MultiMap;
 
 
 // HTTP request
 // HTTP request
 struct Request {
 struct Request {
+    std::string method;
+    std::string url;
     Map         headers;
     Map         headers;
     std::string body;
     std::string body;
-    std::string pattern;
-    Map         params;
+    Map         query;
+    Array       params;
 };
 };
 
 
 // HTTP response
 // HTTP response
@@ -52,14 +55,14 @@ struct Response {
 };
 };
 
 
 struct Context {
 struct Context {
-    const Request request;
-    Response      response;
+    Request  request;
+    Response response;
 };
 };
 
 
 // HTTP server
 // HTTP server
 class Server {
 class Server {
 public:
 public:
-    typedef std::function<void (Context& context)> Handler;
+    typedef std::function<int (Context& context)> Handler;
 
 
     Server();
     Server();
     ~Server();
     ~Server();
@@ -74,11 +77,31 @@ private:
     void process_request(FILE* fp_read, FILE* fp_write);
     void process_request(FILE* fp_read, FILE* fp_write);
 
 
     socket_t sock_;
     socket_t sock_;
-    std::multimap<std::string, Handler> handlers_;
+    std::vector<std::pair<std::regex, Handler>> get_handlers_;
+    std::vector<std::pair<std::string, Handler>> post_handlers_;
 };
 };
 
 
 // Implementation
 // Implementation
 
 
+template <class Fn>
+void split(const char* b, const char* e, char d, Fn fn)
+{
+    int i = 0;
+    int beg = 0;
+
+    while (e ? (b + i != e) : (b[i] != '\0')) {
+        if (b[i] == d) {
+            fn(&b[beg], &b[i]);
+            beg = i + 1;
+        }
+        i++;
+    }
+
+    if (i != 0) {
+        fn(&b[beg], &b[i]);
+    }
+}
+
 inline socket_t create_server_socket(const const char* ipaddr_or_hostname, int port)
 inline socket_t create_server_socket(const const char* ipaddr_or_hostname, int port)
 {
 {
     // Create a server socket
     // Create a server socket
@@ -154,12 +177,12 @@ inline Server::~Server()
 
 
 inline void Server::get(const char* pattern, Handler handler)
 inline void Server::get(const char* pattern, Handler handler)
 {
 {
-    handlers_.insert(std::make_pair(pattern, handler));
+    get_handlers_.push_back(std::make_pair(pattern, handler));
 }
 }
 
 
 inline void Server::post(const char* pattern, Handler handler)
 inline void Server::post(const char* pattern, Handler handler)
 {
 {
-    handlers_.insert(std::make_pair(pattern, handler));
+    post_handlers_.push_back(std::make_pair(pattern, handler));
 }
 }
 
 
 inline bool Server::run(const const char*ipaddr_or_hostname, int port)
 inline bool Server::run(const const char*ipaddr_or_hostname, int port)
@@ -205,9 +228,9 @@ inline void Server::stop()
     sock_ = -1;
     sock_ = -1;
 }
 }
 
 
-inline bool read_request_line(FILE* fp, std::string& method, std::string& url)
+inline bool read_request_line(FILE* fp, Request& request)
 {
 {
-    static std::regex re("(GET|POST) (.+) HTTP/1\\.1\r\n");
+    static std::regex re("(GET|POST) ([^?]+)(?:\\?(.+?))? HTTP/1\\.1\r\n");
 
 
     const size_t BUFSIZ_REQUESTLINE = 2048;
     const size_t BUFSIZ_REQUESTLINE = 2048;
     char buf[BUFSIZ_REQUESTLINE];
     char buf[BUFSIZ_REQUESTLINE];
@@ -215,8 +238,27 @@ inline bool read_request_line(FILE* fp, std::string& method, std::string& url)
 
 
     std::cmatch m;
     std::cmatch m;
     if (std::regex_match(buf, m, re)) {
     if (std::regex_match(buf, m, re)) {
-        method = std::string(m[1]);
-        url = std::string(m[2]);
+        request.method = std::string(m[1]);
+        request.url = std::string(m[2]);
+
+        // Parse query text
+        auto len = std::distance(m[3].first, m[3].second);
+        if (len > 0) {
+            const auto& pos = m[3];
+            split(pos.first, pos.second, '&', [&](const char* b, const char* e) {
+                std::string key;
+                std::string val;
+                split(b, e, '=', [&](const char* b, const char* e) {
+                    if (key.empty()) {
+                        key.assign(b, e);
+                    } else {
+                        val.assign(b, e);
+                    }
+                });
+                request.query[key] = val;
+            });
+        }
+
         return true;
         return true;
     }
     }
 
 
@@ -249,11 +291,11 @@ inline void write_plain_text(FILE* fp, const char* s)
     fprintf(fp, "%s", s);
     fprintf(fp, "%s", s);
 }
 }
 
 
-inline void write_error(FILE* fp, int code)
+inline void write_error(FILE* fp, int status)
 {
 {
     const char* msg = NULL;
     const char* msg = NULL;
 
 
-    switch (code) {
+    switch (status) {
     case 400:
     case 400:
         msg = "Bad Request";
         msg = "Bad Request";
         break;
         break;
@@ -261,47 +303,60 @@ inline void write_error(FILE* fp, int code)
         msg = "Not Found";
         msg = "Not Found";
         break;
         break;
     default:
     default:
-        code = 500;
+        status = 500;
         msg = "Internal Server Error";
         msg = "Internal Server Error";
         break;
         break;
     }
     }
 
 
     assert(msg);
     assert(msg);
 
 
-    fprintf(fp, "HTTP/1.0 %d %s\r\n", code, msg);
+    fprintf(fp, "HTTP/1.0 %d %s\r\n", status, msg);
     fprintf(fp, "Content-type: text/plain\r\n");
     fprintf(fp, "Content-type: text/plain\r\n");
     fprintf(fp, "Connection: close\r\n");
     fprintf(fp, "Connection: close\r\n");
     fprintf(fp, "\r\n");
     fprintf(fp, "\r\n");
-    fprintf(fp, "Status: %d\r\n", code);
+    fprintf(fp, "Status: %d\r\n", status);
 }
 }
 
 
 inline void Server::process_request(FILE* fp_read, FILE* fp_write)
 inline void Server::process_request(FILE* fp_read, FILE* fp_write)
 {
 {
-      // Read and parse request line
-      std::string method, url;
-      if (!read_request_line(fp_read, method, url)) {
-         write_error(fp_write, 400);
-         return;
-      }
-
-      // Read headers
-      Map headers;
-      read_headers(fp_read, headers);
-
-      // Write content
-      char buf[BUFSIZ];
-      std::string content;
-      sprintf(buf, "Method: %s, URL: %s\n", method.c_str(), url.c_str());
-      content += buf;
-
-      //for (const auto& x : headers) {
-      for (auto it = headers.begin(); it != headers.end(); ++it) {
-         const auto& x = *it;
-         sprintf(buf, "%s: %s\n", x.first.c_str(), x.second.c_str());
-         content += buf;
-      }
-
-      write_plain_text(fp_write, content.c_str());
+    Context cxt;
+
+    // Read and parse request line
+    if (!read_request_line(fp_read, cxt.request)) {
+        write_error(fp_write, 400);
+        return;
+    }
+
+    // Read headers
+    read_headers(fp_read, cxt.request.headers);
+
+    // Routing
+    int status = 404;
+
+    if (cxt.request.method == "GET") {
+        for (auto it = get_handlers_.begin(); it != get_handlers_.end(); ++it) {
+            const auto& pattern = it->first;
+            const auto& handler = it->second;
+            
+            std::smatch m;
+            if (std::regex_match(cxt.request.url, m, pattern)) {
+                for (size_t i = 1; i < m.size(); i++) {
+                    cxt.request.params.push_back(m[i]);
+                }
+                status = handler(cxt);
+            }
+        }
+    } else if (cxt.request.method == "POST") {
+        // TODO: parse body
+    } else {
+        status = 400;
+    }
+
+    if (status == 200) {
+        write_plain_text(fp_write, cxt.response.body.c_str());
+    } else {
+        write_error(fp_write, status);
+    }
 }
 }
 
 
 } // namespace httpsvrkit
 } // namespace httpsvrkit