1
0
mirror of synced 2025-04-20 11:47:43 +03:00

Refactoring.

This commit is contained in:
yhirose 2012-10-03 20:11:22 -04:00
parent f91cc98b89
commit aa75fbb5f9
7 changed files with 100 additions and 79 deletions

4
Makefile Normal file
View File

@ -0,0 +1,4 @@
all:
make -C test
make -C example

View File

@ -18,7 +18,7 @@ Inspired by [Sinatra](http://www.sinatrarb.com/)
Server svr("localhost", 1234); Server svr("localhost", 1234);
svr.get("/hi", [](Connection& c) { svr.get("/hi", [](Connection& c) {
c.response.set_content("Hello World!"); c.response.set_content("Hello World!", "text/plain");
}); });
svr.run(); svr.run();

View File

@ -6,21 +6,23 @@
// //
#include <httplib.h> #include <httplib.h>
#include <cstdio> #include <iostream>
#include <signal.h>
using namespace std;
using namespace httplib; using namespace httplib;
int main(void) int main(void)
{ {
using namespace httplib;
const char* hi = "/hi"; const char* hi = "/hi";
Client cli("localhost", 1234); Client cli("localhost", 8080);
Response res; Response res;
cli.get(hi, res); if (cli.get(hi, res)) {
cout << res.status << endl;
cout << res.get_header_value("Content-Type") << endl;
cout << res.body << endl;
}
return 0; return 0;
} }

View File

@ -13,7 +13,7 @@ int main(void)
Server svr("localhost", 1234); Server svr("localhost", 1234);
svr.get("/hi", [](Connection& c) { svr.get("/hi", [](Connection& c) {
c.response.set_content("Hello World!"); c.response.set_content("Hello World!", "text/plain");
}); });
svr.run(); svr.run();

View File

@ -7,14 +7,23 @@
#include <httplib.h> #include <httplib.h>
#include <cstdio> #include <cstdio>
#include <signal.h>
template<typename Fn> void signal(int sig, Fn fn) #ifdef _WIN32
#define snprintf sprintf_s
#endif
std::string dump_headers(const httplib::MultiMap& headers)
{ {
static std::function<void ()> signal_handler_; std::string s;
struct SignalHandler { static void fn(int sig) { signal_handler_(); } }; char buf[BUFSIZ];
signal_handler_ = fn;
signal(sig, SignalHandler::fn); 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;
}
return s;
} }
std::string log(const httplib::Connection& c) std::string log(const httplib::Connection& c)
@ -40,13 +49,13 @@ std::string log(const httplib::Connection& c)
snprintf(buf, sizeof(buf), "%s\n", query.c_str()); snprintf(buf, sizeof(buf), "%s\n", query.c_str());
s += buf; s += buf;
s += httplib::dump_headers(req.headers); s += dump_headers(req.headers);
s += "--------------------------------\n"; s += "--------------------------------\n";
snprintf(buf, sizeof(buf), "%d\n", res.status); snprintf(buf, sizeof(buf), "%d\n", res.status);
s += buf; s += buf;
s += httplib::dump_headers(res.headers); s += dump_headers(res.headers);
if (!res.body.empty()) { if (!res.body.empty()) {
s += res.body; s += res.body;
@ -57,13 +66,6 @@ std::string log(const httplib::Connection& c)
return s; return s;
} }
inline void error_handler(httplib::Connection& c)
{
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), "Error Status: %d\r\n", c.response.status);
c.response.set_content(buf);
}
int main(void) int main(void)
{ {
using namespace httplib; using namespace httplib;
@ -77,21 +79,24 @@ int main(void)
}); });
svr.get("/hi", [](Connection& c) { svr.get("/hi", [](Connection& c) {
c.response.set_content("Hello World!"); c.response.set_content("Hello World!", "text/plain");
}); });
svr.get("/dump", [](Connection& c) { svr.get("/dump", [](Connection& c) {
c.response.set_content(httplib::dump_headers(c.request.headers)); c.response.set_content(dump_headers(c.request.headers), "text/plain");
}); });
svr.error(error_handler); svr.set_error_handler([](httplib::Connection& c) {
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), "<p>Error Status: <span style='color:red;'>%d</span></p>", c.response.status);
c.response.body = buf;
c.response.set_header("Content-Type", "text/html");
});
svr.set_logger([](const Connection& c) { svr.set_logger([](const Connection& c) {
printf("%s", log(c).c_str()); printf("%s", log(c).c_str());
}); });
signal(SIGINT, [&]() { svr.stop(); });
svr.run(); svr.run();
return 0; return 0;

