소스 검색

Impl http header Range

hewei.it 4 년 전
부모
커밋
8b40f81cf2
7개의 변경된 파일183개의 추가작업 그리고 59개의 파일을 삭제
  1. 9 0
      base/hfile.h
  2. 27 0
      http/HttpMessage.h
  3. 5 5
      http/server/FileCache.cpp
  4. 7 3
      http/server/FileCache.h
  5. 116 8
      http/server/HttpHandler.cpp
  6. 15 2
      http/server/HttpHandler.h
  7. 4 41
      http/server/HttpServer.cpp

+ 9 - 0
base/hfile.h

@@ -80,6 +80,15 @@ public:
         return str.length() != 0;
     }
 
+    int readrange(std::string& str, size_t from = 0, size_t to = 0) {
+        size_t filesize = size();
+        if (to == 0 || to >= filesize) to = filesize - 1;
+        size_t readbytes = to - from + 1;
+        str.resize(readbytes);
+        fseek(fp, from, SEEK_SET);
+        return fread((void*)str.data(), 1, readbytes, fp);
+    }
+
 public:
     char  filepath[MAX_PATH];
     FILE* fp;

+ 27 - 0
http/HttpMessage.h

@@ -293,6 +293,20 @@ public:
     void DumpUrl();
     // url -> structed url
     void ParseUrl();
+
+    // Range: bytes=0-4095
+    void SetRange(long from = 0, long to = -1) {
+        headers["Range"] = asprintf("bytes=%ld-%ld", from, to);
+    }
+    bool GetRange(long& from, long& to) {
+        auto iter = headers.find("Range");
+        if (iter != headers.end()) {
+            sscanf(iter->second.c_str(), "bytes=%ld-%ld", &from, &to);
+            return true;
+        }
+        from = to = 0;
+        return false;
+    }
 };
 
 class HV_EXPORT HttpResponse : public HttpMessage {
@@ -317,6 +331,19 @@ public:
     }
 
     virtual std::string Dump(bool is_dump_headers = true, bool is_dump_body = false);
+
+    // Content-Range: bytes 0-4095/10240000
+    void SetRange(long from, long to, long total) {
+        headers["Content-Range"] = asprintf("bytes %ld-%ld/%ld", from, to, total);
+    }
+    bool GetRange(long& from, long& to, long& total) {
+        auto iter = headers.find("Content-Range");
+        if (iter != headers.end()) {
+            sscanf(iter->second.c_str(), "bytes %ld-%ld/%ld", &from, &to, &total);
+        }
+        from = to = total = 0;
+        return false;
+    }
 };
 
 typedef std::shared_ptr<HttpRequest>    HttpRequestPtr;

+ 5 - 5
http/server/FileCache.cpp

@@ -4,8 +4,8 @@
 #include "htime.h"
 #include "hlog.h"
 
-#include "httpdef.h" // for http_content_type_str_by_suffix
-#include "http_page.h" //make_index_of_page
+#include "httpdef.h"    // import http_content_type_str_by_suffix
+#include "http_page.h"  // import make_index_of_page
 
 #define ETAG_FMT    "\"%zx-%zx\""
 
