Browse Source

Added client.

yhirose 13 years ago
parent
commit
762e7938fd
9 changed files with 449 additions and 141 deletions
  1. 6 3
      example/Makefile
  2. 28 0
      example/client.cc
  3. 86 0
      example/client.vcxproj
  4. 7 1
      example/example.sln
  5. 0 47
      example/sample.cc
  6. 91 0
      example/server.cc
  7. 2 2
      example/server.vcxproj
  8. 209 81
      httplib.h
  9. 20 7
      test/test.cc

+ 6 - 3
example/Makefile

@@ -9,10 +9,13 @@ CC = g++
 CFLAGS = -std=c++11 -g
 endif
 
-all: sample hello
+all: server client hello
 
-sample : sample.cc ../httplib.h
-	$(CC) -o sample $(CFLAGS) -I.. sample.cc
+server : server.cc ../httplib.h
+	$(CC) -o server $(CFLAGS) -I.. server.cc
+
+client : client.cc ../httplib.h
+	$(CC) -o client $(CFLAGS) -I.. client.cc
 
 hello : hello.cc ../httplib.h
 	$(CC) -o hello $(CFLAGS) -I.. hello.cc

+ 28 - 0
example/client.cc

@@ -0,0 +1,28 @@
+//
+//  client.cc
+//
+//  Copyright (c) 2012 Yuji Hirose. All rights reserved.
+//  The Boost Software License 1.0
+//
+
+#include <httplib.h>
+#include <cstdio>
+#include <signal.h>
+
+using namespace httplib;
+
+int main(void)
+{
+    using namespace httplib;
+
+    const char* hi = "/hi";
+
+    Client cli("localhost", 1234);
+
+    Response res;
+    cli.get(hi, res);
+
+    return 0;
+}
+
+// vim: et ts=4 sw=4 cin cino={1s ff=unix

+ 86 - 0
example/client.vcxproj

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{6DB1FC63-B153-4279-92B7-D8A11AF285D6}</ProjectGuid>
+    <Keyword>Win32Proj</Keyword>
+    <RootNamespace>client</RootNamespace>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>..</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="client.cc" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 7 - 1
example/sample.sln → example/example.sln

@@ -1,7 +1,9 @@
 
 Microsoft Visual Studio Solution File, Format Version 11.00
 # Visual Studio 2010
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample", "sample.vcxproj", "{864CD288-050A-4C8B-9BEF-3048BD876C5B}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "server", "server.vcxproj", "{864CD288-050A-4C8B-9BEF-3048BD876C5B}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "client", "client.vcxproj", "{6DB1FC63-B153-4279-92B7-D8A11AF285D6}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -13,6 +15,10 @@ Global
 		{864CD288-050A-4C8B-9BEF-3048BD876C5B}.Debug|Win32.Build.0 = Debug|Win32
 		{864CD288-050A-4C8B-9BEF-3048BD876C5B}.Release|Win32.ActiveCfg = Release|Win32
 		{864CD288-050A-4C8B-9BEF-3048BD876C5B}.Release|Win32.Build.0 = Release|Win32
+		{6DB1FC63-B153-4279-92B7-D8A11AF285D6}.Debug|Win32.ActiveCfg = Debug|Win32
+		{6DB1FC63-B153-4279-92B7-D8A11AF285D6}.Debug|Win32.Build.0 = Debug|Win32
+		{6DB1FC63-B153-4279-92B7-D8A11AF285D6}.Release|Win32.ActiveCfg = Release|Win32
+		{6DB1FC63-B153-4279-92B7-D8A11AF285D6}.Release|Win32.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 0 - 47
example/sample.cc

@@ -1,47 +0,0 @@
-//
-//  sample.cc
-//
-//  Copyright (c) 2012 Yuji Hirose. All rights reserved.
-//  The Boost Software License 1.0
-//
-
-#include <httplib.h>
-#include <cstdio>
-#include <signal.h>
-
-using namespace httplib;
-
-template<typename Fn> void signal(int sig, Fn fn)
-{
-    static std::function<void ()> signal_handler_;
-    struct SignalHandler { static void fn(int sig) { signal_handler_(); } };
-    signal_handler_ = fn;
-    signal(sig, SignalHandler::fn);
-}
-
-int main(void)
-{
-    using namespace httplib;
-
-    const char* hi = "/hi";
-
-    Server svr("localhost", 1234);
-
-    svr.get("/", [=](Connection& c) {
-        c.response.set_redirect(hi);
-    });
-
-    svr.get("/hi", [](Connection& c) {
-        c.response.set_content("Hello World!");
-    });
-
-    svr.get("/dump", [](Connection& c) {
-        c.response.set_content(dump_request(c));
-    });
-
-    signal(SIGINT, [&]() { svr.stop(); });
-
-    svr.run();
-}
-
-// vim: et ts=4 sw=4 cin cino={1s ff=unix

