1
0
mirror of synced 2025-04-26 14:28:51 +03:00

Refactoring. Removed some client methods.

This commit is contained in:
yhirose 2012-10-05 13:58:56 -04:00
parent 3c8c835489
commit 6062ea592b
5 changed files with 145 additions and 197 deletions

View File

@ -1,20 +1,23 @@
cpp-httplib cpp-httplib
=========== ===========
A C++ HTTP library. A C++11 header-only HTTP library.
[The Boost Software License 1.0](http://www.boost.org/LICENSE_1_0.txt) [The Boost Software License 1.0](http://www.boost.org/LICENSE_1_0.txt)
It's extremely easy to setup. Just include **httplib.h** file in your code!
Server Example Server Example
-------------- --------------
Inspired by [Sinatra](http://www.sinatrarb.com/) Inspired by [Sinatra](http://www.sinatrarb.com/)
#include <httplib.h> #include <httplib.h>
using namespace httplib;
int main(void) int main(void)
{ {
using namespace httplib;
Server svr("localhost", 1234); Server svr("localhost", 1234);
svr.get("/hi", [](Connection& c) { svr.get("/hi", [](Connection& c) {
@ -29,11 +32,10 @@ Client Example
#include <httplib.h> #include <httplib.h>
#include <iostream> #include <iostream>
using namespace httplib;
int main(void) int main(void)
{ {
Client cli("localhost", 1234); httplib::Client cli("localhost", 1234);
auto res = cli.get("/hi"); auto res = cli.get("/hi");
if (res && res->status == 200) { if (res && res->status == 200) {

View File

@ -9,15 +9,12 @@
#include <iostream> #include <iostream>
using namespace std; using namespace std;
using namespace httplib;
int main(void) int main(void)
{ {
const char* hi = "/hi"; httplib::Client cli("localhost", 8080);
Client cli("localhost", 8080); auto res = cli.get("/hi");
auto res = cli.get(hi);
if (res) { if (res) {
cout << res->status << endl; cout << res->status << endl;
cout << res->get_header_value("Content-Type") << endl; cout << res->get_header_value("Content-Type") << endl;

View File

@ -5,6 +5,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "server", "server.vcxproj",
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "client", "client.vcxproj", "{6DB1FC63-B153-4279-92B7-D8A11AF285D6}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "client", "client.vcxproj", "{6DB1FC63-B153-4279-92B7-D8A11AF285D6}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{280E605F-0CB8-4336-8D9F-CE50A9472AE2}"
ProjectSection(SolutionItems) = preProject
..\httplib.h = ..\httplib.h
..\README.md = ..\README.md
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32 Debug|Win32 = Debug|Win32

168
httplib.h
View File

@ -102,14 +102,14 @@ public:
private: private:
typedef std::vector<std::pair<std::regex, Handler>> Handlers; typedef std::vector<std::pair<std::regex, Handler>> Handlers;
void process_request(FILE* fp_read, FILE* fp_write); void process_request(socket_t sock);
bool read_request_line(FILE* fp, Request& req); bool read_request_line(FILE* fp, Request& req);
bool routing(Connection& c); bool routing(Connection& c);
bool dispatch_request(Connection& c, Handlers& handlers); bool dispatch_request(Connection& c, Handlers& handlers);
const std::string host_; const std::string host_;
const int port_; const int port_;
socket_t sock_; socket_t svr_sock_;
Handlers get_handlers_; Handlers get_handlers_;
Handlers post_handlers_; Handlers post_handlers_;
@ -122,12 +122,11 @@ public:
Client(const char* host, int port); Client(const char* host, int port);
~Client(); ~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);
std::shared_ptr<Response> get(const char* url); std::shared_ptr<Response> get(const char* url);
std::shared_ptr<Response> post(const char* url, const std::string& body, const char* content_type); std::shared_ptr<Response> post(
const char* url, const std::string& body, const char* content_type);
bool send(const Request& req, Response& res);
private: private:
bool read_response_line(FILE* fp, Response& res); bool read_response_line(FILE* fp, Response& res);
@ -137,6 +136,7 @@ private:
}; };
// Implementation // Implementation
namespace detail {
template <class Fn> template <class Fn>
void split(const char* b, const char* e, char d, Fn fn) void split(const char* b, const char* e, char d, Fn fn)
@ -177,7 +177,7 @@ socket_t create_socket(const char* host, int port, Fn fn)
setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char*)&opt, sizeof(opt)); setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char*)&opt, sizeof(opt));
#endif #endif
// Create a server socket // Create a socket
socket_t sock = socket(AF_INET, SOCK_STREAM, 0); socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) { if (sock == -1) {
return -1; return -1;
@ -206,21 +206,17 @@ socket_t create_socket(const char* host, int port, Fn fn)
inline socket_t create_server_socket(const char* host, int port) 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 { return create_socket(host, port, [](socket_t sock, struct sockaddr_in& addr) -> socket_t {
if (::bind(sock, (struct sockaddr*)&addr, sizeof(addr))) { if (::bind(sock, (struct sockaddr*)&addr, sizeof(addr))) {
return -1; return -1;
} }
if (listen(sock, 5)) { // Listen through 5 channels
// Listen through 5 channels
if (listen(sock, 5)) {
return -1; return -1;
} }
return sock; return sock;
}); });
} }
inline int close_socket(socket_t sock) inline int shutdown_and_close_socket(socket_t sock)
{ {
#ifdef _WIN32 #ifdef _WIN32
shutdown(sock, SD_BOTH); shutdown(sock, SD_BOTH);
@ -233,35 +229,23 @@ inline int close_socket(socket_t sock)
inline socket_t create_client_socket(const char* host, int port) inline socket_t create_client_socket(const char* host, int port)
{ {
return create_socket(host, port, return create_socket(host, port, [](socket_t sock, struct sockaddr_in& addr) -> socket_t {
[](socket_t sock, struct sockaddr_in& addr) -> socket_t {
if (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))) { if (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))) {
return -1; return -1;
} }
return sock; return sock;
}); });
} }
inline const char* status_message(int status) inline const char* status_message(int status)
{ {
const char* s = NULL;
switch (status) { switch (status) {
case 400: case 400: return "Bad Request";
s = "Bad Request"; case 404: return "Not Found";
break;
case 404:
s = "Not Found";
break;
default: default:
status = 500; status = 500;
s = "Internal Server Error"; return "Internal Server Error";
break;
} }
return s;
} }
inline const char* get_header_value_text(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)
@ -378,7 +362,14 @@ inline void parse_query_text(const char* b, const char* e, Map& params)
}); });
} }
// HTTP server implementation inline void parse_query_text(const std::string& s, Map& params)
{
parse_query_text(&s[0], &s[s.size()], params);
}
} // namespace detail
// Request implementation
inline bool Request::has_header(const char* key) const inline bool Request::has_header(const char* key) const
{ {
return headers.find(key) != headers.end(); return headers.find(key) != headers.end();
@ -386,7 +377,7 @@ inline bool Request::has_header(const char* key) const
inline std::string Request::get_header_value(const char* key) const inline std::string Request::get_header_value(const char* key) const
{ {
return get_header_value_text(headers, key, ""); return detail::get_header_value_text(headers, key, "");
} }
inline void Request::set_header(const char* key, const char* val) inline void Request::set_header(const char* key, const char* val)
@ -399,6 +390,7 @@ inline bool Request::has_param(const char* key) const
return params.find(key) != params.end(); return params.find(key) != params.end();
} }
// Response implementation
inline bool Response::has_header(const char* key) const inline bool Response::has_header(const char* key) const
{ {
return headers.find(key) != headers.end(); return headers.find(key) != headers.end();
@ -406,7 +398,7 @@ inline bool Response::has_header(const char* key) const
inline std::string Response::get_header_value(const char* key) const inline std::string Response::get_header_value(const char* key) const
{ {
return get_header_value_text(headers, key, ""); return detail::get_header_value_text(headers, key, "");
} }
inline void Response::set_header(const char* key, const char* val) inline void Response::set_header(const char* key, const char* val)
@ -426,10 +418,11 @@ inline void Response::set_content(const std::string& s, const char* content_type
set_header("Content-Type", content_type); set_header("Content-Type", content_type);
} }
// HTTP server implementation
inline Server::Server(const char* host, int port) inline Server::Server(const char* host, int port)
: host_(host) : host_(host)
, port_(port) , port_(port)
, sock_(-1) , svr_sock_(-1)
{ {
#ifdef _WIN32 #ifdef _WIN32
WSADATA wsaData; WSADATA wsaData;
@ -466,31 +459,26 @@ inline void Server::set_logger(Handler logger)
inline bool Server::run() inline bool Server::run()
{ {
sock_ = create_server_socket(host_.c_str(), port_); svr_sock_ = detail::create_server_socket(host_.c_str(), port_);
if (sock_ == -1) { if (svr_sock_ == -1) {
return false; return false;
} }
for (;;) { for (;;) {
socket_t fd = accept(sock_, NULL, NULL); socket_t sock = accept(svr_sock_, NULL, NULL);
if (fd == -1) { if (sock == -1) {
// The server socket was closed by user. if (svr_sock_ == -1) {
if (sock_ == -1) { // The server socket was closed by user.
return true; return true;
} else {
detail::shutdown_and_close_socket(svr_sock_);
return false;
} }
close_socket(sock_);
return false;
} }
FILE* fp_read; // TODO: should be async
FILE* fp_write; process_request(sock);
get_flie_pointers(fd, fp_read, fp_write); detail::shutdown_and_close_socket(sock);
process_request(fp_read, fp_write);
fflush(fp_write);
close_socket(fd);
} }
// NOTREACHED // NOTREACHED
@ -498,8 +486,8 @@ inline bool Server::run()
inline void Server::stop() inline void Server::stop()
{ {
close_socket(sock_); detail::shutdown_and_close_socket(svr_sock_);
sock_ = -1; svr_sock_ = -1;
} }
inline bool Server::read_request_line(FILE* fp, Request& req) inline bool Server::read_request_line(FILE* fp, Request& req)
@ -521,7 +509,7 @@ inline bool Server::read_request_line(FILE* fp, Request& req)
auto len = std::distance(m[3].first, m[3].second); auto len = std::distance(m[3].first, m[3].second);
if (len > 0) { if (len > 0) {
const auto& pos = m[3]; const auto& pos = m[3];
parse_query_text(pos.first, pos.second, req.params); detail::parse_query_text(pos.first, pos.second, req.params);
} }
return true; return true;
@ -554,22 +542,25 @@ inline bool Server::dispatch_request(Connection& c, Handlers& handlers)
return false; return false;
} }
inline void Server::process_request(FILE* fp_read, FILE* fp_write) inline void Server::process_request(socket_t sock)
{ {
Connection c; FILE* fp_read;
auto& req = c.request; FILE* fp_write;
detail::get_flie_pointers(sock, fp_read, fp_write);
if (!read_request_line(fp_read, req) || Connection c;
!read_headers(fp_read, req.headers)) {
if (!read_request_line(fp_read, c.request) ||
!detail::read_headers(fp_read, c.request.headers)) {
return; return;
} }
if (req.method == "POST") { if (c.request.method == "POST") {
if (!read_content(req, fp_read)) { if (!detail::read_content(c.request, fp_read)) {
return; return;
} }
if (req.get_header_value("Content-Type") == "application/x-www-form-urlencoded") { if (c.request.get_header_value("Content-Type") == "application/x-www-form-urlencoded") {
parse_query_text(&req.body[0], &req.body[req.body.size()], req.params); detail::parse_query_text(c.request.body, c.request.params);
} }
} }
@ -586,7 +577,9 @@ inline void Server::process_request(FILE* fp_read, FILE* fp_write)
error_handler_(c); error_handler_(c);
} }
write_response(fp_write, c.response); detail::write_response(fp_write, c.response);
fflush(fp_write);
if (logger_) { if (logger_) {
logger_(c); logger_(c);
@ -629,62 +622,55 @@ inline bool Client::read_response_line(FILE* fp, Response& res)
return true; return true;
} }
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) inline bool Client::send(const Request& req, Response& res)
{ {
socket_t sock = create_client_socket(host_.c_str(), port_); socket_t sock = detail::create_client_socket(host_.c_str(), port_);
if (sock == -1) { if (sock == -1) {
return false; return false;
} }
FILE* fp_read; FILE* fp_read;
FILE* fp_write; FILE* fp_write;
get_flie_pointers(sock, fp_read, fp_write); detail::get_flie_pointers(sock, fp_read, fp_write);
// Send request // Send request
write_request(fp_write, req); detail::write_request(fp_write, req);
fflush(fp_write); fflush(fp_write);
if (!read_response_line(fp_read, res) || if (!read_response_line(fp_read, res) ||
!read_headers(fp_read, res.headers) || !detail::read_headers(fp_read, res.headers) ||
!read_content(res, fp_read)) { !detail::read_content(res, fp_read)) {
return false; return false;
} }
close_socket(sock); detail::shutdown_and_close_socket(sock);
return true; return true;
} }
inline std::shared_ptr<Response> Client::get(const char* url) inline std::shared_ptr<Response> Client::get(const char* url)
{ {
Request req;
req.method = "GET";
req.url = url;
auto res = std::make_shared<Response>(); auto res = std::make_shared<Response>();
return get(url, *res) ? res : nullptr;
return send(req, *res) ? res : nullptr;
} }
inline std::shared_ptr<Response> Client::post( inline std::shared_ptr<Response> Client::post(
const char* url, const std::string& body, const char* content_type) const char* url, const std::string& body, const char* content_type)
{ {
Request req;
req.method = "POST";
req.url = url;
req.set_header("Content-Type", content_type);
req.body = body;
auto res = std::make_shared<Response>(); auto res = std::make_shared<Response>();
return post(url, body, content_type, *res) ? res : nullptr;
return send(req, *res) ? res : nullptr;
} }
} // namespace httplib } // namespace httplib

