initial commit

This commit is contained in:
2019-05-18 22:52:03 +08:00
commit c93c6e19b3
14 changed files with 902 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
/Debug/
# Other
.project
+37
View File
@@ -0,0 +1,37 @@
/*
* headers.cpp
*
* Created on: May 16, 2019
* Author: cmy
*/
#include "headers.h"
using namespace std;
namespace chttp {
Headers::Headers() {
// TODO Auto-generated constructor stub
}
Headers::~Headers() {
// TODO Auto-generated destructor stub
}
void Headers::SetHeader(const std::string& key, const std::string& value) {
content[key] = value;
}
ostream& operator<<(ostream& out, const chttp::Headers &h) {
for (auto &p : h.content) {
out << p.first;
out << ": ";
out << p.second;
out << '\n';
}
return out;
}
} /* namespace chttp */
+59
View File
@@ -0,0 +1,59 @@
/*
* headers.h
*
* Created on: May 16, 2019
* Author: cmy
*/
#ifndef HEADERS_H_
#define HEADERS_H_
#include <unordered_map>
#include <string>
#include <iostream>
namespace chttp {
class Headers final {
public:
constexpr static char PlainText[] = "text/plain";
constexpr static char HtmlText[] = "text/html";
constexpr static char CSSText[] = "text/css";
constexpr static char JavascriptText[] = "textjavascript";
constexpr static char GifImage[] = "image/gif";
constexpr static char PngImage[] = "image/png";
constexpr static char JpegImage[] = "image/jpeg";
constexpr static char BmpImage[] = "image/bmp";
constexpr static char WebpImage[] = "image/webp";
constexpr static char MidiAudio[] = "audio/midi";
constexpr static char MpegAudio[] = "audio/mpeg";
constexpr static char WebmAudio[] = "audio/webm";
constexpr static char OggAudio[] = "audio/ogg";
constexpr static char WavAudio[] = "audio/wav";
constexpr static char WebmVideo[] = "video/webm";
constexpr static char OggVideo[] = "video/ogg";
constexpr static char XmlApplication[] = "application/xml";
constexpr static char PDFApplication[] = "application/pdf";
Headers();
virtual ~Headers();
void SetHeader(const std::string& key, const std::string& value);
inline const std::unordered_map<std::string, std::string>& Content() const {
return content;
}
inline std::unordered_map<std::string, std::string>& Content() {
return content;
}
friend std::ostream& operator<<(std::ostream& out, const Headers &h);
private:
std::unordered_map<std::string, std::string> content { };
};
} /* namespace chttp */
#endif /* HEADERS_H_ */
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Congratuations!</title>
</head>
<body>
<h1>Hello,World!</h1>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
#include <iostream>
#include "server.h"
using namespace std;
int main() {
chttp::Server s { };
chttp::Router router { };
router.Get("/about", [](chttp::Request req, chttp::Response res) {
res.ServeHtml("index.html");
});
s.SetRouter(router);
try {
s.Test(1113);
} catch (runtime_error& error) {
cout << error.what() << endl;
cout << "retry" << endl;
s.Test(1114);
}
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>404 Not Found</h1>
</body>
</html>
+17
View File
@@ -0,0 +1,17 @@
/*
* reauest.cpp
*
* Created on: May 16, 2019
* Author: cmy
*/
#include "request.h"
namespace chttp {
Request::~Request() {
// TODO Auto-generated destructor stub
}
} /* namespace chttp */
+71
View File
@@ -0,0 +1,71 @@
/*
* reauest.h
*
* Created on: May 16, 2019
* Author: cmy
*/
#ifndef REQUEST_H_
#define REQUEST_H_
#include <string>
#include "headers.h"
namespace chttp {
enum class Request_Method {
GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE, ALL, NONE
};
enum class Http_Version {
HTTP1_0, HTTP1_1, HTTP2_0, NONE
};
using URL_type=std::string;
class Request final {
public:
using URL_type=std::string;
Request() = delete;
Request(Request_Method method, URL_type url, Http_Version version,
Headers header) :
method(method), url(url), version(version), header(header) {
}
~Request();
inline void SetHeader(const Headers& headers) {
this->header = headers;
}
inline const URL_type& URL() const {
return url;
}
inline const Headers& Header() const {
return header;
}
inline Headers& Header() {
return header;
}
inline const Request_Method& Method() const {
return method;
}
inline const Http_Version& Version() const {
return version;
}
private:
Request_Method method;
URL_type url;
Http_Version version;
Headers header { };
};
} /* namespace chttp */
#endif /* REQUEST_H_ */
+114
View File
@@ -0,0 +1,114 @@
/*
* response.cpp
*
* Created on: May 16, 2019
* Author: cmy
*/
#include "response.h"
using namespace std;
namespace chttp {
constexpr char Headers::PlainText[];
constexpr char Headers::HtmlText[];
constexpr char Headers::CSSText[];
constexpr char Headers::JavascriptText[];
constexpr char Headers::GifImage[];
constexpr char Headers::PngImage[];
constexpr char Headers::JpegImage[];
constexpr char Headers::BmpImage[];
constexpr char Headers::WebpImage[];
constexpr char Headers::MidiAudio[];
constexpr char Headers::MpegAudio[];
constexpr char Headers::WebmAudio[];
constexpr char Headers::OggAudio[];
constexpr char Headers::WavAudio[];
constexpr char Headers::WebmVideo[];
constexpr char Headers::OggVideo[];
constexpr char Headers::XmlApplication[];
constexpr char Headers::PDFApplication[];
constexpr char HttpStatus::OK[];
constexpr char HttpStatus::BadRequest[];
constexpr char HttpStatus::Unauthorized[];
constexpr char HttpStatus::Forbidden[];
constexpr char HttpStatus::NotFound[];
constexpr char HttpStatus::Unavailable[];
constexpr char HttpVersionStr::Http1_0[];
constexpr char HttpVersionStr::Http1_1[];
constexpr char HttpVersionStr::Http2_0[];
string Response::NotFoundPage = "not_found.html";
Response::~Response() {
// TODO Auto-generated destructor stub
}
void Response::ServeHtml(const char* file_path) {
SendStatusLine(HttpVersionStr::Http1_1, HttpStatus::OK);
SetContentType(Headers::HtmlText);
SendHeader(header);
SendFile(clnt_sock, file_path);
close(clnt_sock);
}
void Response::SendFile(int clnt_sock, const char* file_path) {
#ifdef __linux__
auto file = fopen(file_path, "r");
if (file == NULL) {
throw "file_not_found";
} else {
char buffer[buffer_size];
fgets(buffer, sizeof(buffer), file);
while (!feof(file)) {
WriteSocket(clnt_sock, buffer, strlen(buffer));
fgets(buffer, sizeof(buffer), file);
}
}
fclose(file);
#endif
}
void Response::SendHeader(const Headers& headers) {
const auto content = headers.Content();
for (auto & header_pair : content) {
WriteSocket(clnt_sock, header_pair.first.c_str(), 0);
WriteSocket(clnt_sock, ": ", 2);
WriteSocket(clnt_sock, header_pair.second.c_str(), 0);
WriteSocket(clnt_sock, "\r\n", 0);
}
WriteSocket(clnt_sock, "\r\n", 0);
}
void Response::SendStatusLine(const char* http_version,
const char* http_status) {
WriteSocket(clnt_sock, http_version);
WriteSocket(clnt_sock, " ");
WriteSocket(clnt_sock, http_status);
WriteSocket(clnt_sock, "\r\n");
}
void Response::NotFound() {
SendStatusLine(HttpVersionStr::Http1_1, HttpStatus::NotFound);
SetContentType(Headers::HtmlText);
SendHeader(header);
SendFile(clnt_sock, NotFoundPage.c_str());
}
void Response::WriteSocket(int sock, const char* buffer, size_t size,
int flags) {
#ifdef __linux__
size_t length = size;
if (size == 0)
length = strlen(buffer);
send(sock, buffer, length, flags);
std::cout << "write to socket:";
for (size_t i = 0; i < length; ++i)
cout << buffer[i];
#endif
}
} /* namespace chttp */
+78
View File
@@ -0,0 +1,78 @@
/*
* response.h
*
* Created on: May 16, 2019
* Author: cmy
*/
#ifndef RESPONSE_H_
#define RESPONSE_H_
#include <cstdio>
#include <cstring>
#ifdef __linux__
#include <sys/socket.h>
#include <unistd.h>
#endif
#include "headers.h"
namespace chttp {
struct HttpStatus {
constexpr static char OK[] = "200 OK";
constexpr static char BadRequest[] = "400 Bad Request";
constexpr static char Unauthorized[] = "401 Unauthorized";
constexpr static char Forbidden[] = "403 Forbidden";
constexpr static char NotFound[] = "404 Not Found";
constexpr static char Unavailable[] = "503 Server Unavailable";
};
struct HttpVersionStr {
constexpr static char Http1_0[] = "HTTP/1.0";
constexpr static char Http1_1[] = "HTTP/1.1";
constexpr static char Http2_0[] = "HTTP/2.0";
};
class Response final {
public:
Response() = delete;
Response(int clnt_sock) :
clnt_sock(clnt_sock) {
}
~Response();
void ServeHtml(const char * file_path);
void NotFound();
inline void SetHeaders(const Headers& headers) {
header = headers;
}
inline void SetContentType(std::string&& content_type) {
header.Content()["Content-Type"] = content_type;
}
private:
friend class Server;
int clnt_sock;
int buffer_size { 1024 };
Headers header { };
__uint32_t clnt_ip;
static std::string NotFoundPage;
void SendStatusLine(const char* http_version, const char* http_status);
void SendHeader(const Headers& headers);
void SendFile(int clnt_sock, const char* file_path);
void WriteSocket(int clnt_sock, const char* buffer, size_t size = 0,
int flags = 0);
};
} /* namespace chttp */
#endif /* RESPONSE_H_ */
+16
View File
@@ -0,0 +1,16 @@
/*
* router.cpp
*
* Created on: May 16, 2019
* Author: cmy
*/
#include "router.h"
namespace chttp {
Router::~Router() {
// TODO Auto-generated destructor stub
}
} /* namespace chttp */
+111
View File
@@ -0,0 +1,111 @@
/*
* router.h
*
* Created on: May 16, 2019
* Author: cmy
*/
#ifndef ROUTER_H_
#define ROUTER_H_
#include <unordered_map>
#include <functional>
#include "request.h"
#include "response.h"
namespace chttp {
struct RequestInfo { //TODO:Hash func
Request::URL_type url;
Request_Method method;
RequestInfo(Request::URL_type url, Request_Method method) :
url(url), method(method) {
}
bool operator ==(const RequestInfo& other) const {
return (this->url == other.url) && (this->method == other.method);
}
};
struct RequestInfoHasher {
std::size_t operator()(const RequestInfo & info) const {
using std::hash;
return (hash<Request::URL_type>()(info.url)
^ hash<int>()(static_cast<int>(info.method)));
}
};
class Router final {
public:
using Handler=std::function<void(Request&, Response&)>;
using Handler_table=std::unordered_map<RequestInfo, Handler, RequestInfoHasher>;
Router() = default;
virtual ~Router();
// there should be a out-of-range exception when no handler
inline const Handler GetHandler(const Request::URL_type& url,
Request_Method method) const {
return handler_table.at(RequestInfo(url, method));
}
inline const Handler GetHandler(const RequestInfo& info) const {
return handler_table.at(info);
}
inline bool Get(const Request::URL_type& url, Handler handler) {
return AddHandler(url, Request_Method::GET, handler);
}
inline bool Head(const Request::URL_type& url, Handler handler) {
return AddHandler(url, Request_Method::HEAD, handler);
}
inline bool Post(const Request::URL_type& url, Handler handler) {
return AddHandler(url, Request_Method::POST, handler);
}
inline bool Put(const Request::URL_type& url, Handler handler) {
return AddHandler(url, Request_Method::PUT, handler);
}
inline bool Delete(const Request::URL_type& url, Handler handler) {
return AddHandler(url, Request_Method::DELETE, handler);
}
inline bool Connect(const Request::URL_type& url, Handler handler) {
return AddHandler(url, Request_Method::CONNECT, handler);
}
inline bool Options(const Request::URL_type& url, Handler handler) {
return AddHandler(url, Request_Method::OPTIONS, handler);
}
inline bool Trace(const Request::URL_type& url,
Handler handler) {
return AddHandler(url, Request_Method::TRACE, handler);
}
inline bool AddHandler(const Request::URL_type& url, Request_Method method,
Handler handler) {
auto result = handler_table.insert(
std::make_pair(RequestInfo(url, method), handler));
return result.second;
}
inline bool Contains(const Request::URL_type & url,
const Request_Method & method) const {
return Contains( { url, method });
}
inline bool Contains(const RequestInfo & info) const {
return handler_table.find(info) != handler_table.end();
}
private:
Handler_table handler_table { };
};
} /* namespace chttp */
#endif /* ROUTER_H_ */
+200
View File
@@ -0,0 +1,200 @@
/*
* socket.cpp
*
* Created on: May 16, 2019
* Author: cmy
*/
#include "server.h"
using namespace std;
namespace chttp {
Server::Server() {
// TODO Auto-generated constructor stub
}
Server::~Server() {
// TODO Auto-generated destructor stub
}
int Server::GetLine(int sock, char* buf, int size) {
#ifdef __linux__
int count { };
char c = '\0';
int n { };
while ((count < size - 1) && (c != '\n')) {
n = recv(sock, &c, 1, 0);
if (n > 0) {
if (c == '\r') {
n = recv(sock, &c, 1, MSG_PEEK); //peek the next char
if ((n > 0) && (c == '\n')) {
recv(sock, &c, 1, 0);
break;
} else
c = '\n';
}
buf[count] = c;
count++;
} else {
c = '\n';
}
}
buf[count] = '\0';
return count;
#endif
}
vector<string> Server::ReadRequest(const int & clnt_sock) {
int char_count { 1 };
char buffer[buffer_size];
vector<string> content { };
while (char_count > 0 && strcmp("\n", buffer)) {
char_count = GetLine(clnt_sock, buffer, sizeof(buffer));
content.push_back(buffer);
}
return content;
}
int Server::StartUpSocket(const in_port_t& port) {
int serv_sock { };
sockaddr_in serv_addr { };
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1) {
throw std::runtime_error { "socket_create_error" };
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(serv_sock, (sockaddr*) (&serv_addr), sizeof(serv_addr)) < 0) {
throw std::runtime_error { "bind_error" };
}
if (listen(serv_sock, 5) == -1) {
throw std::runtime_error { "listen error" };
}
return serv_sock;
}
void Server::ProcessRequest(int clnt_sock) {
auto request_string = ReadRequest(clnt_sock);
auto request_line = ParseRequestLine(request_string);
Request_Method method = std::get<0>(request_line);
URL_type url = std::get<1>(request_line);
Http_Version version = std::get<2>(request_line);
Headers header { };
for (auto it = request_string.begin() + 1; it + 1 != request_string.end(); // ignore the last empty line
++it) {
auto pair = GetKeyValuePair(*it);
header.SetHeader(pair.first, pair.second);
}
cout << "url: " << url << endl;
cout << "Headers:\n" << header << endl;
// TODO construct an request object
// TODO add client info to request
Request request { method, url, version, header };
// TODO construct an response object
Response response { clnt_sock };
// TODO call the handler function
RequestInfo request_info { url, method };
if (http_router.Contains(request_info)) {
auto handler = http_router.GetHandler(request_info);
handler(request, response);
cout << "find existing routers" << endl;
} else {
cout << "not find existing routers" << endl;
response.NotFound();
}
}
std::string::const_iterator Server::NextWord(std::string::const_iterator begin,
std::string::const_iterator end) {
for (auto it = begin; it != end; ++it) {
if (*it == ' ' || *it == '\0') {
return it;
}
}
return end;
}
std::tuple<Request_Method, URL_type, Http_Version> Server::ParseRequestLine(
const std::vector<std::string>& request_data) {
auto method_string_end = NextWord(request_data[0].cbegin(),
request_data[0].cend());
auto url_string_end = NextWord(method_string_end + 1,
request_data[0].end());
auto method_string = string { request_data[0].cbegin(), method_string_end };
auto url_string = string { method_string_end + 1, url_string_end };
auto version_string_end = NextWord(url_string_end + 1,
request_data[0].end());
auto version_string = string { url_string_end + 1, version_string_end };
auto method = GetRequestMethod(method_string);
auto url = GetRequestUrl(url_string);
auto http_version = GetHttpVersion(version_string.c_str());
return make_tuple(method, url, http_version);
}
Request_Method Server::GetRequestMethod(const std::string& str) {
auto c_str = str.c_str();
#ifdef __linux__
if (strcasecmp(c_str, "get") == 0) {
return Request_Method::GET;
} else if (strcasecmp(c_str, "post") == 0) {
return Request_Method::POST;
} else if (strcasecmp(c_str, "head") == 0) {
return Request_Method::HEAD;
} else if (strcasecmp(c_str, "put") == 0) {
return Request_Method::PUT;
} else if (strcasecmp(c_str, "delete") == 0) {
return Request_Method::DELETE;
} else if (strcasecmp(c_str, "options") == 0) {
return Request_Method::OPTIONS;
} else if (strcasecmp(c_str, "connect") == 0) {
return Request_Method::CONNECT;
} else if (strcasecmp(c_str, "trace") == 0) {
return Request_Method::TRACE;
} else {
return Request_Method::NONE;
}
#endif
}
Http_Version Server::GetHttpVersion(const std::string& str) {
auto c_str = str.c_str();
#ifdef __linux__
if (strcasecmp(c_str, "HTTP/1.1")) {
return Http_Version::HTTP1_1;
} else if (strcasecmp(c_str, "HTTP/1.0")) {
return Http_Version::HTTP1_0;
} else if (strcasecmp(c_str, "HTTP/2.0")) {
return Http_Version::HTTP2_0;
} else {
return Http_Version::NONE;
}
#endif
}
URL_type Server::GetRequestUrl(const std::string& str) {
return URL_type { str };
}
std::pair<std::string, std::string> Server::GetKeyValuePair(
const std::string& str) {
auto it = str.find(": ");
if (it == string::npos) {
throw runtime_error { "no colon-blank separator found" };
}
string key { str.cbegin(), str.begin() + it };
string value { str.begin() + it + 2, str.end() };
return make_pair(key, value);
}
} /* namespace chttp */
+91
View File
@@ -0,0 +1,91 @@
/*
* socket.h
*
* Created on: May 16, 2019
* Author: cmy
*/
#ifndef SERVER_H_
#define SERVER_H_
#include <vector>
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
#include <stdexcept>
#include <iterator>
#include <cstdio>
#include <cstring>
#ifdef __linux__
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#endif
#include "request.h"
#include "response.h"
#include "router.h"
namespace chttp {
class Server final {
public:
#ifdef __linux__
using Port_type = in_port_t;
#endif
Server();
~Server();
void Test(const in_port_t& port) {
auto serv_sock = StartUpSocket(port);
std::cout << "listening on port:" << port << std::endl;
struct sockaddr_in clnt_addr;
unsigned int clnt_addr_len = sizeof(clnt_addr);
while (true) {
int clnt_sock = accept(serv_sock, (sockaddr*) &clnt_addr,
&clnt_addr_len);
auto clnt_ip = inet_ntoa(clnt_addr.sin_addr);
// ntohl(clnt_addr.sin_addr.s_addr); // binary of 127.0.0.1
auto clnt_port = clnt_addr.sin_port;
std::cout << "port: " << clnt_port << std::endl;
ProcessRequest(clnt_sock);
close(clnt_sock);
}
}
inline void SetRouter(const Router router) {
http_router = router;
}
private:
int serv_socket { };
int buffer_size { 1024 };
Router http_router { };
int StartUpSocket(const in_port_t& port);
void ProcessRequest(int clnt_sock);
std::vector<std::string> ReadRequest(const int& clnt_sock);
std::tuple<Request_Method, URL_type, Http_Version> ParseRequestLine(
const std::vector<std::string>& request_data);
Headers ParseRequestHeader(const std::vector<std::string>& request_data);
int GetLine(int sock, char* buf, int size);
std::string::const_iterator NextWord(std::string::const_iterator begin,
std::string::const_iterator end);
Request_Method GetRequestMethod(const std::string & str);
Http_Version GetHttpVersion(const std::string & str);
URL_type GetRequestUrl(const std::string & str);
std::pair<std::string, std::string> GetKeyValuePair(const std::string& str);
};
} /* namespace chttp */
#endif /* SERVER_H_ */