+ 91 - 0
example/server.cc

@@ -0,0 +1,91 @@
+//
+//  sample.cc
+//
+//  Copyright (c) 2012 Yuji Hirose. All rights reserved.
+//  The Boost Software License 1.0
+//
+
+#include <httplib.h>
+#include <cstdio>
+#include <signal.h>
+
+template<typename Fn> void signal(int sig, Fn fn)
+{
+    static std::function<void ()> signal_handler_;
+    struct SignalHandler { static void fn(int sig) { signal_handler_(); } };
+    signal_handler_ = fn;
+    signal(sig, SignalHandler::fn);
+}
+
+std::string log(const httplib::Connection& c)
+{
+    const auto& req = c.request;
+    const auto& res = c.response;
+
+    std::string s;
+    char buf[BUFSIZ];
+
+    s += "================================\n";
+
+    snprintf(buf, sizeof(buf), "%s %s", req.method.c_str(), req.url.c_str());
+    s += buf;
+
+    std::string query;
+    for (auto it = req.query.begin(); it != req.query.end(); ++it) {
+       const auto& x = *it;
+       snprintf(buf, sizeof(buf), "%c%s=%s",
+           (it == req.query.begin()) ? '?' : '&', x.first.c_str(), x.second.c_str());
+       query += buf;
+    }
+    snprintf(buf, sizeof(buf), "%s\n", query.c_str());
+    s += buf;
+
+    s += httplib::dump_headers(req.headers);
+
+    s += "--------------------------------\n";
+
+    snprintf(buf, sizeof(buf), "%d\n", res.status);
+    s += buf;
+    s += httplib::dump_headers(res.headers);
+    
+    if (!res.body.empty()) {
+        s += res.body;
+    }
+
+    s += "\n";
+
+    return s;
+}
+
+int main(void)
+{
+    using namespace httplib;
+
+    const char* hi = "/hi";
+
+    Server svr("localhost", 1234);
+
+    svr.get("/", [=](Connection& c) {
+        c.response.set_redirect(hi);
+    });
+
+    svr.get("/hi", [](Connection& c) {
+        c.response.set_content("Hello World!");
+    });
+
+    svr.get("/dump", [](Connection& c) {
+        c.response.set_content(log(c));
+    });
+
+    svr.set_logger([](const Connection& c) {
+        printf("%s", log(c).c_str());
+    });
+
+    signal(SIGINT, [&]() { svr.stop(); });
+
+    svr.run();
+
+    return 0;
+}
+
+// vim: et ts=4 sw=4 cin cino={1s ff=unix

+ 2 - 2
example/sample.vcxproj → example/server.vcxproj

@@ -78,9 +78,9 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
-    <ClCompile Include="sample.cc" />
+    <ClCompile Include="server.cc" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 209 - 81
httplib.h

@@ -49,17 +49,15 @@ typedef std::map<std::string, std::string>      Map;
 typedef std::vector<std::string>                Array;
 typedef std::multimap<std::string, std::string> MultiMap;
 
-// HTTP request
 struct Request {
     std::string method;
     std::string url;
-    Map         headers;
+    MultiMap    headers;
     std::string body;
     Map         query;
     Array       params;
 };
 
-// HTTP response
 struct Response {
     int         status;
     MultiMap    headers;
@@ -74,18 +72,18 @@ struct Connection {
     Response response;
 };
 
-// HTTP server
 class Server {
 public:
     typedef std::function<void (Connection& c)> Handler;
 
-    Server(const char* ipaddr_or_hostname, int port);
+    Server(const char* host, int port);
     ~Server();
 
     void get(const char* pattern, Handler handler);
     void post(const char* pattern, Handler handler);
 
     void on_ready(std::function<void ()> callback);
+    void set_logger(std::function<void (const Connection&)> logger);
 
     bool run();
     void stop();
@@ -93,13 +91,32 @@ public:
 private:
     void process_request(FILE* fp_read, FILE* fp_write);
 
-    const std::string ipaddr_or_hostname_;
+    bool read_request_line(FILE* fp, Request& request);
+    void write_response(FILE* fp, const Response& response);
+    void write_error(FILE* fp, int status);
+
+    const std::string host_;
     const int         port_;
     socket_t          sock_;
 
     std::vector<std::pair<std::regex, Handler>>  get_handlers_;
     std::vector<std::pair<std::string, Handler>> post_handlers_;
     std::function<void ()>                       on_ready_;
+    std::function<void (const Connection&)>      logger_;
+};
+
+class Client {
+public:
+    Client(const char* host, int port);
+    ~Client();
+
+    int get(const char* url, Response& response);
+
+private:
+    bool read_response_line(FILE* fp, Response& response);
+
+    const std::string host_;
+    const int         port_;
 };
 
 // Implementation
@@ -118,12 +135,25 @@ void split(const char* b, const char* e, char d, Fn fn)
         i++;
     }
 
