#include "http_server.h"
#include "h.h"
#include "hmain.h"
#include "hloop.h"
#include "hbuf.h"
#include "HttpParser.h"
#include "FileCache.h"
#define RECV_BUFSIZE 4096
#define SEND_BUFSIZE 4096
static HttpService s_default_service;
static FileCache s_filecache;
/*
404 Not Found
404 Not Found
*/
static 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"(
)";
page += szCode; page += status_message;
page += R"(
)";
page += szCode; page += status_message;
page += R"(
)";
}
static void master_init(void* userdata) {
#ifdef OS_UNIX
char proctitle[256] = {0};
snprintf(proctitle, sizeof(proctitle), "%s: master process", g_main_ctx.program_name);
setproctitle(proctitle);
#endif
}
static void master_proc(void* userdata) {
while(1) sleep(1);
}
static void worker_init(void* userdata) {
#ifdef OS_UNIX
char proctitle[256] = {0};
snprintf(proctitle, sizeof(proctitle), "%s: worker process", g_main_ctx.program_name);
setproctitle(proctitle);
signal(SIGNAL_RELOAD, signal_handler);
#endif
}
struct http_connect_userdata {
http_server_t* server;
std::string log;
HttpParser parser;
HttpRequest req;
HttpResponse res;
http_connect_userdata() {
parser.parser_request_init(&req);
}
};
static void on_read(hio_t* io, void* buf, int readbytes) {
//printf("on_read fd=%d readbytes=%d\n", io->fd, readbytes);
http_connect_userdata* hcu = (http_connect_userdata*)io->userdata;
HttpService* service = hcu->server->service;
HttpRequest* req = &hcu->req;
HttpResponse* res = &hcu->res;
int ret, nparse;
char* recvbuf = (char*)buf;
int nrecv = readbytes;
// recv -> http_parser -> http_request -> http_request_handler -> http_response -> send
//printf("%s\n", recvbuf);
nparse = hcu->parser.execute(recvbuf, nrecv);
if (nparse != nrecv || hcu->parser.get_errno() != HPE_OK) {
hcu->log += asprintf("http parser error: %s", http_errno_description(hcu->parser.get_errno()));
hclose(io);
return;
}
if (hcu->parser.get_state() == HP_MESSAGE_COMPLETE) {
http_api_handler api = NULL;
file_cache_t* fc = NULL;
const char* content = NULL;
int content_length = 0;
bool send_in_one_packet = false;
hcu->log += asprintf("[%s %s]", http_method_str(req->method), req->url.c_str());
static std::string s_Server = std::string("httpd/") + std::string(get_compile_version());
res->headers["Server"] = s_Server;
// preprocessor
if (service->preprocessor) {
service->preprocessor(req, res);
}
ret = service->GetApi(req->url.c_str(), req->method, &api);
if (api) {
// api service
api(req, res);
}
else {
if (ret == HTTP_STATUS_METHOD_NOT_ALLOWED) {
// Method Not Allowed
res->status_code = HTTP_STATUS_METHOD_NOT_ALLOWED;
}
else if (req->method == HTTP_GET) {
// web service
std::string filepath = service->document_root;
filepath += req->url.c_str();
if (strcmp(req->url.c_str(), "/") == 0) {
filepath += service->home_page;
}
fc = s_filecache.Open(filepath.c_str());
// Not Found
if (fc == NULL) {
res->status_code = HTTP_STATUS_NOT_FOUND;
}
else {
// Not Modified
auto iter = req->headers.find("if-not-match");
if (iter != req->headers.end() &&
strcmp(iter->second.c_str(), fc->etag) == 0) {
res->status_code = HTTP_STATUS_NOT_MODIFIED;
fc = NULL;
}
else {
iter = req->headers.find("if-modified-since");
if (iter != req->headers.end() &&
strcmp(iter->second.c_str(), fc->last_modified) == 0) {
res->status_code = HTTP_STATUS_NOT_MODIFIED;
fc = NULL;
}
}
}
}
else {
// Not Implemented
res->status_code = HTTP_STATUS_NOT_IMPLEMENTED;
}
// html page
if (res->status_code >= 400 && res->body.size() == 0) {
// error page
if (service->error_page.size() != 0) {
std::string filepath = service->document_root;
filepath += '/';
filepath += service->error_page;
fc = s_filecache.Open(filepath.c_str());
}
// status page
if (fc == NULL && res->body.size() == 0) {
res->content_type = TEXT_HTML;
make_http_status_page(res->status_code, res->body);
}
}
}
// postprocessor
if (service->postprocessor) {
service->postprocessor(req, res);
}
// send
std::string header;
time_t tt;
time(&tt);
char c_str[256] = {0};
strftime(c_str, sizeof(c_str), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tt));
res->headers["Date"] = c_str;
if (fc && fc->filebuf.len) {
content = (const char*)fc->filebuf.base;
content_length = fc->filebuf.len;
if (fc->content_type && *fc->content_type != '\0') {
res->headers["Content-Type"] = fc->content_type;
}
res->headers["Content-Length"] = std::to_string(content_length);
res->headers["Last-Modified"] = fc->last_modified;
res->headers["Etag"] = fc->etag;
}
else if (res->body.size()) {
content = res->body.c_str();
content_length = res->body.size();
}
header = res->dump(true, false);
if (header.size() + content_length <= SEND_BUFSIZE) {
header.insert(header.size(), content, content_length);
send_in_one_packet = true;
}
// send header
hwrite(io->loop, io->fd, header.c_str(), header.size());
// send body
if (!send_in_one_packet && content_length != 0) {
hwrite(io->loop, io->fd, content, content_length);
}
hcu->log += asprintf("=>[%d %s]", res->status_code, http_status_str(res->status_code));
hclose(io);
}
}
static void on_close(hio_t* io) {
http_connect_userdata* hcu = (http_connect_userdata*)io->userdata;
if (hcu) {
hlogi("%s", hcu->log.c_str());
delete hcu;
}
}
static void on_accept(hio_t* io, int connfd) {
//printf("on_accept listenfd=%d connfd=%d\n", io->fd, connfd);
struct sockaddr_in localaddr, peeraddr;
socklen_t addrlen;
addrlen = sizeof(struct sockaddr_in);
getsockname(connfd, (struct sockaddr*)&localaddr, &addrlen);
addrlen = sizeof(struct sockaddr_in);
getpeername(connfd, (struct sockaddr*)&peeraddr, &addrlen);
//printf("accept connfd=%d [%s:%d] <= [%s:%d]\n", connfd,
//inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port),
//inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
// new http_connect_userdata
// delete on_close
http_connect_userdata* hcu = new http_connect_userdata;
hcu->server = (http_server_t*)io->userdata;
hcu->log += asprintf("[%s:%d]", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
nonblocking(connfd);
HBuf* buf = (HBuf*)io->loop->userdata;
hio_t* connio = hread(io->loop, connfd, buf->base, buf->len, on_read);
connio->close_cb = on_close;
connio->userdata = hcu;
}
void handle_cached_files(htimer_t* timer) {
FileCache* pfc = (FileCache*)timer->userdata;
if (pfc == NULL) {
htimer_del(timer);
return;
}
file_cache_t* fc = NULL;
time_t tt;
time(&tt);
auto iter = pfc->cached_files.begin();
while (iter != pfc->cached_files.end()) {
fc = iter->second;
if (tt - fc->stat_time > pfc->file_cached_time) {
delete fc;
iter = pfc->cached_files.erase(iter);
continue;
}
++iter;
}
}
static void worker_proc(void* userdata) {
http_server_t* server = (http_server_t*)userdata;
int listenfd = server->listenfd;
hloop_t loop;
hloop_init(&loop);
htimer_t* timer = htimer_add(&loop, handle_cached_files, s_filecache.file_cached_time*1000);
timer->userdata = &s_filecache;
// one loop one readbuf.
HBuf readbuf;
readbuf.resize(RECV_BUFSIZE);
loop.userdata = &readbuf;
hio_t* listenio = haccept(&loop, listenfd, on_accept);
listenio->userdata = server;
// disable fflush
hlog_set_fflush(0);
hloop_run(&loop);
}
int http_server_run(http_server_t* server, int wait) {
// worker_processes
if (server->worker_processes != 0 && g_worker_processes_num != 0 && g_worker_processes != NULL) {
return ERR_OVER_LIMIT;
}
// service
if (server->service == NULL) {
server->service = &s_default_service;
}
// port
server->listenfd = Listen(server->port);
if (server->listenfd < 0) return server->listenfd;
if (server->worker_processes == 0) {
worker_proc(server);
}
else {
// master-workers processes
g_worker_processes_num = server->worker_processes;
int bytes = g_worker_processes_num * sizeof(proc_ctx_t);
g_worker_processes = (proc_ctx_t*)malloc(bytes);
if (g_worker_processes == NULL) {
perror("malloc");
abort();
}
memset(g_worker_processes, 0, bytes);
for (int i = 0; i < g_worker_processes_num; ++i) {
proc_ctx_t* ctx = g_worker_processes + i;
ctx->init = worker_init;
ctx->init_userdata = NULL;
ctx->proc = worker_proc;
ctx->proc_userdata = server;
spawn_proc(ctx);
}
}
if (wait) {
master_init(NULL);
master_proc(NULL);
}
return 0;
}