View File

@ -24,7 +24,6 @@
#include <winsock2.h> #include <winsock2.h>
typedef SOCKET socket_t; typedef SOCKET socket_t;
#define snprintf sprintf_s
#else #else
#include <pthread.h> #include <pthread.h>
#include <unistd.h> #include <unistd.h>
@ -46,8 +45,8 @@ namespace httplib
{ {
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;
typedef std::smatch Match;
struct Request { struct Request {
std::string method; std::string method;
@ -55,7 +54,10 @@ struct Request {
MultiMap headers; MultiMap headers;
std::string body; std::string body;
Map query; Map query;
Array params; Match match;
bool has_header(const char* key) const;
std::string get_header_value(const char* key) const;
}; };
struct Response { struct Response {
@ -63,8 +65,12 @@ struct Response {
MultiMap headers; MultiMap headers;
std::string body; std::string body;
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);
void set_redirect(const char* url); void set_redirect(const char* url);
void set_content(const std::string& s, const char* content_type = "text/plain"); void set_content(const std::string& s, const char* content_type);
}; };
struct Connection { struct Connection {
@ -81,8 +87,8 @@ public:
void get(const char* pattern, Handler handler); void get(const char* pattern, Handler handler);
void post(const char* pattern, Handler handler); void post(const char* pattern, Handler handler);
void error(Handler handler);
void set_error_handler(Handler handler);
void set_logger(std::function<void (const Connection&)> logger); void set_logger(std::function<void (const Connection&)> logger);
bool run(); bool run();
@ -152,7 +158,7 @@ inline void get_flie_pointers(int fd, FILE*& fp_read, FILE*& fp_write)
} }
template <typename Fn> template <typename Fn>
inline socket_t create_socket(const char* host, int port, Fn fn) socket_t create_socket(const char* host, int port, Fn fn)
{ {
#ifdef _WIN32 #ifdef _WIN32
int opt = SO_SYNCHRONOUS_NONALERT; int opt = SO_SYNCHRONOUS_NONALERT;
@ -255,7 +261,7 @@ inline const char* status_message(int status)
return s; return s;
} }
inline const char* get_header_value(const MultiMap& map, const char* key, const char* def) inline const char* get_header_value_text(const MultiMap& map, const char* key, const char* def)
{ {
auto it = map.find(key); auto it = map.find(key);
if (it != map.end()) { if (it != map.end()) {
@ -290,31 +296,43 @@ inline void read_headers(FILE* fp, MultiMap& headers)
} }
} }
inline std::string dump_headers(const MultiMap& headers) // HTTP server implementation
inline bool Request::has_header(const char* key) const
{ {
std::string s; return headers.find(key) != headers.end();
char buf[BUFSIZ]; }
for (auto it = headers.begin(); it != headers.end(); ++it) { inline std::string Request::get_header_value(const char* key) const
const auto& x = *it; {
snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str()); return get_header_value_text(headers, key, "");
s += buf; }
}
inline bool Response::has_header(const char* key) const
return s; {
return headers.find(key) != headers.end();
}
inline std::string Response::get_header_value(const char* key) const
{
return get_header_value_text(headers, key, "");
}
inline void Response::set_header(const char* key, const char* val)
{
headers.insert(std::make_pair(key, val));
} }
// HTTP server implementation
inline void Response::set_redirect(const char* url) inline void Response::set_redirect(const char* url)
{ {
headers.insert(std::make_pair("Location", url)); set_header("Location", url);
status = 302; status = 302;
} }
inline 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; body = s;
headers.insert(std::make_pair("Content-Type", content_type)); set_header("Content-Type", content_type);
status = 200;
} }
inline Server::Server(const char* host, int port) inline Server::Server(const char* host, int port)
@ -345,7 +363,7 @@ inline void Server::post(const char* pattern, Handler handler)
post_handlers_.push_back(std::make_pair(pattern, handler)); post_handlers_.push_back(std::make_pair(pattern, handler));
} }
inline void Server::error(Handler handler) inline void Server::set_error_handler(Handler handler)
{ {
error_handler_ = handler; error_handler_ = handler;
} }
@ -444,7 +462,7 @@ inline void Server::write_response(FILE* fp, const Response& res)
} }
if (!res.body.empty()) { if (!res.body.empty()) {
auto content_type = get_header_value(res.headers, "Content-Type", "text/plain"); 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-Type: %s\r\n", content_type);
fprintf(fp, "Content-Length: %ld\r\n", res.body.size()); fprintf(fp, "Content-Length: %ld\r\n", res.body.size());
} }
@ -474,22 +492,13 @@ inline void Server::process_request(FILE* fp_read, FILE* fp_write)
const auto& pattern = it->first; const auto& pattern = it->first;
const auto& handler = it->second; const auto& handler = it->second;
std::smatch m; if (std::regex_match(c.request.url, c.request.match, pattern)) {
if (std::regex_match(c.request.url, m, pattern)) {
for (size_t i = 1; i < m.size(); i++) {
c.request.params.push_back(m[i]);
}
handler(c); handler(c);
if (!c.response.status) {
c.response.status = 200;
}
break; break;
} }
} }
} else if (c.request.method == "POST") { } else if (c.request.method == "POST") {
// TODO: parse body // TODO: parse body
} else {
c.response.status = 400;
} }
if (!c.response.status) { if (!c.response.status) {
@ -577,7 +586,7 @@ inline bool Client::get(const char* url, Response& res)
close_client_socket(sock); close_client_socket(sock);
return res.status == 200; return true;
} }
} // namespace httplib } // namespace httplib