-    if (i != 0) {
+    if (i) {
         fn(&b[beg], &b[i]);
     }
 }
 
-inline socket_t create_server_socket(const char* ipaddr_or_hostname, int port)
+inline void get_flie_pointers(int fd, FILE*& fp_read, FILE*& fp_write)
+{
+#ifdef _WIN32
+    int osfhandle = _open_osfhandle(fd, _O_RDONLY);
+    fp_read = fdopen(osfhandle, "rb");
+    fp_write = fdopen(osfhandle, "wb");
+#else
+    fp_read = fdopen(fd, "rb");
+    fp_write = fdopen(fd, "wb");
+#endif
+}
+
+template <typename Fn>
+inline socket_t create_socket(const char* host, int port, Fn fn)
 {
 #ifdef _WIN32
     int opt = SO_SYNCHRONOUS_NONALERT;
@@ -142,7 +172,7 @@ inline socket_t create_server_socket(const char* ipaddr_or_hostname, int port)
 
     // Get a host entry info
     struct hostent* hp;
-    if (!(hp = gethostbyname(ipaddr_or_hostname))) {
+    if (!(hp = gethostbyname(host))) {
         return -1;
     }
 
@@ -153,16 +183,24 @@ inline socket_t create_server_socket(const char* ipaddr_or_hostname, int port)
     addr.sin_family = AF_INET;
     addr.sin_port = htons(port);
 
-    if (::bind(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
-        return -1;
-    }
+    return fn(sock, addr);
+}
 
-    // Listen through 5 channels
-    if (listen(sock, 5) != 0) {
-        return -1;
-    }
+inline socket_t create_server_socket(const char* host, int port)
+{
+    return create_socket(host, port, [](socket_t sock, struct sockaddr_in& addr) -> socket_t {
+
+        if (::bind(sock, (struct sockaddr*)&addr, sizeof(addr))) {
+            return -1;
+        }
 
-    return sock;
+        // Listen through 5 channels
+        if (listen(sock, 5)) {
+            return -1;
+        }
+
+        return sock;
+    });
 }
 
 inline int close_server_socket(socket_t sock)
@@ -176,27 +214,69 @@ inline int close_server_socket(socket_t sock)
 #endif
 }
 
-std::string dump_request(Connection& c)
+inline socket_t create_client_socket(const char* host, int port)
 {
-    const auto& req = c.request;
-    std::string s;
-    char buf[BUFSIZ];
+    return create_socket(host, port,
+            [](socket_t sock, struct sockaddr_in& addr) -> socket_t {
 
-    s += "================================\n";
+        if (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))) {
+            return -1;
+        }
 
-    snprintf(buf, sizeof(buf), "%s %s", req.method.c_str(), req.url.c_str());
-    s += buf;
+        return sock;
+    });
+}
 
