Browse Source

Added post method support.

yhirose 13 years ago
parent
commit
e0a25745cf
2 changed files with 198 additions and 67 deletions
  1. 114 51
      httplib.h
  2. 84 16
      test/test.cc

+ 114 - 51
httplib.h

@@ -53,11 +53,14 @@ struct Request {
     std::string url;
     MultiMap    headers;
     std::string body;
-    Map         query;
-    Match       match;
+    Map         params;
+    Match       matches;
 
     bool has_header(const char* key) const;
     std::string get_header_value(const char* key) const;
+    void set_header(const char* key, const char* val);
+
+    bool has_param(const char* key) const;
 };
 
 struct Response {
@@ -99,7 +102,6 @@ private:
 
     void process_request(FILE* fp_read, FILE* fp_write);
     bool read_request_line(FILE* fp, Request& req);
-    void write_response(FILE* fp, const Response& res);
     void dispatch_request(Connection& c, Handlers& handlers);
 
     const std::string host_;
@@ -118,6 +120,8 @@ public:
     ~Client();
 
     bool get(const char* url, Response& res);
+    bool post(const char* url, const std::string& body, const char* content_type, Response& res);
+    bool send(const Request& req, Response& res);
 
 private:
     bool read_response_line(FILE* fp, Response& res);
@@ -319,6 +323,64 @@ bool read_content(T& x, FILE* fp)
     return true;
 }
 
+template <typename T>
+inline void write_headers(FILE* fp, const T& x)
+{
+    fprintf(fp, "Connection: close\r\n");
+
+    for (auto it = x.headers.begin(); it != x.headers.end(); ++it) {
+        if (it->first != "Content-Type" && it->first != "Content-Length") {
+            fprintf(fp, "%s: %s\r\n", it->first.c_str(), it->second.c_str());
+        }
+    }
+
+    if (!x.body.empty()) {
+        auto content_type = get_header_value_text(x.headers, "Content-Type", "text/plain");
+        fprintf(fp, "Content-Type: %s\r\n", content_type);
+        fprintf(fp, "Content-Length: %ld\r\n", x.body.size());
+    }
+
+    fprintf(fp, "\r\n");
+}
+
+inline void write_response(FILE* fp, const Response& res)
+{
+    fprintf(fp, "HTTP/1.0 %d %s\r\n", res.status, status_message(res.status));
+
+    write_headers(fp, res);
+
+    if (!res.body.empty()) {
+        fprintf(fp, "%s", res.body.c_str());
+    }
+}
+
+inline void write_request(FILE* fp, const Request& req)
+{
+    fprintf(fp, "%s %s HTTP/1.0\r\n", req.method.c_str(), req.url.c_str());
+
+    write_headers(fp, req);
+
+    if (!req.body.empty()) {
+        fprintf(fp, "%s", req.body.c_str());
+    }
+}
+
+inline void parse_query_text(const char* b, const char* e, Map& params)
+{
+    split(b, e, '&', [&](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);
+            }
+        });
+        params[key] = val;
+    });
+}
+
 // HTTP server implementation
 inline bool Request::has_header(const char* key) const
 {
@@ -330,6 +392,16 @@ inline std::string Request::get_header_value(const char* key) const
     return get_header_value_text(headers, key, "");
 }
 
+inline void Request::set_header(const char* key, const char* val)
+{
+    headers.insert(std::make_pair(key, val));
+}
+
+inline bool Request::has_param(const char* key) const
+{
+    return params.find(key) != params.end();
+}
+
 inline bool Response::has_header(const char* key) const
 {
     return headers.find(key) != headers.end();
@@ -452,18 +524,7 @@ inline bool Server::read_request_line(FILE* fp, Request& req)
         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);
-                    }
-                });
-                req.query[key] = val;
-            });
+            parse_query_text(pos.first, pos.second, req.params);
         }
 
         return true;
@@ -472,37 +533,13 @@ inline bool Server::read_request_line(FILE* fp, Request& req)
     return false;
 }
 
