├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── README.md ├── example ├── CMakeLists.txt └── threadpoolserver.cpp ├── include ├── http_req_info.h ├── irequest_handler.h ├── net_util.h └── thread_pool_server.h └── test ├── CMakeLists.txt ├── servertest ├── curl.res ├── load_test.py ├── run_curl.sh └── urls ├── testdata ├── get.urls └── post.data └── unittest ├── CMakeLists.txt ├── gtest_main.cc ├── http_req_info_ut.cc └── thread_pool_server_ut.cc /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml 3 | name: Test 4 | 5 | on: 6 | push: 7 | pull_request: 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | submodules: recursive 24 | 25 | 26 | - name: Configure CMake 27 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 28 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 29 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 30 | 31 | - name: Build 32 | # Build your program with the given configuration 33 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 34 | 35 | - name: Test 36 | working-directory: ${{github.workspace}}/build 37 | # Execute tests defined by the CMake configuration. 38 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 39 | run: ctest -C ${{env.BUILD_TYPE}} 40 | 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *tmp* 2 | tags 3 | *swp 4 | *.out 5 | *.o 6 | *.d 7 | *.ut 8 | log 9 | *.log 10 | *.pyc 11 | *.a 12 | *.demo 13 | *.pid 14 | build 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/limonp"] 2 | path = submodules/limonp 3 | url = https://github.com/yanyiwu/limonp.git 4 | [submodule "test/unittest/googletest"] 5 | path = test/unittest/googletest 6 | url = https://github.com/google/googletest.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | before_install: 3 | - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get install cmake; fi 4 | - if [ $TRAVIS_OS_NAME == osx ]; then brew install cmake; fi 5 | script: 6 | - mkdir build 7 | - cd build 8 | - cmake .. 9 | - make 10 | - ./test/test.run 11 | os: 12 | - linux 13 | - osx 14 | compiler: 15 | - clang 16 | - gcc 17 | notifications: 18 | recipients: 19 | - i@yanyiwu.com 20 | email: 21 | on_success: change 22 | on_failure: always 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## v0.4.0 4 | 5 | + [googletest] git submodule add googletest-1.6.0 6 | 7 | ## v0.3.0 8 | 9 | + Added [github/actions] for CI/CD 10 | + [submodules] using `git submodule` to manage deps/limonp for latest version 11 | + Merged [pr#2] 12 | + Merged [pr#3] 13 | 14 | ## v0.2.2 15 | 16 | + Upgrade [limonp] to v0.5.1 17 | 18 | ## v0.2.1 19 | 20 | + Fix error in Travis-Ci 21 | 22 | ## v0.2.0 23 | 24 | + Fix bug: server is waiting for body even if http header contain `content-length: 0` 25 | + Update Code Style 26 | + Update [limonp] to v0.4.0 27 | 28 | ## v0.1.0 29 | 30 | + Finished as s Simple C++ HTTP Server Framework . 31 | 32 | [limonp]:https://github.com/yanyiwu/limonp 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | PROJECT(HUSKY) 2 | 3 | SET(CMAKE_CXX_FLAGS "-std=c++11") 4 | 5 | CMAKE_MINIMUM_REQUIRED(VERSION 2.6) 6 | 7 | ADD_DEFINITIONS(-O3 -Wall -g) 8 | 9 | INCLUDE_DIRECTORIES( 10 | ${PROJECT_SOURCE_DIR}/submodules/limonp/include 11 | ${PROJECT_SOURCE_DIR}/include 12 | ) 13 | 14 | ADD_SUBDIRECTORY(example) 15 | ADD_SUBDIRECTORY(test) 16 | 17 | ENABLE_TESTING() 18 | ADD_TEST(NAME test COMMAND test.run) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Test](https://github.com/yanyiwu/husky/actions/workflows/test.yml/badge.svg)](https://github.com/yanyiwu/husky/actions/workflows/test.yml) 2 | - - - 3 | 4 | # husky 5 | 6 | ## Introduction 7 | 8 | husky is a **Simple** HTTP Server Frame Based on ThreadPool. 9 | 10 | It is just a simple server, not forcussing on performance, but it is **very easy** to use. That is what it born to be. 11 | 12 | ## Feature 13 | 14 | + Only Headers file: what you need to do is `include` it. 15 | + No dependence: **No dependence, No hurts**. 16 | 17 | ## Example 18 | 19 | ### configure & compile 20 | 21 | ``` 22 | git clone --recurse-submodules https://github.com/yanyiwu/husky.git 23 | cd husky 24 | mkdir build 25 | cd build 26 | cmake .. 27 | make 28 | ``` 29 | 30 | ### start server 31 | 32 | ``` 33 | ./threadpoolserver --port 11257 34 | ``` 35 | 36 | ### GET Request Example 37 | 38 | ``` 39 | curl "http://127.0.0.1:11257/?hello=world&myname=yanyiwu" 40 | ``` 41 | 42 | ### POST Request Example 43 | ``` 44 | curl -d "hello world, my name is yanyiwu." "http://127.0.0.1:11257" 45 | ``` 46 | 47 | 48 | ### husky.demo 49 | 50 | Its source code is `test/demo.cpp`. Its code has only 30+ lines. 51 | 52 | ## Benchmark 53 | 54 | ``` 55 | go get github.com/yanyiwu/go_http_load 56 | ``` 57 | 58 | GET 59 | 60 | ``` 61 | go_http_load -method=GET -get_urls="../test/testdata/get.urls" -goroutines=1 -loop_count=5000 62 | ``` 63 | 64 | ``` 65 | The Number of Queries:10000 66 | The Time Consumed: 4.539 s 67 | Query Per Second: 2203.046 q/s 68 | ``` 69 | 70 | POST 71 | 72 | ``` 73 | go_http_load -method=POST -post_url="http://127.0.0.1:11257" -post_data_file="../test/testdata/post.data" -goroutines=1 -loop_count=5000 74 | ``` 75 | 76 | ``` 77 | The Number of Queries:5000 78 | The Time Consumed: 2.448 s 79 | Query Per Second: 2042.887 q/s 80 | ``` 81 | 82 | ## Reference 83 | 84 | [limonp] 85 | 86 | [limonp]:https://github.com/yanyiwu/limonp.git 87 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}) 2 | 3 | ADD_EXECUTABLE(threadpoolserver threadpoolserver.cpp) 4 | TARGET_LINK_LIBRARIES(threadpoolserver pthread) 5 | -------------------------------------------------------------------------------- /example/threadpoolserver.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_pool_server.h" 2 | 3 | using namespace husky; 4 | 5 | class ReqHandler: public IRequestHandler { 6 | public: 7 | virtual ~ReqHandler() { 8 | } 9 | public: 10 | virtual bool DoGET(const HttpReqInfo& httpReq, string& response) { 11 | const unordered_map& mp = httpReq.GetMethodGetMap(); 12 | string mpStr; 13 | mpStr << mp; 14 | response = StringFormat("{method:GET, arguments:%s}", mpStr.c_str()); 15 | return true; 16 | } 17 | virtual bool DoPOST(const HttpReqInfo& httpReq, string& response) { 18 | response = StringFormat("{body:%s}", httpReq.GetBody().c_str()); 19 | return true; 20 | } 21 | }; 22 | 23 | int main(int argc, char** argv) { 24 | if(argc < 3) { 25 | printf("usage: %s --port 11257 \n", argv[0]); 26 | return EXIT_FAILURE; 27 | } 28 | size_t threadNumber = 4; 29 | int port = atoi(argv[2]); 30 | ReqHandler reqHandler; 31 | ThreadPoolServer server(threadNumber, port, reqHandler); 32 | server.Start(); 33 | return EXIT_SUCCESS; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /include/http_req_info.h: -------------------------------------------------------------------------------- 1 | #ifndef HUSKY_HTTP_REQINFO_H 2 | #define HUSKY_HTTP_REQINFO_H 3 | 4 | #include 5 | #include 6 | #include "limonp/Logging.hpp" 7 | #include "limonp/StringUtil.hpp" 8 | 9 | namespace husky { 10 | using namespace limonp; 11 | using namespace std; 12 | 13 | static const char* const KEY_METHOD = "METHOD"; 14 | static const char* const KEY_URI = "URI"; 15 | static const char* const KEY_PROTOCOL = "PROTOCOL"; 16 | 17 | typedef unsigned char BYTE; 18 | 19 | inline BYTE ToHex(BYTE x) { 20 | return x > 9 ? x -10 + 'A': x + '0'; 21 | } 22 | 23 | inline BYTE FromHex(BYTE x) { 24 | return isdigit(x) ? x-'0' : x-'A'+10; 25 | } 26 | 27 | inline void URLEncode(const string &sIn, string& sOut) { 28 | for( size_t ix = 0; ix < sIn.size(); ix++ ) { 29 | BYTE buf[4]; 30 | memset( buf, 0, 4 ); 31 | if( isalnum( (BYTE)sIn[ix] ) ) { 32 | buf[0] = sIn[ix]; 33 | } else { 34 | buf[0] = '%'; 35 | buf[1] = ToHex( (BYTE)sIn[ix] >> 4 ); 36 | buf[2] = ToHex( (BYTE)sIn[ix] % 16); 37 | } 38 | sOut += (char *)buf; 39 | } 40 | }; 41 | 42 | inline void URLDecode(const string &sIn, string& sOut) { 43 | for( size_t ix = 0; ix < sIn.size(); ix++ ) { 44 | BYTE ch = 0; 45 | if(sIn[ix]=='%') { 46 | ch = (FromHex(sIn[ix+1])<<4); 47 | ch |= FromHex(sIn[ix+2]); 48 | ix += 2; 49 | } else if(sIn[ix] == '+') { 50 | ch = ' '; 51 | } else { 52 | ch = sIn[ix]; 53 | } 54 | sOut += (char)ch; 55 | } 56 | } 57 | 58 | class HttpReqInfo { 59 | public: 60 | HttpReqInfo() { 61 | is_header_finished_ = false; 62 | is_body_finished_ = false; 63 | content_length_ = 0; 64 | } 65 | 66 | bool ParseHeader(const string& buffer) { 67 | return ParseHeader(buffer.c_str(), buffer.size()); 68 | } 69 | bool ParseHeader(const char* buffer, size_t len) { 70 | string headerStr(buffer, len); 71 | size_t lpos = 0, rpos = 0; 72 | vector buf; 73 | rpos = headerStr.find("\n", lpos); 74 | if(string::npos == rpos) { 75 | XLOG(ERROR) << "headerStr[" << headerStr << "] illegal."; 76 | return false; 77 | } 78 | string firstline(headerStr, lpos, rpos - lpos); 79 | Trim(firstline); 80 | Split(firstline, buf, " "); 81 | if (3 != buf.size()) { 82 | XLOG(ERROR) << "parse header firstline [" << firstline << "] failed."; 83 | return false; 84 | } 85 | header_map_[KEY_METHOD] = Trim(buf[0]); 86 | header_map_[KEY_URI] = Trim(buf[1]); 87 | header_map_[KEY_PROTOCOL] = Trim(buf[2]); 88 | ParseUri(header_map_[KEY_URI], path_, method_get_map_); 89 | 90 | lpos = rpos + 1; 91 | if(lpos >= headerStr.size()) { 92 | XLOG(ERROR) << "headerStr[" << headerStr << "] illegal."; 93 | return false; 94 | } 95 | //message header begin 96 | while(lpos < headerStr.size() && string::npos != (rpos = headerStr.find('\n', lpos)) && rpos > lpos) { 97 | string s(headerStr, lpos, rpos - lpos); 98 | size_t p = s.find(':'); 99 | if(string::npos == p) { 100 | break;//encounter empty line 101 | } 102 | string k(s, 0, p); 103 | string v(s, p+1); 104 | Trim(k); 105 | Trim(v); 106 | if(k.empty()||v.empty()) { 107 | XLOG(ERROR) << "headerStr[" << headerStr << "] illegal."; 108 | return false; 109 | } 110 | Upper(k); 111 | header_map_[k] = v; 112 | lpos = rpos + 1; 113 | } 114 | rpos ++; 115 | is_header_finished_ = true; 116 | string content_length; 117 | if(!Find("CONTENT-LENGTH", content_length) || 0 == (content_length_ = atoi(content_length.c_str()))) { 118 | is_body_finished_ = true; 119 | return true; 120 | } 121 | content_length_ = atoi(content_length.c_str()); 122 | if(rpos < headerStr.size()) { 123 | AppendBody(headerStr.c_str() + rpos, headerStr.size() - rpos); 124 | } 125 | return true; 126 | //message header end 127 | } 128 | void AppendBody(const char* buffer, size_t len) { 129 | if(is_body_finished_) { 130 | return; 131 | } 132 | body_.append(buffer, len); 133 | if(body_.size() >= content_length_) { 134 | is_body_finished_ = true; 135 | } else { 136 | is_body_finished_ = false; 137 | } 138 | } 139 | bool IsHeaderFinished() const { 140 | return is_header_finished_; 141 | } 142 | bool IsBodyFinished() const { 143 | return is_body_finished_; 144 | } 145 | 146 | const string& Set(const string& key, const string& value) { 147 | return header_map_[key] = value; 148 | } 149 | bool Find(const string& key, string& res)const { 150 | return Find(header_map_, key, res); 151 | } 152 | bool GET(const string& argKey, string& res)const { 153 | string tmp; 154 | if (!Find(method_get_map_, argKey, tmp)) { 155 | return false; 156 | } 157 | URLDecode(tmp, res); 158 | return true; 159 | } 160 | bool GET(const string& argKey, int& res) const { 161 | string tmp; 162 | if (!GET(argKey, tmp)) { 163 | return false; 164 | } 165 | res = atoi(tmp.c_str()); 166 | return true; 167 | } 168 | bool GET(const string& argKey, size_t& res) const { 169 | int tmp = 0; 170 | if (!GET(argKey, tmp) || tmp < 0) { 171 | return false; 172 | } 173 | res = tmp; 174 | return true; 175 | } 176 | 177 | bool IsGET() const { 178 | string str; 179 | if(!Find(header_map_, KEY_METHOD, str)) { 180 | return false; 181 | } 182 | return str == "GET"; 183 | } 184 | bool IsPOST() const { 185 | string str; 186 | if(!Find(header_map_, KEY_METHOD, str)) { 187 | return false; 188 | } 189 | return str == "POST"; 190 | } 191 | const unordered_map & GetMethodGetMap() const { 192 | return method_get_map_; 193 | } 194 | const unordered_map & GetHeaders() const { 195 | return header_map_; 196 | } 197 | const string& GetBody() const { 198 | return body_; 199 | } 200 | const string& GetPath() const { 201 | return path_; 202 | } 203 | 204 | private: 205 | bool is_header_finished_; 206 | bool is_body_finished_; 207 | size_t content_length_; 208 | unordered_map header_map_; 209 | unordered_map method_get_map_; 210 | string path_; 211 | string body_; 212 | friend ostream& operator<<(ostream& os, const HttpReqInfo& obj); 213 | 214 | bool Find(const std::unordered_map& mp, const string& key, string& res)const { 215 | std::unordered_map::const_iterator it = mp.find(key); 216 | if(it == mp.end()) { 217 | return false; 218 | } 219 | res = it->second; 220 | return true; 221 | } 222 | 223 | void ParseUri(const string& uri, string& path, std::unordered_map& mp) { 224 | if(uri.empty()) { 225 | return; 226 | } 227 | 228 | size_t pos = uri.find('?'); 229 | path = uri.substr(0, pos); 230 | if(string::npos == pos) { 231 | return ; 232 | } 233 | size_t kleft = 0, kright = 0; 234 | size_t vleft = 0, vright = 0; 235 | for(size_t i = pos + 1; i < uri.size();) { 236 | kleft = i; 237 | while(i < uri.size() && uri[i] != '=') { 238 | i++; 239 | } 240 | if(i >= uri.size()) { 241 | break; 242 | } 243 | kright = i; 244 | i++; 245 | vleft = i; 246 | while(i < uri.size() && uri[i] != '&' && uri[i] != ' ') { 247 | i++; 248 | } 249 | vright = i; 250 | mp[uri.substr(kleft, kright - kleft)] = uri.substr(vleft, vright - vleft); 251 | i++; 252 | } 253 | 254 | return; 255 | } 256 | }; 257 | 258 | inline std::ostream& operator << (std::ostream& os, const husky::HttpReqInfo& obj) { 259 | return os << obj.header_map_ << obj.method_get_map_/* << obj._methodPostMap*/ << obj.path_ << obj.body_ ; 260 | } 261 | 262 | } 263 | 264 | #endif 265 | -------------------------------------------------------------------------------- /include/irequest_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef HUSKY_IREQUESTHANDLER_HPP 2 | #define HUSKY_IREQUESTHANDLER_HPP 3 | 4 | #include "http_req_info.h" 5 | 6 | namespace husky { 7 | class IRequestHandler { 8 | public: 9 | virtual ~IRequestHandler() { 10 | } 11 | 12 | virtual bool DoGET(const HttpReqInfo& httpReq, string& res) = 0; 13 | virtual bool DoPOST(const HttpReqInfo& httpReq, string& res) = 0; 14 | }; 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /include/net_util.h: -------------------------------------------------------------------------------- 1 | #ifndef HUSKY_NET_UTILS_HPP 2 | #define HUSKY_NET_UTILS_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "limonp/StdExtension.hpp" 19 | #include "limonp/Logging.hpp" 20 | 21 | namespace husky { 22 | static const size_t LISTEN_QUEUE_LEN = 1024; 23 | 24 | typedef int SocketFd; 25 | inline SocketFd CreateAndListenSocket(int port) { 26 | SocketFd sock = socket(AF_INET, SOCK_STREAM, 0); 27 | XCHECK(sock != -1); 28 | 29 | int optval = 1; // nozero 30 | XCHECK(-1 != setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))); 31 | 32 | struct sockaddr_in addr; 33 | addr.sin_family = AF_INET; 34 | addr.sin_port = htons(port); 35 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 36 | XCHECK(-1 != ::bind(sock, (sockaddr*)&addr, sizeof(addr))); 37 | XCHECK(-1 != ::listen(sock, LISTEN_QUEUE_LEN)); 38 | 39 | return sock; 40 | } 41 | 42 | const char* const HTTP_FORMAT = "HTTP/1.1 200 OK\r\nConnection: close\r\nServer: HuskyServer/1.0.0\r\nContent-Type: text/json; charset=%s\r\nContent-Length: %d\r\n\r\n%s"; 43 | const char* const CHARSET_UTF8 = "UTF-8"; 44 | } // namespace husky 45 | 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /include/thread_pool_server.h: -------------------------------------------------------------------------------- 1 | #ifndef HUSKY_THREADPOOLSERVER_H 2 | #define HUSKY_THREADPOOLSERVER_H 3 | 4 | #include "net_util.h" 5 | #include "irequest_handler.h" 6 | #include "limonp/ThreadPool.hpp" 7 | 8 | namespace husky { 9 | using namespace limonp; 10 | 11 | const char* const CLIENT_IP_K = "CLIENT_IP"; 12 | const size_t RECV_BUFFER_SIZE = 16 * 1024; 13 | 14 | const struct linger LNG = {1, 1}; 15 | const struct timeval SOCKET_TIMEOUT = {16, 0}; 16 | 17 | 18 | class ThreadPoolServer { 19 | public: 20 | ThreadPoolServer(size_t thread_number, size_t port, IRequestHandler & handler): 21 | pool_(thread_number), req_handler_(handler), host_socket_(-1) { 22 | host_socket_ = CreateAndListenSocket(port); 23 | } 24 | ~ThreadPoolServer() {}; 25 | 26 | bool Start() { 27 | pool_.Start(); 28 | sockaddr_in clientaddr; 29 | socklen_t nSize = sizeof(clientaddr); 30 | int clientSock; 31 | 32 | while(true) { 33 | if(-1 == (clientSock = accept(host_socket_, (struct sockaddr*) &clientaddr, &nSize))) { 34 | XLOG(ERROR) << strerror(errno); 35 | break; 36 | } 37 | pool_.Add(NewClosure(this, &ThreadPoolServer::Run, clientSock)); 38 | //pool_.Add(CreateTask(clientSock, req_handler_)); 39 | } 40 | return true; 41 | } 42 | 43 | private: 44 | void Run(int sockfd) { 45 | do { 46 | if(!SetSockopt(sockfd)) { 47 | XLOG(ERROR) << "_getsockopt failed."; 48 | break; 49 | } 50 | string strSnd, strRetByHandler; 51 | HttpReqInfo httpReq; 52 | if(!Receive(sockfd, httpReq)) { 53 | XLOG(ERROR) << "Receive failed."; 54 | break; 55 | } 56 | 57 | if(httpReq.IsGET() && !req_handler_.DoGET(httpReq, strRetByHandler)) { 58 | XLOG(ERROR) << "DoGET failed."; 59 | break; 60 | } 61 | if(httpReq.IsPOST() && !req_handler_.DoPOST(httpReq, strRetByHandler)) { 62 | XLOG(ERROR) << "DoPOST failed."; 63 | break; 64 | } 65 | strSnd = StringFormat(HTTP_FORMAT, CHARSET_UTF8, strRetByHandler.length(), strRetByHandler.c_str()); 66 | 67 | if(!Send(sockfd, strSnd)) { 68 | XLOG(ERROR) << "Send failed."; 69 | break; 70 | } 71 | } while(false); 72 | 73 | 74 | if(-1 == close(sockfd)) { 75 | XLOG(ERROR) << strerror(errno); 76 | } 77 | } 78 | bool Receive(int sockfd, HttpReqInfo& httpInfo) const { 79 | char recvBuf[RECV_BUFFER_SIZE]; 80 | int n = 0; 81 | while(!httpInfo.IsBodyFinished() && (n = recv(sockfd, recvBuf, RECV_BUFFER_SIZE, 0)) > 0) { 82 | if(!httpInfo.IsHeaderFinished()) { 83 | if(!httpInfo.ParseHeader(recvBuf, n)) { 84 | XLOG(ERROR) << "ParseHeader failed. "; 85 | return false; 86 | } 87 | continue; 88 | } 89 | httpInfo.AppendBody(recvBuf, n); 90 | } 91 | if(n < 0) { 92 | XLOG(ERROR) << strerror(errno); 93 | return false; 94 | } 95 | return true; 96 | } 97 | bool Send(int sockfd, const string& strSnd) const { 98 | if(-1 == send(sockfd, strSnd.c_str(), strSnd.length(), 0)) { 99 | XLOG(ERROR) << strerror(errno); 100 | return false; 101 | } 102 | return true; 103 | } 104 | bool SetSockopt(int sockfd) const { 105 | if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (const char*)&LNG, sizeof(LNG))) { 106 | XLOG(ERROR) << strerror(errno); 107 | return false; 108 | } 109 | if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&SOCKET_TIMEOUT, sizeof(SOCKET_TIMEOUT))) { 110 | XLOG(ERROR) << strerror(errno); 111 | return false; 112 | } 113 | if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&SOCKET_TIMEOUT, sizeof(SOCKET_TIMEOUT))) { 114 | XLOG(ERROR) << strerror(errno); 115 | return false; 116 | } 117 | return true; 118 | } 119 | 120 | ThreadPool pool_; 121 | IRequestHandler & req_handler_; 122 | int host_socket_; 123 | }; // class ThreadPoolServer 124 | } // namespace husky 125 | 126 | #endif 127 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}) 2 | 3 | ADD_SUBDIRECTORY(unittest) 4 | -------------------------------------------------------------------------------- /test/servertest/curl.res: -------------------------------------------------------------------------------- 1 | {ACCEPT:*/*, HOST:127.0.0.1:11257, CLIENT_IP:127.0.0.1, PATH:/, PROTOCOL:HTTP/1.1, METHOD:GET, USER-AGENT:curl/7.22.0 (i686-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3}{}{} -------------------------------------------------------------------------------- /test/servertest/load_test.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import time 3 | import urllib2 4 | import threading 5 | from Queue import Queue 6 | from time import sleep 7 | import sys 8 | 9 | # 性能测试页面 10 | #PERF_TEST_URL = "http://10.2.66.38/?yyid=-1&suv=1309231700203264&callback=xxxxx" 11 | URLS = [line for line in open("urls", "r")] 12 | 13 | # 配置:压力测试 14 | THREAD_NUM = 10 # 并发线程总数 15 | ONE_WORKER_NUM = 500 # 每个线程的循环次数 16 | LOOP_SLEEP = 0.01 # 每次请求时间间隔(秒) 17 | 18 | # 配置:模拟运行状态 19 | #THREAD_NUM = 10 # 并发线程总数 20 | #ONE_WORKER_NUM = 10 # 每个线程的循环次数 21 | #LOOP_SLEEP = 0 # 每次请求时间间隔(秒) 22 | 23 | 24 | # 出错数 25 | ERROR_NUM = 0 26 | 27 | 28 | #具体的处理函数,负责处理单个任务 29 | def doWork(index, url): 30 | t = threading.currentThread() 31 | #print "["+t.name+" "+str(index)+"] "+PERF_TEST_URL 32 | 33 | try: 34 | html = urllib2.urlopen(url).read() 35 | except urllib2.URLError, e: 36 | print "["+t.name+" "+str(index)+"] " 37 | print e 38 | global ERROR_NUM 39 | ERROR_NUM += 1 40 | 41 | 42 | #这个是工作进程,负责不断从队列取数据并处理 43 | def working(): 44 | t = threading.currentThread() 45 | print "["+t.name+"] Sub Thread Begin" 46 | 47 | i = 0 48 | while i < ONE_WORKER_NUM: 49 | i += 1 50 | doWork(i, URLS[i % len(URLS)]) 51 | sleep(LOOP_SLEEP) 52 | 53 | print "["+t.name+"] Sub Thread End" 54 | 55 | 56 | def main(): 57 | #doWork(0) 58 | #return 59 | 60 | t1 = time.time() 61 | 62 | Threads = [] 63 | 64 | # 创建线程 65 | for i in range(THREAD_NUM): 66 | t = threading.Thread(target=working, name="T"+str(i)) 67 | t.setDaemon(True) 68 | Threads.append(t) 69 | 70 | for t in Threads: 71 | t.start() 72 | 73 | for t in Threads: 74 | t.join() 75 | 76 | print "main thread end" 77 | 78 | t2 = time.time() 79 | print "========================================" 80 | #print "URL:", PERF_TEST_URL 81 | print "任务数量:", THREAD_NUM, "*", ONE_WORKER_NUM, "=", THREAD_NUM*ONE_WORKER_NUM 82 | print "总耗时(秒):", t2-t1 83 | print "每次请求耗时(秒):", (t2-t1) / (THREAD_NUM*ONE_WORKER_NUM) 84 | print "每秒承载请求数:", 1 / ((t2-t1) / (THREAD_NUM*ONE_WORKER_NUM)) 85 | print "错误数量:", ERROR_NUM 86 | 87 | 88 | if __name__ == "__main__": 89 | main() 90 | 91 | -------------------------------------------------------------------------------- /test/servertest/run_curl.sh: -------------------------------------------------------------------------------- 1 | CURL_RES=curl.res 2 | TMP=curl.res.tmp 3 | curl -s "http://127.0.0.1:11257" >> $TMP 4 | if diff $TMP $CURL_RES >> /dev/null 5 | then 6 | echo "ok"; 7 | else 8 | echo "failed." 9 | fi 10 | 11 | rm $TMP 12 | -------------------------------------------------------------------------------- /test/servertest/urls: -------------------------------------------------------------------------------- 1 | http://127.0.0.1:11257 2 | -------------------------------------------------------------------------------- /test/testdata/get.urls: -------------------------------------------------------------------------------- 1 | http://127.0.0.1:11257/?hello=world&myname=yanyiwu 2 | http://127.0.0.1:11257/?hello=world&myname=yanyiwu 3 | -------------------------------------------------------------------------------- /test/testdata/post.data: -------------------------------------------------------------------------------- 1 | hello world, my name is yanyiwu. 2 | -------------------------------------------------------------------------------- /test/unittest/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/test) 2 | SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/test/lib) 3 | 4 | SET(GTEST_ROOT_DIR googletest) 5 | INCLUDE_DIRECTORIES(${GTEST_ROOT_DIR} ${GTEST_ROOT_DIR}/include) 6 | ADD_DEFINITIONS(-DGTEST_USE_OWN_TR1_TUPLE=1) 7 | ADD_LIBRARY(gtest STATIC ${GTEST_ROOT_DIR}/src/gtest-all.cc) 8 | TARGET_LINK_LIBRARIES(gtest pthread) 9 | ADD_EXECUTABLE(test.run gtest_main.cc http_req_info_ut.cc) 10 | ADD_EXECUTABLE(server.test gtest_main.cc thread_pool_server_ut.cc) 11 | TARGET_LINK_LIBRARIES(gtest pthread) 12 | TARGET_LINK_LIBRARIES(test.run gtest pthread) 13 | TARGET_LINK_LIBRARIES(server.test gtest pthread) 14 | 15 | -------------------------------------------------------------------------------- /test/unittest/gtest_main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2006, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | #include 31 | 32 | #include "gtest/gtest.h" 33 | 34 | GTEST_API_ int main(int argc, char **argv) { 35 | std::cout << "Running main() from gtest_main.cc\n"; 36 | 37 | testing::InitGoogleTest(&argc, argv); 38 | return RUN_ALL_TESTS(); 39 | } 40 | -------------------------------------------------------------------------------- /test/unittest/http_req_info_ut.cc: -------------------------------------------------------------------------------- 1 | #include "http_req_info.h" 2 | #include "gtest/gtest.h" 3 | 4 | using namespace husky; 5 | TEST(HttpReqInfoTest, Test1) { 6 | //string url("http://127.0.0.1/?k1=v1&k2=v2 hh"); 7 | //HashMap mp; 8 | //parseUrl(url, mp); 9 | //cout<