View File

@ -41,7 +41,7 @@ TEST(SocketTest, OpenClose)
TEST(GetHeaderValueTest, DefaultValue) TEST(GetHeaderValueTest, DefaultValue)
{ {
MultiMap map = {{"Dummy","Dummy"}}; MultiMap map = {{"Dummy","Dummy"}};
auto val = get_header_value(map, "Content-Type", "text/plain"); auto val = get_header_value_text(map, "Content-Type", "text/plain");
ASSERT_STREQ("text/plain", val); ASSERT_STREQ("text/plain", val);
} }
@ -55,7 +55,7 @@ TEST(GetHeaderValueTest, DefaultValueInt)
TEST(GetHeaderValueTest, RegularValue) TEST(GetHeaderValueTest, RegularValue)
{ {
MultiMap map = {{"Content-Type","text/html"}, {"Dummy", "Dummy"}}; MultiMap map = {{"Content-Type","text/html"}, {"Dummy", "Dummy"}};
auto val = get_header_value(map, "Content-Type", "text/plain"); auto val = get_header_value_text(map, "Content-Type", "text/plain");
ASSERT_STREQ("text/html", val); ASSERT_STREQ("text/html", val);
} }
@ -68,43 +68,44 @@ TEST(GetHeaderValueTest, RegularValueInt)
class ServerTest : public ::testing::Test { class ServerTest : public ::testing::Test {
protected: protected:
ServerTest() : svr(host, port) { ServerTest() : svr_(host_, port_) {
} }
virtual void SetUp() { virtual void SetUp() {
svr.get(url, [&](httplib::Connection& c) { svr_.get(url_, [&](httplib::Connection& c) {
c.response.set_content(content); c.response.set_content(content_, mime_);
}); });
f = async([&](){ svr.run(); }); f_ = async([&](){ svr_.run(); });
} }
virtual void TearDown() { virtual void TearDown() {
svr.stop(); svr_.stop();
f.get(); f_.get();
} }
const char* host = "localhost"; const char* host_ = "localhost";
int port = 1914; int port_ = 1914;
const char* url = "/hi"; const char* url_ = "/hi";
const char* content = "Hello World!"; const char* content_ = "Hello World!";
const char* mime_ = "text/plain";
Server svr; Server svr_;
std::future<void> f; std::future<void> f_;
}; };
TEST_F(ServerTest, GetMethod200) TEST_F(ServerTest, GetMethod200)
{ {
Response res; Response res;
bool ret = Client(host, port).get(url, res); bool ret = Client(host_, port_).get(url_, res);
ASSERT_EQ(true, ret); ASSERT_EQ(true, ret);
ASSERT_EQ(200, res.status); ASSERT_EQ(200, res.status);
ASSERT_EQ(content, res.body); ASSERT_EQ(content_, res.body);
} }
TEST_F(ServerTest, GetMethod404) TEST_F(ServerTest, GetMethod404)
{ {
Response res; Response res;
bool ret = Client(host, port).get("/invalid", res); bool ret = Client(host_, port_).get("/invalid", res);
ASSERT_EQ(false, ret); ASSERT_EQ(false, ret);
ASSERT_EQ(404, res.status); ASSERT_EQ(404, res.status);
} }