-inline void Server::write_response(FILE* fp, const Response& res)
-{
-    fprintf(fp, "HTTP/1.0 %d %s\r\n", res.status, status_message(res.status));
-    fprintf(fp, "Connection: close\r\n");
-
-    for (auto it = res.headers.begin(); it != res.headers.end(); ++it) {
-        if (it->first != "Content-Type" && it->second != "Content-Length") {
-            fprintf(fp, "%s: %s\r\n", it->first.c_str(), it->second.c_str());
-        }
-    }
-
-    if (!res.body.empty()) {
-        auto content_type = get_header_value_text(res.headers, "Content-Type", "text/plain");
-        fprintf(fp, "Content-Type: %s\r\n", content_type);
-        fprintf(fp, "Content-Length: %ld\r\n", res.body.size());
-    }
-
-    fprintf(fp, "\r\n");
-
-    if (!res.body.empty()) {
-        fprintf(fp, "%s", res.body.c_str());
-    }
-}
-
 inline void Server::dispatch_request(Connection& c, Handlers& handlers)
 {
     for (auto it = handlers.begin(); it != handlers.end(); ++it) {
         const auto& pattern = it->first;
         const auto& handler = it->second;
 
-        if (std::regex_match(c.request.url, c.request.match, pattern)) {
+        if (std::regex_match(c.request.url, c.request.matches, pattern)) {
             handler(c);
 
             if (!c.response.status) {
@@ -516,33 +553,41 @@ inline void Server::dispatch_request(Connection& c, Handlers& handlers)
 inline void Server::process_request(FILE* fp_read, FILE* fp_write)
 {
     Connection c;
+    auto& req = c.request;
+    auto& res = c.response;
 
-    if (!read_request_line(fp_read, c.request) ||
-        !read_headers(fp_read, c.request.headers)) {
+    if (!read_request_line(fp_read, req) ||
+        !read_headers(fp_read, req.headers)) {
         return;
     }
     
     // Routing
-    c.response.status = 0;
+    res.status = 0;
 
-    if (c.request.method == "GET") {
+    if (req.method == "GET") {
         dispatch_request(c, get_handlers_);
-    } else if (c.request.method == "POST") {
-        if (!read_content(c.request, fp_read)) {
+    } else if (req.method == "POST") {
+        if (!read_content(req, fp_read)) {
             return;
         }
+        if (req.get_header_value("Content-Type") == "application/x-www-form-urlencoded") {
+            // Parse query text
+            const char* b = &req.body[0];
+            const char* e = &req.body[req.body.size()];
+            parse_query_text(b, e, req.params);
+        }
         dispatch_request(c, post_handlers_);
     }
 
-    if (!c.response.status) {
-        c.response.status = 404;
+    if (!res.status) {
+        res.status = 404;
     }
 
-    if (400 <= c.response.status && error_handler_) {
+    if (400 <= res.status && error_handler_) {
         error_handler_(c);
     }
 
-    write_response(fp_write, c.response);
+    write_response(fp_write, res);
 
     if (logger_) {
         logger_(c);
@@ -586,6 +631,24 @@ inline bool Client::read_response_line(FILE* fp, Response& res)
 }
 
 inline bool Client::get(const char* url, Response& res)
+{
+    Request req;
+    req.method = "GET";
+    req.url = url;
+    return send(req, res);
+}
+
+inline bool Client::post(const char* url, const std::string& body, const char* content_type, Response& res)
+{
+    Request req;
+    req.method = "POST";
+    req.url = url;
+    req.set_header("Content-Type", content_type);
+    req.body = body;
+    return send(req, res);
+}
+
+inline bool Client::send(const Request& req, Response& res)
 {
     socket_t sock = create_client_socket(host_.c_str(), port_);
     if (sock == -1) {
@@ -597,7 +660,7 @@ inline bool Client::get(const char* url, Response& res)
     get_flie_pointers(sock, fp_read, fp_write);
 
     // Send request
-    fprintf(fp_write, "GET %s HTTP/1.0\r\n\r\n", url);
+    write_request(fp_write, req);
     fflush(fp_write);
 
     if (!read_response_line(fp_read, res) ||

+ 84 - 16
test/test.cc

@@ -24,9 +24,21 @@ TEST(SplitTest, ParseQueryString)
         dic[key] = val;
     });
 
-    ASSERT_EQ("val1", dic["key1"]);
-    ASSERT_EQ("val2", dic["key2"]);
-    ASSERT_EQ("val3", dic["key3"]);
+    EXPECT_EQ("val1", dic["key1"]);
+    EXPECT_EQ("val2", dic["key2"]);
+    EXPECT_EQ("val3", dic["key3"]);
+}
+
+TEST(ParseQueryTest, ParseQueryString)
+{
+    string s = "key1=val1&key2=val2&key3=val3";
+    map<string, string> dic;
+
+    parse_query_text(&s[0], &s[s.size()], dic);
+
+    EXPECT_EQ("val1", dic["key1"]);
+    EXPECT_EQ("val2", dic["key2"]);
+    EXPECT_EQ("val3", dic["key3"]);
 }
 
 TEST(SocketTest, OpenClose)
@@ -35,7 +47,7 @@ TEST(SocketTest, OpenClose)
     ASSERT_NE(-1, sock);
 
     auto ret = close_server_socket(sock);
-    ASSERT_EQ(0, ret);
+    EXPECT_EQ(0, ret);
 }
 
 TEST(GetHeaderValueTest, DefaultValue)
@@ -49,7 +61,7 @@ TEST(GetHeaderValueTest, DefaultValueInt)
 {
     MultiMap map = {{"Dummy","Dummy"}};
     auto val = get_header_value_int(map, "Content-Length", 100);
-    ASSERT_EQ(100, val);
+    EXPECT_EQ(100, val);
 }
 
 TEST(GetHeaderValueTest, RegularValue)
@@ -63,12 +75,13 @@ TEST(GetHeaderValueTest, RegularValueInt)
 {
     MultiMap map = {{"Content-Length","100"}, {"Dummy", "Dummy"}};
     auto val = get_header_value_int(map, "Content-Length", 0);
-    ASSERT_EQ(100, val);
+    EXPECT_EQ(100, val);
 }
 
 class ServerTest : public ::testing::Test {
 protected:
     ServerTest() : svr_(host_, port_) {
+        persons_["john"] = "programmer";
     }
 
     virtual void SetUp() {
@@ -78,6 +91,24 @@ protected:
         svr_.get("/", [&](httplib::Connection& c) {
             c.response.set_redirect("/hi");
         });
+        svr_.post("/person", [&](httplib::Connection& c) {
+            const auto& req = c.request;
+            if (req.has_param("name") && req.has_param("note")) {
+                persons_[req.params.at("name")] = req.params.at("note");
+            } else {
+                c.response.status = 400;
+            }
+        });
+        svr_.get("/person/(.*)", [&](httplib::Connection& c) {
+            const auto& req = c.request;
+            std::string name = req.matches[1];
+            if (persons_.find(name) != persons_.end()) {
+                auto note = persons_[name];
+                c.response.set_content(note, "text/plain");
+            } else {
+                c.response.status = 404;
+            }
+        });
         f_ = async([&](){ svr_.run(); });
     }
 
@@ -86,10 +117,11 @@ protected:
         f_.get();
     }
 
-    const char*       host_ = "localhost";
-    int               port_ = 1914;
-    Server            svr_;
-    std::future<void> f_;
+    const char*                        host_ = "localhost";
+    int                                port_ = 1914;
+    std::map<std::string, std::string> persons_;
+    Server                             svr_;
+    std::future<void>                  f_;
 };
 
 TEST_F(ServerTest, GetMethod200)
@@ -97,9 +129,9 @@ TEST_F(ServerTest, GetMethod200)
     Response res;
     bool ret = Client(host_, port_).get("/hi", res);
     ASSERT_EQ(true, ret);
-    ASSERT_EQ(200, res.status);
-    ASSERT_EQ("text/plain", res.get_header_value("Content-Type"));
-    ASSERT_EQ("Hello World!", res.body);
+    EXPECT_EQ(200, res.status);
+    EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
+    EXPECT_EQ("Hello World!", res.body);
 }
 
 TEST_F(ServerTest, GetMethod302)
@@ -107,8 +139,8 @@ TEST_F(ServerTest, GetMethod302)
     Response res;
     bool ret = Client(host_, port_).get("/", res);
     ASSERT_EQ(true, ret);
-    ASSERT_EQ(302, res.status);
-    ASSERT_EQ("/hi", res.get_header_value("Location"));
+    EXPECT_EQ(302, res.status);
+    EXPECT_EQ("/hi", res.get_header_value("Location"));
 }
 
 TEST_F(ServerTest, GetMethod404)
@@ -116,7 +148,43 @@ TEST_F(ServerTest, GetMethod404)
     Response res;
     bool ret = Client(host_, port_).get("/invalid", res);
     ASSERT_EQ(true, ret);
-    ASSERT_EQ(404, res.status);
+    EXPECT_EQ(404, res.status);
+}
+
+TEST_F(ServerTest, GetMethodPersonJohn)
+{
+    Response res;
+    bool ret = Client(host_, port_).get("/person/john", res);
+    ASSERT_EQ(true, ret);
+    EXPECT_EQ(200, res.status);
+    EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
+    EXPECT_EQ("programmer", res.body);
+}
+
+TEST_F(ServerTest, PostMethod)
+{
+    {
+        Response res;
+        bool ret = Client(host_, port_).get("/person/john2", res);
+        ASSERT_EQ(true, ret);
+        ASSERT_EQ(404, res.status);
+    }
+    {
+        auto content = "name=john2&note=coder";
+        auto content_type = "application/x-www-form-urlencoded";
+        Response res;
+        bool ret = Client(host_, port_).post("/person", content, content_type, res);
+        ASSERT_EQ(true, ret);
+        ASSERT_EQ(200, res.status);
+    }
+    {
+        Response res;
+        bool ret = Client(host_, port_).get("/person/john2", res);
+        ASSERT_EQ(true, ret);
+        ASSERT_EQ(200, res.status);
+        ASSERT_EQ("text/plain", res.get_header_value("Content-Type"));
+        ASSERT_EQ("coder", res.body);
+    }
 }
 
 // vim: et ts=4 sw=4 cin cino={1s ff=unix