|
@@ -2,34 +2,90 @@
|
|
|
|
|
|
|
|
#include "hv.h"
|
|
#include "hv.h"
|
|
|
#include "hmain.h"
|
|
#include "hmain.h"
|
|
|
-#include "hloop.h"
|
|
|
|
|
|
|
|
|
|
#include "httpdef.h"
|
|
#include "httpdef.h"
|
|
|
#include "http2def.h"
|
|
#include "http2def.h"
|
|
|
#include "wsdef.h"
|
|
#include "wsdef.h"
|
|
|
|
|
|
|
|
|
|
+#include "EventLoop.h"
|
|
|
|
|
+using namespace hv;
|
|
|
|
|
+
|
|
|
#include "HttpHandler.h"
|
|
#include "HttpHandler.h"
|
|
|
|
|
|
|
|
#define MIN_HTTP_REQUEST "GET / HTTP/1.1\r\n\r\n"
|
|
#define MIN_HTTP_REQUEST "GET / HTTP/1.1\r\n\r\n"
|
|
|
#define MIN_HTTP_REQUEST_LEN 14 // exclude CRLF
|
|
#define MIN_HTTP_REQUEST_LEN 14 // exclude CRLF
|
|
|
|
|
|
|
|
-static HttpService s_default_service;
|
|
|
|
|
-static FileCache s_filecache;
|
|
|
|
|
|
|
+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);
|
|
|
|
|
+
|
|
|
|
|
+static HttpService* default_http_service() {
|
|
|
|
|
+ static HttpService* s_default_service = new HttpService;
|
|
|
|
|
+ return s_default_service;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static FileCache* default_filecache() {
|
|
|
|
|
+ static FileCache s_filecache;
|
|
|
|
|
+ return &s_filecache;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
struct HttpServerPrivdata {
|
|
struct HttpServerPrivdata {
|
|
|
- int quit;
|
|
|
|
|
std::vector<hloop_t*> loops;
|
|
std::vector<hloop_t*> loops;
|
|
|
std::vector<hthread_t> threads;
|
|
std::vector<hthread_t> threads;
|
|
|
std::mutex mutex_;
|
|
std::mutex mutex_;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+static void websocket_heartbeat(hio_t* io) {
|
|
|
|
|
+ HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
|
|
|
|
|
+ WebSocketHandler* ws = handler->ws.get();
|
|
|
|
|
+ if (ws->last_recv_pong_time < ws->last_send_ping_time) {
|
|
|
|
|
+ hlogw("[%s:%d] websocket no pong!", handler->ip, handler->port);
|
|
|
|
|
+ hio_close(io);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // printf("send ping\n");
|
|
|
|
|
+ hio_write(io, WS_SERVER_PING_FRAME, WS_SERVER_MIN_FRAME_SIZE);
|
|
|
|
|
+ ws->last_send_ping_time = gethrtime_us();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static void websocket_onmessage(int opcode, const std::string& msg, hio_t* io) {
|
|
|
|
|
+ HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
|
|
|
|
|
+ WebSocketHandler* ws = handler->ws.get();
|
|
|
|
|
+ switch(opcode) {
|
|
|
|
|
+ case WS_OPCODE_CLOSE:
|
|
|
|
|
+ hio_close(io);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case WS_OPCODE_PING:
|
|
|
|
|
+ // printf("recv ping\n");
|
|
|
|
|
+ // printf("send pong\n");
|
|
|
|
|
+ hio_write(io, WS_SERVER_PONG_FRAME, WS_SERVER_MIN_FRAME_SIZE);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case WS_OPCODE_PONG:
|
|
|
|
|
+ // printf("recv pong\n");
|
|
|
|
|
+ ws->last_recv_pong_time = gethrtime_us();
|
|
|
|
|
+ break;
|
|
|
|
|
+ case WS_OPCODE_TEXT:
|
|
|
|
|
+ case WS_OPCODE_BINARY:
|
|
|
|
|
+ // onmessage
|
|
|
|
|
+ handler->WebSocketOnMessage(msg);
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
static void on_recv(hio_t* io, void* _buf, int readbytes) {
|
|
static void on_recv(hio_t* io, void* _buf, int readbytes) {
|
|
|
// printf("on_recv fd=%d readbytes=%d\n", hio_fd(io), readbytes);
|
|
// printf("on_recv fd=%d readbytes=%d\n", hio_fd(io), readbytes);
|
|
|
const char* buf = (const char*)_buf;
|
|
const char* buf = (const char*)_buf;
|
|
|
HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
|
|
HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
|
|
|
|
|
|
|
|
- if (handler->proto == HttpHandler::WEBSOCKET) {
|
|
|
|
|
- // TODO: HandleWebsocketMessage
|
|
|
|
|
|
|
+ if (handler->protocol == HttpHandler::WEBSOCKET) {
|
|
|
|
|
+ WebSocketParser* parser = handler->ws->parser.get();
|
|
|
|
|
+ int nfeed = parser->FeedRecvData(buf, readbytes);
|
|
|
|
|
+ if (nfeed != readbytes) {
|
|
|
|
|
+ hloge("[%s:%d] websocket parse error!", handler->ip, handler->port);
|
|
|
|
|
+ hio_close(io);
|
|
|
|
|
+ }
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -52,10 +108,10 @@ static void on_recv(hio_t* io, void* _buf, int readbytes) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
http_version version = HTTP_V1;
|
|
http_version version = HTTP_V1;
|
|
|
- handler->proto = HttpHandler::HTTP_V1;
|
|
|
|
|
|
|
+ handler->protocol = HttpHandler::HTTP_V1;
|
|
|
if (strncmp((char*)buf, HTTP2_MAGIC, MIN(readbytes, HTTP2_MAGIC_LEN)) == 0) {
|
|
if (strncmp((char*)buf, HTTP2_MAGIC, MIN(readbytes, HTTP2_MAGIC_LEN)) == 0) {
|
|
|
version = HTTP_V2;
|
|
version = HTTP_V2;
|
|
|
- handler->proto = HttpHandler::HTTP_V2;
|
|
|
|
|
|
|
+ handler->protocol = HttpHandler::HTTP_V2;
|
|
|
handler->req.http_major = 2;
|
|
handler->req.http_major = 2;
|
|
|
handler->req.http_minor = 0;
|
|
handler->req.http_minor = 0;
|
|
|
}
|
|
}
|
|
@@ -138,7 +194,7 @@ static void on_recv(hio_t* io, void* _buf, int readbytes) {
|
|
|
ws_encode_key(iter_key->second.c_str(), ws_accept);
|
|
ws_encode_key(iter_key->second.c_str(), ws_accept);
|
|
|
res->headers[SEC_WEBSOCKET_ACCEPT] = ws_accept;
|
|
res->headers[SEC_WEBSOCKET_ACCEPT] = ws_accept;
|
|
|
}
|
|
}
|
|
|
- handler->proto = HttpHandler::WEBSOCKET;
|
|
|
|
|
|
|
+ handler->protocol = HttpHandler::WEBSOCKET;
|
|
|
}
|
|
}
|
|
|
// h2/h2c
|
|
// h2/h2c
|
|
|
else if (strnicmp(upgrade_proto, "h2", 2) == 0) {
|
|
else if (strnicmp(upgrade_proto, "h2", 2) == 0) {
|
|
@@ -210,6 +266,22 @@ static void on_recv(hio_t* io, void* _buf, int readbytes) {
|
|
|
http_method_str(req->method), req->path.c_str(),
|
|
http_method_str(req->method), req->path.c_str(),
|
|
|
res->status_code, res->status_message());
|
|
res->status_code, res->status_message());
|
|
|
|
|
|
|
|
|
|
+ // switch protocol to websocket
|
|
|
|
|
+ if (upgrade && handler->protocol == HttpHandler::WEBSOCKET) {
|
|
|
|
|
+ WebSocketHandler* ws = handler->SwitchWebSocket();
|
|
|
|
|
+ ws->channel.reset(new WebSocketChannel(io, WS_SERVER));
|
|
|
|
|
+ ws->parser->onMessage = std::bind(websocket_onmessage, std::placeholders::_1, std::placeholders::_2, io);
|
|
|
|
|
+ // NOTE: need to reset callbacks
|
|
|
|
|
+ hio_setcb_read(io, on_recv);
|
|
|
|
|
+ hio_setcb_close(io, on_close);
|
|
|
|
|
+ // NOTE: cancel keepalive timer, judge alive by heartbeat.
|
|
|
|
|
+ hio_set_keepalive_timeout(io, 0);
|
|
|
|
|
+ hio_set_heartbeat(io, HIO_DEFAULT_HEARTBEAT_INTERVAL, websocket_heartbeat);
|
|
|
|
|
+ // onopen
|
|
|
|
|
+ handler->WebSocketOnOpen();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (keepalive) {
|
|
if (keepalive) {
|
|
|
handler->Reset();
|
|
handler->Reset();
|
|
|
parser->InitRequest(req);
|
|
parser->InitRequest(req);
|
|
@@ -221,14 +293,18 @@ static void on_recv(hio_t* io, void* _buf, int readbytes) {
|
|
|
static void on_close(hio_t* io) {
|
|
static void on_close(hio_t* io) {
|
|
|
HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
|
|
HttpHandler* handler = (HttpHandler*)hevent_userdata(io);
|
|
|
if (handler) {
|
|
if (handler) {
|
|
|
|
|
+ if (handler->protocol == HttpHandler::WEBSOCKET) {
|
|
|
|
|
+ // onclose
|
|
|
|
|
+ handler->WebSocketOnClose();
|
|
|
|
|
+ }
|
|
|
hevent_set_userdata(io, NULL);
|
|
hevent_set_userdata(io, NULL);
|
|
|
delete handler;
|
|
delete handler;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void on_accept(hio_t* io) {
|
|
static void on_accept(hio_t* io) {
|
|
|
- printd("on_accept connfd=%d\n", hio_fd(io));
|
|
|
|
|
/*
|
|
/*
|
|
|
|
|
+ printf("on_accept connfd=%d\n", hio_fd(io));
|
|
|
char localaddrstr[SOCKADDR_STRLEN] = {0};
|
|
char localaddrstr[SOCKADDR_STRLEN] = {0};
|
|
|
char peeraddrstr[SOCKADDR_STRLEN] = {0};
|
|
char peeraddrstr[SOCKADDR_STRLEN] = {0};
|
|
|
printf("accept connfd=%d [%s] <= [%s]\n", hio_fd(io),
|
|
printf("accept connfd=%d [%s] <= [%s]\n", hio_fd(io),
|
|
@@ -240,25 +316,26 @@ static void on_accept(hio_t* io) {
|
|
|
hio_setcb_read(io, on_recv);
|
|
hio_setcb_read(io, on_recv);
|
|
|
hio_read(io);
|
|
hio_read(io);
|
|
|
hio_set_keepalive_timeout(io, HIO_DEFAULT_KEEPALIVE_TIMEOUT);
|
|
hio_set_keepalive_timeout(io, HIO_DEFAULT_KEEPALIVE_TIMEOUT);
|
|
|
- // new HttpHandler
|
|
|
|
|
- // delete on_close
|
|
|
|
|
|
|
+ // new HttpHandler, delete on_close
|
|
|
HttpHandler* handler = new HttpHandler;
|
|
HttpHandler* handler = new HttpHandler;
|
|
|
- handler->service = (HttpService*)hevent_userdata(io);
|
|
|
|
|
- handler->files = &s_filecache;
|
|
|
|
|
|
|
+ // ip
|
|
|
sockaddr_ip((sockaddr_u*)hio_peeraddr(io), handler->ip, sizeof(handler->ip));
|
|
sockaddr_ip((sockaddr_u*)hio_peeraddr(io), handler->ip, sizeof(handler->ip));
|
|
|
|
|
+ // port
|
|
|
handler->port = sockaddr_port((sockaddr_u*)hio_peeraddr(io));
|
|
handler->port = sockaddr_port((sockaddr_u*)hio_peeraddr(io));
|
|
|
|
|
+ // service
|
|
|
|
|
+ http_server_t* server = (http_server_t*)hevent_userdata(io);
|
|
|
|
|
+ handler->service = server->service;
|
|
|
|
|
+ // ws
|
|
|
|
|
+ handler->ws_cbs = server->ws;
|
|
|
|
|
+ // FileCache
|
|
|
|
|
+ handler->files = default_filecache();
|
|
|
hevent_set_userdata(io, handler);
|
|
hevent_set_userdata(io, handler);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void handle_cached_files(htimer_t* timer) {
|
|
static void handle_cached_files(htimer_t* timer) {
|
|
|
FileCache* pfc = (FileCache*)hevent_userdata(timer);
|
|
FileCache* pfc = (FileCache*)hevent_userdata(timer);
|
|
|
- if (pfc == NULL) {
|
|
|
|
|
- htimer_del(timer);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
file_cache_t* fc = NULL;
|
|
file_cache_t* fc = NULL;
|
|
|
- time_t tt;
|
|
|
|
|
- time(&tt);
|
|
|
|
|
|
|
+ time_t tt = time(NULL);
|
|
|
std::lock_guard<std::mutex> locker(pfc->mutex_);
|
|
std::lock_guard<std::mutex> locker(pfc->mutex_);
|
|
|
auto iter = pfc->cached_files.begin();
|
|
auto iter = pfc->cached_files.begin();
|
|
|
while (iter != pfc->cached_files.end()) {
|
|
while (iter != pfc->cached_files.end()) {
|
|
@@ -272,88 +349,107 @@ static void handle_cached_files(htimer_t* timer) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static void fsync_logfile(hidle_t* idle) {
|
|
|
|
|
- hlog_fsync();
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static void worker_fn(void* userdata) {
|
|
|
|
|
|
|
+static void loop_thread(void* userdata) {
|
|
|
http_server_t* server = (http_server_t*)userdata;
|
|
http_server_t* server = (http_server_t*)userdata;
|
|
|
int listenfd = server->listenfd;
|
|
int listenfd = server->listenfd;
|
|
|
- hloop_t* loop = hloop_new(0);
|
|
|
|
|
- hio_t* listenio = haccept(loop, listenfd, on_accept);
|
|
|
|
|
- hevent_set_userdata(listenio, server->service);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ EventLoopPtr loop(new EventLoop);
|
|
|
|
|
+ hloop_t* hloop = loop->loop();
|
|
|
|
|
+ hio_t* listenio = haccept(hloop, listenfd, on_accept);
|
|
|
|
|
+ hevent_set_userdata(listenio, server);
|
|
|
if (server->ssl) {
|
|
if (server->ssl) {
|
|
|
hio_enable_ssl(listenio);
|
|
hio_enable_ssl(listenio);
|
|
|
}
|
|
}
|
|
|
// fsync logfile when idle
|
|
// fsync logfile when idle
|
|
|
hlog_disable_fsync();
|
|
hlog_disable_fsync();
|
|
|
- hidle_add(loop, fsync_logfile, INFINITE);
|
|
|
|
|
|
|
+ hidle_add(hloop, [](hidle_t*) {
|
|
|
|
|
+ hlog_fsync();
|
|
|
|
|
+ }, INFINITE);
|
|
|
// timer handle_cached_files
|
|
// timer handle_cached_files
|
|
|
- htimer_t* timer = htimer_add(loop, handle_cached_files, s_filecache.file_cached_time * 1000);
|
|
|
|
|
- hevent_set_userdata(timer, &s_filecache);
|
|
|
|
|
|
|
+ FileCache* filecache = default_filecache();
|
|
|
|
|
+ htimer_t* timer = htimer_add(hloop, handle_cached_files, filecache->file_cached_time * 1000);
|
|
|
|
|
+ hevent_set_userdata(timer, filecache);
|
|
|
|
|
|
|
|
- // for SDK implement http_server_stop
|
|
|
|
|
HttpServerPrivdata* privdata = (HttpServerPrivdata*)server->privdata;
|
|
HttpServerPrivdata* privdata = (HttpServerPrivdata*)server->privdata;
|
|
|
if (privdata) {
|
|
if (privdata) {
|
|
|
- std::lock_guard<std::mutex> locker(privdata->mutex_);
|
|
|
|
|
- if (privdata->quit) {
|
|
|
|
|
- hloop_free(&loop);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- privdata->loops.push_back(loop);
|
|
|
|
|
|
|
+ privdata->mutex_.lock();
|
|
|
|
|
+ privdata->loops.push_back(hloop);
|
|
|
|
|
+ privdata->mutex_.unlock();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- hloop_run(loop);
|
|
|
|
|
- hloop_free(&loop);
|
|
|
|
|
|
|
+ loop->run();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int http_server_run(http_server_t* server, int wait) {
|
|
int http_server_run(http_server_t* server, int wait) {
|
|
|
- // service
|
|
|
|
|
- if (server->service == NULL) {
|
|
|
|
|
- server->service = &s_default_service;
|
|
|
|
|
- }
|
|
|
|
|
// port
|
|
// port
|
|
|
server->listenfd = Listen(server->port, server->host);
|
|
server->listenfd = Listen(server->port, server->host);
|
|
|
if (server->listenfd < 0) return server->listenfd;
|
|
if (server->listenfd < 0) return server->listenfd;
|
|
|
|
|
+ // service
|
|
|
|
|
+ if (server->service == NULL) {
|
|
|
|
|
+ server->service = default_http_service();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (server->worker_processes) {
|
|
if (server->worker_processes) {
|
|
|
// multi-processes
|
|
// multi-processes
|
|
|
- return master_workers_run(worker_fn, server, server->worker_processes, server->worker_threads, wait);
|
|
|
|
|
|
|
+ return master_workers_run(loop_thread, server, server->worker_processes, server->worker_threads, wait);
|
|
|
}
|
|
}
|
|
|
else {
|
|
else {
|
|
|
// multi-threads
|
|
// multi-threads
|
|
|
- int worker_threads = server->worker_threads;
|
|
|
|
|
- if (worker_threads == 0) worker_threads = 1;
|
|
|
|
|
|
|
+ if (server->worker_threads == 0) server->worker_threads = 1;
|
|
|
|
|
|
|
|
// for SDK implement http_server_stop
|
|
// for SDK implement http_server_stop
|
|
|
HttpServerPrivdata* privdata = new HttpServerPrivdata;
|
|
HttpServerPrivdata* privdata = new HttpServerPrivdata;
|
|
|
- privdata->quit = 0;
|
|
|
|
|
server->privdata = privdata;
|
|
server->privdata = privdata;
|
|
|
|
|
|
|
|
int i = wait ? 1 : 0;
|
|
int i = wait ? 1 : 0;
|
|
|
- for (; i < worker_threads; ++i) {
|
|
|
|
|
- hthread_t thrd = hthread_create((hthread_routine)worker_fn, server);
|
|
|
|
|
|
|
+ for (; i < server->worker_threads; ++i) {
|
|
|
|
|
+ hthread_t thrd = hthread_create((hthread_routine)loop_thread, server);
|
|
|
privdata->threads.push_back(thrd);
|
|
privdata->threads.push_back(thrd);
|
|
|
}
|
|
}
|
|
|
if (wait) {
|
|
if (wait) {
|
|
|
- worker_fn(server);
|
|
|
|
|
|
|
+ loop_thread(server);
|
|
|
}
|
|
}
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int http_server_stop(http_server_t* server) {
|
|
int http_server_stop(http_server_t* server) {
|
|
|
|
|
+#ifdef OS_UNIX
|
|
|
|
|
+ if (server->worker_processes) {
|
|
|
|
|
+ signal_handle("stop");
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
HttpServerPrivdata* privdata = (HttpServerPrivdata*)server->privdata;
|
|
HttpServerPrivdata* privdata = (HttpServerPrivdata*)server->privdata;
|
|
|
if (privdata == NULL) return 0;
|
|
if (privdata == NULL) return 0;
|
|
|
|
|
|
|
|
|
|
+ // 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
|
|
|
|
|
+ for (auto& loop : privdata->loops) {
|
|
|
|
|
+ if (hloop_status(loop) == HLOOP_STATUS_STOP) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // stop all loops
|
|
|
privdata->mutex_.lock();
|
|
privdata->mutex_.lock();
|
|
|
- privdata->quit = 1;
|
|
|
|
|
- for (auto loop : privdata->loops) {
|
|
|
|
|
|
|
+ for (auto& loop : privdata->loops) {
|
|
|
hloop_stop(loop);
|
|
hloop_stop(loop);
|
|
|
}
|
|
}
|
|
|
privdata->mutex_.unlock();
|
|
privdata->mutex_.unlock();
|
|
|
|
|
|
|
|
- for (auto thrd : privdata->threads) {
|
|
|
|
|
|
|
+ // join all threads
|
|
|
|
|
+ for (auto& thrd : privdata->threads) {
|
|
|
hthread_join(thrd);
|
|
hthread_join(thrd);
|
|
|
}
|
|
}
|
|
|
|
|
|