-    std::string query;
-    for (auto it = req.query.begin(); it != req.query.end(); ++it) {
-       const auto& x = *it;
-       snprintf(buf, sizeof(buf), "%c%s=%s", (it == req.query.begin()) ? '?' : '&', x.first.c_str(), x.second.c_str());
-       query += buf;
+inline int close_client_socket(socket_t sock)
+{
+#ifdef _WIN32
+    return closesocket(sock);
+#else
+    return close(sock);
+#endif
+}
+
+inline const char* get_header_value(const MultiMap& map, const char* key, const char* def)
+{
+    auto it = map.find(key);
+    if (it != map.end()) {
+        return it->second.c_str();
+    }
+    return def;
+}
+
+inline int get_header_value_int(const MultiMap& map, const char* key, int def)
+{
+    auto it = map.find(key);
+    if (it != map.end()) {
+        return std::atoi(it->second.c_str());
+    }
+    return def;
+}
+
+inline void read_headers(FILE* fp, MultiMap& headers)
+{
+    static std::regex re("(.+?): (.+?)\r\n");
+
+    const size_t BUFSIZ_HEADER = 2048;
+    char buf[BUFSIZ_HEADER];
+
+    while (fgets(buf, BUFSIZ_HEADER, fp) && strcmp(buf, "\r\n")) {
+        std::cmatch m;
+        if (std::regex_match(buf, m, re)) {
+            auto key = std::string(m[1]);
+            auto val = std::string(m[2]);
+            headers.insert(std::make_pair(key, val));
+        }
     }
-    snprintf(buf, sizeof(buf), "%s\n", query.c_str());
-    s += buf;
+}
+
+inline std::string dump_headers(const MultiMap& headers)
+{
+    std::string s;
+    char buf[BUFSIZ];
 
-    for (auto it = req.headers.begin(); it != req.headers.end(); ++it) {
+    for (auto it = headers.begin(); it != headers.end(); ++it) {
        const auto& x = *it;
        snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
        s += buf;
@@ -205,21 +285,22 @@ std::string dump_request(Connection& c)
     return s;
 }
 
-void Response::set_redirect(const char* url)
+// HTTP server implementation
+inline void Response::set_redirect(const char* url)
 {
     headers.insert(std::make_pair("Location", url));
     status = 302;
 }
 
-void Response::set_content(const std::string& s, const char* content_type)
+inline void Response::set_content(const std::string& s, const char* content_type)
 {
     body = s;
     headers.insert(std::make_pair("Content-Type", content_type));
     status = 200;
 }
 
-inline Server::Server(const char* ipaddr_or_hostname, int port)
-    : ipaddr_or_hostname_(ipaddr_or_hostname)
+inline Server::Server(const char* host, int port)
+    : host_(host)
     , port_(port)
     , sock_(-1)
 {
@@ -251,9 +332,14 @@ inline void Server::on_ready(std::function<void ()> callback)
     on_ready_ = callback;
 }
 
+inline void Server::set_logger(std::function<void (const Connection&)> logger)
+{
+    logger_ = logger;
+}
+
 inline bool Server::run()
 {
-    sock_ = create_server_socket(ipaddr_or_hostname_.c_str(), port_);
+    sock_ = create_server_socket(host_.c_str(), port_);
     if (sock_ == -1) {
         return false;
     }
@@ -274,14 +360,9 @@ inline bool Server::run()
             return false;
         }
 
-#ifdef _WIN32
-        int osfhandle = _open_osfhandle(fd, _O_RDONLY);
-        FILE* fp_read = fdopen(osfhandle, "rb");
-        FILE* fp_write = fdopen(osfhandle, "wb");
-#else
-        FILE* fp_read = fdopen(fd, "rb");
-        FILE* fp_write = fdopen(fd, "wb");
-#endif
+        FILE* fp_read;
+        FILE* fp_write;
+        get_flie_pointers(fd, fp_read, fp_write);
 
         process_request(fp_read, fp_write);
 
@@ -298,13 +379,15 @@ inline void Server::stop()
     sock_ = -1;
 }
 
-inline bool read_request_line(FILE* fp, Request& request)
+inline bool Server::read_request_line(FILE* fp, Request& request)
 {
-    static std::regex re("(GET|POST) ([^?]+)(?:\\?(.+?))? HTTP/1\\.1\r\n");
-
     const size_t BUFSIZ_REQUESTLINE = 2048;
     char buf[BUFSIZ_REQUESTLINE];
-    fgets(buf, BUFSIZ_REQUESTLINE, fp);
+    if (!fgets(buf, BUFSIZ_REQUESTLINE, fp)) {
+        return false;
+    }
+
+    static std::regex re("(GET|POST) ([^?]+)(?:\\?(.+?))? HTTP/1\\.[01]\r\n");
 
     std::cmatch m;
     if (std::regex_match(buf, m, re)) {
@@ -335,33 +418,7 @@ inline bool read_request_line(FILE* fp, Request& request)
     return false;
 }
 
-inline void read_headers(FILE* fp, Map& headers)
-{
-    static std::regex re("(.+?): (.+?)\r\n");
-
-    const size_t BUFSIZ_HEADER = 2048;
-    char buf[BUFSIZ_HEADER];
-
-    while (fgets(buf, BUFSIZ_HEADER, fp) && strcmp(buf, "\r\n")) {
-        std::cmatch m;
-        if (std::regex_match(buf, m, re)) {
-            auto key = std::string(m[1]);
-            auto val = std::string(m[2]);
-            headers[key] = val;
-        }
-    }
-}
-
-inline const char* get_header_value(const MultiMap& map, const char* key, const char* def)
-{
-    auto it = map.find(key);
-    if (it != map.end()) {
-        return it->second.c_str();
-    }
-    return def;
-}
-
-inline void write_response(FILE* fp, const Response& response)
+inline void Server::write_response(FILE* fp, const Response& response)
 {
     fprintf(fp, "HTTP/1.0 %d OK\r\n", response.status);
     fprintf(fp, "Connection: close\r\n");
@@ -385,7 +442,7 @@ inline void write_response(FILE* fp, const Response& response)
     }
 }
 
-inline void write_error(FILE* fp, int status)
+inline void Server::write_error(FILE* fp, int status)
 {
     const char* msg = NULL;
 
@@ -415,17 +472,13 @@ inline void Server::process_request(FILE* fp_read, FILE* fp_write)
 {
     Connection c;
 
-    // Read and parse request line
     if (!read_request_line(fp_read, c.request)) {
         write_error(fp_write, 400);
         return;
     }
 
-    // Read headers
     read_headers(fp_read, c.request.headers);
     
-    printf("%s", dump_request(c).c_str());
-
     // Routing
     c.response.status = 404;
 
@@ -449,6 +502,10 @@ inline void Server::process_request(FILE* fp_read, FILE* fp_write)
         c.response.status = 400;
     }
 
+    if (logger_) {
+        logger_(c);
+    }
+
     if (200 <= c.response.status && c.response.status < 400) {
         write_response(fp_write, c.response);
     } else {
@@ -456,6 +513,77 @@ inline void Server::process_request(FILE* fp_read, FILE* fp_write)
     }
 }
 
+// HTTP client implementation
+inline Client::Client(const char* host, int port)
+    : host_(host)
+    , port_(port)
+{
+#ifdef _WIN32
+    WSADATA wsaData;
+    WSAStartup(0x0002, &wsaData);
+#endif
+}
+
+inline Client::~Client()
+{
+#ifdef _WIN32
+    WSACleanup();
+#endif
+}
+
+inline bool Client::read_response_line(FILE* fp, Response& response)
+{
+    const size_t BUFSIZ_RESPONSELINE = 2048;
+    char buf[BUFSIZ_RESPONSELINE];
+    if (!fgets(buf, BUFSIZ_RESPONSELINE, fp)) {
+        return false;
+    }
+
+    static std::regex re("HTTP/1\\.[01] (\\d+?) .+\r\n");
+
+    std::cmatch m;
+    if (std::regex_match(buf, m, re)) {
+        response.status = std::atoi(std::string(m[1]).c_str());
+    }
+
+    return true;
+}
+
+inline int Client::get(const char* url, Response& response)
+{
+    socket_t sock = create_client_socket(host_.c_str(), port_);
+    if (sock == -1) {
+        return -1;
+    }
+
+    FILE* fp_read;
+    FILE* fp_write;
+    get_flie_pointers(sock, fp_read, fp_write);
+
+    // Send request
+    fprintf(fp_write, "GET %s HTTP/1.0\r\n\r\n", url);
+    fflush(fp_write);
+
+    if (!read_response_line(fp_read, response)) {
+        return -1;
+    }
+
+    read_headers(fp_read, response.headers);
+
+    // Read content body
+    auto len = get_header_value_int(response.headers, "Content-Length", 0);
+    if (len) {
+        response.body.assign(len, 0);
+        if (!fgets(&response.body[0], response.body.size() + 1, fp_read)) {
+            return -1;
+        }
+    }
+
+    close_client_socket(sock);
+
+    return 0;
+}
+
 } // namespace httplib
 
 #endif

+ 20 - 7
test/test.cc

@@ -2,6 +2,7 @@
 #include <gtest/gtest.h>
 #include <httplib.h>
 #include <future>
+#include <iostream>
 
 using namespace std;
 using namespace httplib;
@@ -53,18 +54,30 @@ TEST(GetHeaderValueTest, RegularValue)
 
 TEST(ServerTest, GetMethod)
 {
-    Server svr("localhost", 1914);
+    const char* host = "localhost";
+    int port = 1914;
+    const char* url = "/hi";
+    const char* content = "Hello World!";
 
-    svr.get("hi", [&](httplib::Connection& c) {
-        c.response.set_content("Hello World!");
-    });
+    Server svr(host, port);
 
-    svr.on_ready([&]() {
-        // TODO: HTTP GET request...
-        svr.stop();
+    svr.get(url, [&](httplib::Connection& c) {
+        c.response.set_content(content);
     });
+
+    //svr.on_ready([&]() { svr.stop(); });
     
     auto f = async([&](){ svr.run(); });
+
+    sleep(1);
+
+    Client cli(host, port);
+
+    Response res;
+    cli.get(url, res);
+    EXPECT_EQ(content, res.body);
+
+    svr.stop();
 }
 
 // vim: et ts=4 sw=4 cin cino={1s ff=unix