Supported GET routing.

This commit is contained in:
yhirose 2012-09-24 23:13:01 -04:00
parent 7953abff07
commit 107a2b9b5d
2 changed files with 131 additions and 46 deletions

View File

@ -8,22 +8,52 @@
#include <httpsvrkit.h> #include <httpsvrkit.h>
#include <cstdio> #include <cstdio>
int main(void)
{
using namespace httpsvrkit; using namespace httpsvrkit;
int dump_request(Context& cxt)
{
auto& body = cxt.response.body;
char buf[BUFSIZ];
body += "================================\n";
sprintf(buf, "Method: %s, URL: %s\n",
cxt.request.method.c_str(),
cxt.request.url.c_str());
body += buf;
//for (const auto& x : cxt.request.headers) {
for (auto it = cxt.request.headers.begin(); it != cxt.request.headers.end(); ++it) {
const auto& x = *it;
sprintf(buf, "%s: %s\n", x.first.c_str(), x.second.c_str());
body += buf;
}
body += "================================\n";
return 200;
}
int main(void)
{
Server svr; Server svr;
svr.get("/", [](Context& cxt) { svr.get("/", [](Context& cxt) -> int {
cxt.response.body = "<html><head></head><body><ul></ul></body></html>"; dump_request(cxt);
return 200;
}); });
svr.post("/item", [](Context& cxt) { svr.post("/item", [](Context& cxt) -> int {
cxt.response.body = cxt.request.pattern; dump_request(cxt);
cxt.response.body += cxt.request.url;
return 200;
}); });
svr.get("/item/:name", [](Context& cxt) { svr.get("/item/([^/]+)", [](Context& cxt) -> int {
cxt.response.body = cxt.request.params.at("name"); dump_request(cxt);
cxt.response.body += cxt.request.params[0];
return 200;
}); });
svr.run("localhost", 1234); svr.run("localhost", 1234);

View File

@ -35,14 +35,17 @@ namespace httpsvrkit
{ {
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;
// HTTP request // HTTP request
struct Request { struct Request {
std::string method;
std::string url;
Map headers; Map headers;
std::string body; std::string body;
std::string pattern; Map query;
Map params; Array params;
}; };
// HTTP response // HTTP response
@ -52,14 +55,14 @@ struct Response {
}; };
struct Context { struct Context {
const Request request; Request request;
Response response; Response response;
}; };
// HTTP server // HTTP server
class Server { class Server {
public: public:
typedef std::function<void (Context& context)> Handler; typedef std::function<int (Context& context)> Handler;
Server(); Server();
~Server(); ~Server();
@ -74,11 +77,31 @@ private:
void process_request(FILE* fp_read, FILE* fp_write); void process_request(FILE* fp_read, FILE* fp_write);
socket_t sock_; socket_t sock_;
std::multimap<std::string, Handler> handlers_; std::vector<std::pair<std::regex, Handler>> get_handlers_;
std::vector<std::pair<std::string, Handler>> post_handlers_;
}; };
// Implementation // Implementation
template <class Fn>
void split(const char* b, const char* e, char d, Fn fn)
{
int i = 0;
int beg = 0;
while (e ? (b + i != e) : (b[i] != '\0')) {
if (b[i] == d) {
fn(&b[beg], &b[i]);
beg = i + 1;
}
i++;
}
if (i != 0) {
fn(&b[beg], &b[i]);
}
}
inline socket_t create_server_socket(const const char* ipaddr_or_hostname, int port) inline socket_t create_server_socket(const const char* ipaddr_or_hostname, int port)
{ {
// Create a server socket // Create a server socket
@ -154,12 +177,12 @@ inline Server::~Server()
inline void Server::get(const char* pattern, Handler handler) inline void Server::get(const char* pattern, Handler handler)
{ {
handlers_.insert(std::make_pair(pattern, handler)); get_handlers_.push_back(std::make_pair(pattern, handler));
} }
inline void Server::post(const char* pattern, Handler handler) inline void Server::post(const char* pattern, Handler handler)
{ {
handlers_.insert(std::make_pair(pattern, handler)); post_handlers_.push_back(std::make_pair(pattern, handler));
} }
inline bool Server::run(const const char*ipaddr_or_hostname, int port) inline bool Server::run(const const char*ipaddr_or_hostname, int port)
@ -205,9 +228,9 @@ inline void Server::stop()
sock_ = -1; sock_ = -1;
} }
inline bool read_request_line(FILE* fp, std::string& method, std::string& url) inline bool read_request_line(FILE* fp, Request& request)
{ {
static std::regex re("(GET|POST) (.+) HTTP/1\\.1\r\n"); static std::regex re("(GET|POST) ([^?]+)(?:\\?(.+?))? HTTP/1\\.1\r\n");
const size_t BUFSIZ_REQUESTLINE = 2048; const size_t BUFSIZ_REQUESTLINE = 2048;
char buf[BUFSIZ_REQUESTLINE]; char buf[BUFSIZ_REQUESTLINE];
@ -215,8 +238,27 @@ inline bool read_request_line(FILE* fp, std::string& method, std::string& url)
std::cmatch m; std::cmatch m;
if (std::regex_match(buf, m, re)) { if (std::regex_match(buf, m, re)) {
method = std::string(m[1]); request.method = std::string(m[1]);
url = std::string(m[2]); request.url = std::string(m[2]);
// Parse query text
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);
}
});
request.query[key] = val;
});
}
return true; return true;
} }
@ -249,11 +291,11 @@ inline void write_plain_text(FILE* fp, const char* s)
fprintf(fp, "%s", s); fprintf(fp, "%s", s);
} }
inline void write_error(FILE* fp, int code) inline void write_error(FILE* fp, int status)
{ {
const char* msg = NULL; const char* msg = NULL;
switch (code) { switch (status) {
case 400: case 400:
msg = "Bad Request"; msg = "Bad Request";
break; break;
@ -261,47 +303,60 @@ inline void write_error(FILE* fp, int code)
msg = "Not Found"; msg = "Not Found";
break; break;
default: default:
code = 500; status = 500;
msg = "Internal Server Error"; msg = "Internal Server Error";
break; break;
} }
assert(msg); assert(msg);
fprintf(fp, "HTTP/1.0 %d %s\r\n", code, msg); fprintf(fp, "HTTP/1.0 %d %s\r\n", status, msg);
fprintf(fp, "Content-type: text/plain\r\n"); fprintf(fp, "Content-type: text/plain\r\n");
fprintf(fp, "Connection: close\r\n"); fprintf(fp, "Connection: close\r\n");
fprintf(fp, "\r\n"); fprintf(fp, "\r\n");
fprintf(fp, "Status: %d\r\n", code); fprintf(fp, "Status: %d\r\n", status);
} }
inline void Server::process_request(FILE* fp_read, FILE* fp_write) inline void Server::process_request(FILE* fp_read, FILE* fp_write)
{ {
Context cxt;
// Read and parse request line // Read and parse request line
std::string method, url; if (!read_request_line(fp_read, cxt.request)) {
if (!read_request_line(fp_read, method, url)) {
write_error(fp_write, 400); write_error(fp_write, 400);
return; return;
} }
// Read headers // Read headers
Map headers; read_headers(fp_read, cxt.request.headers);
read_headers(fp_read, headers);
// Write content // Routing
char buf[BUFSIZ]; int status = 404;
std::string content;
sprintf(buf, "Method: %s, URL: %s\n", method.c_str(), url.c_str());
content += buf;
//for (const auto& x : headers) { if (cxt.request.method == "GET") {
for (auto it = headers.begin(); it != headers.end(); ++it) { for (auto it = get_handlers_.begin(); it != get_handlers_.end(); ++it) {
const auto& x = *it; const auto& pattern = it->first;
sprintf(buf, "%s: %s\n", x.first.c_str(), x.second.c_str()); const auto& handler = it->second;
content += buf;
std::smatch m;
if (std::regex_match(cxt.request.url, m, pattern)) {
for (size_t i = 1; i < m.size(); i++) {
cxt.request.params.push_back(m[i]);
}
status = handler(cxt);
}
}
} else if (cxt.request.method == "POST") {
// TODO: parse body
} else {
status = 400;
} }
write_plain_text(fp_write, content.c_str()); if (status == 200) {
write_plain_text(fp_write, cxt.response.body.c_str());
} else {
write_error(fp_write, status);
}
} }
} // namespace httpsvrkit } // namespace httpsvrkit