@@ -16,12 +16,12 @@ file_cache_ptr FileCache::Open(const char* filepath, bool need_read, void* ctx)
     if (fc) {
         time_t now = time(NULL);
         if (now - fc->stat_time > file_stat_interval) {
-            modified = fc->if_modified(filepath);
+            modified = fc->is_modified();
             fc->stat_time = now;
             fc->stat_cnt++;
         }
         if (need_read) {
-            if ((!modified) && fc->filebuf.len == fc->st.st_size) {
+            if (!modified && fc->is_complete()) {
                 need_read = false;
             }
         }
@@ -43,7 +43,7 @@ file_cache_ptr FileCache::Open(const char* filepath, bool need_read, void* ctx)
                 (S_ISDIR(st.st_mode) &&
                  filepath[strlen(filepath)-1] == '/')) {
                 fc.reset(new file_cache_t);
-                //fc->filepath = filepath;
+                fc->filepath = filepath;
                 fc->st = st;
                 time(&fc->open_time);
                 fc->stat_time = fc->open_time;

+ 7 - 3
http/server/FileCache.h

@@ -13,7 +13,7 @@
 #define FILE_CACHE_MAX_SIZE         (1 << 30)   // 1G
 
 typedef struct file_cache_s {
-    //std::string filepath;
+    std::string filepath;
     struct stat st;
     time_t      open_time;
     time_t      stat_time;
@@ -30,15 +30,19 @@ typedef struct file_cache_s {
         content_type = NULL;
     }
 
-    bool if_modified(const char* filepath) {
+    bool is_modified() {
         time_t mtime = st.st_mtime;
-        stat(filepath, &st);
+        stat(filepath.c_str(), &st);
         if (mtime == st.st_mtime) {
             return false;
         }
         return true;
     }
 
+    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;

+ 116 - 8
http/server/HttpHandler.cpp

@@ -86,7 +86,7 @@ make_http_status_page:
     if (ret >= 100 && ret < 600) {
         res.status_code = (http_status)ret;
     }
-    if (res.status_code >= 400 && res.body.size() == 0) {
+    if (res.status_code >= 400 && res.body.size() == 0 && req.method != HTTP_HEAD) {
         // error page
         if (service->error_page.size() != 0) {
             std::string filepath = service->document_root;
@@ -102,19 +102,13 @@ make_http_status_page:
     }
 
     if (fc) {
+        res.content = fc->filebuf.base;
         res.content_length = fc->filebuf.len;
         if (fc->content_type && *fc->content_type != '\0') {
             res.headers["Content-Type"] = fc->content_type;
         }
         res.headers["Last-Modified"] = fc->last_modified;
         res.headers["Etag"] = fc->etag;
-        if (req.method == HTTP_HEAD) {
-            res.headers["Accept-Ranges"] = "bytes";
-            res.content = NULL;
-            fc = NULL;
-        } else {
-            res.content = fc->filebuf.base;
-        }
     }
 
 postprocessor:
@@ -122,5 +116,119 @@ postprocessor:
         ret = service->postprocessor(&req, &res);
     }
 
+    state = WANT_SEND;
     return ret;
 }
+
+int HttpHandler::GetSendData(char** data, size_t* len) {
+    if (protocol == HTTP_V1) {
+        switch(state) {
+        case WANT_RECV:
+            if (parser->IsComplete()) state = WANT_SEND;
+            else return 0;
+        case WANT_SEND:
+            state = SEND_HEADER;
+        case SEND_HEADER:
+        {
+            int content_length = 0;
+            const char* content = NULL;
+            // HEAD
+            if (req.method == HTTP_HEAD) {
+                if (fc) {
+                    res.headers["Accept-Ranges"] = "bytes";
+                    res.headers["Content-Length"] = hv::to_string(fc->st.st_size);
+                }
+                state = SEND_DONE;
+                goto return_nobody;
+            }
+            // File service
+            if (fc) {
+                long from, to, total;
+                int nread;
+                // Range:
+                if (req.GetRange(from, to)) {
+                    HFile file;
+                    if (file.open(fc->filepath.c_str(), "rb") != 0) {
+                        res.status_code = HTTP_STATUS_NOT_FOUND;
+                        state = SEND_DONE;
+                        goto return_nobody;
+                    }
+                    total = file.size();
+                    if (to == 0 || to >= total) to = total - 1;
+                    res.content_length = to - from + 1;
+                    nread = file.readrange(body, from, to);
+                    if (nread != res.content_length) {
+                        res.status_code = HTTP_STATUS_INTERNAL_SERVER_ERROR;
+                        state = SEND_DONE;
+                        goto return_nobody;
+                    }
+                    res.SetRange(from, to, total);
+                    state = SEND_BODY;
+                    goto return_header;
+                }
+                // FileCache
+                // NOTE: no copy filebuf, more efficient
+                header = res.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 = res.ContentLength();
+            content = (const char*)res.Content();
+            if (content) {
+                if (content_length > (1 << 20)) {
+                    state = SEND_BODY;
+                    goto return_header;
+                } else {
+                    // NOTE: header+body in one package if <= 1M
+                    header = res.Dump(true, false);
+                    header.append(content, content_length);
+                    state = SEND_DONE;
+                    goto return_header;
+                }
+            } else {
+                state = SEND_DONE;
+                goto return_header;
+            }
+return_nobody:
+            res.content_length = 0;
+return_header:
+            if (header.empty()) header = res.Dump(true, false);
+            *data = (char*)header.c_str();
+            *len = header.size();
+            return *len;
+        }
+        case SEND_BODY:
+        {
+            if (body.empty()) {
+                *data = (char*)res.Content();
+                *len = res.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;
+}

+ 15 - 2
http/server/HttpHandler.h

@@ -53,6 +53,13 @@ public:
         HTTP_V2,
         WEBSOCKET,
     } protocol;
+    enum State {
+        WANT_RECV,
+        WANT_SEND,
+        SEND_HEADER,
+        SEND_BODY,
+        SEND_DONE,
+    } state;
 
     // peeraddr
     char                    ip[64];
@@ -61,32 +68,38 @@ public:
     // for http
     HttpService             *service;
     FileCache               *files;
-    file_cache_ptr          fc;
 
     HttpRequest             req;
     HttpResponse            res;
     HttpParserPtr           parser;
 
+    // for GetSendData
+    file_cache_ptr          fc;
+    std::string             header;
+    std::string             body;
+
     // for websocket
     WebSocketHandlerPtr         ws;
     WebSocketServerCallbacks*   ws_cbs;
 
     HttpHandler() {
         protocol = UNKNOWN;
+        state = WANT_RECV;
         service = NULL;
         files = NULL;
         ws_cbs = NULL;
     }
 
     void Reset() {
+        state = WANT_RECV;
         req.Reset();
         res.Reset();
-        fc = NULL;
     }
 
     // @workflow: preprocessor -> api -> web -> postprocessor
     // @result: HttpRequest -> HttpResponse/file_cache_t
     int HandleHttpRequest();
+    int GetSendData(char** data, size_t* len);
 
     // websocket
     WebSocketHandler* SwitchWebSocket() {

+ 4 - 41
http/server/HttpServer.cpp

@@ -224,51 +224,14 @@ static void on_recv(hio_t* io, void* _buf, int readbytes) {
         parser->SubmitResponse(res);
     }
 
-    if (req->http_major == 1) {
-        hbuf_t sendbuf;
-        bool send_in_one_packet = true;
-        std::string header = res->Dump(true, false);
-        int content_length = res->ContentLength();
-        const char* content = (const char*)res->Content();
-        if (handler->fc) {
-            // NOTE: no copy filebuf, more efficient
-            handler->fc->prepend_header(header.c_str(), header.size());
-            sendbuf = handler->fc->httpbuf;
-        }
-        else {
-            if (content) {
-                // NOTE: send header+body in one package if < 1M
-                if (content_length > (1 << 20)) {
-                    send_in_one_packet = false;
-                }
-                else if (content_length != 0) {
-                    header.append(content);
-                }
-            }
-            sendbuf.base = (char*)header.c_str();
-            sendbuf.len = header.size();
-        }
-        // send header/body
-        hio_write(io, sendbuf.base, sendbuf.len);
-        if (send_in_one_packet == false) {
-            // send body
-            hio_write(io, content, content_length);
-        }
-    }
-    else if (req->http_major == 2) {
-        char* data = NULL;
-        size_t len = 0;
-        while (parser->GetSendData(&data, &len)) {
+    char* data = NULL;
+    size_t len = 0;
+    while (handler->GetSendData(&data, &len)) {
+        if (data && len) {
             hio_write(io, data, len);
         }
     }
 
-    // NOTE: remove file cache if > 16M
-    if (handler->fc && handler->fc->filebuf.len > (1 << 24)) {
-        handler->files->Close(handler->fc);
-        handler->fc = NULL;
-    }
-
     // LOG
     hloop_t* loop = hevent_loop(io);
     hlogi("[%ld-%ld][%s:%d][%s %s]=>[%d %s]",