Просмотр исходного кода

Refactor ResponseData, Add BinaryResponseData child class

xcbosa-itx 2 лет назад
Родитель
Сommit
3ac542c0f6

+ 53 - 4
httpserver/ClientConnection.cpp

@@ -24,6 +24,13 @@ namespace xc {
         void ClientConnection::workLoop() {
             ::FILE *clRead = fdopen(this->sockFd, "r");
             ::FILE *clWrite = fdopen(dup(this->sockFd), "w");
+
+            struct timeval timeout = { conf::clientSocketTimeoutSeconds, 0 };
+            if (setsockopt(this->sockFd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) {
+                cleanUpAndDestroy();
+                return;
+            }
+
             this->clRead = clRead;
             this->clWrite = clWrite;
             char *requestBuff = (char *)::malloc(urlRequestBuffSize);
@@ -42,16 +49,58 @@ namespace xc {
             ptr = strtok_r(requestBuff, " ", &p);
             if (ptr != nullptr) {
                 method = string(ptr);
-                ptr = strtok_r(NULL, " ", &p);
+                ptr = strtok_r(p, " ", &p);
                 if (ptr != nullptr) {
                     url = string(ptr);
                 }
             }
 
-            if (method == "GET1") {
+            if (method == "GET" || method == "POST") {
+                map<string, string> headers;
+                ostringstream body;
 
-            } else if (method == "POST1") {
+                bool isHeader = true;
+                while (::fgets(requestBuff, urlRequestBuffSize, clRead)) {
+                    int len = ::strlen(requestBuff);
+                    char *lineBuff = (char *) ::malloc(len + 1), *pLineBuff = lineBuff;
+                    bool leftHasContext = false;
+                    for (int i = 0; i < len; i++) {
+                        if (requestBuff[i] == '\r' || requestBuff[i] == '\n') continue;
+                        if (requestBuff[i] == ' ' || requestBuff[i] == '\t') {
+                            if (leftHasContext) {
+                                *pLineBuff++ = requestBuff[i];
+                            }
+                        } else {
+                            leftHasContext = true;
+                            *pLineBuff++ = requestBuff[i];
+                        }
+                    }
+                    if (leftHasContext) {
+                        pLineBuff--;
+                        while (pLineBuff >= lineBuff && (*pLineBuff == ' ' || *pLineBuff == '\t')) {
+                            *pLineBuff = 0; // 从右往左,删掉右边的空白符
+                            pLineBuff--;
+                        }
+                    }
+                    if (::strlen(lineBuff) == 0) {
+                        isHeader = false;
+                        continue;
+                    }
+                    if (isHeader) {
+                        char *headerValue;
+                        char *headerName = strtok_r(lineBuff, ":", &headerValue);
+                        if (headerName != nullptr && headerValue != nullptr) {
+                            string name(headerName);
+                            string value(headerValue);
+                            headers[name] = value;
+                        }
+                    } else {
+                        body << lineBuff << endl;
+                    }
+                }
 
+                RequestData requestData(url, method, headers, body.str());
+                cout << "[HTTPServer] Received " << method << " Request URL = " << url << endl;
             } else {
                 conf::errorPage.applyReplacements(400, {
                     Replacement("errorCode", "400"),
@@ -61,7 +110,7 @@ namespace xc {
                 return;
             }
 
-            ResponseData(200, string(requestBuff)).writeTo(clWrite);
+            TextResponseData(200, string(requestBuff)).writeTo(clWrite);
             cleanUpAndDestroy();
             return;
         }

+ 78 - 0
utils/BinaryResponseData.cpp

@@ -0,0 +1,78 @@
+//
+// Created by xcbosa on 2023/1/28.
+//
+
+#include "BinaryResponseData.h"
+#include "../webuiconf.h"
+
+namespace xc {
+    namespace utils {
+        BinaryResponseData::BinaryResponseData(int statusCode, uint8_t *body, int bodySize, string contentType): headers() {
+            this->statusCode = statusCode;
+            this->body = body;
+            this->bodySize = bodySize;
+            this->headers["Server"] = "XCHttpServer";
+            this->headers["Transfer-Encoding"] = "chunked";
+            this->headers["Content-Type"] = contentType;
+            this->filePath = nullptr;
+        }
+
+        BinaryResponseData::BinaryResponseData(int statusCode, char *filePath, string contentType): headers() {
+            this->statusCode = statusCode;
+            this->body = nullptr;
+            this->bodySize = 0;
+            this->headers["Server"] = "XCHttpServer";
+            this->headers["Transfer-Encoding"] = "chunked";
+            this->headers["Content-Type"] = contentType;
+            this->filePath = filePath;
+        }
+
+        void BinaryResponseData::setHeader(string headerName, string value) {
+            this->headers[headerName] = value;
+        }
+
+        void BinaryResponseData::writeTo(::FILE *fp) const {
+            ::fprintf(fp, "HTTP/1.0 %d FRPCWebUI\r\n", this->statusCode);
+            for (auto item : this->headers) {
+                ::fprintf(fp, "%s: %s\r\n", item.first.c_str(), item.second.c_str());
+            }
+            ::fputs("\r\n", fp);
+            int mtu = conf::mtu;
+            if (this->isWriteFromMemory()) {
+                int writeTimes = this->bodySize / mtu;
+                if (this->bodySize % mtu) {
+                    writeTimes++;
+                }
+                ::uint8_t *cursor = this->body;
+                ::uint8_t *endNextCursor = this->body + this->bodySize;
+                for (int i = 0; i < writeTimes; i++) {
+                    long writeSize = min((long)mtu, endNextCursor - cursor);
+                    ::fwrite(cursor, writeSize, mtu, fp);
+                    cursor += writeSize;
+                }
+            }
+            if (this->isWriteFromFile()) {
+                string filePath = this->filePath;
+                ::FILE *inputFile = ::fopen(filePath.c_str(), "rb");
+                if (inputFile) {
+                    ::uint8_t buff[mtu];
+                    long readPerPack = 0;
+                    while ((readPerPack = ::fread(buff, 1, mtu, inputFile)) == mtu) {
+                        ::fwrite(buff, 1, readPerPack, fp);
+                    }
+                } else {
+                    cerr << "[FileIOError]: " << ::strerror(errno) << endl;
+                }
+            }
+            ::fflush(fp);
+        }
+
+        bool BinaryResponseData::isWriteFromFile() const {
+            return this->filePath != nullptr;
+        }
+
+        bool BinaryResponseData::isWriteFromMemory() const {
+            return this->bodySize > 0;
+        }
+    } // xc
+} // utils

+ 29 - 0
utils/BinaryResponseData.h

@@ -0,0 +1,29 @@
+//
+// Created by xcbosa on 2023/1/28.
+//
+
+#pragma once
+
+#include "utils-private.h"
+
+namespace xc {
+    namespace utils {
+
+        class BinaryResponseData: public ResponseData {
+        public:
+            BinaryResponseData(int statusCode, uint8_t *body, int bodySize, string contentType);
+            BinaryResponseData(int statusCode, char *filePath, string contentType);
+            void writeTo(::FILE *fp) const;
+            void setHeader(string headerName, string value);
+            bool isWriteFromFile() const;
+            bool isWriteFromMemory() const;
+        private:
+            int statusCode;
+            map<string, string> headers;
+            uint8_t *body;
+            char *filePath;
+            int bodySize;
+        };
+
+    } // xc
+} // utils

+ 5 - 0
utils/FileReader.cpp

@@ -3,12 +3,17 @@
 //
 
 #include "utils-private.h"
+#include "../webuiconf.h"
 
 using namespace std;
 
 namespace xc::utils {
     string contentsOfTextFile(string filePath) {
         ifstream fin(filePath);
+        if (fin.fail()) {
+            cerr << "[FileIOError]: " << ::strerror(errno) << endl;
+            return "404";
+        }
         stringstream buffer;
         buffer << fin.rdbuf();
         string str(buffer.str());

+ 2 - 2
utils/FileResponseData.cpp

@@ -14,13 +14,13 @@ namespace xc {
         }
 
         FileResponseData::FileResponseData(int statusCode, string filePath, string contentType):
-                ResponseData(statusCode, "", contentType),
+                TextResponseData(statusCode, "", contentType),
                 filePath() {
             this->setFilePath(filePath);
         }
 
         FileResponseData::FileResponseData(int statusCode, string filePath, string contentType, vector<Replacement> replacements):
-                ResponseData(statusCode, "", contentType) {
+                TextResponseData(statusCode, "", contentType) {
             this->setFilePath(filePath, replacements);
         }
 

+ 2 - 2
utils/FileResponseData.h

@@ -5,7 +5,7 @@
 #pragma once
 
 #include "utils-private.h"
-#include "ResponseData.h"
+#include "TextResponseData.h"
 
 using namespace std;
 
@@ -19,7 +19,7 @@ namespace xc {
             Replacement(string replace, string with);
         };
 
-        class FileResponseData: public ResponseData {
+        class FileResponseData: public TextResponseData {
         public:
             FileResponseData(int statusCode, string filePath, string contentType);
             FileResponseData(int statusCode, string filePath, string contentType, vector<Replacement> replacements);

+ 32 - 0
utils/RequestData.cpp

@@ -0,0 +1,32 @@
+//
+// Created by xcbosa on 2023/1/28.
+//
+
+#include "RequestData.h"
+
+namespace xc {
+    namespace utils {
+        RequestData::RequestData(string url, string method, map<string, string> headers, string body) {
+            this->url = url;
+            this->method = method;
+            this->headers = headers;
+            this->body = body;
+        }
+
+        string RequestData::getURL() const {
+            return this->url;
+        }
+
+        string RequestData::getMethod() const {
+            return this->method;
+        }
+
+        string RequestData::getHeader(string name) {
+            return this->headers[name];
+        }
+
+        string RequestData::getBody() const {
+            return this->body;
+        }
+    } // xc
+} // utils

+ 27 - 0
utils/RequestData.h

@@ -0,0 +1,27 @@
+//
+// Created by xcbosa on 2023/1/28.
+//
+
+#pragma once
+
+#include "utils-private.h"
+
+namespace xc {
+    namespace utils {
+
+        class RequestData {
+        public:
+            RequestData(string url, string method, map<string, string> headers, string body);
+            string getURL() const;
+            string getMethod() const;
+            string getHeader(string name);
+            string getBody() const;
+        private:
+            string url;
+            string method;
+            map<string, string> headers;
+            string body;
+        };
+
+    } // xc
+} // utils

+ 1 - 17
utils/ResponseData.h

@@ -6,28 +6,12 @@
 
 #include "utils-private.h"
 
-using namespace std;
-
 namespace xc {
     namespace utils {
 
         class ResponseData {
         public:
-            ResponseData(int statusCode, string body);
-            ResponseData(int statusCode, string body, string contentType);
-            void setHeader(string headerName, string value);
-            void removeHeader(string headerName);
-            string getHeader(string headerName);
-            void setContentType(string mimeType);
-            void setStatusCode(int statusCode);
-            int getStatusCode() const;
-            void setBody(string body);
-            string getBody();
-            void writeTo(::FILE *fp) const;
-        private:
-            int statusCode;
-            map<string, string> headers;
-            string body;
+            virtual void writeTo(::FILE *fp) const = 0;
         };
 
     } // xc

+ 13 - 13
utils/ResponseData.cpp → utils/TextResponseData.cpp

@@ -2,13 +2,13 @@
 // Created by xcbosa on 2023/1/28.
 //
 
-#include "ResponseData.h"
+#include "TextResponseData.h"
 
 using namespace std;
 
 namespace xc {
     namespace utils {
-        ResponseData::ResponseData(int statusCode, string body): headers() {
+        TextResponseData::TextResponseData(int statusCode, string body): headers() {
             this->statusCode = statusCode;
             this->body = body;
             this->headers["Server"] = "XCHttpServer";
@@ -16,44 +16,44 @@ namespace xc {
             this->headers["Content-Type"] = "text/html";
         }
 
-        ResponseData::ResponseData(int statusCode, string body, string contentType):
-                ResponseData(statusCode, body) {
+        TextResponseData::TextResponseData(int statusCode, string body, string contentType):
+                TextResponseData(statusCode, body) {
             this->headers["Content-Type"] = contentType;
         }
 
-        void ResponseData::setHeader(string headerName, string value) {
+        void TextResponseData::setHeader(string headerName, string value) {
             this->headers[headerName] = value;
         }
 
-        void ResponseData::removeHeader(string headerName) {
+        void TextResponseData::removeHeader(string headerName) {
             this->headers[headerName] = nullptr;
         }
 
-        string ResponseData::getHeader(string headerName) {
+        string TextResponseData::getHeader(string headerName) {
             return this->headers[headerName];
         }
 
-        void ResponseData::setStatusCode(int statusCode) {
+        void TextResponseData::setStatusCode(int statusCode) {
             this->statusCode = statusCode;
         }
 
-        void ResponseData::setContentType(string mimeType) {
+        void TextResponseData::setContentType(string mimeType) {
             this->setHeader("Content-Type", mimeType);
         }
 
-        int ResponseData::getStatusCode() const {
+        int TextResponseData::getStatusCode() const {
             return this->statusCode;
         }
 
-        void ResponseData::setBody(string body) {
+        void TextResponseData::setBody(string body) {
             this->body = body;
         }
 
-        string ResponseData::getBody() {
+        string TextResponseData::getBody() {
             return this->body;
         }
 
-        void ResponseData::writeTo(::FILE *fp) const {
+        void TextResponseData::writeTo(::FILE *fp) const {
             ::fprintf(fp, "HTTP/1.0 %d FRPCWebUI\r\n", this->statusCode);
             for (auto item : this->headers) {
                 ::fprintf(fp, "%s: %s\r\n", item.first.c_str(), item.second.c_str());

+ 35 - 0
utils/TextResponseData.h

@@ -0,0 +1,35 @@
+//
+// Created by xcbosa on 2023/1/28.
+//
+
+#pragma once
+
+#include "ResponseData.h"
+#include "utils-private.h"
+
+using namespace std;
+
+namespace xc {
+    namespace utils {
+
+        class TextResponseData: public ResponseData {
+        public:
+            TextResponseData(int statusCode, string body);
+            TextResponseData(int statusCode, string body, string contentType);
+            void setHeader(string headerName, string value);
+            void removeHeader(string headerName);
+            string getHeader(string headerName);
+            void setContentType(string mimeType);
+            void setStatusCode(int statusCode);
+            int getStatusCode() const;
+            void setBody(string body);
+            string getBody();
+            void writeTo(::FILE *fp) const;
+        private:
+            int statusCode;
+            map<string, string> headers;
+            string body;
+        };
+
+    } // xc
+} // utils

+ 3 - 0
utils/utils.h

@@ -4,8 +4,11 @@
 
 #pragma once
 
+#include "RequestData.h"
 #include "ResponseData.h"
+#include "TextResponseData.h"
 #include "FileResponseData.h"
+#include "BinaryResponseData.h"
 
 using namespace std;
 

+ 3 - 0
webuiconf.h

@@ -11,6 +11,9 @@ using namespace std;
 using namespace xc::utils;
 
 namespace xc::conf {
+    const int clientSocketTimeoutSeconds = 3;
+    const int mtu = 1536;
+
     const IncompleteFileResponseData errorPage(FileResponseData(500, "html/error.html", "text/html"));
 
     const auto errorPage400 = errorPage.applyReplacements(400, {