Refactoring.
This commit is contained in:
parent
f91cc98b89
commit
aa75fbb5f9
@ -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();
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
77
httplib.h
77
httplib.h
@ -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
|
||||||
|
35
test/test.cc
35
test/test.cc
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user