#include "HttpHandler.h" #include "hbase.h" #include "http_page.h" int HttpHandler::HandleHttpRequest() { // preprocessor -> api -> web -> postprocessor int status_code = 200; http_sync_handler sync_handler = NULL; http_async_handler async_handler = NULL; HttpRequest* pReq = req.get(); HttpResponse* pResp = resp.get(); pReq->ParseUrl(); pReq->client_addr.ip = ip; pReq->client_addr.port = port; preprocessor: state = HANDLE_BEGIN; if (service->preprocessor) { status_code = service->preprocessor(pReq, pResp); if (status_code != 0) { goto make_http_status_page; } } if (service->api_handlers.size() != 0) { service->GetApi(pReq, &sync_handler, &async_handler); } if (sync_handler) { // sync api service status_code = sync_handler(pReq, pResp); if (status_code != 0) { goto make_http_status_page; } } else if (async_handler) { // async api service async_handler(req, writer); status_code = 0; } else if (service->document_root.size() != 0 && (pReq->method == HTTP_GET || pReq->method == HTTP_HEAD)) { // file service status_code = 200; const char* s = pReq->path.c_str(); const char* e = s; while (*e && *e != '?' && *e != '#') ++e; std::string path = std::string(s, e); const char* req_path = path.c_str(); // path safe check if (*req_path != '/' || strstr(req_path, "/../")) { pResp->status_code = HTTP_STATUS_BAD_REQUEST; goto make_http_status_page; } std::string filepath = service->document_root; filepath += req_path; if (req_path[1] == '\0') { filepath += service->home_page; } bool is_dir = filepath.c_str()[filepath.size()-1] == '/'; bool is_index_of = false; if (service->index_of.size() != 0 && strstartswith(req_path, service->index_of.c_str())) { is_index_of = true; } if (!is_dir || is_index_of) { bool need_read = pReq->method == HTTP_HEAD ? false : true; fc = files->Open(filepath.c_str(), need_read, (void*)req_path); } if (fc == NULL) { // Not Found status_code = HTTP_STATUS_NOT_FOUND; } else { // Not Modified auto iter = pReq->headers.find("if-not-match"); if (iter != pReq->headers.end() && strcmp(iter->second.c_str(), fc->etag) == 0) { status_code = HTTP_STATUS_NOT_MODIFIED; fc = NULL; } else { iter = pReq->headers.find("if-modified-since"); if (iter != pReq->headers.end() && strcmp(iter->second.c_str(), fc->last_modified) == 0) { status_code = HTTP_STATUS_NOT_MODIFIED; fc = NULL; } } } } else { // Not Implemented status_code = HTTP_STATUS_NOT_IMPLEMENTED; } make_http_status_page: if (status_code >= 100 && status_code < 600) { pResp->status_code = (http_status)status_code; } if (pResp->status_code >= 400 && pResp->body.size() == 0 && pReq->method != HTTP_HEAD) { // error page if (service->error_page.size() != 0) { std::string filepath = service->document_root; filepath += '/'; filepath += service->error_page; fc = files->Open(filepath.c_str(), true, NULL); } // status page if (fc == NULL && pResp->body.size() == 0) { pResp->content_type = TEXT_HTML; make_http_status_page(pResp->status_code, pResp->body); } } if (fc) { pResp->content = fc->filebuf.base; pResp->content_length = fc->filebuf.len; if (fc->content_type && *fc->content_type != '\0') { pResp->headers["Content-Type"] = fc->content_type; } pResp->headers["Last-Modified"] = fc->last_modified; pResp->headers["Etag"] = fc->etag; } postprocessor: if (service->postprocessor) { service->postprocessor(pReq, pResp); } if (status_code == 0) { state = HANDLE_CONTINUE; } else { state = HANDLE_END; parser->SubmitResponse(pResp); } return status_code; } int HttpHandler::FeedRecvData(const char* data, size_t len) { int nfeed = 0; if (protocol == HttpHandler::WEBSOCKET) { nfeed = ws->parser->FeedRecvData(data, len); if (nfeed != len) { hloge("[%s:%d] websocket parse error!", ip, port); } } else { if (state != WANT_RECV) { Reset(); } nfeed = parser->FeedRecvData(data, len); if (nfeed != len) { hloge("[%s:%d] http parse error: %s", ip, port, parser->StrError(parser->GetError())); } } return nfeed; } int HttpHandler::GetSendData(char** data, size_t* len) { if (state == HANDLE_CONTINUE) { return 0; } HttpRequest* pReq = req.get(); HttpResponse* pResp = resp.get(); if (protocol == HTTP_V1) { switch(state) { case WANT_RECV: if (parser->IsComplete()) state = WANT_SEND; else return 0; case HANDLE_END: state = WANT_SEND; case WANT_SEND: state = SEND_HEADER; case SEND_HEADER: { int content_length = 0; const char* content = NULL; // HEAD if (pReq->method == HTTP_HEAD) { if (fc) { pResp->headers["Accept-Ranges"] = "bytes"; pResp->headers["Content-Length"] = hv::to_string(fc->st.st_size); } else { pResp->headers["Content-Type"] = "text/html"; pResp->headers["Content-Length"] = "0"; } state = SEND_DONE; goto return_nobody; } // File service if (fc) { long from, to, total; int nread; // Range: if (pReq->GetRange(from, to)) { HFile file; if (file.open(fc->filepath.c_str(), "rb") != 0) { pResp->status_code = HTTP_STATUS_NOT_FOUND; state = SEND_DONE; goto return_nobody; } total = file.size(); if (to == 0 || to >= total) to = total - 1; pResp->content_length = to - from + 1; nread = file.readrange(body, from, to); if (nread != pResp->content_length) { pResp->status_code = HTTP_STATUS_INTERNAL_SERVER_ERROR; state = SEND_DONE; goto return_nobody; } pResp->SetRange(from, to, total); state = SEND_BODY; goto return_header; } // FileCache // NOTE: no copy filebuf, more efficient header = pResp->Dump(true, false); fc->prepend_header(header.c_str(), header.size()); *data = fc->httpbuf.base; *len = fc->httpbuf.len; state = SEND_DONE; return *len; } // API service content_length = pResp->ContentLength(); content = (const char*)pResp->Content(); if (content) { if (content_length > (1 << 20)) { state = SEND_BODY; goto return_header; } else { // NOTE: header+body in one package if <= 1M header = pResp->Dump(true, false); header.append(content, content_length); state = SEND_DONE; goto return_header; } } else { state = SEND_DONE; goto return_header; } return_nobody: pResp->content_length = 0; return_header: if (header.empty()) header = pResp->Dump(true, false); *data = (char*)header.c_str(); *len = header.size(); return *len; } case SEND_BODY: { if (body.empty()) { *data = (char*)pResp->Content(); *len = pResp->ContentLength(); } else { *data = (char*)body.c_str(); *len = body.size(); } state = SEND_DONE; return *len; } case SEND_DONE: { // NOTE: remove file cache if > 16M if (fc && fc->filebuf.len > (1 << 24)) { files->Close(fc); } fc = NULL; header.clear(); body.clear(); return 0; } default: return 0; } } else if (protocol == HTTP_V2) { return parser->GetSendData(data, len); } return 0; }