initial commit

This commit is contained in:
2025-08-05 15:53:44 +08:00
commit 09dc02ae52
553 changed files with 137665 additions and 0 deletions

177
third_party/libhv/http/server/FileCache.cpp vendored Executable file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

186
third_party/libhv/http/server/HttpHandler.h vendored Executable file
View 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_

View 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

View 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_

View 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;
}
}

View 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
View 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
View 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
View 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
View 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_

View 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
View 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
View 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_