initial commit
This commit is contained in:
177
third_party/libhv/http/server/FileCache.cpp
vendored
Executable file
177
third_party/libhv/http/server/FileCache.cpp
vendored
Executable file
@@ -0,0 +1,177 @@
|
||||
#include "FileCache.h"
|
||||
|
||||
#include "herr.h"
|
||||
#include "hscope.h"
|
||||
#include "htime.h"
|
||||
#include "hlog.h"
|
||||
|
||||
#include "httpdef.h" // import http_content_type_str_by_suffix
|
||||
#include "http_page.h" // import make_index_of_page
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <codecvt>
|
||||
#endif
|
||||
|
||||
#define ETAG_FMT "\"%zx-%zx\""
|
||||
|
||||
FileCache::FileCache() {
|
||||
stat_interval = 10; // s
|
||||
expired_time = 60; // s
|
||||
}
|
||||
|
||||
static int hv_open(char const* filepath, int flags) {
|
||||
#ifdef _MSC_VER
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> conv;
|
||||
auto wfilepath = conv.from_bytes(filepath);
|
||||
int fd = _wopen(wfilepath.c_str(), flags);
|
||||
#else
|
||||
int fd = open(filepath, flags);
|
||||
#endif
|
||||
return fd;
|
||||
}
|
||||
|
||||
file_cache_ptr FileCache::Open(const char* filepath, OpenParam* param) {
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
file_cache_ptr fc = Get(filepath);
|
||||
bool modified = false;
|
||||
if (fc) {
|
||||
time_t now = time(NULL);
|
||||
if (now - fc->stat_time > stat_interval) {
|
||||
modified = fc->is_modified();
|
||||
fc->stat_time = now;
|
||||
fc->stat_cnt++;
|
||||
}
|
||||
if (param->need_read) {
|
||||
if (!modified && fc->is_complete()) {
|
||||
param->need_read = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fc == NULL || modified || param->need_read) {
|
||||
int flags = O_RDONLY;
|
||||
#ifdef O_BINARY
|
||||
flags |= O_BINARY;
|
||||
#endif
|
||||
int fd = hv_open(filepath, flags);
|
||||
if (fd < 0) {
|
||||
#ifdef OS_WIN
|
||||
// NOTE: open(dir) return -1 on windows
|
||||
if (!hv_isdir(filepath)) {
|
||||
param->error = ERR_OPEN_FILE;
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
param->error = ERR_OPEN_FILE;
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
defer(if (fd > 0) { close(fd); })
|
||||
if (fc == NULL) {
|
||||
struct stat st;
|
||||
if (fd > 0) {
|
||||
fstat(fd, &st);
|
||||
} else {
|
||||
stat(filepath, &st);
|
||||
}
|
||||
if (S_ISREG(st.st_mode) ||
|
||||
(S_ISDIR(st.st_mode) &&
|
||||
filepath[strlen(filepath)-1] == '/')) {
|
||||
fc = std::make_shared<file_cache_t>();
|
||||
fc->filepath = filepath;
|
||||
fc->st = st;
|
||||
time(&fc->open_time);
|
||||
fc->stat_time = fc->open_time;
|
||||
fc->stat_cnt = 1;
|
||||
cached_files[filepath] = fc;
|
||||
}
|
||||
else {
|
||||
param->error = ERR_MISMATCH;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (S_ISREG(fc->st.st_mode)) {
|
||||
param->filesize = fc->st.st_size;
|
||||
// FILE
|
||||
if (param->need_read) {
|
||||
if (fc->st.st_size > param->max_read) {
|
||||
param->error = ERR_OVER_LIMIT;
|
||||
return NULL;
|
||||
}
|
||||
fc->resize_buf(fc->st.st_size);
|
||||
int nread = read(fd, fc->filebuf.base, fc->filebuf.len);
|
||||
if (nread != fc->filebuf.len) {
|
||||
hloge("Failed to read file: %s", filepath);
|
||||
param->error = ERR_READ_FILE;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
const char* suffix = strrchr(filepath, '.');
|
||||
if (suffix) {
|
||||
http_content_type content_type = http_content_type_enum_by_suffix(suffix+1);
|
||||
if (content_type == TEXT_HTML) {
|
||||
fc->content_type = "text/html; charset=utf-8";
|
||||
} else if (content_type == TEXT_PLAIN) {
|
||||
fc->content_type = "text/plain; charset=utf-8";
|
||||
} else {
|
||||
fc->content_type = http_content_type_str_by_suffix(suffix+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (S_ISDIR(fc->st.st_mode)) {
|
||||
// DIR
|
||||
std::string page;
|
||||
make_index_of_page(filepath, page, param->path);
|
||||
fc->resize_buf(page.size());
|
||||
memcpy(fc->filebuf.base, page.c_str(), page.size());
|
||||
fc->content_type = "text/html; charset=utf-8";
|
||||
}
|
||||
gmtime_fmt(fc->st.st_mtime, fc->last_modified);
|
||||
snprintf(fc->etag, sizeof(fc->etag), ETAG_FMT, (size_t)fc->st.st_mtime, (size_t)fc->st.st_size);
|
||||
}
|
||||
return fc;
|
||||
}
|
||||
|
||||
bool FileCache::Close(const char* filepath) {
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
auto iter = cached_files.find(filepath);
|
||||
if (iter != cached_files.end()) {
|
||||
iter = cached_files.erase(iter);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileCache::Close(const file_cache_ptr& fc) {
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
auto iter = cached_files.begin();
|
||||
while (iter != cached_files.end()) {
|
||||
if (iter->second == fc) {
|
||||
iter = cached_files.erase(iter);
|
||||
return true;
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
file_cache_ptr FileCache::Get(const char* filepath) {
|
||||
auto iter = cached_files.find(filepath);
|
||||
if (iter != cached_files.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void FileCache::RemoveExpiredFileCache() {
|
||||
std::lock_guard<std::mutex> locker(mutex_);
|
||||
time_t now = time(NULL);
|
||||
auto iter = cached_files.begin();
|
||||
while (iter != cached_files.end()) {
|
||||
if (now - iter->second->stat_time > expired_time) {
|
||||
iter = cached_files.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
third_party/libhv/http/server/FileCache.h
vendored
Executable file
93
third_party/libhv/http/server/FileCache.h
vendored
Executable file
@@ -0,0 +1,93 @@
|
||||
#ifndef HV_FILE_CACHE_H_
|
||||
#define HV_FILE_CACHE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
#include "hbuf.h"
|
||||
#include "hstring.h"
|
||||
|
||||
#define HTTP_HEADER_MAX_LENGTH 1024 // 1K
|
||||
#define FILE_CACHE_MAX_SIZE (1 << 22) // 4M
|
||||
|
||||
typedef struct file_cache_s {
|
||||
std::string filepath;
|
||||
struct stat st;
|
||||
time_t open_time;
|
||||
time_t stat_time;
|
||||
uint32_t stat_cnt;
|
||||
HBuf buf; // http_header + file_content
|
||||
hbuf_t filebuf;
|
||||
hbuf_t httpbuf;
|
||||
char last_modified[64];
|
||||
char etag[64];
|
||||
std::string content_type;
|
||||
|
||||
file_cache_s() {
|
||||
stat_cnt = 0;
|
||||
}
|
||||
|
||||
bool is_modified() {
|
||||
time_t mtime = st.st_mtime;
|
||||
stat(filepath.c_str(), &st);
|
||||
return mtime != st.st_mtime;
|
||||
}
|
||||
|
||||
bool is_complete() {
|
||||
return filebuf.len == st.st_size;
|
||||
}
|
||||
|
||||
void resize_buf(int filesize) {
|
||||
buf.resize(HTTP_HEADER_MAX_LENGTH + filesize);
|
||||
filebuf.base = buf.base + HTTP_HEADER_MAX_LENGTH;
|
||||
filebuf.len = filesize;
|
||||
}
|
||||
|
||||
void prepend_header(const char* header, int len) {
|
||||
if (len > HTTP_HEADER_MAX_LENGTH) return;
|
||||
httpbuf.base = filebuf.base - len;
|
||||
httpbuf.len = len + filebuf.len;
|
||||
memcpy(httpbuf.base, header, len);
|
||||
}
|
||||
} file_cache_t;
|
||||
|
||||
typedef std::shared_ptr<file_cache_t> file_cache_ptr;
|
||||
// filepath => file_cache_ptr
|
||||
typedef std::map<std::string, file_cache_ptr> FileCacheMap;
|
||||
|
||||
class FileCache {
|
||||
public:
|
||||
FileCacheMap cached_files;
|
||||
std::mutex mutex_;
|
||||
int stat_interval;
|
||||
int expired_time;
|
||||
|
||||
FileCache();
|
||||
|
||||
struct OpenParam {
|
||||
bool need_read;
|
||||
int max_read;
|
||||
const char* path;
|
||||
size_t filesize;
|
||||
int error;
|
||||
|
||||
OpenParam() {
|
||||
need_read = true;
|
||||
max_read = FILE_CACHE_MAX_SIZE;
|
||||
path = "/";
|
||||
filesize = 0;
|
||||
error = 0;
|
||||
}
|
||||
};
|
||||
file_cache_ptr Open(const char* filepath, OpenParam* param);
|
||||
bool Close(const char* filepath);
|
||||
bool Close(const file_cache_ptr& fc);
|
||||
void RemoveExpiredFileCache();
|
||||
|
||||
protected:
|
||||
file_cache_ptr Get(const char* filepath);
|
||||
};
|
||||
|
||||
#endif // HV_FILE_CACHE_H_
|
||||
213
third_party/libhv/http/server/HttpContext.h
vendored
Executable file
213
third_party/libhv/http/server/HttpContext.h
vendored
Executable file
@@ -0,0 +1,213 @@
|
||||
#ifndef HV_HTTP_CONTEXT_H_
|
||||
#define HV_HTTP_CONTEXT_H_
|
||||
|
||||
#include "hexport.h"
|
||||
#include "HttpMessage.h"
|
||||
#include "HttpResponseWriter.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
struct HttpService;
|
||||
struct HV_EXPORT HttpContext {
|
||||
HttpService* service;
|
||||
HttpRequestPtr request;
|
||||
HttpResponsePtr response;
|
||||
HttpResponseWriterPtr writer;
|
||||
void* userdata;
|
||||
|
||||
HttpContext() {
|
||||
service = NULL;
|
||||
userdata = NULL;
|
||||
}
|
||||
|
||||
// HttpRequest aliases
|
||||
// return request->xxx
|
||||
std::string ip() {
|
||||
return request->client_addr.ip;
|
||||
}
|
||||
|
||||
int port() {
|
||||
return request->client_addr.port;
|
||||
}
|
||||
|
||||
http_method method() {
|
||||
return request->method;
|
||||
}
|
||||
|
||||
std::string url() {
|
||||
return request->url;
|
||||
}
|
||||
|
||||
std::string path() {
|
||||
return request->Path();
|
||||
}
|
||||
|
||||
std::string fullpath() {
|
||||
return request->FullPath();
|
||||
}
|
||||
|
||||
std::string host() {
|
||||
return request->Host();
|
||||
}
|
||||
|
||||
const http_headers& headers() {
|
||||
return request->headers;
|
||||
}
|
||||
|
||||
std::string header(const char* key, const std::string& defvalue = hv::empty_string) {
|
||||
return request->GetHeader(key, defvalue);
|
||||
}
|
||||
|
||||
const hv::QueryParams& params() {
|
||||
return request->query_params;
|
||||
}
|
||||
|
||||
std::string param(const char* key, const std::string& defvalue = hv::empty_string) {
|
||||
return request->GetParam(key, defvalue);
|
||||
}
|
||||
|
||||
const HttpCookie& cookie(const char* name) {
|
||||
return request->GetCookie(name);
|
||||
}
|
||||
|
||||
int length() {
|
||||
return request->ContentLength();
|
||||
}
|
||||
|
||||
http_content_type type() {
|
||||
return request->ContentType();
|
||||
}
|
||||
|
||||
bool is(http_content_type content_type) {
|
||||
return request->ContentType() == content_type;
|
||||
}
|
||||
|
||||
bool is(const char* content_type) {
|
||||
return request->ContentType() == http_content_type_enum(content_type);
|
||||
}
|
||||
|
||||
std::string& body() {
|
||||
return request->body;
|
||||
}
|
||||
|
||||
#ifndef WITHOUT_HTTP_CONTENT
|
||||
// Content-Type: application/json
|
||||
const hv::Json& json() {
|
||||
return request->GetJson();
|
||||
}
|
||||
|
||||
// Content-Type: multipart/form-data
|
||||
const hv::MultiPart& form() {
|
||||
return request->GetForm();
|
||||
}
|
||||
std::string form(const char* name, const std::string& defvalue = hv::empty_string) {
|
||||
return request->GetFormData(name, defvalue);
|
||||
}
|
||||
|
||||
// Content-Type: application/x-www-form-urlencoded
|
||||
const hv::KeyValue& urlencoded() {
|
||||
return request->GetUrlEncoded();
|
||||
}
|
||||
std::string urlencoded(const char* key, const std::string& defvalue = hv::empty_string) {
|
||||
return request->GetUrlEncoded(key, defvalue);
|
||||
}
|
||||
|
||||
// T=[bool, int, int64_t, float, double]
|
||||
template<typename T>
|
||||
T get(const char* key, T defvalue = 0) {
|
||||
return request->Get(key, defvalue);
|
||||
}
|
||||
std::string get(const char* key, const std::string& defvalue = hv::empty_string) {
|
||||
return request->GetString(key, defvalue);
|
||||
}
|
||||
#endif
|
||||
|
||||
// HttpResponse aliases
|
||||
// response->xxx = xxx
|
||||
void setStatus(http_status status) {
|
||||
response->status_code = status;
|
||||
}
|
||||
|
||||
void setContentType(http_content_type type) {
|
||||
response->content_type = type;
|
||||
}
|
||||
|
||||
void setContentType(const char* type) {
|
||||
response->content_type = http_content_type_enum(type);
|
||||
}
|
||||
|
||||
void setHeader(const char* key, const std::string& value) {
|
||||
response->SetHeader(key, value);
|
||||
if (stricmp(key, "Content-Type") == 0) {
|
||||
setContentType(value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void setCookie(const HttpCookie& cookie) {
|
||||
response->AddCookie(cookie);
|
||||
}
|
||||
|
||||
void setBody(const std::string& body) {
|
||||
response->body = body;
|
||||
}
|
||||
|
||||
// response->sendXxx
|
||||
int send() {
|
||||
if (writer) {
|
||||
writer->End();
|
||||
}
|
||||
return response->status_code;
|
||||
}
|
||||
|
||||
int send(const std::string& str, http_content_type type = APPLICATION_JSON) {
|
||||
response->content_type = type;
|
||||
response->body = str;
|
||||
return send();
|
||||
}
|
||||
|
||||
int sendString(const std::string& str) {
|
||||
response->String(str);
|
||||
return send();
|
||||
}
|
||||
|
||||
int sendData(void* data, int len, bool nocopy = true) {
|
||||
response->Data(data, len, nocopy);
|
||||
return send();
|
||||
}
|
||||
|
||||
int sendFile(const char* filepath) {
|
||||
response->File(filepath);
|
||||
return send();
|
||||
}
|
||||
|
||||
#ifndef WITHOUT_HTTP_CONTENT
|
||||
// T=[bool, int, int64_t, float, double, string]
|
||||
template<typename T>
|
||||
void set(const char* key, const T& value) {
|
||||
response->Set(key, value);
|
||||
}
|
||||
|
||||
// @see HttpMessage::Json
|
||||
// @usage https://github.com/nlohmann/json
|
||||
template<typename T>
|
||||
int sendJson(const T& t) {
|
||||
response->Json(t);
|
||||
return send();
|
||||
}
|
||||
#endif
|
||||
|
||||
int redirect(const std::string& location, http_status status = HTTP_STATUS_FOUND) {
|
||||
response->Redirect(location, status);
|
||||
return send();
|
||||
}
|
||||
|
||||
int close() {
|
||||
return writer ? writer->close(true) : -1;
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace hv
|
||||
|
||||
typedef std::shared_ptr<hv::HttpContext> HttpContextPtr;
|
||||
|
||||
#endif // HV_HTTP_CONTEXT_H_
|
||||
1148
third_party/libhv/http/server/HttpHandler.cpp
vendored
Executable file
1148
third_party/libhv/http/server/HttpHandler.cpp
vendored
Executable file
File diff suppressed because it is too large
Load Diff
186
third_party/libhv/http/server/HttpHandler.h
vendored
Executable file
186
third_party/libhv/http/server/HttpHandler.h
vendored
Executable file
@@ -0,0 +1,186 @@
|
||||
#ifndef HV_HTTP_HANDLER_H_
|
||||
#define HV_HTTP_HANDLER_H_
|
||||
|
||||
#include "HttpService.h"
|
||||
#include "HttpParser.h"
|
||||
#include "FileCache.h"
|
||||
|
||||
#include "WebSocketServer.h"
|
||||
#include "WebSocketParser.h"
|
||||
|
||||
class HttpHandler {
|
||||
public:
|
||||
enum ProtocolType {
|
||||
UNKNOWN,
|
||||
HTTP_V1,
|
||||
HTTP_V2,
|
||||
WEBSOCKET,
|
||||
} protocol;
|
||||
|
||||
enum State {
|
||||
WANT_RECV,
|
||||
HANDLE_BEGIN,
|
||||
HANDLE_CONTINUE,
|
||||
HANDLE_END,
|
||||
WANT_SEND,
|
||||
SEND_HEADER,
|
||||
SEND_BODY,
|
||||
SEND_DONE,
|
||||
WANT_CLOSE,
|
||||
} state;
|
||||
|
||||
// errno
|
||||
int error;
|
||||
|
||||
// flags
|
||||
unsigned ssl :1;
|
||||
unsigned keepalive :1;
|
||||
unsigned upgrade :1;
|
||||
unsigned proxy :1;
|
||||
unsigned proxy_connected :1;
|
||||
unsigned forward_proxy :1;
|
||||
unsigned reverse_proxy :1;
|
||||
|
||||
// peeraddr
|
||||
char ip[64];
|
||||
int port;
|
||||
|
||||
// for log
|
||||
long pid;
|
||||
long tid;
|
||||
|
||||
// for http
|
||||
hio_t *io;
|
||||
HttpService *service;
|
||||
HttpRequestPtr req;
|
||||
HttpResponsePtr resp;
|
||||
HttpResponseWriterPtr writer;
|
||||
HttpParserPtr parser;
|
||||
HttpContextPtr ctx;
|
||||
http_handler* api_handler;
|
||||
|
||||
// for GetSendData
|
||||
std::string header;
|
||||
// std::string body;
|
||||
|
||||
// for websocket
|
||||
WebSocketService* ws_service;
|
||||
WebSocketChannelPtr ws_channel;
|
||||
WebSocketParserPtr ws_parser;
|
||||
uint64_t last_send_ping_time;
|
||||
uint64_t last_recv_pong_time;
|
||||
|
||||
// for sendfile
|
||||
FileCache *files;
|
||||
file_cache_ptr fc; // cache small file
|
||||
struct LargeFile : public HFile {
|
||||
HBuf buf;
|
||||
uint64_t timer;
|
||||
} *file; // for large file
|
||||
|
||||
// for proxy
|
||||
std::string proxy_host;
|
||||
int proxy_port;
|
||||
|
||||
HttpHandler(hio_t* io = NULL);
|
||||
~HttpHandler();
|
||||
|
||||
bool Init(int http_version = 1);
|
||||
void Reset();
|
||||
void Close();
|
||||
|
||||
/* @workflow:
|
||||
* HttpServer::on_recv -> HttpHandler::FeedRecvData -> Init -> HttpParser::InitRequest -> HttpRequest::http_cb ->
|
||||
* onHeadersComplete -> proxy ? handleProxy -> connectProxy :
|
||||
* onMessageComplete -> upgrade ? handleUpgrade : HandleHttpRequest -> HttpParser::SubmitResponse ->
|
||||
* SendHttpResponse -> while(GetSendData) hio_write ->
|
||||
* keepalive ? Reset : Close -> hio_close
|
||||
*
|
||||
* @return
|
||||
* == len: ok
|
||||
* == 0: WANT_CLOSE
|
||||
* < 0: error
|
||||
*/
|
||||
int FeedRecvData(const char* data, size_t len);
|
||||
|
||||
/* @workflow:
|
||||
* preprocessor -> middleware -> processor -> postprocessor
|
||||
*
|
||||
* @return status_code
|
||||
* == 0: HANDLE_CONTINUE
|
||||
* != 0: HANDLE_END
|
||||
*/
|
||||
int HandleHttpRequest();
|
||||
|
||||
int GetSendData(char** data, size_t* len);
|
||||
|
||||
int SendHttpResponse(bool submit = true);
|
||||
int SendHttpStatusResponse(http_status status_code);
|
||||
|
||||
// HTTP2
|
||||
bool SwitchHTTP2();
|
||||
|
||||
// websocket
|
||||
bool SwitchWebSocket();
|
||||
void WebSocketOnOpen() {
|
||||
ws_channel->status = hv::SocketChannel::CONNECTED;
|
||||
if (ws_service && ws_service->onopen) {
|
||||
ws_service->onopen(ws_channel, req);
|
||||
}
|
||||
}
|
||||
void WebSocketOnClose() {
|
||||
ws_channel->status = hv::SocketChannel::DISCONNECTED;
|
||||
if (ws_service && ws_service->onclose) {
|
||||
ws_service->onclose(ws_channel);
|
||||
}
|
||||
}
|
||||
|
||||
int SetError(int error_code, http_status status_code = HTTP_STATUS_BAD_REQUEST) {
|
||||
error = error_code;
|
||||
if (resp) resp->status_code = status_code;
|
||||
return error;
|
||||
}
|
||||
|
||||
private:
|
||||
const HttpContextPtr& context();
|
||||
int handleRequestHeaders();
|
||||
// Expect: 100-continue
|
||||
void handleExpect100();
|
||||
void addResponseHeaders();
|
||||
|
||||
// http_cb
|
||||
void onHeadersComplete();
|
||||
void onBody(const char* data, size_t size);
|
||||
void onMessageComplete();
|
||||
|
||||
// default handlers
|
||||
int defaultRequestHandler();
|
||||
int defaultStaticHandler();
|
||||
int defaultLargeFileHandler();
|
||||
int defaultErrorHandler();
|
||||
int customHttpHandler(const http_handler& handler);
|
||||
int invokeHttpHandler(const http_handler* handler);
|
||||
|
||||
// sendfile
|
||||
int openFile(const char* filepath);
|
||||
int sendFile();
|
||||
void closeFile();
|
||||
bool isFileOpened();
|
||||
|
||||
// upgrade
|
||||
int handleUpgrade(const char* upgrade_protocol);
|
||||
int upgradeWebSocket();
|
||||
int upgradeHTTP2();
|
||||
|
||||
// proxy
|
||||
int handleProxy();
|
||||
int handleForwardProxy();
|
||||
int handleReverseProxy();
|
||||
int connectProxy(const std::string& url);
|
||||
int closeProxy();
|
||||
int sendProxyRequest();
|
||||
static void onProxyConnect(hio_t* upstream_io);
|
||||
static void onProxyClose(hio_t* upstream_io);
|
||||
};
|
||||
|
||||
#endif // HV_HTTP_HANDLER_H_
|
||||
16
third_party/libhv/http/server/HttpMiddleware.cpp
vendored
Executable file
16
third_party/libhv/http/server/HttpMiddleware.cpp
vendored
Executable file
@@ -0,0 +1,16 @@
|
||||
#include "HttpMiddleware.h"
|
||||
#include "HttpService.h"
|
||||
|
||||
BEGIN_NAMESPACE_HV
|
||||
|
||||
int HttpMiddleware::CORS(HttpRequest* req, HttpResponse* resp) {
|
||||
resp->headers["Access-Control-Allow-Origin"] = req->GetHeader("Origin", "*");
|
||||
if (req->method == HTTP_OPTIONS) {
|
||||
resp->headers["Access-Control-Allow-Methods"] = req->GetHeader("Access-Control-Request-Method", "OPTIONS, HEAD, GET, POST, PUT, DELETE, PATCH");
|
||||
resp->headers["Access-Control-Allow-Headers"] = req->GetHeader("Access-Control-Request-Headers", "Content-Type");
|
||||
return HTTP_STATUS_NO_CONTENT;
|
||||
}
|
||||
return HTTP_STATUS_NEXT;
|
||||
}
|
||||
|
||||
END_NAMESPACE_HV
|
||||
16
third_party/libhv/http/server/HttpMiddleware.h
vendored
Executable file
16
third_party/libhv/http/server/HttpMiddleware.h
vendored
Executable file
@@ -0,0 +1,16 @@
|
||||
#ifndef HV_HTTP_MIDDLEWARE_H_
|
||||
#define HV_HTTP_MIDDLEWARE_H_
|
||||
|
||||
#include "hexport.h"
|
||||
#include "HttpContext.h"
|
||||
|
||||
BEGIN_NAMESPACE_HV
|
||||
|
||||
class HttpMiddleware {
|
||||
public:
|
||||
static int CORS(HttpRequest* req, HttpResponse* resp);
|
||||
};
|
||||
|
||||
END_NAMESPACE_HV
|
||||
|
||||
#endif // HV_HTTP_MIDDLEWARE_H_
|
||||
121
third_party/libhv/http/server/HttpResponseWriter.cpp
vendored
Executable file
121
third_party/libhv/http/server/HttpResponseWriter.cpp
vendored
Executable file
@@ -0,0 +1,121 @@
|
||||
#include "HttpResponseWriter.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
int HttpResponseWriter::EndHeaders(const char* key /* = NULL */, const char* value /* = NULL */) {
|
||||
if (state != SEND_BEGIN) return -1;
|
||||
if (key && value) {
|
||||
response->SetHeader(key, value);
|
||||
}
|
||||
std::string headers = response->Dump(true, false);
|
||||
// erase Content-Length: 0\r\n
|
||||
std::string content_length_0("Content-Length: 0\r\n");
|
||||
auto pos = headers.find(content_length_0);
|
||||
if (pos != std::string::npos) {
|
||||
headers.erase(pos, content_length_0.size());
|
||||
}
|
||||
state = SEND_HEADER;
|
||||
return write(headers);
|
||||
}
|
||||
|
||||
int HttpResponseWriter::WriteChunked(const char* buf, int len /* = -1 */) {
|
||||
int ret = 0;
|
||||
if (len == -1) len = strlen(buf);
|
||||
if (state == SEND_BEGIN) {
|
||||
EndHeaders("Transfer-Encoding", "chunked");
|
||||
}
|
||||
char chunked_header[64];
|
||||
int chunked_header_len = snprintf(chunked_header, sizeof(chunked_header), "%x\r\n", len);
|
||||
write(chunked_header, chunked_header_len);
|
||||
if (buf && len) {
|
||||
state = SEND_CHUNKED;
|
||||
ret = write(buf, len);
|
||||
} else {
|
||||
state = SEND_CHUNKED_END;
|
||||
}
|
||||
write("\r\n", 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int HttpResponseWriter::WriteBody(const char* buf, int len /* = -1 */) {
|
||||
if (response->IsChunked()) {
|
||||
return WriteChunked(buf, len);
|
||||
}
|
||||
|
||||
if (len == -1) len = strlen(buf);
|
||||
if (state == SEND_BEGIN) {
|
||||
response->body.append(buf, len);
|
||||
return len;
|
||||
} else {
|
||||
state = SEND_BODY;
|
||||
return write(buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
int HttpResponseWriter::WriteResponse(HttpResponse* resp) {
|
||||
if (resp == NULL) {
|
||||
response->status_code = HTTP_STATUS_INTERNAL_SERVER_ERROR;
|
||||
return 0;
|
||||
}
|
||||
bool is_dump_headers = state == SEND_BEGIN ? true : false;
|
||||
std::string msg = resp->Dump(is_dump_headers, true);
|
||||
state = SEND_BODY;
|
||||
return write(msg);
|
||||
}
|
||||
|
||||
int HttpResponseWriter::SSEvent(const std::string& data, const char* event /* = "message" */) {
|
||||
if (state == SEND_BEGIN) {
|
||||
EndHeaders("Content-Type", "text/event-stream");
|
||||
}
|
||||
std::string msg;
|
||||
if (event) {
|
||||
msg = "event: "; msg += event; msg += "\n";
|
||||
}
|
||||
msg += "data: "; msg += data; msg += "\n\n";
|
||||
state = SEND_BODY;
|
||||
return write(msg);
|
||||
}
|
||||
|
||||
int HttpResponseWriter::End(const char* buf /* = NULL */, int len /* = -1 */) {
|
||||
if (end == SEND_END) return 0;
|
||||
end = SEND_END;
|
||||
|
||||
if (!isConnected()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
bool keepAlive = response->IsKeepAlive();
|
||||
if (state == SEND_CHUNKED) {
|
||||
if (buf) {
|
||||
ret = WriteChunked(buf, len);
|
||||
}
|
||||
if (state == SEND_CHUNKED) {
|
||||
EndChunked();
|
||||
}
|
||||
} else {
|
||||
if (buf) {
|
||||
ret = WriteBody(buf, len);
|
||||
}
|
||||
bool is_dump_headers = true;
|
||||
bool is_dump_body = true;
|
||||
if (state == SEND_HEADER) {
|
||||
is_dump_headers = false;
|
||||
} else if (state == SEND_BODY) {
|
||||
is_dump_headers = false;
|
||||
is_dump_body = false;
|
||||
}
|
||||
if (is_dump_body) {
|
||||
std::string msg = response->Dump(is_dump_headers, is_dump_body);
|
||||
state = SEND_BODY;
|
||||
ret = write(msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepAlive) {
|
||||
close(true);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
100
third_party/libhv/http/server/HttpResponseWriter.h
vendored
Executable file
100
third_party/libhv/http/server/HttpResponseWriter.h
vendored
Executable file
@@ -0,0 +1,100 @@
|
||||
#ifndef HV_HTTP_RESPONSE_WRITER_H_
|
||||
#define HV_HTTP_RESPONSE_WRITER_H_
|
||||
|
||||
#include "Channel.h"
|
||||
#include "HttpMessage.h"
|
||||
|
||||
namespace hv {
|
||||
|
||||
class HV_EXPORT HttpResponseWriter : public SocketChannel {
|
||||
public:
|
||||
HttpResponsePtr response;
|
||||
enum State {
|
||||
SEND_BEGIN = 0,
|
||||
SEND_HEADER,
|
||||
SEND_BODY,
|
||||
SEND_CHUNKED,
|
||||
SEND_CHUNKED_END,
|
||||
SEND_END,
|
||||
} state: 8, end: 8;
|
||||
HttpResponseWriter(hio_t* io, const HttpResponsePtr& resp)
|
||||
: SocketChannel(io)
|
||||
, response(resp)
|
||||
, state(SEND_BEGIN)
|
||||
, end(SEND_BEGIN)
|
||||
{}
|
||||
~HttpResponseWriter() {}
|
||||
|
||||
// Begin -> End
|
||||
// Begin -> WriteResponse -> End
|
||||
// Begin -> WriteStatus -> WriteHeader -> WriteBody -> End
|
||||
// Begin -> EndHeaders("Content-Type", "text/event-stream") -> write -> write -> ... -> close
|
||||
// Begin -> EndHeaders("Content-Length", content_length) -> WriteBody -> WriteBody -> ... -> End
|
||||
// Begin -> EndHeaders("Transfer-Encoding", "chunked") -> WriteChunked -> WriteChunked -> ... -> End
|
||||
|
||||
int Begin() {
|
||||
state = end = SEND_BEGIN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WriteStatus(http_status status_codes) {
|
||||
response->status_code = status_codes;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WriteHeader(const char* key, const char* value) {
|
||||
response->SetHeader(key, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int WriteHeader(const char* key, T num) {
|
||||
response->SetHeader(key, hv::to_string(num));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WriteCookie(const HttpCookie& cookie) {
|
||||
response->cookies.push_back(cookie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int EndHeaders(const char* key = NULL, const char* value = NULL);
|
||||
|
||||
template<typename T>
|
||||
int EndHeaders(const char* key, T num) {
|
||||
std::string value = hv::to_string(num);
|
||||
return EndHeaders(key, value.c_str());
|
||||
}
|
||||
|
||||
int WriteChunked(const char* buf, int len = -1);
|
||||
|
||||
int WriteChunked(const std::string& str) {
|
||||
return WriteChunked(str.c_str(), str.size());
|
||||
}
|
||||
|
||||
int EndChunked() {
|
||||
return WriteChunked(NULL, 0);
|
||||
}
|
||||
|
||||
int WriteBody(const char* buf, int len = -1);
|
||||
|
||||
int WriteBody(const std::string& str) {
|
||||
return WriteBody(str.c_str(), str.size());
|
||||
}
|
||||
|
||||
int WriteResponse(HttpResponse* resp);
|
||||
|
||||
int SSEvent(const std::string& data, const char* event = "message");
|
||||
|
||||
int End(const char* buf = NULL, int len = -1);
|
||||
|
||||
int End(const std::string& str) {
|
||||
return End(str.c_str(), str.size());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
typedef std::shared_ptr<hv::HttpResponseWriter> HttpResponseWriterPtr;
|
||||
|
||||
#endif // HV_HTTP_RESPONSE_WRITER_H_
|
||||
311
third_party/libhv/http/server/HttpServer.cpp
vendored
Executable file
311
third_party/libhv/http/server/HttpServer.cpp
vendored
Executable file
@@ -0,0 +1,311 @@
|
||||
#include "HttpServer.h"
|
||||
|
||||
#include "hmain.h" // import master_workers_run
|
||||
#include "herr.h"
|
||||
#include "hlog.h"
|
||||
#include "htime.h"
|
||||
|
||||
#include "EventLoop.h"
|
||||
using namespace hv;
|
||||
|
||||
#include "HttpHandler.h"
|
||||
|
||||
static void on_accept(hio_t* io);
|
||||
static void on_recv(hio_t* io, void* _buf, int readbytes);
|
||||
static void on_close(hio_t* io);
|
||||
|
||||
struct HttpServerPrivdata {
|
||||
std::vector<EventLoopPtr> loops;
|
||||
std::vector<hthread_t> threads;
|
||||
std::mutex mutex_;
|
||||
std::shared_ptr<HttpService> service;
|
||||
FileCache filecache;
|
||||
};
|
||||
|
||||
static void on_recv(hio_t* io, void* buf, int readbytes) {
|
||||
// printf("on_recv fd=%d readbytes=%d\n", hio_fd(io), readbytes);
|
||||
HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
|
||||
assert(handler != NULL);
|
||||
|
||||
int nfeed = handler->FeedRecvData((const char*)buf, readbytes);
|
||||
if (nfeed != readbytes) {
|
||||
hio_close(io);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void on_close(hio_t* io) {
|
||||
HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
|
||||
if (handler == NULL) return;
|
||||
|
||||
hevent_set_userdata(io, NULL);
|
||||
delete handler;
|
||||
|
||||
EventLoop* loop = currentThreadEventLoop;
|
||||
if (loop) {
|
||||
--loop->connectionNum;
|
||||
}
|
||||
}
|
||||
|
||||
static void on_accept(hio_t* io) {
|
||||
http_server_t* server = (http_server_t*)hevent_userdata(io);
|
||||
HttpService* service = server->service;
|
||||
/*
|
||||
printf("on_accept connfd=%d\n", hio_fd(io));
|
||||
char localaddrstr[SOCKADDR_STRLEN] = {0};
|
||||
char peeraddrstr[SOCKADDR_STRLEN] = {0};
|
||||
printf("accept connfd=%d [%s] <= [%s]\n", hio_fd(io),
|
||||
SOCKADDR_STR(hio_localaddr(io), localaddrstr),
|
||||
SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));
|
||||
*/
|
||||
|
||||
EventLoop* loop = currentThreadEventLoop;
|
||||
if (loop->connectionNum >= server->worker_connections) {
|
||||
hlogw("over worker_connections");
|
||||
hio_close(io);
|
||||
return;
|
||||
}
|
||||
++loop->connectionNum;
|
||||
|
||||
hio_setcb_close(io, on_close);
|
||||
hio_setcb_read(io, on_recv);
|
||||
hio_read(io);
|
||||
if (service->keepalive_timeout > 0) {
|
||||
hio_set_keepalive_timeout(io, service->keepalive_timeout);
|
||||
}
|
||||
|
||||
// new HttpHandler, delete on_close
|
||||
HttpHandler* handler = new HttpHandler(io);
|
||||
// ssl
|
||||
handler->ssl = hio_is_ssl(io);
|
||||
// ip:port
|
||||
sockaddr_u* peeraddr = (sockaddr_u*)hio_peeraddr(io);
|
||||
sockaddr_ip(peeraddr, handler->ip, sizeof(handler->ip));
|
||||
handler->port = sockaddr_port(peeraddr);
|
||||
// http service
|
||||
handler->service = service;
|
||||
// websocket service
|
||||
handler->ws_service = server->ws;
|
||||
// FileCache
|
||||
HttpServerPrivdata* privdata = (HttpServerPrivdata*)server->privdata;
|
||||
handler->files = &privdata->filecache;
|
||||
hevent_set_userdata(io, handler);
|
||||
}
|
||||
|
||||
static void loop_thread(void* userdata) {
|
||||
http_server_t* server = (http_server_t*)userdata;
|
||||
HttpService* service = server->service;
|
||||
|
||||
auto loop = std::make_shared<EventLoop>();
|
||||
hloop_t* hloop = loop->loop();
|
||||
// http
|
||||
if (server->listenfd[0] >= 0) {
|
||||
hio_t* listenio = haccept(hloop, server->listenfd[0], on_accept);
|
||||
hevent_set_userdata(listenio, server);
|
||||
}
|
||||
// https
|
||||
if (server->listenfd[1] >= 0) {
|
||||
hio_t* listenio = haccept(hloop, server->listenfd[1], on_accept);
|
||||
hevent_set_userdata(listenio, server);
|
||||
hio_enable_ssl(listenio);
|
||||
if (server->ssl_ctx) {
|
||||
hio_set_ssl_ctx(listenio, server->ssl_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
HttpServerPrivdata* privdata = (HttpServerPrivdata*)server->privdata;
|
||||
privdata->mutex_.lock();
|
||||
if (privdata->loops.size() == 0) {
|
||||
// NOTE: fsync logfile when idle
|
||||
hlog_disable_fsync();
|
||||
hidle_add(hloop, [](hidle_t*) {
|
||||
hlog_fsync();
|
||||
}, INFINITE);
|
||||
|
||||
// NOTE: add timer to update s_date every 1s
|
||||
htimer_add(hloop, [](htimer_t* timer) {
|
||||
gmtime_fmt(hloop_now(hevent_loop(timer)), HttpMessage::s_date);
|
||||
}, 1000);
|
||||
|
||||
// document_root
|
||||
if (service->document_root.size() > 0 && service->GetStaticFilepath("/").empty()) {
|
||||
service->Static("/", service->document_root.c_str());
|
||||
}
|
||||
|
||||
// FileCache
|
||||
FileCache* filecache = &privdata->filecache;
|
||||
filecache->stat_interval = service->file_cache_stat_interval;
|
||||
filecache->expired_time = service->file_cache_expired_time;
|
||||
if (filecache->expired_time > 0) {
|
||||
// NOTE: add timer to remove expired file cache
|
||||
htimer_t* timer = htimer_add(hloop, [](htimer_t* timer) {
|
||||
FileCache* filecache = (FileCache*)hevent_userdata(timer);
|
||||
filecache->RemoveExpiredFileCache();
|
||||
}, filecache->expired_time * 1000);
|
||||
hevent_set_userdata(timer, filecache);
|
||||
}
|
||||
}
|
||||
privdata->loops.push_back(loop);
|
||||
privdata->mutex_.unlock();
|
||||
|
||||
hlogi("EventLoop started, pid=%ld tid=%ld", hv_getpid(), hv_gettid());
|
||||
if (server->onWorkerStart) {
|
||||
loop->queueInLoop([server](){
|
||||
server->onWorkerStart();
|
||||
});
|
||||
}
|
||||
|
||||
loop->run();
|
||||
|
||||
if (server->onWorkerStop) {
|
||||
server->onWorkerStop();
|
||||
}
|
||||
hlogi("EventLoop stopped, pid=%ld tid=%ld", hv_getpid(), hv_gettid());
|
||||
}
|
||||
|
||||
/* @workflow:
|
||||
* http_server_run -> Listen -> master_workers_run / hthread_create ->
|
||||
* loop_thread -> accept -> EventLoop::run ->
|
||||
* on_accept -> new HttpHandler -> hio_read ->
|
||||
* on_recv -> HttpHandler::FeedRecvData ->
|
||||
* on_close -> delete HttpHandler
|
||||
*/
|
||||
int http_server_run(http_server_t* server, int wait) {
|
||||
// http_port
|
||||
if (server->port > 0) {
|
||||
server->listenfd[0] = Listen(server->port, server->host);
|
||||
if (server->listenfd[0] < 0) return server->listenfd[0];
|
||||
hlogi("http server listening on %s:%d", server->host, server->port);
|
||||
}
|
||||
// https_port
|
||||
if (server->https_port > 0 && HV_WITH_SSL) {
|
||||
server->listenfd[1] = Listen(server->https_port, server->host);
|
||||
if (server->listenfd[1] < 0) return server->listenfd[1];
|
||||
hlogi("https server listening on %s:%d", server->host, server->https_port);
|
||||
}
|
||||
// SSL_CTX
|
||||
if (server->listenfd[1] >= 0) {
|
||||
if (server->ssl_ctx == NULL) {
|
||||
server->ssl_ctx = hssl_ctx_instance();
|
||||
}
|
||||
if (server->ssl_ctx == NULL) {
|
||||
hloge("new SSL_CTX failed!");
|
||||
return ERR_NEW_SSL_CTX;
|
||||
}
|
||||
#ifdef WITH_NGHTTP2
|
||||
#ifdef WITH_OPENSSL
|
||||
static unsigned char s_alpn_protos[] = "\x02h2\x08http/1.1\x08http/1.0\x08http/0.9";
|
||||
hssl_ctx_set_alpn_protos(server->ssl_ctx, s_alpn_protos, sizeof(s_alpn_protos) - 1);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
HttpServerPrivdata* privdata = new HttpServerPrivdata;
|
||||
server->privdata = privdata;
|
||||
if (server->service == NULL) {
|
||||
privdata->service = std::make_shared<HttpService>();
|
||||
server->service = privdata->service.get();
|
||||
}
|
||||
|
||||
if (server->worker_processes) {
|
||||
// multi-processes
|
||||
return master_workers_run(loop_thread, server, server->worker_processes, server->worker_threads, wait);
|
||||
}
|
||||
else {
|
||||
// multi-threads
|
||||
if (server->worker_threads == 0) server->worker_threads = 1;
|
||||
for (int i = wait ? 1 : 0; i < server->worker_threads; ++i) {
|
||||
hthread_t thrd = hthread_create((hthread_routine)loop_thread, server);
|
||||
privdata->threads.push_back(thrd);
|
||||
}
|
||||
if (wait) {
|
||||
loop_thread(server);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* @workflow:
|
||||
* http_server_stop -> EventLoop::stop -> hthread_join
|
||||
*/
|
||||
int http_server_stop(http_server_t* server) {
|
||||
HttpServerPrivdata* privdata = (HttpServerPrivdata*)server->privdata;
|
||||
if (privdata == NULL) return 0;
|
||||
|
||||
#ifdef OS_UNIX
|
||||
if (server->worker_processes) {
|
||||
signal_handle("stop");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// wait for all threads started and all loops running
|
||||
while (1) {
|
||||
hv_delay(1);
|
||||
std::lock_guard<std::mutex> locker(privdata->mutex_);
|
||||
// wait for all loops created
|
||||
if (privdata->loops.size() < server->worker_threads) {
|
||||
continue;
|
||||
}
|
||||
// wait for all loops running
|
||||
bool all_loops_running = true;
|
||||
for (auto& loop : privdata->loops) {
|
||||
if (loop->status() < hv::Status::kRunning) {
|
||||
all_loops_running = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_loops_running) break;
|
||||
}
|
||||
|
||||
// stop all loops
|
||||
for (auto& loop : privdata->loops) {
|
||||
loop->stop();
|
||||
}
|
||||
|
||||
// join all threads
|
||||
for (auto& thrd : privdata->threads) {
|
||||
hthread_join(thrd);
|
||||
}
|
||||
|
||||
if (server->alloced_ssl_ctx && server->ssl_ctx) {
|
||||
hssl_ctx_free(server->ssl_ctx);
|
||||
server->alloced_ssl_ctx = 0;
|
||||
server->ssl_ctx = NULL;
|
||||
}
|
||||
|
||||
delete privdata;
|
||||
server->privdata = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace hv {
|
||||
|
||||
std::shared_ptr<hv::EventLoop> HttpServer::loop(int idx) {
|
||||
HttpServerPrivdata* privdata = (HttpServerPrivdata*)this->privdata;
|
||||
if (privdata == NULL) return NULL;
|
||||
std::lock_guard<std::mutex> locker(privdata->mutex_);
|
||||
if (privdata->loops.empty()) return NULL;
|
||||
if (idx >= 0 && idx < (int)privdata->loops.size()) {
|
||||
return privdata->loops[idx];
|
||||
}
|
||||
EventLoop* cur = currentThreadEventLoop;
|
||||
for (auto& loop : privdata->loops) {
|
||||
if (loop.get() == cur) return loop;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t HttpServer::connectionNum() {
|
||||
HttpServerPrivdata* privdata = (HttpServerPrivdata*)this->privdata;
|
||||
if (privdata == NULL) return 0;
|
||||
std::lock_guard<std::mutex> locker(privdata->mutex_);
|
||||
if (privdata->loops.empty()) return 0;
|
||||
size_t total = 0;
|
||||
for (auto& loop : privdata->loops) {
|
||||
total += loop->connectionNum;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
163
third_party/libhv/http/server/HttpServer.h
vendored
Executable file
163
third_party/libhv/http/server/HttpServer.h
vendored
Executable file
@@ -0,0 +1,163 @@
|
||||
#ifndef HV_HTTP_SERVER_H_
|
||||
#define HV_HTTP_SERVER_H_
|
||||
|
||||
#include "hexport.h"
|
||||
#include "hssl.h"
|
||||
// #include "EventLoop.h"
|
||||
#include "HttpService.h"
|
||||
// #include "WebSocketServer.h"
|
||||
namespace hv {
|
||||
class EventLoop;
|
||||
struct WebSocketService;
|
||||
}
|
||||
using hv::HttpService;
|
||||
using hv::WebSocketService;
|
||||
|
||||
typedef struct http_server_s {
|
||||
char host[64];
|
||||
int port; // http_port
|
||||
int https_port;
|
||||
int http_version;
|
||||
int worker_processes;
|
||||
int worker_threads;
|
||||
uint32_t worker_connections; // max_connections = workers * worker_connections
|
||||
HttpService* service; // http service
|
||||
WebSocketService* ws; // websocket service
|
||||
void* userdata;
|
||||
int listenfd[2]; // 0: http, 1: https
|
||||
void* privdata;
|
||||
// hooks
|
||||
std::function<void()> onWorkerStart;
|
||||
std::function<void()> onWorkerStop;
|
||||
// SSL/TLS
|
||||
hssl_ctx_t ssl_ctx;
|
||||
unsigned alloced_ssl_ctx: 1;
|
||||
|
||||
#ifdef __cplusplus
|
||||
http_server_s() {
|
||||
strcpy(host, "0.0.0.0");
|
||||
// port = DEFAULT_HTTP_PORT;
|
||||
// https_port = DEFAULT_HTTPS_PORT;
|
||||
// port = 8080;
|
||||
// https_port = 8443;
|
||||
port = https_port = 0;
|
||||
http_version = 1;
|
||||
worker_processes = 0;
|
||||
worker_threads = 0;
|
||||
worker_connections = 1024;
|
||||
service = NULL;
|
||||
ws = NULL;
|
||||
listenfd[0] = listenfd[1] = -1;
|
||||
userdata = NULL;
|
||||
privdata = NULL;
|
||||
// SSL/TLS
|
||||
ssl_ctx = NULL;
|
||||
alloced_ssl_ctx = 0;
|
||||
}
|
||||
#endif
|
||||
} http_server_t;
|
||||
|
||||
// @param wait: Whether to occupy current thread
|
||||
HV_EXPORT int http_server_run(http_server_t* server, int wait = 1);
|
||||
|
||||
// NOTE: stop all loops and join all threads
|
||||
HV_EXPORT int http_server_stop(http_server_t* server);
|
||||
|
||||
/*
|
||||
#include "HttpServer.h"
|
||||
using namespace hv;
|
||||
|
||||
int main() {
|
||||
HttpService service;
|
||||
service.GET("/ping", [](HttpRequest* req, HttpResponse* resp) {
|
||||
resp->body = "pong";
|
||||
return 200;
|
||||
});
|
||||
|
||||
HttpServer server(&service);
|
||||
server.setThreadNum(4);
|
||||
server.run(":8080");
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
namespace hv {
|
||||
|
||||
class HV_EXPORT HttpServer : public http_server_t {
|
||||
public:
|
||||
HttpServer(HttpService* service = NULL)
|
||||
: http_server_t()
|
||||
{
|
||||
this->service = service;
|
||||
}
|
||||
~HttpServer() { stop(); }
|
||||
|
||||
void registerHttpService(HttpService* service) {
|
||||
this->service = service;
|
||||
}
|
||||
|
||||
std::shared_ptr<hv::EventLoop> loop(int idx = -1);
|
||||
|
||||
void setHost(const char* host = "0.0.0.0") {
|
||||
if (host) strcpy(this->host, host);
|
||||
}
|
||||
|
||||
void setPort(int port = 0, int ssl_port = 0) {
|
||||
if (port >= 0) this->port = port;
|
||||
if (ssl_port >= 0) this->https_port = ssl_port;
|
||||
}
|
||||
void setListenFD(int fd = -1, int ssl_fd = -1) {
|
||||
if (fd >= 0) this->listenfd[0] = fd;
|
||||
if (ssl_fd >= 0) this->listenfd[1] = ssl_fd;
|
||||
}
|
||||
|
||||
void setProcessNum(int num) {
|
||||
this->worker_processes = num;
|
||||
}
|
||||
|
||||
void setThreadNum(int num) {
|
||||
this->worker_threads = num;
|
||||
}
|
||||
|
||||
void setMaxWorkerConnectionNum(uint32_t num) {
|
||||
this->worker_connections = num;
|
||||
}
|
||||
size_t connectionNum();
|
||||
|
||||
// SSL/TLS
|
||||
int setSslCtx(hssl_ctx_t ssl_ctx) {
|
||||
this->ssl_ctx = ssl_ctx;
|
||||
return 0;
|
||||
}
|
||||
int newSslCtx(hssl_ctx_opt_t* opt) {
|
||||
// NOTE: hssl_ctx_free in http_server_stop
|
||||
hssl_ctx_t ssl_ctx = hssl_ctx_new(opt);
|
||||
if (ssl_ctx == NULL) return -1;
|
||||
this->alloced_ssl_ctx = 1;
|
||||
return setSslCtx(ssl_ctx);
|
||||
}
|
||||
|
||||
// run(":8080")
|
||||
// run("0.0.0.0:8080")
|
||||
// run("[::]:8080")
|
||||
int run(const char* ip_port = NULL, bool wait = true) {
|
||||
if (ip_port) {
|
||||
hv::NetAddr listen_addr(ip_port);
|
||||
if (listen_addr.ip.size() != 0) setHost(listen_addr.ip.c_str());
|
||||
if (listen_addr.port != 0) setPort(listen_addr.port);
|
||||
}
|
||||
return http_server_run(this, wait);
|
||||
}
|
||||
|
||||
int start(const char* ip_port = NULL) {
|
||||
return run(ip_port, false);
|
||||
}
|
||||
|
||||
int stop() {
|
||||
return http_server_stop(this);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_HTTP_SERVER_H_
|
||||
209
third_party/libhv/http/server/HttpService.cpp
vendored
Executable file
209
third_party/libhv/http/server/HttpService.cpp
vendored
Executable file
@@ -0,0 +1,209 @@
|
||||
#include "HttpService.h"
|
||||
#include "HttpMiddleware.h"
|
||||
|
||||
#include "hbase.h" // import hv_strendswith
|
||||
|
||||
namespace hv {
|
||||
|
||||
void HttpService::AddRoute(const char* path, http_method method, const http_handler& handler) {
|
||||
std::shared_ptr<http_method_handlers> method_handlers = NULL;
|
||||
auto iter = pathHandlers.find(path);
|
||||
if (iter == pathHandlers.end()) {
|
||||
// add path
|
||||
method_handlers = std::make_shared<http_method_handlers>();
|
||||
pathHandlers[path] = method_handlers;
|
||||
}
|
||||
else {
|
||||
method_handlers = iter->second;
|
||||
}
|
||||
for (auto iter = method_handlers->begin(); iter != method_handlers->end(); ++iter) {
|
||||
if (iter->method == method) {
|
||||
// update
|
||||
iter->handler = handler;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// add
|
||||
method_handlers->push_back(http_method_handler(method, handler));
|
||||
}
|
||||
|
||||
int HttpService::GetRoute(const char* url, http_method method, http_handler** handler) {
|
||||
// {base_url}/path?query
|
||||
const char* s = url;
|
||||
const char* b = base_url.c_str();
|
||||
while (*s && *b && *s == *b) {++s;++b;}
|
||||
if (*b != '\0') {
|
||||
return HTTP_STATUS_NOT_FOUND;
|
||||
}
|
||||
const char* e = s;
|
||||
while (*e && *e != '?') ++e;
|
||||
|
||||
std::string path = std::string(s, e);
|
||||
auto iter = pathHandlers.find(path);
|
||||
if (iter == pathHandlers.end()) {
|
||||
if (handler) *handler = NULL;
|
||||
return HTTP_STATUS_NOT_FOUND;
|
||||
}
|
||||
auto method_handlers = iter->second;
|
||||
for (auto iter = method_handlers->begin(); iter != method_handlers->end(); ++iter) {
|
||||
if (iter->method == method) {
|
||||
if (handler) *handler = &iter->handler;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (handler) *handler = NULL;
|
||||
return HTTP_STATUS_METHOD_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
int HttpService::GetRoute(HttpRequest* req, http_handler** handler) {
|
||||
// {base_url}/path?query
|
||||
const char* s = req->path.c_str();
|
||||
const char* b = base_url.c_str();
|
||||
while (*s && *b && *s == *b) {++s;++b;}
|
||||
if (*b != '\0') {
|
||||
return HTTP_STATUS_NOT_FOUND;
|
||||
}
|
||||
const char* e = s;
|
||||
while (*e && *e != '?') ++e;
|
||||
|
||||
std::string path = std::string(s, e);
|
||||
const char *kp, *ks, *vp, *vs;
|
||||
bool match;
|
||||
for (auto iter = pathHandlers.begin(); iter != pathHandlers.end(); ++iter) {
|
||||
kp = iter->first.c_str();
|
||||
vp = path.c_str();
|
||||
match = false;
|
||||
std::map<std::string, std::string> params;
|
||||
|
||||
while (*kp && *vp) {
|
||||
if (kp[0] == '*') {
|
||||
// wildcard *
|
||||
match = hv_strendswith(vp, kp+1);
|
||||
break;
|
||||
} else if (*kp != *vp) {
|
||||
match = false;
|
||||
break;
|
||||
} else if (kp[0] == '/' && (kp[1] == ':' || kp[1] == '{')) {
|
||||
// RESTful /:field/
|
||||
// RESTful /{field}/
|
||||
kp += 2;
|
||||
ks = kp;
|
||||
while (*kp && *kp != '/') {++kp;}
|
||||
vp += 1;
|
||||
vs = vp;
|
||||
while (*vp && *vp != '/') {++vp;}
|
||||
int klen = kp - ks;
|
||||
if (*(ks-1) == '{' && *(kp-1) == '}') {
|
||||
--klen;
|
||||
}
|
||||
params[std::string(ks, klen)] = std::string(vs, vp-vs);
|
||||
continue;
|
||||
} else {
|
||||
++kp;
|
||||
++vp;
|
||||
}
|
||||
}
|
||||
|
||||
match = match ? match : (*kp == '\0' && *vp == '\0');
|
||||
|
||||
if (match) {
|
||||
auto method_handlers = iter->second;
|
||||
for (auto iter = method_handlers->begin(); iter != method_handlers->end(); ++iter) {
|
||||
if (iter->method == req->method) {
|
||||
for (auto& param : params) {
|
||||
// RESTful /:field/ => req->query_params[field]
|
||||
req->query_params[param.first] = param.second;
|
||||
}
|
||||
if (handler) *handler = &iter->handler;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.size() == 0) {
|
||||
if (handler) *handler = NULL;
|
||||
return HTTP_STATUS_METHOD_NOT_ALLOWED;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handler) *handler = NULL;
|
||||
return HTTP_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
void HttpService::Static(const char* path, const char* dir) {
|
||||
std::string strPath(path);
|
||||
if (strPath.back() != '/') strPath += '/';
|
||||
std::string strDir(dir);
|
||||
if (strDir.back() == '/') strDir.pop_back();
|
||||
staticDirs[strPath] = strDir;
|
||||
}
|
||||
|
||||
std::string HttpService::GetStaticFilepath(const char* path) {
|
||||
std::string filepath;
|
||||
for (auto iter = staticDirs.begin(); iter != staticDirs.end(); ++iter) {
|
||||
if (hv_strstartswith(path, iter->first.c_str())) {
|
||||
filepath = iter->second + (path + iter->first.length() - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (filepath.empty()) {
|
||||
return filepath;
|
||||
}
|
||||
|
||||
if (filepath.back() == '/') {
|
||||
filepath += home_page;
|
||||
}
|
||||
return filepath;
|
||||
}
|
||||
|
||||
void HttpService::Proxy(const char* path, const char* url) {
|
||||
proxies[path] = url;
|
||||
}
|
||||
|
||||
std::string HttpService::GetProxyUrl(const char* path) {
|
||||
std::string url;
|
||||
for (auto iter = proxies.begin(); iter != proxies.end(); ++iter) {
|
||||
if (hv_strstartswith(path, iter->first.c_str())) {
|
||||
url = iter->second + (path + iter->first.length());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
void HttpService::AddTrustProxy(const char* host) {
|
||||
trustProxies.emplace_back(host);
|
||||
}
|
||||
|
||||
void HttpService::AddNoProxy(const char* host) {
|
||||
noProxies.emplace_back(host);
|
||||
}
|
||||
|
||||
bool HttpService::IsTrustProxy(const char* host) {
|
||||
if (!host || *host == '\0') return false;
|
||||
bool trust = true;
|
||||
if (trustProxies.size() != 0) {
|
||||
trust = false;
|
||||
for (const auto& trust_proxy : trustProxies) {
|
||||
if (hv_wildcard_match(host, trust_proxy.c_str())) {
|
||||
trust = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (noProxies.size() != 0) {
|
||||
for (const auto& no_proxy : noProxies) {
|
||||
if (hv_wildcard_match(host, no_proxy.c_str())) {
|
||||
trust = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return trust;
|
||||
}
|
||||
|
||||
void HttpService::AllowCORS() {
|
||||
Use(HttpMiddleware::CORS);
|
||||
}
|
||||
|
||||
}
|
||||
281
third_party/libhv/http/server/HttpService.h
vendored
Executable file
281
third_party/libhv/http/server/HttpService.h
vendored
Executable file
@@ -0,0 +1,281 @@
|
||||
#ifndef HV_HTTP_SERVICE_H_
|
||||
#define HV_HTTP_SERVICE_H_
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include "hexport.h"
|
||||
#include "HttpMessage.h"
|
||||
#include "HttpResponseWriter.h"
|
||||
#include "HttpContext.h"
|
||||
|
||||
#define DEFAULT_BASE_URL "/api/v1"
|
||||
#define DEFAULT_DOCUMENT_ROOT "/var/www/html"
|
||||
#define DEFAULT_HOME_PAGE "index.html"
|
||||
#define DEFAULT_ERROR_PAGE "error.html"
|
||||
#define DEFAULT_INDEXOF_DIR "/downloads/"
|
||||
#define DEFAULT_KEEPALIVE_TIMEOUT 75000 // ms
|
||||
|
||||
// for FileCache
|
||||
#define MAX_FILE_CACHE_SIZE (1 << 22) // 4M
|
||||
#define DEFAULT_FILE_CACHE_STAT_INTERVAL 10 // s
|
||||
#define DEFAULT_FILE_CACHE_EXPIRED_TIME 60 // s
|
||||
|
||||
/*
|
||||
* @param[in] req: parsed structured http request
|
||||
* @param[out] resp: structured http response
|
||||
* @return 0: handle next
|
||||
* http_status_code: handle done
|
||||
*/
|
||||
#define HTTP_STATUS_NEXT 0
|
||||
#define HTTP_STATUS_UNFINISHED 0
|
||||
// NOTE: http_sync_handler run on IO thread
|
||||
typedef std::function<int(HttpRequest* req, HttpResponse* resp)> http_sync_handler;
|
||||
// NOTE: http_async_handler run on hv::async threadpool
|
||||
typedef std::function<void(const HttpRequestPtr& req, const HttpResponseWriterPtr& writer)> http_async_handler;
|
||||
// NOTE: http_ctx_handler run on IO thread, you can easily post HttpContextPtr to your consumer thread for processing.
|
||||
typedef std::function<int(const HttpContextPtr& ctx)> http_ctx_handler;
|
||||
// NOTE: http_state_handler run on IO thread
|
||||
typedef std::function<int(const HttpContextPtr& ctx, http_parser_state state, const char* data, size_t size)> http_state_handler;
|
||||
|
||||
struct http_handler {
|
||||
http_sync_handler sync_handler;
|
||||
http_async_handler async_handler;
|
||||
http_ctx_handler ctx_handler;
|
||||
http_state_handler state_handler;
|
||||
|
||||
http_handler() {}
|
||||
http_handler(http_sync_handler fn) : sync_handler(std::move(fn)) {}
|
||||
http_handler(http_async_handler fn) : async_handler(std::move(fn)) {}
|
||||
http_handler(http_ctx_handler fn) : ctx_handler(std::move(fn)) {}
|
||||
http_handler(http_state_handler fn) : state_handler(std::move(fn)) {}
|
||||
http_handler(const http_handler& rhs)
|
||||
: sync_handler(std::move(const_cast<http_handler&>(rhs).sync_handler))
|
||||
, async_handler(std::move(const_cast<http_handler&>(rhs).async_handler))
|
||||
, ctx_handler(std::move(const_cast<http_handler&>(rhs).ctx_handler))
|
||||
, state_handler(std::move(const_cast<http_handler&>(rhs).state_handler))
|
||||
{}
|
||||
|
||||
const http_handler& operator=(http_sync_handler fn) {
|
||||
sync_handler = std::move(fn);
|
||||
return *this;
|
||||
}
|
||||
const http_handler& operator=(http_async_handler fn) {
|
||||
async_handler = std::move(fn);
|
||||
return *this;
|
||||
}
|
||||
const http_handler& operator=(http_ctx_handler fn) {
|
||||
ctx_handler = std::move(fn);
|
||||
return *this;
|
||||
}
|
||||
const http_handler& operator=(http_state_handler fn) {
|
||||
state_handler = std::move(fn);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isNull() {
|
||||
return sync_handler == NULL &&
|
||||
async_handler == NULL &&
|
||||
ctx_handler == NULL;
|
||||
}
|
||||
|
||||
operator bool() {
|
||||
return !isNull();
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<http_handler> http_handlers;
|
||||
|
||||
struct http_method_handler {
|
||||
http_method method;
|
||||
http_handler handler;
|
||||
|
||||
http_method_handler() {}
|
||||
http_method_handler(http_method m, const http_handler& h) : method(m), handler(h) {}
|
||||
};
|
||||
|
||||
// method => http_method_handler
|
||||
typedef std::list<http_method_handler> http_method_handlers;
|
||||
// path => http_method_handlers
|
||||
typedef std::unordered_map<std::string, std::shared_ptr<http_method_handlers>> http_path_handlers;
|
||||
|
||||
namespace hv {
|
||||
|
||||
struct HV_EXPORT HttpService {
|
||||
/* handler chain */
|
||||
// preprocessor -> middleware -> processor -> postprocessor
|
||||
http_handler preprocessor;
|
||||
http_handlers middleware;
|
||||
// processor: pathHandlers -> staticHandler -> errorHandler
|
||||
http_handler processor;
|
||||
http_handler postprocessor;
|
||||
|
||||
/* API handlers */
|
||||
std::string base_url;
|
||||
http_path_handlers pathHandlers;
|
||||
|
||||
/* Static file service */
|
||||
http_handler staticHandler;
|
||||
http_handler largeFileHandler;
|
||||
std::string document_root;
|
||||
std::string home_page;
|
||||
std::string error_page;
|
||||
// nginx: location => root
|
||||
std::map<std::string, std::string, std::greater<std::string>> staticDirs;
|
||||
/* Indexof directory service */
|
||||
std::string index_of;
|
||||
http_handler errorHandler;
|
||||
|
||||
/* Proxy service */
|
||||
/* Reverse proxy service */
|
||||
// nginx: location => proxy_pass
|
||||
std::map<std::string, std::string, std::greater<std::string>> proxies;
|
||||
/* Forward proxy service */
|
||||
StringList trustProxies;
|
||||
StringList noProxies;
|
||||
int proxy_connect_timeout;
|
||||
int proxy_read_timeout;
|
||||
int proxy_write_timeout;
|
||||
|
||||
// options
|
||||
int keepalive_timeout;
|
||||
int max_file_cache_size; // cache small file
|
||||
int file_cache_stat_interval; // stat file is modified
|
||||
int file_cache_expired_time; // remove expired file cache
|
||||
/*
|
||||
* @test limit_rate
|
||||
* @build make examples
|
||||
* @server bin/httpd -c etc/httpd.conf -s restart -d
|
||||
* @client bin/wget http://127.0.0.1:8080/downloads/test.zip
|
||||
*/
|
||||
int limit_rate; // limit send rate, unit: KB/s
|
||||
|
||||
unsigned enable_access_log :1;
|
||||
unsigned enable_forward_proxy :1;
|
||||
|
||||
HttpService() {
|
||||
// base_url = DEFAULT_BASE_URL;
|
||||
|
||||
document_root = DEFAULT_DOCUMENT_ROOT;
|
||||
home_page = DEFAULT_HOME_PAGE;
|
||||
// error_page = DEFAULT_ERROR_PAGE;
|
||||
// index_of = DEFAULT_INDEXOF_DIR;
|
||||
|
||||
proxy_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
|
||||
proxy_read_timeout = 0;
|
||||
proxy_write_timeout = 0;
|
||||
|
||||
keepalive_timeout = DEFAULT_KEEPALIVE_TIMEOUT;
|
||||
max_file_cache_size = MAX_FILE_CACHE_SIZE;
|
||||
file_cache_stat_interval = DEFAULT_FILE_CACHE_STAT_INTERVAL;
|
||||
file_cache_expired_time = DEFAULT_FILE_CACHE_EXPIRED_TIME;
|
||||
limit_rate = -1; // unlimited
|
||||
|
||||
enable_access_log = 1;
|
||||
enable_forward_proxy = 0;
|
||||
}
|
||||
|
||||
void AddRoute(const char* path, http_method method, const http_handler& handler);
|
||||
// @retval 0 OK, else HTTP_STATUS_NOT_FOUND, HTTP_STATUS_METHOD_NOT_ALLOWED
|
||||
int GetRoute(const char* url, http_method method, http_handler** handler);
|
||||
// RESTful API /:field/ => req->query_params["field"]
|
||||
int GetRoute(HttpRequest* req, http_handler** handler);
|
||||
|
||||
// Static("/", "/var/www/html")
|
||||
void Static(const char* path, const char* dir);
|
||||
// @retval / => /var/www/html/index.html
|
||||
std::string GetStaticFilepath(const char* path);
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
void AllowCORS();
|
||||
|
||||
// proxy
|
||||
// forward proxy
|
||||
void EnableForwardProxy() { enable_forward_proxy = 1; }
|
||||
void AddTrustProxy(const char* host);
|
||||
void AddNoProxy(const char* host);
|
||||
bool IsTrustProxy(const char* host);
|
||||
// reverse proxy
|
||||
// Proxy("/api/v1/", "http://www.httpbin.org/");
|
||||
void Proxy(const char* path, const char* url);
|
||||
// @retval /api/v1/test => http://www.httpbin.org/test
|
||||
std::string GetProxyUrl(const char* path);
|
||||
|
||||
hv::StringList Paths() {
|
||||
hv::StringList paths;
|
||||
for (auto& pair : pathHandlers) {
|
||||
paths.emplace_back(pair.first);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
// Handler = [ http_sync_handler, http_ctx_handler ]
|
||||
template<typename Handler>
|
||||
void Use(Handler handlerFunc) {
|
||||
middleware.emplace_back(handlerFunc);
|
||||
}
|
||||
|
||||
// Inspired by github.com/gin-gonic/gin
|
||||
// Handler = [ http_sync_handler, http_async_handler, http_ctx_handler, http_state_handler ]
|
||||
template<typename Handler>
|
||||
void Handle(const char* httpMethod, const char* relativePath, Handler handlerFunc) {
|
||||
AddRoute(relativePath, http_method_enum(httpMethod), http_handler(handlerFunc));
|
||||
}
|
||||
|
||||
// HEAD
|
||||
template<typename Handler>
|
||||
void HEAD(const char* relativePath, Handler handlerFunc) {
|
||||
Handle("HEAD", relativePath, handlerFunc);
|
||||
}
|
||||
|
||||
// GET
|
||||
template<typename Handler>
|
||||
void GET(const char* relativePath, Handler handlerFunc) {
|
||||
Handle("GET", relativePath, handlerFunc);
|
||||
}
|
||||
|
||||
// POST
|
||||
template<typename Handler>
|
||||
void POST(const char* relativePath, Handler handlerFunc) {
|
||||
Handle("POST", relativePath, handlerFunc);
|
||||
}
|
||||
|
||||
// PUT
|
||||
template<typename Handler>
|
||||
void PUT(const char* relativePath, Handler handlerFunc) {
|
||||
Handle("PUT", relativePath, handlerFunc);
|
||||
}
|
||||
|
||||
// DELETE
|
||||
// NOTE: Windows <winnt.h> #define DELETE as a macro, we have to replace DELETE with Delete.
|
||||
template<typename Handler>
|
||||
void Delete(const char* relativePath, Handler handlerFunc) {
|
||||
Handle("DELETE", relativePath, handlerFunc);
|
||||
}
|
||||
|
||||
// PATCH
|
||||
template<typename Handler>
|
||||
void PATCH(const char* relativePath, Handler handlerFunc) {
|
||||
Handle("PATCH", relativePath, handlerFunc);
|
||||
}
|
||||
|
||||
// Any
|
||||
template<typename Handler>
|
||||
void Any(const char* relativePath, Handler handlerFunc) {
|
||||
Handle("HEAD", relativePath, handlerFunc);
|
||||
Handle("GET", relativePath, handlerFunc);
|
||||
Handle("POST", relativePath, handlerFunc);
|
||||
Handle("PUT", relativePath, handlerFunc);
|
||||
Handle("DELETE", relativePath, handlerFunc);
|
||||
Handle("PATCH", relativePath, handlerFunc);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_HTTP_SERVICE_H_
|
||||
46
third_party/libhv/http/server/WebSocketServer.h
vendored
Executable file
46
third_party/libhv/http/server/WebSocketServer.h
vendored
Executable file
@@ -0,0 +1,46 @@
|
||||
#ifndef HV_WEBSOCKET_SERVER_H_
|
||||
#define HV_WEBSOCKET_SERVER_H_
|
||||
|
||||
/*
|
||||
* @demo examples/websocket_server_test.cpp
|
||||
*/
|
||||
|
||||
#include "HttpServer.h"
|
||||
#include "WebSocketChannel.h"
|
||||
|
||||
#define websocket_server_t http_server_t
|
||||
#define websocket_server_run http_server_run
|
||||
#define websocket_server_stop http_server_stop
|
||||
|
||||
namespace hv {
|
||||
|
||||
struct WebSocketService {
|
||||
std::function<void(const WebSocketChannelPtr&, const HttpRequestPtr&)> onopen;
|
||||
std::function<void(const WebSocketChannelPtr&, const std::string&)> onmessage;
|
||||
std::function<void(const WebSocketChannelPtr&)> onclose;
|
||||
int ping_interval;
|
||||
|
||||
WebSocketService() : ping_interval(0) {}
|
||||
|
||||
void setPingInterval(int ms) {
|
||||
ping_interval = ms;
|
||||
}
|
||||
};
|
||||
|
||||
class WebSocketServer : public HttpServer {
|
||||
public:
|
||||
WebSocketServer(WebSocketService* service = NULL)
|
||||
: HttpServer()
|
||||
{
|
||||
this->ws = service;
|
||||
}
|
||||
~WebSocketServer() { stop(); }
|
||||
|
||||
void registerWebSocketService(WebSocketService* service) {
|
||||
this->ws = service;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HV_WEBSOCKET_SERVER_H_
|
||||
108
third_party/libhv/http/server/http_page.cpp
vendored
Executable file
108
third_party/libhv/http/server/http_page.cpp
vendored
Executable file
@@ -0,0 +1,108 @@
|
||||
#include "http_page.h"
|
||||
#include "hdir.h"
|
||||
#include "hurl.h"
|
||||
|
||||
#define AUTOINDEX_FILENAME_MAXLEN 50
|
||||
|
||||
void make_http_status_page(http_status status_code, std::string& page) {
|
||||
char szCode[8];
|
||||
snprintf(szCode, sizeof(szCode), "%d ", status_code);
|
||||
const char* status_message = http_status_str(status_code);
|
||||
page += R"(<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>)";
|
||||
page += szCode; page += status_message;
|
||||
page += R"(</title>
|
||||
</head>
|
||||
<body>
|
||||
<center><h1>)";
|
||||
page += szCode; page += status_message;
|
||||
page += R"(</h1></center>
|
||||
<hr>
|
||||
</body>
|
||||
</html>)";
|
||||
}
|
||||
|
||||
void make_index_of_page(const char* dir, std::string& page, const char* url) {
|
||||
char c_str[1024] = {0};
|
||||
snprintf(c_str, sizeof(c_str), R"(<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Index of %s</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Index of %s</h1>
|
||||
<hr>
|
||||
)", url, url);
|
||||
page += c_str;
|
||||
|
||||
page += " <table border=\"0\">\n";
|
||||
page += R"( <tr>
|
||||
<th align="left" width="30%">Name</th>
|
||||
<th align="left" width="20%">Date</th>
|
||||
<th align="left" width="20%">Size</th>
|
||||
</tr>
|
||||
)";
|
||||
|
||||
#define _ADD_TD_(page, td) \
|
||||
page += " <td>"; \
|
||||
page += td; \
|
||||
page += "</td>\n"; \
|
||||
|
||||
std::list<hdir_t> dirs;
|
||||
listdir(dir, dirs);
|
||||
std::string escaped_name;
|
||||
for (auto& item : dirs) {
|
||||
if (item.name[0] == '.' && item.name[1] == '\0') continue;
|
||||
page += " <tr>\n";
|
||||
// fix CVE-2023-26146
|
||||
escaped_name = hv::escapeHTML(item.name);
|
||||
const char* filename = escaped_name.c_str();
|
||||
size_t len = escaped_name.size() + (item.type == 'd');
|
||||
// name
|
||||
snprintf(c_str, sizeof(c_str), "<a href=\"%s%s\">%s%s</a>",
|
||||
filename,
|
||||
item.type == 'd' ? "/" : "",
|
||||
len < AUTOINDEX_FILENAME_MAXLEN ? filename : std::string(filename, filename+AUTOINDEX_FILENAME_MAXLEN-4).append("...").c_str(),
|
||||
item.type == 'd' ? "/" : "");
|
||||
_ADD_TD_(page, c_str)
|
||||
if (strcmp(filename, "..") != 0) {
|
||||
// mtime
|
||||
struct tm* tm = localtime(&item.mtime);
|
||||
snprintf(c_str, sizeof(c_str), "%04d-%02d-%02d %02d:%02d:%02d",
|
||||
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
_ADD_TD_(page, c_str)
|
||||
// size
|
||||
if (item.type == 'd') {
|
||||
page += '-';
|
||||
}
|
||||
else {
|
||||
float hsize;
|
||||
if (item.size < 1024) {
|
||||
snprintf(c_str, sizeof(c_str), "%lu", (unsigned long)item.size);
|
||||
}
|
||||
else if ((hsize = item.size/1024.0f) < 1024.0f) {
|
||||
snprintf(c_str, sizeof(c_str), "%.1fK", hsize);
|
||||
}
|
||||
else if ((hsize /= 1024.0f) < 1024.0f) {
|
||||
snprintf(c_str, sizeof(c_str), "%.1fM", hsize);
|
||||
}
|
||||
else {
|
||||
hsize /= 1024.0f;
|
||||
snprintf(c_str, sizeof(c_str), "%.1fG", hsize);
|
||||
}
|
||||
_ADD_TD_(page, c_str)
|
||||
}
|
||||
}
|
||||
page += " </tr>\n";
|
||||
}
|
||||
|
||||
#undef _ADD_TD_
|
||||
|
||||
page += R"( </table>
|
||||
<hr>
|
||||
</body>
|
||||
</html>
|
||||
)";
|
||||
}
|
||||
56
third_party/libhv/http/server/http_page.h
vendored
Executable file
56
third_party/libhv/http/server/http_page.h
vendored
Executable file
@@ -0,0 +1,56 @@
|
||||
#ifndef HV_HTTP_PAGE_H_
|
||||
#define HV_HTTP_PAGE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "httpdef.h"
|
||||
|
||||
/*
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>404 Not Found</title>
|
||||
</head>
|
||||
<body>
|
||||
<center><h1>404 Not Found</h1></center>
|
||||
<hr>
|
||||
</body>
|
||||
</html>
|
||||
*/
|
||||
void make_http_status_page(http_status status_code, std::string& page);
|
||||
|
||||
/*
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Index of /downloads/</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Index of /downloads/</h1>
|
||||
<hr>
|
||||
<table border="0">
|
||||
<tr>
|
||||
<th align="left" width="30%">Name</th>
|
||||
<th align="left" width="20%">Date</th>
|
||||
<th align="left" width="20%">Size</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../">../</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="libhv-vs-nginx.png">libhv-vs-nginx.png</a></td>
|
||||
<td>2021-03-10 12:33:57</td>
|
||||
<td>211.4K</td>
|
||||
</tr>
|
||||
<td><a href="中文.html">中文.html</a></td>
|
||||
<td>2022-04-25 15:37:12</td>
|
||||
<td>191</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
</body>
|
||||
</html>
|
||||
*/
|
||||
void make_index_of_page(const char* dir, std::string& page, const char* url = "");
|
||||
|
||||
#endif // HV_HTTP_PAGE_H_
|
||||
Reference in New Issue
Block a user