View File

@ -12,9 +12,9 @@ TEST(SplitTest, ParseQueryString)
string s = "key1=val1&key2=val2&key3=val3"; string s = "key1=val1&key2=val2&key3=val3";
map<string, string> dic; map<string, string> dic;
split(&s[0], &s[s.size()], '&', [&](const char* b, const char* e) { detail::split(&s[0], &s[s.size()], '&', [&](const char* b, const char* e) {
string key, val; string key, val;
split(b, e, '=', [&](const char* b, const char* e) { detail::split(b, e, '=', [&](const char* b, const char* e) {
if (key.empty()) { if (key.empty()) {
key.assign(b, e); key.assign(b, e);
} else { } else {
@ -34,7 +34,7 @@ TEST(ParseQueryTest, ParseQueryString)
string s = "key1=val1&key2=val2&key3=val3"; string s = "key1=val1&key2=val2&key3=val3";
map<string, string> dic; map<string, string> dic;
parse_query_text(&s[0], &s[s.size()], dic); detail::parse_query_text(&s[0], &s[s.size()], dic);
EXPECT_EQ("val1", dic["key1"]); EXPECT_EQ("val1", dic["key1"]);
EXPECT_EQ("val2", dic["key2"]); EXPECT_EQ("val2", dic["key2"]);
@ -43,44 +43,44 @@ TEST(ParseQueryTest, ParseQueryString)
TEST(SocketTest, OpenClose) TEST(SocketTest, OpenClose)
{ {
socket_t sock = create_server_socket("localhost", 1914); socket_t sock = detail::create_server_socket("localhost", 1914);
ASSERT_NE(-1, sock); ASSERT_NE(-1, sock);
auto ret = close_socket(sock); auto ret = detail::shutdown_and_close_socket(sock);
EXPECT_EQ(0, ret); EXPECT_EQ(0, ret);
} }
TEST(GetHeaderValueTest, DefaultValue) TEST(GetHeaderValueTest, DefaultValue)
{ {
MultiMap map = {{"Dummy","Dummy"}}; MultiMap map = {{"Dummy","Dummy"}};
auto val = get_header_value_text(map, "Content-Type", "text/plain"); auto val = detail::get_header_value_text(map, "Content-Type", "text/plain");
ASSERT_STREQ("text/plain", val); ASSERT_STREQ("text/plain", val);
} }
TEST(GetHeaderValueTest, DefaultValueInt) TEST(GetHeaderValueTest, DefaultValueInt)
{ {
MultiMap map = {{"Dummy","Dummy"}}; MultiMap map = {{"Dummy","Dummy"}};
auto val = get_header_value_int(map, "Content-Length", 100); auto val = detail::get_header_value_int(map, "Content-Length", 100);
EXPECT_EQ(100, val); EXPECT_EQ(100, val);
} }
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_text(map, "Content-Type", "text/plain"); auto val = detail::get_header_value_text(map, "Content-Type", "text/plain");
ASSERT_STREQ("text/html", val); ASSERT_STREQ("text/html", val);
} }
TEST(GetHeaderValueTest, RegularValueInt) TEST(GetHeaderValueTest, RegularValueInt)
{ {
MultiMap map = {{"Content-Length","100"}, {"Dummy", "Dummy"}}; MultiMap map = {{"Content-Length","100"}, {"Dummy", "Dummy"}};
auto val = get_header_value_int(map, "Content-Length", 0); auto val = detail::get_header_value_int(map, "Content-Length", 0);
EXPECT_EQ(100, val); EXPECT_EQ(100, val);
} }
class ServerTest : public ::testing::Test { class ServerTest : public ::testing::Test {
protected: protected:
ServerTest() : svr_(host_, port_) { ServerTest() : svr_(HOST, PORT), cli_(HOST, PORT) {
persons_["john"] = "programmer"; persons_["john"] = "programmer";
} }
@ -117,106 +117,63 @@ protected:
f_.get(); f_.get();
} }
const char* host_ = "localhost"; const char* HOST = "localhost";
int port_ = 1914; const int PORT = 1914;
std::map<std::string, std::string> persons_; std::map<std::string, std::string> persons_;
Server svr_; Server svr_;
Client cli_;
std::future<void> f_; std::future<void> f_;
}; };
TEST_F(ServerTest, GetMethod200) TEST_F(ServerTest, GetMethod200)
{ {
Response res; auto res = cli_.get("/hi");
bool ret = Client(host_, port_).get("/hi", res);
ASSERT_EQ(true, ret);
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)
{
Response res;
bool ret = Client(host_, port_).get("/", res);
ASSERT_EQ(true, ret);
EXPECT_EQ(302, res.status);
EXPECT_EQ("/hi", res.get_header_value("Location"));
}
TEST_F(ServerTest, GetMethod404)
{
Response res;
bool ret = Client(host_, port_).get("/invalid", res);
ASSERT_EQ(true, ret);
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);
}
}
TEST_F(ServerTest, GetMethod200Shared)
{
auto res = Client(host_, port_).get("/hi");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status); EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("Hello World!", res->body); EXPECT_EQ("Hello World!", res->body);
} }
TEST_F(ServerTest, PostMethodShared) TEST_F(ServerTest, GetMethod302)
{ {
{ auto res = cli_.get("/");
auto res = Client(host_, port_).get("/person/john3"); ASSERT_TRUE(res != nullptr);
ASSERT_TRUE(res != nullptr); EXPECT_EQ(302, res->status);
ASSERT_EQ(404, res->status); EXPECT_EQ("/hi", res->get_header_value("Location"));
} }
{
auto content = "name=john3&note=coder"; TEST_F(ServerTest, GetMethod404)
auto content_type = "application/x-www-form-urlencoded"; {
auto res = Client(host_, port_).post("/person", content, content_type); auto res = cli_.get("/invalid");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status); EXPECT_EQ(404, res->status);
} }
{
auto res = Client(host_, port_).get("/person/john3"); TEST_F(ServerTest, GetMethodPersonJohn)
ASSERT_TRUE(res != nullptr); {
ASSERT_EQ(200, res->status); auto res = cli_.get("/person/john");
ASSERT_EQ("text/plain", res->get_header_value("Content-Type")); ASSERT_TRUE(res != nullptr);
ASSERT_EQ("coder", res->body); EXPECT_EQ(200, res->status);
} EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("programmer", res->body);
}
TEST_F(ServerTest, PostMethod)
{
auto res = cli_.get("/person/john3");
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(404, res->status);
res = cli_.post("/person", "name=john3&note=coder", "application/x-www-form-urlencoded");
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status);
res = cli_.get("/person/john3");
ASSERT_TRUE(res != nullptr);
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 // vim: et ts=4 sw=4 cin cino={1s ff=unix