├── .gitignore ├── LICENSE ├── Makefile ├── build └── Makefile ├── code ├── buffer │ ├── buffer.cpp │ └── buffer.h ├── config │ └── config.h ├── http │ ├── httpconn.cpp │ ├── httpconn.h │ ├── httprequest.cpp │ ├── httprequest.h │ ├── httpresponse.cpp │ └── httpresponse.h ├── log │ ├── blockqueue.h │ ├── log.cpp │ └── log.h ├── main.cpp ├── pool │ ├── sqlconnRAII.h │ ├── sqlconnpool.cpp │ ├── sqlconnpool.h │ └── threadpool.h ├── server │ ├── epoller.cpp │ ├── epoller.h │ ├── webserver.cpp │ └── webserver.h └── timer │ ├── heaptimer.cpp │ └── heaptimer.h ├── readme.assest └── 压力测试.png ├── readme.md ├── resources ├── 400.html ├── 403.html ├── 404.html ├── 405.html ├── css │ ├── .DS_Store │ ├── animate.css │ ├── bootstrap.min.css │ ├── font-awesome.min.css │ ├── magnific-popup.css │ └── style.css ├── error.html ├── fonts │ ├── .DS_Store │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── images │ ├── .DS_Store │ ├── favicon.ico │ ├── instagram-image1.jpg │ ├── instagram-image2.jpg │ ├── instagram-image3.jpg │ ├── instagram-image4.jpg │ ├── instagram-image5.jpg │ └── profile-image.jpg ├── index.html ├── js │ ├── .DS_Store │ ├── bootstrap.min.js │ ├── custom.js │ ├── jquery.js │ ├── jquery.magnific-popup.min.js │ ├── magnific-popup-options.js │ ├── smoothscroll.js │ └── wow.min.js ├── login.html ├── picture.html ├── register.html ├── video.html ├── video │ └── xxx.mp4 └── welcome.html ├── test ├── Makefile ├── readme.md └── test.cpp └── webbench-1.5 ├── Makefile ├── socket.c └── webbench.c /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.* 3 | !*/ 4 | !*.cpp 5 | !*.h 6 | !LICENSE 7 | !makefile 8 | !Makefile 9 | 10 | *.o 11 | *.1 12 | *.log 13 | *.exe 14 | /.vscode 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | mkdir -p bin 3 | cd build && make 4 | -------------------------------------------------------------------------------- /build/Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CFLAGS = -std=c++14 -O2 -Wall -g 3 | 4 | TARGET = server 5 | OBJS = ../code/log/*.cpp ../code/pool/*.cpp ../code/timer/*.cpp \ 6 | ../code/http/*.cpp ../code/server/*.cpp \ 7 | ../code/buffer/*.cpp ../code/main.cpp 8 | 9 | all: $(OBJS) 10 | $(CXX) $(CFLAGS) $(OBJS) -o ../bin/$(TARGET) -pthread -lmysqlclient 11 | 12 | clean: 13 | rm -rf ../bin/$(OBJS) $(TARGET) 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /code/buffer/buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | Buffer::Buffer(int initBuffSize) : buffer_(initBuffSize), readPos_(0), writePos_(0) {} 4 | 5 | // 可以读的数据的大小 写位置 - 读位置,中间的数据就是可以读的大小 6 | size_t Buffer::ReadableBytes() const { 7 | return writePos_ - readPos_; 8 | } 9 | 10 | // 可以写的数据大小,缓冲区的总大小 - 写位置 11 | size_t Buffer::WritableBytes() const { 12 | return buffer_.size() - writePos_; 13 | } 14 | 15 | // 前面可以用的空间,当前读取到哪个位置,就是前面可以用的空间大小 16 | size_t Buffer::PrependableBytes() const { 17 | return readPos_; 18 | } 19 | 20 | const char* Buffer::Peek() const { 21 | return BeginPtr_() + readPos_; 22 | } 23 | 24 | void Buffer::Retrieve(size_t len) { 25 | assert(len <= ReadableBytes()); 26 | readPos_ += len; 27 | } 28 | 29 | //buff.RetrieveUntil(lineEnd + 2); 30 | void Buffer::RetrieveUntil(const char* end) { 31 | assert(Peek() <= end ); 32 | Retrieve(end - Peek()); 33 | } 34 | 35 | void Buffer::RetrieveAll() { 36 | bzero(&buffer_[0], buffer_.size()); 37 | readPos_ = 0; 38 | writePos_ = 0; 39 | } 40 | 41 | std::string Buffer::RetrieveAllToStr() { 42 | std::string str(Peek(), ReadableBytes()); 43 | RetrieveAll(); 44 | return str; 45 | } 46 | 47 | const char* Buffer::BeginWriteConst() const { 48 | return BeginPtr_() + writePos_; 49 | } 50 | 51 | char* Buffer::BeginWrite() { 52 | return BeginPtr_() + writePos_; 53 | } 54 | 55 | void Buffer::HasWritten(size_t len) { 56 | writePos_ += len; 57 | } 58 | 59 | void Buffer::Append(const std::string& str) { 60 | Append(str.data(), str.length()); 61 | } 62 | 63 | void Buffer::Append(const void* data, size_t len) { 64 | assert(data); 65 | Append(static_cast(data), len); 66 | } 67 | 68 | // Append(buff, len - writable); buff临时数组,len-writable是临时数组中的数据个数 69 | void Buffer::Append(const char* str, size_t len) { 70 | assert(str); 71 | EnsureWriteable(len); 72 | std::copy(str, str + len, BeginWrite()); 73 | HasWritten(len); 74 | } 75 | 76 | void Buffer::Append(const Buffer& buff) { 77 | Append(buff.Peek(), buff.ReadableBytes()); 78 | } 79 | 80 | void Buffer::EnsureWriteable(size_t len) { 81 | if(WritableBytes() < len) { 82 | MakeSpace_(len); 83 | } 84 | assert(WritableBytes() >= len); 85 | } 86 | 87 | ssize_t Buffer::ReadFd(int fd, int* saveErrno) { 88 | // 64KB 89 | char buff[65535]; // 临时的数组,保证能够把所有的数据都读出来 90 | 91 | struct iovec iov[2]; 92 | const size_t writable = WritableBytes(); 93 | 94 | /* 分散读,保证数据全部读完 */ 95 | // iov[0] Buffer内置的数组_buffer,默认大小是1024 96 | // iov[1] buff临时数组,大小为65535 97 | iov[0].iov_base = BeginPtr_() + writePos_; 98 | iov[0].iov_len = writable; 99 | iov[1].iov_base = buff; 100 | iov[1].iov_len = sizeof(buff); 101 | 102 | const ssize_t len = readv(fd, iov, 2); // 真正的读操作 103 | if(len < 0) { 104 | *saveErrno = errno; 105 | } 106 | else if(static_cast(len) <= writable) { 107 | writePos_ += len; 108 | } 109 | else { 110 | writePos_ = buffer_.size(); 111 | Append(buff, len - writable); 112 | } 113 | return len; 114 | } 115 | 116 | ssize_t Buffer::WriteFd(int fd, int* saveErrno) { 117 | size_t readSize = ReadableBytes(); 118 | ssize_t len = write(fd, Peek(), readSize); 119 | if(len < 0) { 120 | *saveErrno = errno; 121 | return len; 122 | } 123 | readPos_ += len; 124 | return len; 125 | } 126 | 127 | char* Buffer::BeginPtr_() { 128 | return &*buffer_.begin(); // 等价于&buffer[0] 129 | } 130 | 131 | const char* Buffer::BeginPtr_() const { 132 | return &*buffer_.begin(); 133 | } 134 | 135 | void Buffer::MakeSpace_(size_t len) { 136 | if(WritableBytes() + PrependableBytes() < len) { 137 | buffer_.resize(writePos_ + len + 1); 138 | } 139 | else { 140 | size_t readable = ReadableBytes(); 141 | std::copy(BeginPtr_() + readPos_, BeginPtr_() + writePos_, BeginPtr_()); 142 | readPos_ = 0; 143 | writePos_ = readPos_ + readable; 144 | assert(readable == ReadableBytes()); 145 | } 146 | } -------------------------------------------------------------------------------- /code/buffer/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H 2 | #define BUFFER_H 3 | #include //perror 4 | #include 5 | #include // write 6 | #include //readv 7 | #include //readv 8 | #include 9 | #include 10 | class Buffer { 11 | public: 12 | Buffer(int initBuffSize = 1024); 13 | ~Buffer() = default; 14 | 15 | size_t WritableBytes() const; 16 | size_t ReadableBytes() const ; 17 | size_t PrependableBytes() const; 18 | 19 | const char* Peek() const; 20 | void EnsureWriteable(size_t len); 21 | void HasWritten(size_t len); 22 | 23 | void Retrieve(size_t len); 24 | void RetrieveUntil(const char* end); 25 | 26 | void RetrieveAll() ; 27 | std::string RetrieveAllToStr(); 28 | 29 | const char* BeginWriteConst() const; 30 | char* BeginWrite(); 31 | 32 | void Append(const std::string& str); 33 | void Append(const char* str, size_t len); 34 | void Append(const void* data, size_t len); 35 | void Append(const Buffer& buff); 36 | 37 | ssize_t ReadFd(int fd, int* Errno); 38 | ssize_t WriteFd(int fd, int* Errno); 39 | 40 | private: 41 | char* BeginPtr_(); // 获取内存起始位置 42 | const char* BeginPtr_() const; // 获取内存起始位置 43 | void MakeSpace_(size_t len); // 创建空间 44 | 45 | std::vector buffer_; // 具体装数据的vector 46 | std::atomic readPos_; // 读的位置 47 | std::atomic writePos_; // 写的位置 48 | }; 49 | 50 | #endif //BUFFER_H -------------------------------------------------------------------------------- /code/config/config.h: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /code/http/httpconn.cpp: -------------------------------------------------------------------------------- 1 | #include "httpconn.h" 2 | using namespace std; 3 | 4 | const char* HttpConn::srcDir; 5 | std::atomic HttpConn::userCount; 6 | 7 | bool HttpConn::isET = true; 8 | 9 | HttpConn::HttpConn() { 10 | fd_ = -1; 11 | addr_ = { 0 }; 12 | isClose_ = true; 13 | }; 14 | 15 | HttpConn::~HttpConn() { 16 | Close(); 17 | }; 18 | 19 | void HttpConn::init(int fd, const sockaddr_in& addr) { 20 | assert(fd > 0); 21 | userCount++; 22 | addr_ = addr; 23 | fd_ = fd; 24 | // 初始化写缓冲和读缓冲 25 | writeBuff_.RetrieveAll(); 26 | readBuff_.RetrieveAll(); 27 | isClose_ = false; 28 | LOG_INFO("Client[%d](%s:%d) in, userCount:%d", fd_, GetIP(), GetPort(), (int)userCount); 29 | } 30 | 31 | void HttpConn::Close() { 32 | response_.UnmapFile(); // 解除内存映射 33 | if(isClose_ == false){ 34 | isClose_ = true; 35 | userCount--; 36 | close(fd_); 37 | LOG_INFO("Client[%d](%s:%d) quit, UserCount:%d", fd_, GetIP(), GetPort(), (int)userCount); 38 | } 39 | } 40 | 41 | int HttpConn::GetFd() const { 42 | return fd_; 43 | }; 44 | 45 | struct sockaddr_in HttpConn::GetAddr() const { 46 | return addr_; 47 | } 48 | 49 | const char* HttpConn::GetIP() const { 50 | return inet_ntoa(addr_.sin_addr); 51 | } 52 | 53 | int HttpConn::GetPort() const { 54 | return addr_.sin_port; 55 | } 56 | 57 | ssize_t HttpConn::read(int* saveErrno) { 58 | // 一次性读出所有数据(ET+非阻塞) 59 | ssize_t len = -1; 60 | do { 61 | len = readBuff_.ReadFd(fd_, saveErrno); 62 | if (len <= 0) { 63 | break; 64 | } 65 | } while (isET); 66 | return len; 67 | } 68 | 69 | ssize_t HttpConn::write(int* saveErrno) { 70 | ssize_t len = -1; 71 | do { 72 | // 分散写数据 73 | len = writev(fd_, iov_, iovCnt_); 74 | if(len <= 0) { 75 | *saveErrno = errno; 76 | break; 77 | } 78 | // 这种情况是所有数据都传输结束了 79 | if(iov_[0].iov_len + iov_[1].iov_len == 0) { break; } /* 传输结束 */ 80 | // 写到了第二块内存,做相应的处理 81 | else if(static_cast(len) > iov_[0].iov_len) { 82 | iov_[1].iov_base = (uint8_t*) iov_[1].iov_base + (len - iov_[0].iov_len); 83 | iov_[1].iov_len -= (len - iov_[0].iov_len); 84 | if(iov_[0].iov_len) { 85 | writeBuff_.RetrieveAll(); 86 | iov_[0].iov_len = 0; 87 | } 88 | } 89 | // 还没有写到第二块内存的数据 90 | else { 91 | iov_[0].iov_base = (uint8_t*)iov_[0].iov_base + len; 92 | iov_[0].iov_len -= len; 93 | writeBuff_.Retrieve(len); 94 | } 95 | } while(isET || ToWriteBytes() > 10240); 96 | return len; 97 | } 98 | 99 | // 业务逻辑处理 100 | bool HttpConn::process() { 101 | // 初始化请求对象 102 | request_.Init(); 103 | 104 | if(readBuff_.ReadableBytes() <= 0) {// 没有请求数据 105 | return false; 106 | } 107 | else if(request_.parse(readBuff_)) { // 解析请求数据 108 | LOG_DEBUG("%s", request_.path().c_str()); 109 | // 解析完请求数据以后,初始化响应对象 110 | response_.Init(srcDir, request_.path(), request_.IsKeepAlive(), 200); 111 | } else { 112 | // 解析失败 113 | response_.Init(srcDir, request_.path(), false, 400); // 请求报文中有语法错误 114 | } 115 | 116 | // 生成响应信息(writeBuff_中保存着响应的一些信息) 117 | response_.MakeResponse(writeBuff_); 118 | /* 响应头 */ 119 | iov_[0].iov_base = const_cast(writeBuff_.Peek()); 120 | iov_[0].iov_len = writeBuff_.ReadableBytes(); 121 | iovCnt_ = 1; 122 | 123 | /* 文件 */ 124 | if(response_.FileLen() > 0 && response_.File()) { 125 | iov_[1].iov_base = response_.File(); 126 | iov_[1].iov_len = response_.FileLen(); 127 | iovCnt_ = 2; 128 | } 129 | LOG_DEBUG("filesize:%d, %d to %d", response_.FileLen() , iovCnt_, ToWriteBytes()); 130 | return true; 131 | } 132 | -------------------------------------------------------------------------------- /code/http/httpconn.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_CONN_H 2 | #define HTTP_CONN_H 3 | 4 | #include 5 | #include // readv/writev 6 | #include // sockaddr_in 7 | #include // atoi() 8 | #include 9 | 10 | #include "../log/log.h" 11 | #include "../pool/sqlconnRAII.h" 12 | #include "../buffer/buffer.h" 13 | #include "httprequest.h" 14 | #include "httpresponse.h" 15 | 16 | // Http连接类,其中封装了请求和响应对象 17 | class HttpConn { 18 | public: 19 | HttpConn(); 20 | 21 | ~HttpConn(); 22 | 23 | void init(int sockFd, const sockaddr_in& addr); 24 | 25 | ssize_t read(int* saveErrno); 26 | 27 | ssize_t write(int* saveErrno); 28 | 29 | void Close(); 30 | 31 | int GetFd() const; 32 | 33 | int GetPort() const; 34 | 35 | const char* GetIP() const; 36 | 37 | sockaddr_in GetAddr() const; 38 | 39 | bool process(); 40 | 41 | int ToWriteBytes() { 42 | return iov_[0].iov_len + iov_[1].iov_len; 43 | } 44 | 45 | bool IsKeepAlive() const { 46 | return request_.IsKeepAlive(); 47 | } 48 | 49 | static bool isET; 50 | static const char* srcDir; // 资源的目录 51 | static std::atomic userCount; // 总共的客户单的连接数 52 | 53 | private: 54 | 55 | int fd_; 56 | struct sockaddr_in addr_; 57 | 58 | bool isClose_; 59 | 60 | int iovCnt_; // 分散内存的数量 61 | struct iovec iov_[2]; // 分散内存 62 | 63 | Buffer readBuff_; // 读(请求)缓冲区,保存请求数据的内容 64 | Buffer writeBuff_; // 写(响应)缓冲区,保存响应数据的内容 65 | 66 | HttpRequest request_; // 请求对象 67 | HttpResponse response_; // 响应对象 68 | }; 69 | 70 | 71 | #endif //HTTP_CONN_H -------------------------------------------------------------------------------- /code/http/httprequest.cpp: -------------------------------------------------------------------------------- 1 | #include "httprequest.h" 2 | using namespace std; 3 | 4 | const unordered_set HttpRequest::DEFAULT_HTML{ 5 | "/index", "/register", "/login", 6 | "/welcome", "/video", "/picture", }; 7 | 8 | const unordered_map HttpRequest::DEFAULT_HTML_TAG { 9 | {"/register.html", 0}, {"/login.html", 1}, }; 10 | 11 | // 初始化请求对象信息 12 | void HttpRequest::Init() { 13 | method_ = path_ = version_ = body_ = ""; 14 | state_ = REQUEST_LINE; // 初始状态是请求首行 15 | header_.clear(); 16 | post_.clear(); 17 | } 18 | 19 | bool HttpRequest::IsKeepAlive() const { 20 | if(header_.count("Connection") == 1) { 21 | return header_.find("Connection")->second == "keep-alive" && version_ == "1.1"; 22 | } 23 | return false; 24 | } 25 | 26 | // 解析请求数据 27 | bool HttpRequest::parse(Buffer& buff) { 28 | const char CRLF[] = "\r\n"; // 行结束符(回车换行) 29 | if(buff.ReadableBytes() <= 0) { 30 | return false; 31 | } 32 | // buff中有数据可读,并且状态没有到FINISH,就一直解析 33 | while(buff.ReadableBytes() && state_ != FINISH) { 34 | // 获取一行数据,根据\r\n为结束标志 35 | const char* lineEnd = search(buff.Peek(), buff.BeginWriteConst(), CRLF, CRLF + 2); 36 | std::string line(buff.Peek(), lineEnd); 37 | switch(state_) 38 | { 39 | case REQUEST_LINE: 40 | // 解析请求首行 41 | if(!ParseRequestLine_(line)) { 42 | return false; 43 | } 44 | // 解析出请求资源路径 45 | ParsePath_(); 46 | break; 47 | case HEADERS: 48 | // 解析请求头 49 | ParseHeader_(line); 50 | if(buff.ReadableBytes() <= 2) { // 此时已读到buff末尾 51 | state_ = FINISH; 52 | } 53 | break; 54 | case BODY: 55 | // 解析请求体 56 | ParseBody_(line); 57 | break; 58 | default: 59 | break; 60 | } 61 | if(lineEnd == buff.BeginWrite()) { break; } 62 | buff.RetrieveUntil(lineEnd + 2); 63 | } 64 | LOG_DEBUG("[%s], [%s], [%s]", method_.c_str(), path_.c_str(), version_.c_str()); 65 | return true; 66 | } 67 | 68 | void HttpRequest::ParsePath_() { 69 | // 如果访问根目录,默认表示访问index.html 70 | // 例如 http://192.168.110.111:10000/ 71 | if(path_ == "/") { 72 | path_ = "/index.html"; 73 | } 74 | else { 75 | // 其他默认的一些页面 76 | // 例如 http://192.168.110.111:10000/regist 77 | for(auto &item: DEFAULT_HTML) { 78 | if(item == path_) { 79 | path_ += ".html"; 80 | break; 81 | } 82 | } 83 | } 84 | } 85 | 86 | bool HttpRequest::ParseRequestLine_(const string& line) { 87 | // GET / HTTP/1.1 88 | regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$"); // 正则表达式 89 | smatch subMatch; 90 | if(regex_match(line, subMatch, patten)) { 91 | method_ = subMatch[1]; 92 | path_ = subMatch[2]; 93 | version_ = subMatch[3]; 94 | state_ = HEADERS; // 状态变为解析请求头 95 | return true; 96 | } 97 | LOG_ERROR("RequestLine Error"); 98 | return false; 99 | } 100 | 101 | // Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 102 | // Connection: keep-alive 103 | void HttpRequest::ParseHeader_(const string& line) { 104 | regex patten("^([^:]*): ?(.*)$"); 105 | smatch subMatch; 106 | if(regex_match(line, subMatch, patten)) { 107 | header_[subMatch[1]] = subMatch[2]; 108 | } 109 | else { // 此时到解析请求空行,状态变为解析请求体 110 | state_ = BODY; 111 | } 112 | } 113 | 114 | void HttpRequest::ParseBody_(const string& line) { 115 | body_ = line; 116 | ParsePost_(); 117 | state_ = FINISH; 118 | LOG_DEBUG("Body:%s, len:%d", line.c_str(), line.size()); 119 | } 120 | 121 | // 将十六进制的字符,转换成十进制的整数 122 | int HttpRequest::ConverHex(char ch) { 123 | if(ch >= 'A' && ch <= 'F') return ch -'A' + 10; 124 | if(ch >= 'a' && ch <= 'f') return ch -'a' + 10; 125 | return ch; 126 | } 127 | 128 | void HttpRequest::ParsePost_() { 129 | if(method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") { 130 | // 解析表单信息 131 | ParseFromUrlencoded_(); 132 | if(DEFAULT_HTML_TAG.count(path_)) { 133 | int tag = DEFAULT_HTML_TAG.find(path_)->second; 134 | LOG_DEBUG("Tag:%d", tag); 135 | if(tag == 0 || tag == 1) { 136 | bool isLogin = (tag == 1); 137 | if(UserVerify(post_["username"], post_["password"], isLogin)) { 138 | path_ = "/welcome.html"; 139 | } 140 | else { 141 | path_ = "/error.html"; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | 148 | void HttpRequest::ParseFromUrlencoded_() { 149 | if(body_.size() == 0) { return; } 150 | // username=zhangsan&password=123 151 | string key, value; 152 | int num = 0; 153 | int n = body_.size(); 154 | int i = 0, j = 0; 155 | 156 | for(; i < n; i++) { 157 | char ch = body_[i]; 158 | switch (ch) { 159 | case '=': 160 | key = body_.substr(j, i - j); 161 | j = i + 1; 162 | break; 163 | case '+': 164 | body_[i] = ' '; 165 | break; 166 | case '%': 167 | // 简单的加密的操作,编码 168 | num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]); 169 | body_[i + 2] = num % 10 + '0'; 170 | body_[i + 1] = num / 10 + '0'; 171 | i += 2; 172 | break; 173 | case '&': 174 | value = body_.substr(j, i - j); 175 | j = i + 1; 176 | post_[key] = value; 177 | LOG_DEBUG("%s = %s", key.c_str(), value.c_str()); 178 | break; 179 | default: 180 | break; 181 | } 182 | } 183 | assert(j <= i); 184 | if(post_.count(key) == 0 && j < i) { 185 | value = body_.substr(j, i - j); 186 | post_[key] = value; 187 | } 188 | } 189 | 190 | // 用户验证(整合了登录和注册的验证) 191 | bool HttpRequest::UserVerify(const string &name, const string &pwd, bool isLogin) { 192 | if(name == "" || pwd == "") { return false; } 193 | LOG_INFO("Verify name:%s pwd:%s", name.c_str(), pwd.c_str()); 194 | MYSQL* sql; 195 | SqlConnRAII(&sql, SqlConnPool::Instance()); 196 | assert(sql); 197 | 198 | bool flag = false; 199 | unsigned int j = 0; 200 | char order[256] = { 0 }; 201 | MYSQL_FIELD *fields = nullptr; 202 | MYSQL_RES *res = nullptr; 203 | 204 | if(!isLogin) { flag = true; } 205 | /* 查询用户及密码 */ 206 | snprintf(order, 256, "SELECT username, password FROM user WHERE username='%s' LIMIT 1", name.c_str()); 207 | LOG_DEBUG("%s", order); 208 | 209 | if(mysql_query(sql, order)) { 210 | mysql_free_result(res); 211 | return false; 212 | } 213 | res = mysql_store_result(sql); 214 | j = mysql_num_fields(res); 215 | fields = mysql_fetch_fields(res); 216 | 217 | while(MYSQL_ROW row = mysql_fetch_row(res)) { 218 | LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]); 219 | string password(row[1]); 220 | /* 注册行为 且 用户名未被使用*/ 221 | if(isLogin) { 222 | if(pwd == password) { flag = true; } 223 | else { 224 | flag = false; 225 | LOG_DEBUG("pwd error!"); 226 | } 227 | } 228 | else { 229 | flag = false; 230 | LOG_DEBUG("user used!"); 231 | } 232 | } 233 | mysql_free_result(res); 234 | 235 | /* 注册行为 且 用户名未被使用*/ 236 | if(!isLogin && flag == true) { 237 | LOG_DEBUG("regirster!"); 238 | bzero(order, 256); 239 | snprintf(order, 256,"INSERT INTO user(username, password) VALUES('%s','%s')", name.c_str(), pwd.c_str()); 240 | LOG_DEBUG( "%s", order); 241 | if(mysql_query(sql, order)) { 242 | LOG_DEBUG( "Insert error!"); 243 | flag = false; 244 | } 245 | flag = true; 246 | } 247 | SqlConnPool::Instance()->FreeConn(sql); 248 | LOG_DEBUG( "UserVerify success!!"); 249 | return flag; 250 | } 251 | 252 | std::string HttpRequest::path() const{ 253 | return path_; 254 | } 255 | 256 | std::string& HttpRequest::path(){ 257 | return path_; 258 | } 259 | std::string HttpRequest::method() const { 260 | return method_; 261 | } 262 | 263 | std::string HttpRequest::version() const { 264 | return version_; 265 | } 266 | 267 | std::string HttpRequest::GetPost(const std::string& key) const { 268 | assert(key != ""); 269 | if(post_.count(key) == 1) { 270 | return post_.find(key)->second; 271 | } 272 | return ""; 273 | } 274 | 275 | std::string HttpRequest::GetPost(const char* key) const { 276 | assert(key != nullptr); 277 | if(post_.count(key) == 1) { 278 | return post_.find(key)->second; 279 | } 280 | return ""; 281 | } -------------------------------------------------------------------------------- /code/http/httprequest.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_REQUEST_H 2 | #define HTTP_REQUEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include //mysql 10 | 11 | #include "../buffer/buffer.h" 12 | #include "../log/log.h" 13 | #include "../pool/sqlconnpool.h" 14 | #include "../pool/sqlconnRAII.h" 15 | 16 | class HttpRequest { 17 | public: 18 | enum PARSE_STATE { 19 | REQUEST_LINE, // 正在解析请求首行 20 | HEADERS, // 头 21 | BODY, // 体 22 | FINISH, // 完成 23 | }; 24 | 25 | enum HTTP_CODE { 26 | NO_REQUEST = 0, 27 | GET_REQUEST, 28 | BAD_REQUEST, 29 | NO_RESOURSE, 30 | FORBIDDENT_REQUEST, 31 | FILE_REQUEST, 32 | INTERNAL_ERROR, 33 | CLOSED_CONNECTION, 34 | }; 35 | 36 | HttpRequest() { Init(); } 37 | ~HttpRequest() = default; 38 | 39 | void Init(); 40 | bool parse(Buffer& buff); 41 | 42 | std::string path() const; 43 | std::string& path(); 44 | std::string method() const; 45 | std::string version() const; 46 | std::string GetPost(const std::string& key) const; 47 | std::string GetPost(const char* key) const; 48 | 49 | bool IsKeepAlive() const; 50 | 51 | private: 52 | bool ParseRequestLine_(const std::string& line); 53 | void ParseHeader_(const std::string& line); 54 | void ParseBody_(const std::string& line); 55 | 56 | void ParsePath_(); 57 | void ParsePost_(); 58 | void ParseFromUrlencoded_(); 59 | 60 | static bool UserVerify(const std::string& name, const std::string& pwd, bool isLogin); 61 | 62 | PARSE_STATE state_; // 解析的状态 63 | std::string method_, path_, version_, body_; // 请求方法,请求路径,协议版本,请求体 64 | std::unordered_map header_; // 请求头(键值对形式) 65 | std::unordered_map post_; // post请求表单数据 66 | 67 | static const std::unordered_set DEFAULT_HTML; // 默认的网页 68 | static const std::unordered_map DEFAULT_HTML_TAG; 69 | static int ConverHex(char ch); // 将十六进制字符转换成十进制整数 70 | }; 71 | 72 | 73 | #endif //HTTP_REQUEST_H -------------------------------------------------------------------------------- /code/http/httpresponse.cpp: -------------------------------------------------------------------------------- 1 | #include "httpresponse.h" 2 | 3 | using namespace std; 4 | 5 | // 文件后缀 对应的 MIME-TYPE类型,用在响应头Content-Type中 6 | const unordered_map HttpResponse::SUFFIX_TYPE = { 7 | { ".html", "text/html" }, 8 | { ".xml", "text/xml" }, 9 | { ".xhtml", "application/xhtml+xml" }, 10 | { ".txt", "text/plain" }, 11 | { ".rtf", "application/rtf" }, 12 | { ".pdf", "application/pdf" }, 13 | { ".word", "application/nsword" }, 14 | { ".png", "image/png" }, 15 | { ".gif", "image/gif" }, 16 | { ".jpg", "image/jpeg" }, 17 | { ".jpeg", "image/jpeg" }, 18 | { ".au", "audio/basic" }, 19 | { ".mpeg", "video/mpeg" }, 20 | { ".mpg", "video/mpeg" }, 21 | { ".avi", "video/x-msvideo" }, 22 | { ".gz", "application/x-gzip" }, 23 | { ".tar", "application/x-tar" }, 24 | { ".css", "text/css "}, 25 | { ".js", "text/javascript "}, 26 | }; 27 | 28 | // 响应状态码对应的描述语 29 | const unordered_map HttpResponse::CODE_STATUS = { 30 | { 200, "OK" }, 31 | { 400, "Bad Request" }, 32 | { 403, "Forbidden" }, 33 | { 404, "Not Found" }, 34 | }; 35 | 36 | // 响应码对应的资源路径 37 | const unordered_map HttpResponse::CODE_PATH = { 38 | { 400, "/400.html" }, 39 | { 403, "/403.html" }, 40 | { 404, "/404.html" }, 41 | }; 42 | 43 | HttpResponse::HttpResponse() { 44 | code_ = -1; 45 | path_ = srcDir_ = ""; 46 | isKeepAlive_ = false; 47 | mmFile_ = nullptr; 48 | mmFileStat_ = { 0 }; 49 | }; 50 | 51 | HttpResponse::~HttpResponse() { 52 | UnmapFile(); 53 | } 54 | 55 | void HttpResponse::Init(const string& srcDir, string& path, bool isKeepAlive, int code){ 56 | assert(srcDir != ""); 57 | 58 | if(mmFile_) { UnmapFile(); } 59 | 60 | code_ = code; 61 | isKeepAlive_ = isKeepAlive; 62 | path_ = path; 63 | srcDir_ = srcDir; 64 | mmFile_ = nullptr; 65 | mmFileStat_ = { 0 }; 66 | } 67 | 68 | void HttpResponse::MakeResponse(Buffer& buff) { 69 | /* 判断请求的资源文件 */ 70 | // index.html 71 | // /home/nowcoder/WebServer-master/resources/index.html 72 | if(stat((srcDir_ + path_).data(), &mmFileStat_) < 0 || S_ISDIR(mmFileStat_.st_mode)) { 73 | code_ = 404; // 服务器上无法找到请求的资源 74 | } 75 | else if(!(mmFileStat_.st_mode & S_IROTH)) { 76 | code_ = 403; // 请求资源的访问被服务器拒绝 77 | } 78 | else if(code_ == -1) { 79 | code_ = 200; 80 | } 81 | ErrorHtml_(); 82 | AddStateLine_(buff); 83 | AddHeader_(buff); 84 | AddContent_(buff); 85 | } 86 | 87 | char* HttpResponse::File() { 88 | return mmFile_; 89 | } 90 | 91 | size_t HttpResponse::FileLen() const { 92 | return mmFileStat_.st_size; 93 | } 94 | 95 | void HttpResponse::ErrorHtml_() { 96 | if(CODE_PATH.count(code_) == 1) { 97 | path_ = CODE_PATH.find(code_)->second; 98 | stat((srcDir_ + path_).data(), &mmFileStat_); 99 | } 100 | } 101 | 102 | // 添加响应状态行 103 | void HttpResponse::AddStateLine_(Buffer& buff) { 104 | string status; 105 | if(CODE_STATUS.count(code_) == 1) { 106 | status = CODE_STATUS.find(code_)->second; 107 | } 108 | else { 109 | code_ = 400; 110 | status = CODE_STATUS.find(400)->second; 111 | } 112 | buff.Append("HTTP/1.1 " + to_string(code_) + " " + status + "\r\n"); 113 | } 114 | 115 | // 添加响应头 116 | void HttpResponse::AddHeader_(Buffer& buff) { 117 | buff.Append("Connection: "); 118 | if(isKeepAlive_) { 119 | buff.Append("keep-alive\r\n"); 120 | buff.Append("keep-alive: max=6, timeout=120\r\n"); 121 | } else{ 122 | buff.Append("close\r\n"); 123 | } 124 | buff.Append("Content-type: " + GetFileType_() + "\r\n"); 125 | } 126 | 127 | // 添加响应体 128 | void HttpResponse::AddContent_(Buffer& buff) { 129 | int srcFd = open((srcDir_ + path_).data(), O_RDONLY); // 得到资源文件的文件描述符 130 | if(srcFd < 0) { 131 | ErrorContent(buff, "File NotFound!"); 132 | return; 133 | } 134 | 135 | /* 将文件映射到内存提高文件的访问速度 136 | MAP_PRIVATE 建立一个写入时拷贝的私有映射 */ 137 | LOG_DEBUG("file path %s", (srcDir_ + path_).data()); 138 | int* mmRet = (int*)mmap(0, mmFileStat_.st_size, PROT_READ, MAP_PRIVATE, srcFd, 0); 139 | if(*mmRet == -1) { 140 | ErrorContent(buff, "File NotFound!"); 141 | return; 142 | } 143 | mmFile_ = (char*)mmRet; 144 | close(srcFd); 145 | buff.Append("Content-length: " + to_string(mmFileStat_.st_size) + "\r\n\r\n"); 146 | } 147 | 148 | // 解除内存映射 149 | void HttpResponse::UnmapFile() { 150 | if(mmFile_) { 151 | munmap(mmFile_, mmFileStat_.st_size); 152 | mmFile_ = nullptr; 153 | } 154 | } 155 | 156 | string HttpResponse::GetFileType_() { 157 | /* 判断文件类型 */ 158 | string::size_type idx = path_.find_last_of('.'); 159 | if(idx == string::npos) { 160 | return "text/plain"; 161 | } 162 | string suffix = path_.substr(idx); 163 | if(SUFFIX_TYPE.count(suffix) == 1) { 164 | return SUFFIX_TYPE.find(suffix)->second; 165 | } 166 | return "text/plain"; 167 | } 168 | 169 | void HttpResponse::ErrorContent(Buffer& buff, string message) 170 | { 171 | string body; 172 | string status; 173 | body += "Error"; 174 | body += ""; 175 | if(CODE_STATUS.count(code_) == 1) { 176 | status = CODE_STATUS.find(code_)->second; 177 | } else { 178 | status = "Bad Request"; 179 | } 180 | body += to_string(code_) + " : " + status + "\n"; 181 | body += "

" + message + "

"; 182 | body += "
TinyWebServer"; 183 | 184 | buff.Append("Content-length: " + to_string(body.size()) + "\r\n\r\n"); 185 | buff.Append(body); 186 | } 187 | -------------------------------------------------------------------------------- /code/http/httpresponse.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_RESPONSE_H 2 | #define HTTP_RESPONSE_H 3 | 4 | #include 5 | #include // open 6 | #include // close 7 | #include // stat 8 | #include // mmap, munmap 9 | 10 | #include "../buffer/buffer.h" 11 | #include "../log/log.h" 12 | 13 | class HttpResponse { 14 | public: 15 | HttpResponse(); 16 | ~HttpResponse(); 17 | 18 | void Init(const std::string& srcDir, std::string& path, bool isKeepAlive = false, int code = -1); 19 | void MakeResponse(Buffer& buff); 20 | void UnmapFile(); 21 | char* File(); 22 | size_t FileLen() const; 23 | void ErrorContent(Buffer& buff, std::string message); 24 | int Code() const { return code_; } 25 | 26 | private: 27 | void AddStateLine_(Buffer &buff); 28 | void AddHeader_(Buffer &buff); 29 | void AddContent_(Buffer &buff); 30 | 31 | void ErrorHtml_(); 32 | std::string GetFileType_(); 33 | 34 | int code_; // 响应状态码 35 | bool isKeepAlive_; // 是否保持连接 36 | 37 | std::string path_; // 资源的路径 38 | std::string srcDir_; // 资源的目录 39 | 40 | char* mmFile_; // 文件内存映射的指针 41 | struct stat mmFileStat_; // 文件的状态信息 42 | 43 | static const std::unordered_map SUFFIX_TYPE; // 后缀 - 类型 44 | static const std::unordered_map CODE_STATUS; // 状态码 - 描述 45 | static const std::unordered_map CODE_PATH; // 状态码 - 路径 46 | }; 47 | 48 | 49 | #endif //HTTP_RESPONSE_H -------------------------------------------------------------------------------- /code/log/blockqueue.h: -------------------------------------------------------------------------------- 1 | #ifndef BLOCKQUEUE_H 2 | #define BLOCKQUEUE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | class BlockDeque { 11 | public: 12 | explicit BlockDeque(size_t MaxCapacity = 1000); 13 | 14 | ~BlockDeque(); 15 | 16 | void clear(); 17 | 18 | bool empty(); 19 | 20 | bool full(); 21 | 22 | void Close(); 23 | 24 | size_t size(); 25 | 26 | size_t capacity(); 27 | 28 | T front(); 29 | 30 | T back(); 31 | 32 | void push_back(const T &item); 33 | 34 | void push_front(const T &item); 35 | 36 | bool pop(T &item); 37 | 38 | bool pop(T &item, int timeout); 39 | 40 | void flush(); 41 | 42 | private: 43 | std::deque deq_; 44 | 45 | size_t capacity_; 46 | 47 | std::mutex mtx_; 48 | 49 | bool isClose_; 50 | 51 | std::condition_variable condConsumer_; 52 | 53 | std::condition_variable condProducer_; 54 | }; 55 | 56 | 57 | template 58 | BlockDeque::BlockDeque(size_t MaxCapacity) :capacity_(MaxCapacity) { 59 | assert(MaxCapacity > 0); 60 | isClose_ = false; 61 | } 62 | 63 | template 64 | BlockDeque::~BlockDeque() { 65 | Close(); 66 | }; 67 | 68 | template 69 | void BlockDeque::Close() { 70 | { 71 | std::lock_guard locker(mtx_); 72 | deq_.clear(); 73 | isClose_ = true; 74 | } 75 | condProducer_.notify_all(); 76 | condConsumer_.notify_all(); 77 | }; 78 | 79 | template 80 | void BlockDeque::flush() { 81 | condConsumer_.notify_one(); 82 | }; 83 | 84 | template 85 | void BlockDeque::clear() { 86 | std::lock_guard locker(mtx_); 87 | deq_.clear(); 88 | } 89 | 90 | template 91 | T BlockDeque::front() { 92 | std::lock_guard locker(mtx_); 93 | return deq_.front(); 94 | } 95 | 96 | template 97 | T BlockDeque::back() { 98 | std::lock_guard locker(mtx_); 99 | return deq_.back(); 100 | } 101 | 102 | template 103 | size_t BlockDeque::size() { 104 | std::lock_guard locker(mtx_); 105 | return deq_.size(); 106 | } 107 | 108 | template 109 | size_t BlockDeque::capacity() { 110 | std::lock_guard locker(mtx_); 111 | return capacity_; 112 | } 113 | 114 | template 115 | void BlockDeque::push_back(const T &item) { 116 | std::unique_lock locker(mtx_); 117 | while(deq_.size() >= capacity_) { 118 | condProducer_.wait(locker); 119 | } 120 | deq_.push_back(item); 121 | condConsumer_.notify_one(); 122 | } 123 | 124 | template 125 | void BlockDeque::push_front(const T &item) { 126 | std::unique_lock locker(mtx_); 127 | while(deq_.size() >= capacity_) { 128 | condProducer_.wait(locker); 129 | } 130 | deq_.push_front(item); 131 | condConsumer_.notify_one(); 132 | } 133 | 134 | template 135 | bool BlockDeque::empty() { 136 | std::lock_guard locker(mtx_); 137 | return deq_.empty(); 138 | } 139 | 140 | template 141 | bool BlockDeque::full(){ 142 | std::lock_guard locker(mtx_); 143 | return deq_.size() >= capacity_; 144 | } 145 | 146 | template 147 | bool BlockDeque::pop(T &item) { 148 | std::unique_lock locker(mtx_); 149 | while(deq_.empty()){ 150 | condConsumer_.wait(locker); 151 | if(isClose_){ 152 | return false; 153 | } 154 | } 155 | item = deq_.front(); 156 | deq_.pop_front(); 157 | condProducer_.notify_one(); 158 | return true; 159 | } 160 | 161 | template 162 | bool BlockDeque::pop(T &item, int timeout) { 163 | std::unique_lock locker(mtx_); 164 | while(deq_.empty()){ 165 | if(condConsumer_.wait_for(locker, std::chrono::seconds(timeout)) 166 | == std::cv_status::timeout){ 167 | return false; 168 | } 169 | if(isClose_){ 170 | return false; 171 | } 172 | } 173 | item = deq_.front(); 174 | deq_.pop_front(); 175 | condProducer_.notify_one(); 176 | return true; 177 | } 178 | 179 | #endif // BLOCKQUEUE_H -------------------------------------------------------------------------------- /code/log/log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | using namespace std; 4 | 5 | Log::Log() { 6 | lineCount_ = 0; 7 | isAsync_ = false; 8 | writeThread_ = nullptr; 9 | deque_ = nullptr; 10 | toDay_ = 0; 11 | fp_ = nullptr; 12 | } 13 | 14 | Log::~Log() { 15 | if(writeThread_ && writeThread_->joinable()) { 16 | while(!deque_->empty()) { 17 | deque_->flush(); 18 | }; 19 | deque_->Close(); 20 | writeThread_->join(); 21 | } 22 | if(fp_) { 23 | lock_guard locker(mtx_); 24 | flush(); 25 | fclose(fp_); 26 | } 27 | } 28 | 29 | int Log::GetLevel() { 30 | lock_guard locker(mtx_); 31 | return level_; 32 | } 33 | 34 | void Log::SetLevel(int level) { 35 | lock_guard locker(mtx_); 36 | level_ = level; 37 | } 38 | 39 | void Log::init(int level = 1, const char* path, const char* suffix, 40 | int maxQueueSize) { 41 | isOpen_ = true; 42 | level_ = level; 43 | if(maxQueueSize > 0) { 44 | isAsync_ = true; 45 | if(!deque_) { 46 | unique_ptr> newDeque(new BlockDeque); 47 | deque_ = move(newDeque); 48 | 49 | std::unique_ptr NewThread(new thread(FlushLogThread)); 50 | writeThread_ = move(NewThread); // 写线程 51 | } 52 | } else { 53 | isAsync_ = false; 54 | } 55 | 56 | lineCount_ = 0; 57 | 58 | time_t timer = time(nullptr); 59 | struct tm *sysTime = localtime(&timer); 60 | struct tm t = *sysTime; 61 | 62 | path_ = path; 63 | suffix_ = suffix; 64 | char fileName[LOG_NAME_LEN] = {0}; 65 | snprintf(fileName, LOG_NAME_LEN - 1, "%s/%04d_%02d_%02d%s", 66 | path_, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, suffix_); 67 | toDay_ = t.tm_mday; 68 | 69 | { 70 | lock_guard locker(mtx_); 71 | buff_.RetrieveAll(); 72 | if(fp_) { 73 | flush(); 74 | fclose(fp_); 75 | } 76 | 77 | fp_ = fopen(fileName, "a"); 78 | if(fp_ == nullptr) { 79 | mkdir(path_, 0777); 80 | fp_ = fopen(fileName, "a"); 81 | } 82 | assert(fp_ != nullptr); 83 | } 84 | } 85 | 86 | void Log::write(int level, const char *format, ...) { 87 | struct timeval now = {0, 0}; 88 | gettimeofday(&now, nullptr); 89 | time_t tSec = now.tv_sec; 90 | struct tm *sysTime = localtime(&tSec); 91 | struct tm t = *sysTime; 92 | va_list vaList; 93 | 94 | /* 日志日期 日志行数 */ 95 | if (toDay_ != t.tm_mday || (lineCount_ && (lineCount_ % MAX_LINES == 0))) 96 | { 97 | unique_lock locker(mtx_); 98 | locker.unlock(); 99 | 100 | char newFile[LOG_NAME_LEN]; 101 | char tail[36] = {0}; 102 | snprintf(tail, 36, "%04d_%02d_%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday); 103 | 104 | if (toDay_ != t.tm_mday) 105 | { 106 | snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s%s", path_, tail, suffix_); 107 | toDay_ = t.tm_mday; 108 | lineCount_ = 0; 109 | } 110 | else { 111 | snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s-%d%s", path_, tail, (lineCount_ / MAX_LINES), suffix_); 112 | } 113 | 114 | locker.lock(); 115 | flush(); 116 | fclose(fp_); 117 | fp_ = fopen(newFile, "a"); 118 | assert(fp_ != nullptr); 119 | } 120 | 121 | { 122 | unique_lock locker(mtx_); 123 | lineCount_++; 124 | int n = snprintf(buff_.BeginWrite(), 128, "%d-%02d-%02d %02d:%02d:%02d.%06ld ", 125 | t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, 126 | t.tm_hour, t.tm_min, t.tm_sec, now.tv_usec); 127 | 128 | buff_.HasWritten(n); 129 | AppendLogLevelTitle_(level); 130 | 131 | va_start(vaList, format); 132 | int m = vsnprintf(buff_.BeginWrite(), buff_.WritableBytes(), format, vaList); 133 | va_end(vaList); 134 | 135 | buff_.HasWritten(m); 136 | buff_.Append("\n\0", 2); 137 | 138 | if(isAsync_ && deque_ && !deque_->full()) { 139 | deque_->push_back(buff_.RetrieveAllToStr()); 140 | } else { 141 | fputs(buff_.Peek(), fp_); 142 | } 143 | buff_.RetrieveAll(); 144 | } 145 | } 146 | 147 | void Log::AppendLogLevelTitle_(int level) { 148 | switch(level) { 149 | case 0: 150 | buff_.Append("[debug]: ", 9); 151 | break; 152 | case 1: 153 | buff_.Append("[info] : ", 9); 154 | break; 155 | case 2: 156 | buff_.Append("[warn] : ", 9); 157 | break; 158 | case 3: 159 | buff_.Append("[error]: ", 9); 160 | break; 161 | default: 162 | buff_.Append("[info] : ", 9); 163 | break; 164 | } 165 | } 166 | 167 | void Log::flush() { 168 | if(isAsync_) { 169 | deque_->flush(); 170 | } 171 | fflush(fp_); 172 | } 173 | 174 | void Log::AsyncWrite_() { 175 | string str = ""; 176 | while(deque_->pop(str)) { 177 | lock_guard locker(mtx_); 178 | fputs(str.c_str(), fp_); 179 | } 180 | } 181 | 182 | Log* Log::Instance() { 183 | static Log inst; 184 | return &inst; 185 | } 186 | 187 | void Log::FlushLogThread() { 188 | Log::Instance()->AsyncWrite_(); 189 | } -------------------------------------------------------------------------------- /code/log/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include // vastart va_end 10 | #include 11 | #include //mkdir 12 | #include "blockqueue.h" 13 | #include "../buffer/buffer.h" 14 | 15 | class Log { 16 | public: 17 | void init(int level, const char* path = "./log", 18 | const char* suffix =".log", 19 | int maxQueueCapacity = 1024); 20 | 21 | static Log* Instance(); 22 | static void FlushLogThread(); 23 | 24 | void write(int level, const char *format,...); 25 | void flush(); 26 | 27 | int GetLevel(); 28 | void SetLevel(int level); 29 | bool IsOpen() { return isOpen_; } 30 | 31 | private: 32 | Log(); 33 | void AppendLogLevelTitle_(int level); 34 | virtual ~Log(); 35 | void AsyncWrite_(); 36 | 37 | private: 38 | static const int LOG_PATH_LEN = 256; // 日志路径的最大长度 39 | static const int LOG_NAME_LEN = 256; // 日志名称的最大长度 40 | static const int MAX_LINES = 50000; 41 | 42 | const char* path_; 43 | const char* suffix_; 44 | 45 | int MAX_LINES_; 46 | 47 | int lineCount_; 48 | int toDay_; 49 | 50 | bool isOpen_; 51 | 52 | Buffer buff_; 53 | int level_; 54 | bool isAsync_; 55 | 56 | FILE* fp_; 57 | std::unique_ptr> deque_; 58 | std::unique_ptr writeThread_; 59 | std::mutex mtx_; 60 | }; 61 | 62 | // level = 1 63 | // LOG_DEBUG 0 <= 1 y 64 | // LOG_INFO 1 <= 1 y 65 | // LOG_WARN 2 <= 1 n 66 | // LOG_ERROR 3 <= 1 n 67 | #define LOG_BASE(level, format, ...) \ 68 | do {\ 69 | Log* log = Log::Instance();\ 70 | if (log->IsOpen() && log->GetLevel() <= level) {\ 71 | log->write(level, format, ##__VA_ARGS__); \ 72 | log->flush();\ 73 | }\ 74 | } while(0); 75 | 76 | #define LOG_DEBUG(format, ...) do {LOG_BASE(0, format, ##__VA_ARGS__)} while(0); 77 | #define LOG_INFO(format, ...) do {LOG_BASE(1, format, ##__VA_ARGS__)} while(0); 78 | #define LOG_WARN(format, ...) do {LOG_BASE(2, format, ##__VA_ARGS__)} while(0); 79 | #define LOG_ERROR(format, ...) do {LOG_BASE(3, format, ##__VA_ARGS__)} while(0); 80 | 81 | #endif //LOG_H -------------------------------------------------------------------------------- /code/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "server/webserver.h" 3 | 4 | int main() { 5 | /* 守护进程 后台运行 */ 6 | // daemon(1, 0); 7 | 8 | WebServer server( 9 | 1316, 3, 60000, false, /* 端口 ET模式 timeoutMs 优雅退出 */ 10 | 3306, "root", "root", "webserver", /* Mysql配置 */ 11 | 12, 6, true, 1, 1024); /* 连接池数量 线程池的线程数量 日志开关 日志等级 日志异步队列容量 */ 12 | 13 | 14 | // 启动服务器 15 | server.Start(); 16 | } 17 | -------------------------------------------------------------------------------- /code/pool/sqlconnRAII.h: -------------------------------------------------------------------------------- 1 | #ifndef SQLCONNRAII_H 2 | #define SQLCONNRAII_H 3 | #include "sqlconnpool.h" 4 | 5 | /* 资源在对象构造初始化 资源在对象析构时释放*/ 6 | class SqlConnRAII { 7 | public: 8 | SqlConnRAII(MYSQL** sql, SqlConnPool *connpool) { 9 | assert(connpool); 10 | *sql = connpool->GetConn(); 11 | sql_ = *sql; 12 | connpool_ = connpool; 13 | } 14 | 15 | ~SqlConnRAII() { 16 | if(sql_) { connpool_->FreeConn(sql_); } 17 | } 18 | 19 | private: 20 | MYSQL *sql_; 21 | SqlConnPool* connpool_; 22 | }; 23 | 24 | #endif //SQLCONNRAII_H -------------------------------------------------------------------------------- /code/pool/sqlconnpool.cpp: -------------------------------------------------------------------------------- 1 | #include "sqlconnpool.h" 2 | using namespace std; 3 | 4 | SqlConnPool::SqlConnPool() { 5 | useCount_ = 0; 6 | freeCount_ = 0; 7 | } 8 | 9 | SqlConnPool* SqlConnPool::Instance() { 10 | static SqlConnPool connPool; 11 | return &connPool; 12 | } 13 | 14 | void SqlConnPool::Init(const char* host, int port, 15 | const char* user,const char* pwd, const char* dbName, 16 | int connSize = 10) { 17 | assert(connSize > 0); 18 | for (int i = 0; i < connSize; i++) { 19 | MYSQL *sql = nullptr; 20 | sql = mysql_init(sql); 21 | if (!sql) { 22 | LOG_ERROR("MySql init error!"); 23 | assert(sql); 24 | } 25 | sql = mysql_real_connect(sql, host, 26 | user, pwd, 27 | dbName, port, nullptr, 0); 28 | if (!sql) { 29 | LOG_ERROR("MySql Connect error!"); 30 | } 31 | connQue_.push(sql); 32 | } 33 | MAX_CONN_ = connSize; 34 | sem_init(&semId_, 0, MAX_CONN_); 35 | } 36 | 37 | MYSQL* SqlConnPool::GetConn() { 38 | MYSQL *sql = nullptr; 39 | if(connQue_.empty()){ 40 | LOG_WARN("SqlConnPool busy!"); 41 | return nullptr; 42 | } 43 | sem_wait(&semId_); 44 | { 45 | lock_guard locker(mtx_); 46 | sql = connQue_.front(); 47 | connQue_.pop(); 48 | } 49 | return sql; 50 | } 51 | 52 | void SqlConnPool::FreeConn(MYSQL* sql) { 53 | assert(sql); 54 | lock_guard locker(mtx_); 55 | connQue_.push(sql); 56 | sem_post(&semId_); 57 | } 58 | 59 | void SqlConnPool::ClosePool() { 60 | lock_guard locker(mtx_); 61 | while(!connQue_.empty()) { 62 | auto item = connQue_.front(); 63 | connQue_.pop(); 64 | mysql_close(item); 65 | } 66 | mysql_library_end(); 67 | } 68 | 69 | int SqlConnPool::GetFreeConnCount() { 70 | lock_guard locker(mtx_); 71 | return connQue_.size(); 72 | } 73 | 74 | SqlConnPool::~SqlConnPool() { 75 | ClosePool(); 76 | } 77 | -------------------------------------------------------------------------------- /code/pool/sqlconnpool.h: -------------------------------------------------------------------------------- 1 | #ifndef SQLCONNPOOL_H 2 | #define SQLCONNPOOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../log/log.h" 11 | 12 | class SqlConnPool { 13 | public: 14 | static SqlConnPool *Instance(); 15 | 16 | MYSQL *GetConn(); 17 | void FreeConn(MYSQL * conn); 18 | int GetFreeConnCount(); 19 | 20 | void Init(const char* host, int port, 21 | const char* user,const char* pwd, 22 | const char* dbName, int connSize); 23 | void ClosePool(); 24 | 25 | private: 26 | SqlConnPool(); 27 | ~SqlConnPool(); 28 | 29 | int MAX_CONN_; // 最大的连接数 30 | int useCount_; // 当前的用户数 31 | int freeCount_; // 空闲的用户数 32 | 33 | std::queue connQue_; // 队列(MYSQL *) 34 | std::mutex mtx_; // 互斥锁 35 | sem_t semId_; // 信号量 36 | }; 37 | 38 | 39 | #endif // SQLCONNPOOL_H -------------------------------------------------------------------------------- /code/pool/threadpool.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADPOOL_H 2 | #define THREADPOOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | class ThreadPool { 10 | public: 11 | explicit ThreadPool(size_t threadCount = 8): pool_(std::make_shared()) { // explicit防止构造函数进行隐式类型转换 12 | assert(threadCount > 0); 13 | 14 | // 创建threadCount个子线程 15 | for(size_t i = 0; i < threadCount; i++) { 16 | std::thread([pool = pool_] { 17 | std::unique_lock locker(pool->mtx); 18 | while(true) { 19 | if(!pool->tasks.empty()) { 20 | // 从任务队列中取第一个任务 21 | auto task = std::move(pool->tasks.front()); 22 | // 移除掉队列中第一个元素 23 | pool->tasks.pop(); 24 | locker.unlock(); 25 | task(); 26 | locker.lock(); // 这里是对工作队列加锁 27 | } 28 | else if(pool->isClosed) break; 29 | else pool->cond.wait(locker); // 如果队列为空,等待 30 | } 31 | }).detach();// 线程分离 32 | } 33 | } 34 | 35 | ThreadPool() = default; 36 | 37 | ThreadPool(ThreadPool&&) = default; 38 | 39 | ~ThreadPool() { 40 | if(static_cast(pool_)) { 41 | { 42 | std::lock_guard locker(pool_->mtx); 43 | pool_->isClosed = true; 44 | } 45 | pool_->cond.notify_all(); 46 | } 47 | } 48 | 49 | template 50 | void AddTask(F&& task) { 51 | { 52 | std::lock_guard locker(pool_->mtx); 53 | pool_->tasks.emplace(std::forward(task)); 54 | } 55 | pool_->cond.notify_one(); // 唤醒一个等待的线程 56 | } 57 | 58 | private: 59 | // 结构体 60 | struct Pool { 61 | std::mutex mtx; // 互斥锁 62 | std::condition_variable cond; // 条件变量 63 | bool isClosed; // 是否关闭 64 | std::queue> tasks; // 队列(保存的是任务) 65 | }; 66 | std::shared_ptr pool_; // 池子 67 | }; 68 | 69 | 70 | #endif //THREADPOOL_H -------------------------------------------------------------------------------- /code/server/epoller.cpp: -------------------------------------------------------------------------------- 1 | #include "epoller.h" 2 | 3 | // 创建epoll对象 epoll_create(512) 4 | Epoller::Epoller(int maxEvent):epollFd_(epoll_create(512)), events_(maxEvent){ 5 | assert(epollFd_ >= 0 && events_.size() > 0); 6 | } 7 | 8 | Epoller::~Epoller() { 9 | close(epollFd_); 10 | } 11 | 12 | // 添加文件描述符到epoll中进行管理 13 | bool Epoller::AddFd(int fd, uint32_t events) { 14 | if(fd < 0) return false; 15 | epoll_event ev = {0}; 16 | ev.data.fd = fd; 17 | ev.events = events; 18 | return 0 == epoll_ctl(epollFd_, EPOLL_CTL_ADD, fd, &ev); 19 | } 20 | 21 | // 修改 22 | bool Epoller::ModFd(int fd, uint32_t events) { 23 | if(fd < 0) return false; 24 | epoll_event ev = {0}; 25 | ev.data.fd = fd; 26 | ev.events = events; 27 | return 0 == epoll_ctl(epollFd_, EPOLL_CTL_MOD, fd, &ev); // 此时修改了事件的模式如EPOLLOUT 28 | } 29 | 30 | // 删除 31 | bool Epoller::DelFd(int fd) { 32 | if(fd < 0) return false; 33 | epoll_event ev = {0}; 34 | return 0 == epoll_ctl(epollFd_, EPOLL_CTL_DEL, fd, &ev); 35 | } 36 | 37 | // 调用epoll_wait()进行事件检测 38 | int Epoller::Wait(int timeoutMs) { 39 | return epoll_wait(epollFd_, &events_[0], static_cast(events_.size()), timeoutMs); // events == &events[0] 40 | } 41 | 42 | // 获取产生事件的文件描述符 43 | int Epoller::GetEventFd(size_t i) const { 44 | assert(i < events_.size() && i >= 0); 45 | return events_[i].data.fd; 46 | } 47 | 48 | // 获取事件 49 | uint32_t Epoller::GetEvents(size_t i) const { 50 | assert(i < events_.size() && i >= 0); 51 | return events_[i].events; 52 | } -------------------------------------------------------------------------------- /code/server/epoller.h: -------------------------------------------------------------------------------- 1 | #ifndef EPOLLER_H 2 | #define EPOLLER_H 3 | 4 | #include //epoll_ctl() 5 | #include // fcntl() 6 | #include // close() 7 | #include // close() 8 | #include 9 | #include 10 | 11 | class Epoller { 12 | public: 13 | explicit Epoller(int maxEvent = 1024); 14 | 15 | ~Epoller(); 16 | 17 | bool AddFd(int fd, uint32_t events); 18 | 19 | bool ModFd(int fd, uint32_t events); 20 | 21 | bool DelFd(int fd); 22 | 23 | int Wait(int timeoutMs = -1); 24 | 25 | int GetEventFd(size_t i) const; 26 | 27 | uint32_t GetEvents(size_t i) const; 28 | 29 | private: 30 | int epollFd_; // epoll_create()创建一个epoll对象,返回值就是epollFd 31 | 32 | std::vector events_; // 检测到的事件的集合 33 | }; 34 | 35 | #endif //EPOLLER_H -------------------------------------------------------------------------------- /code/server/webserver.cpp: -------------------------------------------------------------------------------- 1 | #include "webserver.h" 2 | 3 | using namespace std; 4 | 5 | WebServer::WebServer( 6 | int port, int trigMode, int timeoutMS, bool OptLinger, 7 | int sqlPort, const char* sqlUser, const char* sqlPwd, 8 | const char* dbName, int connPoolNum, int threadNum, 9 | bool openLog, int logLevel, int logQueSize): 10 | port_(port), openLinger_(OptLinger), timeoutMS_(timeoutMS), isClose_(false), 11 | timer_(new HeapTimer()), threadpool_(new ThreadPool(threadNum)), epoller_(new Epoller()) 12 | { 13 | // /home/nowcoder/WebServer-master/ 14 | srcDir_ = getcwd(nullptr, 256); // 获取当前的工作路径 15 | assert(srcDir_); 16 | // /home/nowcoder/WebServer-master/resources/ 17 | strncat(srcDir_, "/resources/", 16); // 拼接资源路径 18 | 19 | // 当前所有连接数 20 | HttpConn::userCount = 0; 21 | HttpConn::srcDir = srcDir_; 22 | 23 | // 初始化数据库连接池 24 | SqlConnPool::Instance()->Init("localhost", sqlPort, sqlUser, sqlPwd, dbName, connPoolNum); 25 | 26 | // 初始化事件的模式 27 | InitEventMode_(trigMode); 28 | 29 | // 初始化网络通信相关的一些内容 30 | if(!InitSocket_()) { isClose_ = true;} 31 | 32 | if(openLog) { 33 | // 初始化日志信息 34 | Log::Instance()->init(logLevel, "./log", ".log", logQueSize); 35 | if(isClose_) { LOG_ERROR("========== Server init error!=========="); } 36 | else { 37 | LOG_INFO("========== Server init =========="); 38 | LOG_INFO("Port:%d, OpenLinger: %s", port_, OptLinger? "true":"false"); 39 | LOG_INFO("Listen Mode: %s, OpenConn Mode: %s", 40 | (listenEvent_ & EPOLLET ? "ET": "LT"), 41 | (connEvent_ & EPOLLET ? "ET": "LT")); 42 | LOG_INFO("LogSys level: %d", logLevel); 43 | LOG_INFO("srcDir: %s", HttpConn::srcDir); 44 | LOG_INFO("SqlConnPool num: %d, ThreadPool num: %d", connPoolNum, threadNum); 45 | } 46 | } 47 | } 48 | 49 | WebServer::~WebServer() { 50 | close(listenFd_); 51 | isClose_ = true; 52 | free(srcDir_); 53 | SqlConnPool::Instance()->ClosePool(); 54 | } 55 | 56 | // 设置监听的文件描述符和通信的文件描述符的模式 57 | void WebServer::InitEventMode_(int trigMode) { 58 | listenEvent_ = EPOLLRDHUP; 59 | connEvent_ = EPOLLONESHOT | EPOLLRDHUP; // 注册EPOLLONESHOT,防止多个线程同时操作一个socket的情况 60 | switch (trigMode) 61 | { 62 | case 0: 63 | break; 64 | case 1: 65 | connEvent_ |= EPOLLET; 66 | break; 67 | case 2: 68 | listenEvent_ |= EPOLLET; 69 | break; 70 | case 3: 71 | listenEvent_ |= EPOLLET; 72 | connEvent_ |= EPOLLET; 73 | break; 74 | default: 75 | listenEvent_ |= EPOLLET; 76 | connEvent_ |= EPOLLET; 77 | break; 78 | } 79 | HttpConn::isET = (connEvent_ & EPOLLET); 80 | } 81 | 82 | // 启动服务器 83 | void WebServer::Start() { 84 | int timeMS = -1; /* epoll wait timeout == -1 无事件将阻塞 */ 85 | if(!isClose_) { LOG_INFO("========== Server start =========="); } 86 | while(!isClose_) { 87 | 88 | // 如果设置了超时时间,例如60s,则只要一个连接60秒没有读写操作,则关闭 89 | if(timeoutMS_ > 0) { 90 | // 通过定时器GetNextTick(),清除超时的节点,然后获取最先要超时的连接的超时的时间 91 | timeMS = timer_->GetNextTick(); 92 | } 93 | 94 | // timeMS是最先要超时的连接的超时的时间,传递到epoll_wait()函数中 95 | // 当timeMS时间内有事件发生,epoll_wait()返回,否则等到了timeMS时间后才返回 96 | // 这样做的目的是为了让epoll_wait()调用次数变少,提高效率 97 | int eventCnt = epoller_->Wait(timeMS); 98 | 99 | // 循环处理每一个事件 100 | for(int i = 0; i < eventCnt; i++) { 101 | /* 处理事件 */ 102 | int fd = epoller_->GetEventFd(i); // 获取事件对应的fd 103 | uint32_t events = epoller_->GetEvents(i); // 获取事件的类型 104 | 105 | // 监听的文件描述符有事件,说明有新的连接进来 106 | if(fd == listenFd_) { 107 | DealListen_(); // 处理监听的操作,接受客户端连接(可能存在有多个客户端连接进来) 108 | } // 这是在主线程中完成的 109 | 110 | // 错误的一些情况 111 | else if(events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { 112 | assert(users_.count(fd) > 0); 113 | CloseConn_(&users_[fd]); // 关闭连接 114 | } 115 | 116 | // 有数据到达(数据到达TCP的读缓冲区,需要我们去处理读操作) 117 | else if(events & EPOLLIN) { 118 | assert(users_.count(fd) > 0); 119 | DealRead_(&users_[fd]); // 处理读操作 120 | } 121 | 122 | // 可发送数据(往TCP写缓冲区中写数据,需要我们去处理写操作) 注意: 只要TCP写缓冲区还有空余空间,其EPOLLOUT事件就会被触发 123 | else if(events & EPOLLOUT) { 124 | assert(users_.count(fd) > 0); 125 | DealWrite_(&users_[fd]); // 处理写操作 126 | } else { 127 | LOG_ERROR("Unexpected event"); 128 | } 129 | } 130 | } 131 | } 132 | 133 | // 发送错误提示信息 134 | void WebServer::SendError_(int fd, const char*info) { 135 | assert(fd > 0); 136 | int ret = send(fd, info, strlen(info), 0); 137 | if(ret < 0) { 138 | LOG_WARN("send error to client[%d] error!", fd); 139 | } 140 | close(fd); 141 | } 142 | 143 | // 关闭连接(从epoll中删除,解除响应对象中的内存映射,用户数递减,关闭文件描述符) 144 | void WebServer::CloseConn_(HttpConn* client) { 145 | assert(client); 146 | LOG_INFO("Client[%d] quit!", client->GetFd()); 147 | epoller_->DelFd(client->GetFd()); 148 | client->Close(); 149 | } 150 | 151 | // 添加客户端 152 | void WebServer::AddClient_(int fd, sockaddr_in addr) { 153 | assert(fd > 0); 154 | users_[fd].init(fd, addr); 155 | if(timeoutMS_ > 0) { // timeoutMS_ = 60000ms 156 | // 添加到定时器对象中,当检测到超时时执行CloseConn_函数进行关闭连接 157 | timer_->add(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd])); 158 | } 159 | // 添加到epoll中进行管理 160 | epoller_->AddFd(fd, EPOLLIN | connEvent_); 161 | // 设置文件描述符非阻塞 162 | SetFdNonblock(fd); 163 | LOG_INFO("Client[%d] in!", users_[fd].GetFd()); 164 | } 165 | 166 | void WebServer::DealListen_() { 167 | struct sockaddr_in addr; // 保存连接的客户端的信息 168 | socklen_t len = sizeof(addr); 169 | // 如果监听文件描述符设置的是 ET模式(和非阻塞一起使用),则需要循环把所有连接处理了 170 | do { 171 | int fd = accept(listenFd_, (struct sockaddr *)&addr, &len); 172 | if(fd <= 0) { return;} 173 | else if(HttpConn::userCount >= MAX_FD) { 174 | SendError_(fd, "Server busy!"); 175 | LOG_WARN("Clients is full!"); 176 | return; 177 | } 178 | AddClient_(fd, addr); // 添加客户端 179 | } while(listenEvent_ & EPOLLET); 180 | } 181 | 182 | // 处理读 183 | void WebServer::DealRead_(HttpConn* client) { 184 | assert(client); 185 | ExtentTime_(client); // 延长这个客户端的超时时间(延长了60s) 186 | // 加入到队列中等待线程池中的线程处理(读取数据) 187 | threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client)); 188 | } 189 | 190 | // 处理写 191 | void WebServer::DealWrite_(HttpConn* client) { 192 | assert(client); 193 | ExtentTime_(client);// 延长这个客户端的超时时间(延长了60s) 194 | // 加入到队列中等待线程池中的线程处理(写数据) 195 | threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client)); 196 | } 197 | 198 | // 延长客户端的超时时间 199 | void WebServer::ExtentTime_(HttpConn* client) { 200 | assert(client); 201 | if(timeoutMS_ > 0) { timer_->adjust(client->GetFd(), timeoutMS_); } 202 | } 203 | 204 | // 这个方法是在子线程中执行的(读取数据) 205 | void WebServer::OnRead_(HttpConn* client) { 206 | assert(client); 207 | int ret = -1; 208 | int readErrno = 0; 209 | ret = client->read(&readErrno); // 读取客户端的数据 210 | if(ret <= 0 && readErrno != EAGAIN) { 211 | CloseConn_(client); 212 | return; 213 | } 214 | 215 | // 业务逻辑的处理 216 | OnProcess(client); 217 | } 218 | 219 | // 业务逻辑的处理 220 | void WebServer::OnProcess(HttpConn* client) { 221 | if(client->process()) { 222 | epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT); 223 | } else { 224 | epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLIN); 225 | } 226 | } 227 | 228 | // 写数据 229 | void WebServer::OnWrite_(HttpConn* client) { 230 | assert(client); 231 | int ret = -1; 232 | int writeErrno = 0; 233 | ret = client->write(&writeErrno); // 写数据 234 | 235 | // 如果将要写的字节等于0,说明写完了,判断是否要保持连接,保持连接继续去处理 236 | if(client->ToWriteBytes() == 0) { 237 | /* 传输完成 */ 238 | if(client->IsKeepAlive()) { 239 | OnProcess(client); 240 | return; 241 | } 242 | } 243 | else if(ret < 0) { 244 | if(writeErrno == EAGAIN) { 245 | /* 继续传输 */ 246 | epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT); 247 | return; 248 | } 249 | } 250 | CloseConn_(client); 251 | } 252 | 253 | /* Create listenFd */ 254 | bool WebServer::InitSocket_() { 255 | int ret; 256 | struct sockaddr_in addr; 257 | if(port_ > 65535 || port_ < 1024) { 258 | LOG_ERROR("Port:%d error!", port_); 259 | return false; 260 | } 261 | addr.sin_family = AF_INET; 262 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 263 | addr.sin_port = htons(port_); 264 | struct linger optLinger = { 0 }; 265 | 266 | if(openLinger_) { 267 | /* 优雅关闭: 直到所剩数据发送完毕或超时 */ 268 | optLinger.l_onoff = 1; 269 | optLinger.l_linger = 1; 270 | } 271 | 272 | listenFd_ = socket(AF_INET, SOCK_STREAM, 0); 273 | if(listenFd_ < 0) { 274 | LOG_ERROR("Create socket error!", port_); 275 | return false; 276 | } 277 | 278 | /* 设置端口复用 */ 279 | ret = setsockopt(listenFd_, SOL_SOCKET, SO_LINGER, &optLinger, sizeof(optLinger)); 280 | if(ret < 0) { 281 | close(listenFd_); 282 | LOG_ERROR("Init linger error!", port_); 283 | return false; 284 | } 285 | 286 | int optval = 1; 287 | /* 端口复用 */ 288 | /* 只有最后一个套接字会正常接收数据。 */ 289 | ret = setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int)); 290 | if(ret == -1) { 291 | LOG_ERROR("set socket setsockopt error !"); 292 | close(listenFd_); 293 | return false; 294 | } 295 | 296 | ret = bind(listenFd_, (struct sockaddr *)&addr, sizeof(addr)); 297 | if(ret < 0) { 298 | LOG_ERROR("Bind Port:%d error!", port_); 299 | close(listenFd_); 300 | return false; 301 | } 302 | 303 | ret = listen(listenFd_, 6); 304 | if(ret < 0) { 305 | LOG_ERROR("Listen port:%d error!", port_); 306 | close(listenFd_); 307 | return false; 308 | } 309 | 310 | ret = epoller_->AddFd(listenFd_, listenEvent_ | EPOLLIN); 311 | if(ret == 0) { 312 | LOG_ERROR("Add listen error!"); 313 | close(listenFd_); 314 | return false; 315 | } 316 | SetFdNonblock(listenFd_); 317 | LOG_INFO("Server port:%d", port_); 318 | return true; 319 | } 320 | 321 | // 设置文件描述符非阻塞 322 | int WebServer::SetFdNonblock(int fd) { 323 | assert(fd > 0); 324 | // int flag = fcntl(fd, F_GETFD, 0); 325 | // flag = flag | O_NONBLOCK; 326 | // // flag |= O_NONBLOCK; 327 | // fcntl(fd, F_SETFL, flag); 328 | return fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK); 329 | } 330 | 331 | 332 | -------------------------------------------------------------------------------- /code/server/webserver.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBSERVER_H 2 | #define WEBSERVER_H 3 | 4 | #include 5 | #include // fcntl() 6 | #include // close() 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "epoller.h" 14 | #include "../log/log.h" 15 | #include "../timer/heaptimer.h" 16 | #include "../pool/sqlconnpool.h" 17 | #include "../pool/threadpool.h" 18 | #include "../pool/sqlconnRAII.h" 19 | #include "../http/httpconn.h" 20 | 21 | class WebServer { 22 | public: 23 | WebServer( 24 | int port, int trigMode, int timeoutMS, bool OptLinger, 25 | int sqlPort, const char* sqlUser, const char* sqlPwd, 26 | const char* dbName, int connPoolNum, int threadNum, 27 | bool openLog, int logLevel, int logQueSize); 28 | 29 | ~WebServer(); 30 | void Start(); 31 | 32 | private: 33 | bool InitSocket_(); 34 | void InitEventMode_(int trigMode); 35 | void AddClient_(int fd, sockaddr_in addr); 36 | 37 | void DealListen_(); 38 | void DealWrite_(HttpConn* client); 39 | void DealRead_(HttpConn* client); 40 | 41 | void SendError_(int fd, const char*info); 42 | void ExtentTime_(HttpConn* client); 43 | void CloseConn_(HttpConn* client); 44 | 45 | void OnRead_(HttpConn* client); // 子线程中执行 46 | void OnWrite_(HttpConn* client); // 子线程中执行 47 | void OnProcess(HttpConn* client); // 子线程中执行 48 | 49 | static const int MAX_FD = 65536; // 最大的文件描述符的个数 50 | 51 | static int SetFdNonblock(int fd); // 设置文件描述符非阻塞 52 | 53 | int port_; // 端口 54 | bool openLinger_; // 是否打开优雅关闭 55 | int timeoutMS_; // 定时时间 56 | bool isClose_; // 是否关闭 57 | int listenFd_; // 监听的文件描述符 58 | char* srcDir_; // 资源的目录 59 | 60 | uint32_t listenEvent_; // 监听的文件描述符的事件 61 | uint32_t connEvent_; // 连接的文件描述符的事件 62 | 63 | std::unique_ptr timer_; // 定时器 64 | std::unique_ptr threadpool_; // 线程池 65 | std::unique_ptr epoller_; // epoll对象 66 | std::unordered_map users_; // 保存的是客户端连接的信息,通过文件描述符进行映射 67 | }; 68 | 69 | 70 | #endif //WEBSERVER_H -------------------------------------------------------------------------------- /code/timer/heaptimer.cpp: -------------------------------------------------------------------------------- 1 | #include "heaptimer.h" 2 | 3 | void HeapTimer::siftup_(size_t i) { 4 | assert(i >= 0 && i < heap_.size()); 5 | size_t j = (i - 1) / 2; 6 | while(j >= 0) { 7 | if(heap_[j] < heap_[i]) { break; } 8 | SwapNode_(i, j); 9 | i = j; 10 | j = (i - 1) / 2; 11 | } 12 | } 13 | 14 | void HeapTimer::SwapNode_(size_t i, size_t j) { 15 | assert(i >= 0 && i < heap_.size()); 16 | assert(j >= 0 && j < heap_.size()); 17 | std::swap(heap_[i], heap_[j]); 18 | ref_[heap_[i].id] = i; 19 | ref_[heap_[j].id] = j; 20 | } 21 | 22 | bool HeapTimer::siftdown_(size_t index, size_t n) { 23 | assert(index >= 0 && index < heap_.size()); 24 | assert(n >= 0 && n <= heap_.size()); 25 | size_t i = index; 26 | size_t j = i * 2 + 1; 27 | while(j < n) { 28 | if(j + 1 < n && heap_[j + 1] < heap_[j]) j++; 29 | if(heap_[i] < heap_[j]) break; 30 | SwapNode_(i, j); 31 | i = j; 32 | j = i * 2 + 1; 33 | } 34 | return i > index; 35 | } 36 | 37 | void HeapTimer::add(int id, int timeout, const TimeoutCallBack& cb) { 38 | assert(id >= 0); 39 | size_t i; 40 | if(ref_.count(id) == 0) { 41 | /* 新节点:堆尾插入,调整堆 */ 42 | i = heap_.size(); // 节点编号 43 | ref_[id] = i; // 文件描述符和节点编号之间映射关系 id - i(key - value) 44 | heap_.push_back({id, Clock::now() + MS(timeout), cb}); 45 | siftup_(i); // 向上调整,跟父亲比较 46 | } 47 | else { 48 | /* 已有结点:调整堆 */ 49 | i = ref_[id]; 50 | heap_[i].expires = Clock::now() + MS(timeout); 51 | heap_[i].cb = cb; 52 | if(!siftdown_(i, heap_.size())) { 53 | siftup_(i); 54 | } 55 | } 56 | } 57 | 58 | void HeapTimer::doWork(int id) { 59 | /* 删除指定id结点,并触发回调函数 */ 60 | if(heap_.empty() || ref_.count(id) == 0) { 61 | return; 62 | } 63 | size_t i = ref_[id]; 64 | TimerNode node = heap_[i]; 65 | node.cb(); 66 | del_(i); 67 | } 68 | 69 | void HeapTimer::del_(size_t index) { 70 | /* 删除指定位置的结点 */ 71 | assert(!heap_.empty() && index >= 0 && index < heap_.size()); 72 | /* 将要删除的结点换到队尾,然后调整堆 */ 73 | size_t i = index; 74 | size_t n = heap_.size() - 1; 75 | assert(i <= n); 76 | if(i < n) { 77 | SwapNode_(i, n); 78 | if(!siftdown_(i, n)) { 79 | siftup_(i); 80 | } 81 | } 82 | /* 队尾元素删除 */ 83 | ref_.erase(heap_.back().id); 84 | heap_.pop_back(); 85 | } 86 | 87 | void HeapTimer::adjust(int id, int timeout) { 88 | /* 调整指定id的结点 */ 89 | assert(!heap_.empty() && ref_.count(id) > 0); 90 | heap_[ref_[id]].expires = Clock::now() + MS(timeout); 91 | siftdown_(ref_[id], heap_.size()); 92 | } 93 | 94 | void HeapTimer::tick() { 95 | /* 清除超时结点 */ 96 | if(heap_.empty()) { 97 | return; 98 | } 99 | while(!heap_.empty()) { 100 | TimerNode node = heap_.front(); 101 | if(std::chrono::duration_cast(node.expires - Clock::now()).count() > 0) { 102 | break; 103 | } 104 | node.cb(); 105 | pop(); 106 | } 107 | } 108 | 109 | void HeapTimer::pop() { 110 | assert(!heap_.empty()); 111 | del_(0); 112 | } 113 | 114 | void HeapTimer::clear() { 115 | ref_.clear(); 116 | heap_.clear(); 117 | } 118 | 119 | int HeapTimer::GetNextTick() { 120 | tick(); 121 | size_t res = -1; 122 | if(!heap_.empty()) { 123 | res = std::chrono::duration_cast(heap_.front().expires - Clock::now()).count(); 124 | if(res < 0) { res = 0; } 125 | } 126 | return res; 127 | } -------------------------------------------------------------------------------- /code/timer/heaptimer.h: -------------------------------------------------------------------------------- 1 | #ifndef HEAP_TIMER_H 2 | #define HEAP_TIMER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "../log/log.h" 13 | 14 | typedef std::function TimeoutCallBack; 15 | typedef std::chrono::high_resolution_clock Clock; 16 | typedef std::chrono::milliseconds MS; 17 | typedef Clock::time_point TimeStamp; 18 | 19 | struct TimerNode { 20 | int id; 21 | TimeStamp expires; 22 | TimeoutCallBack cb; 23 | bool operator<(const TimerNode& t) { // 重载了<运算符,实现对时间的比较 24 | return expires < t.expires; // 小根堆 25 | } 26 | }; 27 | class HeapTimer { 28 | public: 29 | HeapTimer() { heap_.reserve(64); } // 默认堆大小是64 30 | 31 | ~HeapTimer() { clear(); } 32 | 33 | void adjust(int id, int newExpires); 34 | 35 | void add(int id, int timeOut, const TimeoutCallBack& cb); 36 | 37 | void doWork(int id); 38 | 39 | void clear(); 40 | 41 | void tick(); 42 | 43 | void pop(); 44 | 45 | int GetNextTick(); 46 | 47 | private: 48 | void del_(size_t i); 49 | 50 | void siftup_(size_t i); 51 | 52 | bool siftdown_(size_t index, size_t n); 53 | 54 | void SwapNode_(size_t i, size_t j); 55 | 56 | std::vector heap_; // 用数组vector去模拟堆 57 | 58 | std::unordered_map ref_; 59 | }; 60 | 61 | #endif //HEAP_TIMER_H -------------------------------------------------------------------------------- /readme.assest/压力测试.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/readme.assest/压力测试.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Tiny-WebServer 2 | 用C++实现的高性能WEB服务器,经过webbenchh压力测试可以实现上万的QPS 3 | 4 | ## 功能 5 | * 利用IO复用技术Epoll与线程池实现多线程的Reactor高并发模型; 6 | * 利用正则与状态机解析HTTP请求报文,实现处理静态资源的请求; 7 | * 利用标准库容器封装char,实现自动增长的缓冲区; 8 | * 基于小根堆实现的定时器,关闭超时的非活动连接; 9 | * 利用单例模式与阻塞队列实现异步的日志系统,记录服务器运行状态; 10 | * 利用RAII机制实现了数据库连接池,减少数据库连接建立与关闭的开销,同时实现了用户注册登录功能。 11 | 12 | * 增加logsys,threadpool测试单元(todo: timer, sqlconnpool, httprequest, httpresponse) 13 | 14 | ## 环境要求 15 | * Linux 16 | * C++14 17 | * MySql 18 | 19 | ## 目录树 20 | ``` 21 | . 22 | ├── code 源代码 23 | │ ├── buffer 24 | │   ├── config 25 | │   ├── http 26 | │   ├── log 27 | │   ├── timer 28 | │   ├── pool 29 | │   ├── server 30 | │   └── main.cpp 31 | ├── test 单元测试 32 | │ ├── Makefile 33 | │   └── test.cpp 34 | ├── resources 静态资源 35 | │   ├── index.html 36 | │   ├── image 37 | │   ├── video 38 | │   ├── js 39 | │ └── css 40 | ├── bin 可执行文件 41 | │ └── server 42 | ├── log 日志文件 43 | ├── webbench-1.5 压力测试 44 | ├── build 45 | │ └── Makefile 46 | ├── Makefile 47 | ├── LICENSE 48 | └── readme.md 49 | ``` 50 | 51 | 52 | ## 项目启动 53 | 需要先配置好对应的数据库 54 | ```bash 55 | // 建立yourdb库 56 | create database yourdb; 57 | 58 | // 创建user表 59 | USE yourdb; 60 | CREATE TABLE user( 61 | username char(50) NULL, 62 | password char(50) NULL 63 | )ENGINE=InnoDB; 64 | 65 | // 添加数据 66 | INSERT INTO user(username, password) VALUES('name', 'password'); 67 | ``` 68 | 69 | ```bash 70 | make 71 | ./bin/server 72 | ``` 73 | 74 | ## 单元测试 75 | ```bash 76 | cd test 77 | make 78 | ./test 79 | ``` 80 | 81 | ## 压力测试 82 | 83 | ```bash 84 | cd webbench-1.5 85 | make 86 | ``` 87 | 88 | ```bash 89 | ./webbench-1.5/webbench -c 100 -t 10 http://ip:port/ 90 | ./webbench-1.5/webbench -c 1000 -t 10 http://ip:port/ 91 | ./webbench-1.5/webbench -c 5000 -t 10 http://ip:port/ 92 | ./webbench-1.5/webbench -c 10000 -t 10 http://ip:port/ 93 | ``` 94 | 95 | * 测试环境: Ubuntu:18.40 cpu:i5-8400 内存:12G 96 | * QPS 10000+ 97 | 98 | ## TODO 99 | * config配置 100 | * 完善单元测试 101 | * 实现循环缓冲区 102 | 103 | ## 庖丁解牛 104 | 105 | - [C++ Linux轻量级WebServer(一)前言介绍](https://juejin.cn/post/7098659904721780749) 106 | - [C++ Linux轻量级WebServer(二)并发模型](https://juejin.cn/post/7099035785613033509) 107 | - [C++ Linux轻量级WebServer(三)解析请求](https://juejin.cn/post/7106108431395717128) 108 | - [C++ Linux轻量级WebServer(四)超时连接](https://juejin.cn/post/7107831560769896461) 109 | - [C++ Linux轻量级WebServer(五)日志系统](https://juejin.cn/post/7108553758753161230) 110 | - [C++ Linux轻量级WebServer(六)压力测试](https://juejin.cn/post/7109690826422222862) 111 | 112 | ## 致谢 113 | 114 | [@qinguoyi](https://github.com/qinguoyi/TinyWebServer)、[@markparticle](https://github.com/markparticle/WebServer/) 115 | 116 | -------------------------------------------------------------------------------- /resources/400.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MARK-首页 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 60 | 61 |
62 |
63 |
64 | 65 |
66 | about image 68 |
69 |
70 |

400 无法解析请求

71 |
72 |
73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /resources/403.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MARK-首页 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 60 | 61 |
62 |
63 |
64 | 65 |
66 | about image 68 |
69 |
70 |

403 禁止访问

71 |
72 |
73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /resources/404.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MARK-首页 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 60 | 61 |
62 |
63 |
64 | 65 |
66 | about image 68 |
69 |
70 |

404 没有找到该页面

71 |
72 |
73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /resources/405.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MARK-首页 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 60 | 61 |
62 |
63 |
64 | 65 |
66 | about image 68 |
69 |
70 |

404 没有找到该页面

71 |
72 |
73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /resources/css/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/css/.DS_Store -------------------------------------------------------------------------------- /resources/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot');src:url('../fonts/fontawesome-webfont.eot') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2') format('woff2'),url('../fonts/fontawesome-webfont.woff') format('woff'),url('../fonts/fontawesome-webfont.ttf') format('truetype'),url('../fonts/fontawesome-webfont.svg') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} 5 | -------------------------------------------------------------------------------- /resources/css/magnific-popup.css: -------------------------------------------------------------------------------- 1 | /* Magnific Popup CSS */ 2 | .mfp-bg { 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | z-index: 1042; 8 | overflow: hidden; 9 | position: fixed; 10 | background: #0b0b0b; 11 | opacity: 0.8; } 12 | 13 | .mfp-wrap { 14 | top: 0; 15 | left: 0; 16 | width: 100%; 17 | height: 100%; 18 | z-index: 1043; 19 | position: fixed; 20 | outline: none !important; 21 | -webkit-backface-visibility: hidden; } 22 | 23 | .mfp-container { 24 | text-align: center; 25 | position: absolute; 26 | width: 100%; 27 | height: 100%; 28 | left: 0; 29 | top: 0; 30 | padding: 0 8px; 31 | box-sizing: border-box; } 32 | 33 | .mfp-container:before { 34 | content: ''; 35 | display: inline-block; 36 | height: 100%; 37 | vertical-align: middle; } 38 | 39 | .mfp-align-top .mfp-container:before { 40 | display: none; } 41 | 42 | .mfp-content { 43 | position: relative; 44 | display: inline-block; 45 | vertical-align: middle; 46 | margin: 0 auto; 47 | text-align: left; 48 | z-index: 1045; } 49 | 50 | .mfp-inline-holder .mfp-content, 51 | .mfp-ajax-holder .mfp-content { 52 | width: 100%; 53 | cursor: auto; } 54 | 55 | .mfp-ajax-cur { 56 | cursor: progress; } 57 | 58 | .mfp-zoom-out-cur, .mfp-zoom-out-cur .mfp-image-holder .mfp-close { 59 | cursor: -moz-zoom-out; 60 | cursor: -webkit-zoom-out; 61 | cursor: zoom-out; } 62 | 63 | .mfp-zoom { 64 | cursor: pointer; 65 | cursor: -webkit-zoom-in; 66 | cursor: -moz-zoom-in; 67 | cursor: zoom-in; } 68 | 69 | .mfp-auto-cursor .mfp-content { 70 | cursor: auto; } 71 | 72 | .mfp-close, 73 | .mfp-arrow, 74 | .mfp-preloader, 75 | .mfp-counter { 76 | -webkit-user-select: none; 77 | -moz-user-select: none; 78 | user-select: none; } 79 | 80 | .mfp-loading.mfp-figure { 81 | display: none; } 82 | 83 | .mfp-hide { 84 | display: none !important; } 85 | 86 | .mfp-preloader { 87 | color: #CCC; 88 | position: absolute; 89 | top: 50%; 90 | width: auto; 91 | text-align: center; 92 | margin-top: -0.8em; 93 | left: 8px; 94 | right: 8px; 95 | z-index: 1044; } 96 | .mfp-preloader a { 97 | color: #CCC; } 98 | .mfp-preloader a:hover { 99 | color: #FFF; } 100 | 101 | .mfp-s-ready .mfp-preloader { 102 | display: none; } 103 | 104 | .mfp-s-error .mfp-content { 105 | display: none; } 106 | 107 | button.mfp-close, 108 | button.mfp-arrow { 109 | overflow: visible; 110 | cursor: pointer; 111 | background: transparent; 112 | border: 0; 113 | -webkit-appearance: none; 114 | display: block; 115 | outline: none; 116 | padding: 0; 117 | z-index: 1046; 118 | box-shadow: none; 119 | touch-action: manipulation; } 120 | 121 | button::-moz-focus-inner { 122 | padding: 0; 123 | border: 0; } 124 | 125 | .mfp-close { 126 | width: 44px; 127 | height: 44px; 128 | line-height: 44px; 129 | position: absolute; 130 | right: 0; 131 | top: 0; 132 | text-decoration: none; 133 | text-align: center; 134 | opacity: 0.65; 135 | padding: 0 0 18px 10px; 136 | color: #FFF; 137 | font-style: normal; 138 | font-size: 28px; 139 | font-family: Arial, Baskerville, monospace; } 140 | .mfp-close:hover, 141 | .mfp-close:focus { 142 | opacity: 1; } 143 | .mfp-close:active { 144 | top: 1px; } 145 | 146 | .mfp-close-btn-in .mfp-close { 147 | color: #333; } 148 | 149 | .mfp-image-holder .mfp-close, 150 | .mfp-iframe-holder .mfp-close { 151 | color: #FFF; 152 | right: -6px; 153 | text-align: right; 154 | padding-right: 6px; 155 | width: 100%; } 156 | 157 | .mfp-counter { 158 | position: absolute; 159 | top: 0; 160 | right: 0; 161 | color: #CCC; 162 | font-size: 12px; 163 | line-height: 18px; 164 | white-space: nowrap; } 165 | 166 | .mfp-arrow { 167 | position: absolute; 168 | opacity: 0.65; 169 | margin: 0; 170 | top: 50%; 171 | margin-top: -55px; 172 | padding: 0; 173 | width: 90px; 174 | height: 110px; 175 | -webkit-tap-highlight-color: transparent; } 176 | .mfp-arrow:active { 177 | margin-top: -54px; } 178 | .mfp-arrow:hover, 179 | .mfp-arrow:focus { 180 | opacity: 1; } 181 | .mfp-arrow:before, 182 | .mfp-arrow:after { 183 | content: ''; 184 | display: block; 185 | width: 0; 186 | height: 0; 187 | position: absolute; 188 | left: 0; 189 | top: 0; 190 | margin-top: 35px; 191 | margin-left: 35px; 192 | border: medium inset transparent; } 193 | .mfp-arrow:after { 194 | border-top-width: 13px; 195 | border-bottom-width: 13px; 196 | top: 8px; } 197 | .mfp-arrow:before { 198 | border-top-width: 21px; 199 | border-bottom-width: 21px; 200 | opacity: 0.7; } 201 | 202 | .mfp-arrow-left { 203 | left: 0; } 204 | .mfp-arrow-left:after { 205 | border-right: 17px solid #FFF; 206 | margin-left: 31px; } 207 | .mfp-arrow-left:before { 208 | margin-left: 25px; 209 | border-right: 27px solid #3F3F3F; } 210 | 211 | .mfp-arrow-right { 212 | right: 0; } 213 | .mfp-arrow-right:after { 214 | border-left: 17px solid #FFF; 215 | margin-left: 39px; } 216 | .mfp-arrow-right:before { 217 | border-left: 27px solid #3F3F3F; } 218 | 219 | .mfp-iframe-holder { 220 | padding-top: 40px; 221 | padding-bottom: 40px; } 222 | .mfp-iframe-holder .mfp-content { 223 | line-height: 0; 224 | width: 100%; 225 | max-width: 900px; } 226 | .mfp-iframe-holder .mfp-close { 227 | top: -40px; } 228 | 229 | .mfp-iframe-scaler { 230 | width: 100%; 231 | height: 0; 232 | overflow: hidden; 233 | padding-top: 56.25%; } 234 | .mfp-iframe-scaler iframe { 235 | position: absolute; 236 | display: block; 237 | top: 0; 238 | left: 0; 239 | width: 100%; 240 | height: 100%; 241 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); 242 | background: #000; } 243 | 244 | /* Main image in popup */ 245 | img.mfp-img { 246 | width: auto; 247 | max-width: 100%; 248 | height: auto; 249 | display: block; 250 | line-height: 0; 251 | box-sizing: border-box; 252 | padding: 40px 0 40px; 253 | margin: 0 auto; } 254 | 255 | /* The shadow behind the image */ 256 | .mfp-figure { 257 | line-height: 0; } 258 | .mfp-figure:after { 259 | content: ''; 260 | position: absolute; 261 | left: 0; 262 | top: 40px; 263 | bottom: 40px; 264 | display: block; 265 | right: 0; 266 | width: auto; 267 | height: auto; 268 | z-index: -1; 269 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.6); 270 | background: #444; } 271 | .mfp-figure small { 272 | color: #BDBDBD; 273 | display: block; 274 | font-size: 12px; 275 | line-height: 14px; } 276 | .mfp-figure figure { 277 | margin: 0; } 278 | 279 | .mfp-bottom-bar { 280 | margin-top: -36px; 281 | position: absolute; 282 | top: 100%; 283 | left: 0; 284 | width: 100%; 285 | cursor: auto; } 286 | 287 | .mfp-title { 288 | text-align: left; 289 | line-height: 18px; 290 | color: #F3F3F3; 291 | word-wrap: break-word; 292 | padding-right: 36px; } 293 | 294 | .mfp-image-holder .mfp-content { 295 | max-width: 100%; } 296 | 297 | .mfp-gallery .mfp-image-holder .mfp-figure { 298 | cursor: pointer; } 299 | 300 | @media screen and (max-width: 800px) and (orientation: landscape), screen and (max-height: 300px) { 301 | /** 302 | * Remove all paddings around the image on small screen 303 | */ 304 | .mfp-img-mobile .mfp-image-holder { 305 | padding-left: 0; 306 | padding-right: 0; } 307 | .mfp-img-mobile img.mfp-img { 308 | padding: 0; } 309 | .mfp-img-mobile .mfp-figure:after { 310 | top: 0; 311 | bottom: 0; } 312 | .mfp-img-mobile .mfp-figure small { 313 | display: inline; 314 | margin-left: 5px; } 315 | .mfp-img-mobile .mfp-bottom-bar { 316 | background: rgba(0, 0, 0, 0.6); 317 | bottom: 0; 318 | margin: 0; 319 | top: auto; 320 | padding: 3px 5px; 321 | position: fixed; 322 | box-sizing: border-box; } 323 | .mfp-img-mobile .mfp-bottom-bar:empty { 324 | padding: 0; } 325 | .mfp-img-mobile .mfp-counter { 326 | right: 5px; 327 | top: 3px; } 328 | .mfp-img-mobile .mfp-close { 329 | top: 0; 330 | right: 0; 331 | width: 35px; 332 | height: 35px; 333 | line-height: 35px; 334 | background: rgba(0, 0, 0, 0.6); 335 | position: fixed; 336 | text-align: center; 337 | padding: 0; } } 338 | 339 | @media all and (max-width: 900px) { 340 | .mfp-arrow { 341 | -webkit-transform: scale(0.75); 342 | transform: scale(0.75); } 343 | .mfp-arrow-left { 344 | -webkit-transform-origin: 0; 345 | transform-origin: 0; } 346 | .mfp-arrow-right { 347 | -webkit-transform-origin: 100%; 348 | transform-origin: 100%; } 349 | .mfp-container { 350 | padding-left: 6px; 351 | padding-right: 6px; } } 352 | -------------------------------------------------------------------------------- /resources/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | @import url('https://fonts.googleapis.com/css?family=Muli:200,300,400'); 3 | 4 | body { 5 | background: #ffffff; 6 | font-family: 'Muli', sans-serif; 7 | font-style: normal; 8 | font-weight: 300; 9 | overflow-x: hidden; 10 | } 11 | 12 | html, body { 13 | width: 100%; 14 | height: 100%; 15 | } 16 | 17 | 18 | 19 | /*--------------------------------------- 20 | Typorgraphy 21 | -----------------------------------------*/ 22 | 23 | h1,h2,h3,h4,h5,h6 { 24 | font-style: normal; 25 | font-weight: 200; 26 | letter-spacing: 0px; 27 | } 28 | 29 | h1 { 30 | color: #3d3d3f; 31 | font-size: 50px; 32 | line-height: normal; 33 | } 34 | 35 | h2 { 36 | color: #575757; 37 | font-size: 40px; 38 | line-height: 52px; 39 | margin-top: 0px; 40 | } 41 | 42 | h4 { 43 | color: #797979; 44 | font-size: 18px; 45 | font-weight: normal; 46 | } 47 | 48 | p { 49 | color: #878787; 50 | font-size: 16px; 51 | font-weight: 300; 52 | line-height: 25px; 53 | letter-spacing: 0.2px; 54 | } 55 | 56 | strong, span { 57 | color: #878787; 58 | font-weight: normal; 59 | } 60 | 61 | 62 | 63 | /*--------------------------------------- 64 | Buttons 65 | -----------------------------------------*/ 66 | 67 | .section-btn { 68 | background: #d7b065; 69 | border: none; 70 | border-radius: 50px; 71 | color: #ffffff; 72 | font-size: 13px; 73 | font-weight: bold; 74 | letter-spacing: 1.6px; 75 | padding: 14px 32px 18px 32px; 76 | margin-top: 32px; 77 | -webkit-transition: all ease-in-out 0.4s; 78 | transition: all ease-in-out 0.4s; 79 | } 80 | 81 | .section-btn:focus, 82 | .section-btn:hover { 83 | background: #000000; 84 | color: #ffffff; 85 | } 86 | 87 | .copyrights{ 88 | text-indent:-9999px; 89 | height:0; 90 | line-height:0; 91 | font-size:0; 92 | overflow:hidden; 93 | } 94 | 95 | /*--------------------------------------- 96 | General 97 | -----------------------------------------*/ 98 | 99 | html{ 100 | -webkit-font-smoothing: antialiased; 101 | } 102 | 103 | a { 104 | color: #575757; 105 | -webkit-transition: 0.5s; 106 | transition: 0.5s; 107 | text-decoration: none !important; 108 | } 109 | 110 | a:hover, a:active, a:focus { 111 | color: #000000; 112 | outline: none; 113 | } 114 | 115 | * { 116 | -webkit-box-sizing: border-box; 117 | box-sizing: border-box; 118 | } 119 | 120 | *:before, 121 | *:after { 122 | -webkit-box-sizing: border-box; 123 | box-sizing: border-box; 124 | } 125 | 126 | .section-title { 127 | margin: 0; 128 | padding-bottom: 32px; 129 | } 130 | 131 | #about, #work, 132 | #contact { 133 | position: relative; 134 | padding-top: 80px; 135 | padding-bottom: 80px; 136 | } 137 | 138 | #about img, #team img { 139 | border-radius: 5px; 140 | } 141 | 142 | #work { 143 | border-top: 1px solid #f0f0f0; 144 | border-bottom: 1px solid #f0f0f0; 145 | } 146 | 147 | #contact { 148 | text-align: center; 149 | } 150 | 151 | 152 | 153 | /*--------------------------------------- 154 | Pre loader section 155 | -----------------------------------------*/ 156 | 157 | .preloader { 158 | position: fixed; 159 | top: 0; 160 | left: 0; 161 | width: 100%; 162 | height: 100%; 163 | z-index: 99999; 164 | display: flex; 165 | flex-flow: row nowrap; 166 | justify-content: center; 167 | align-items: center; 168 | background: none repeat scroll 0 0 #ffffff; 169 | } 170 | 171 | .spinner { 172 | border: 1px solid transparent; 173 | border-radius: 5px; 174 | position: relative; 175 | } 176 | 177 | .spinner:before { 178 | content: ''; 179 | box-sizing: border-box; 180 | position: absolute; 181 | top: 50%; 182 | left: 50%; 183 | width: 65px; 184 | height: 65px; 185 | margin-top: -10px; 186 | margin-left: -10px; 187 | border-radius: 50%; 188 | border: 1px solid #000000; 189 | border-top-color: #f9f9f9; 190 | animation: spinner .9s linear infinite; 191 | } 192 | 193 | @-webkit-@keyframes spinner { 194 | to {transform: rotate(360deg);} 195 | } 196 | 197 | @keyframes spinner { 198 | to {transform: rotate(360deg);} 199 | } 200 | 201 | 202 | 203 | /*--------------------------------------- 204 | Navigation section 205 | -----------------------------------------*/ 206 | 207 | .custom-navbar { 208 | border: none; 209 | margin-bottom: 0; 210 | background-color: #ffffff; 211 | padding-top: 22px; 212 | } 213 | 214 | .custom-navbar .navbar-brand { 215 | color: #444; 216 | font-weight: normal; 217 | font-size: 20px; 218 | } 219 | 220 | .custom-navbar .nav li a { 221 | font-size: 12px; 222 | font-weight: normal; 223 | color: #656565; 224 | letter-spacing: 1px; 225 | -webkit-transition: all ease-in-out 0.4s; 226 | transition: all ease-in-out 0.4s; 227 | padding: 0; 228 | margin: 15px; 229 | } 230 | 231 | .custom-navbar .navbar-nav > li > a:hover, 232 | .custom-navbar .navbar-nav > li > a:focus { 233 | background-color: transparent; 234 | color: #454545; 235 | } 236 | 237 | .custom-navbar .navbar-nav li a:after { 238 | content: ""; 239 | position: absolute; 240 | display: block; 241 | width: 0px; 242 | height: 2px; 243 | margin: auto; 244 | background: transparent; 245 | transition: width .3s ease, background-color .3s ease; 246 | } 247 | 248 | .custom-navbar .navbar-nav li a:hover:after, 249 | .custom-navbar .nav li.active > a:after { 250 | background: #000000; 251 | color: #ffffff; 252 | width: 100%; 253 | } 254 | 255 | .custom-navbar .nav li.active > a { 256 | background-color: transparent; 257 | color: #454545; 258 | } 259 | 260 | .custom-navbar .navbar-toggle { 261 | border: none; 262 | padding-top: 12px; 263 | } 264 | 265 | .custom-navbar .navbar-toggle { 266 | background-color: transparent; 267 | } 268 | 269 | .custom-navbar .navbar-toggle .icon-bar { 270 | background: #000000; 271 | border-color: transparent; 272 | } 273 | 274 | @media(min-width:768px) { 275 | .custom-navbar { 276 | border-bottom: 0; 277 | background: 0 0; 278 | } 279 | .custom-navbar.top-nav-collapse { 280 | background: #ffffff; 281 | box-shadow:0 40px 100px rgba(0,0,0,.2); 282 | padding: 10px 0; 283 | } 284 | 285 | } 286 | 287 | 288 | 289 | /*--------------------------------------- 290 | Home section 291 | -----------------------------------------*/ 292 | 293 | #home { 294 | display: -webkit-box; 295 | display: -webkit-flex; 296 | display: -ms-flexbox; 297 | display: flex; 298 | -webkit-box-align: center; 299 | -webkit-align-items: center; 300 | -ms-flex-align: center; 301 | align-items: center; 302 | height: 100vh; 303 | position: relative; 304 | padding-top: 62px; 305 | } 306 | 307 | #home img { 308 | width: 120px; 309 | height: 120px; 310 | } 311 | 312 | 313 | /*--------------------------------------- 314 | About section 315 | -----------------------------------------*/ 316 | 317 | #about .section-title { 318 | padding-bottom: 16px; 319 | } 320 | 321 | #about .col-md-4 a { 322 | width: 100px; 323 | height: 100px; 324 | display: inline-block; 325 | margin: 6px 6px 0px 0; 326 | } 327 | 328 | #about .about-thumb { 329 | margin-top: 22px; 330 | } 331 | 332 | #about .about-thumb strong { 333 | font-weight: normal; 334 | display: block; 335 | padding-top: 4px; 336 | } 337 | 338 | 339 | 340 | /*--------------------------------------- 341 | Skill section 342 | -----------------------------------------*/ 343 | 344 | #skill { 345 | border-top: 1px solid #f0f0f0; 346 | padding-top: 80px; 347 | padding-bottom: 60px; 348 | } 349 | 350 | .skill-thumb strong, 351 | .skill-thumb span { 352 | color: #575757; 353 | font-size: 16px; 354 | padding-bottom: 8px; 355 | display: inline-block; 356 | } 357 | 358 | .skill-thumb .progress { 359 | background: #ffffff; 360 | border-radius: 5px; 361 | box-shadow: none; 362 | height: 4px; 363 | } 364 | 365 | .skill-thumb .progress-bar-primary { 366 | background: #3d3d3f; 367 | } 368 | 369 | 370 | 371 | /*--------------------------------------- 372 | Work section 373 | -----------------------------------------*/ 374 | 375 | #work .work-thumb { 376 | border-radius: 5px; 377 | margin-bottom: 15px; 378 | padding: 0; 379 | overflow: hidden; 380 | position: relative; 381 | top: 0; 382 | -webkit-transition: all ease-in-out 0.4s; 383 | transition: all ease-in-out 0.4s; 384 | } 385 | 386 | #work .work-thumb:hover { 387 | background: #ffffff; 388 | box-shadow: 0px 16px 22px 0px rgba(90, 91, 95, 0.3); 389 | top: -5px; 390 | } 391 | 392 | #work .work-thumb img { 393 | border-radius: 5px; 394 | } 395 | 396 | 397 | 398 | /*--------------------------------------- 399 | Contact section 400 | -----------------------------------------*/ 401 | 402 | #contact .form-control { 403 | border-radius: 0px; 404 | border-color: #f0f0f0; 405 | box-shadow: none; 406 | font-size: 16px; 407 | margin-top: 12px; 408 | margin-bottom: 12px; 409 | -webkit-transition: all ease-in-out 0.4s; 410 | transition: all ease-in-out 0.4s; 411 | } 412 | 413 | #contact .form-control:focus { 414 | border-bottom: 2px solid #999999; 415 | } 416 | 417 | #contact input { 418 | height: 55px; 419 | border: none; 420 | border-bottom: 1px solid #f0f0f0; 421 | } 422 | 423 | #contact button#submit { 424 | background: #000000; 425 | border: none; 426 | border-radius: 50px; 427 | color: #ffffff; 428 | font-weight: 300; 429 | height: 55px; 430 | padding-bottom: 10px; 431 | margin-top: 24px; 432 | } 433 | 434 | #contact button#submit:hover { 435 | background: #d7b065; 436 | color: #ffffff; 437 | } 438 | 439 | 440 | 441 | /*--------------------------------------- 442 | Social icon 443 | -----------------------------------------*/ 444 | 445 | .social-icon { 446 | position: relative; 447 | padding: 0; 448 | margin: 0; 449 | } 450 | 451 | .social-icon li { 452 | display: inline-block; 453 | list-style: none; 454 | } 455 | 456 | .social-icon li a { 457 | background: #292929; 458 | border-radius: 100%; 459 | color: #ffffff; 460 | cursor: pointer; 461 | font-size: 16px; 462 | text-decoration: none; 463 | transition: all 0.4s ease-in-out; 464 | width: 30px; 465 | height: 30px; 466 | line-height: 30px; 467 | text-align: center; 468 | vertical-align: middle; 469 | position: relative; 470 | margin: 20px 6px 10px 6px; 471 | } 472 | 473 | .social-icon li a:hover { 474 | background: #d7b065; 475 | transform: scale(1.1); 476 | } 477 | 478 | 479 | /*--------------------------------------- 480 | Mobile Responsive styles 481 | -----------------------------------------*/ 482 | 483 | @media (min-width: 768px) and (max-width: 1024px) { 484 | #home { 485 | height: 50vh; 486 | } 487 | } 488 | 489 | @media (min-width: 667px) and (max-width: 767px) { 490 | #home { 491 | height: 140vh; 492 | } 493 | } 494 | 495 | @media (min-width: 568px) and (max-width: 665px) { 496 | #home { 497 | height: 190vh; 498 | } 499 | } 500 | 501 | @media (max-width: 980px) { 502 | 503 | h1 {font-size: 33px;} 504 | 505 | #work .work-thumb { 506 | margin-top: 30px; 507 | } 508 | 509 | } 510 | 511 | 512 | @media (max-width: 768px) { 513 | 514 | h1 { 515 | font-size: 30px; 516 | line-height: normal; 517 | } 518 | 519 | h2 {font-size: 30px;} 520 | 521 | .custom-navbar { 522 | background-color: #ffffff; 523 | box-shadow:0 40px 100px rgba(0,0,0,.2); 524 | padding-top: 0px; 525 | padding-bottom: 5px; 526 | } 527 | 528 | .custom-navbar .nav { 529 | padding-bottom: 10px; 530 | } 531 | 532 | .custom-navbar .nav li a { 533 | display: inline-block; 534 | margin-bottom: 5px; 535 | } 536 | 537 | } 538 | 539 | 540 | @media (max-width: 580px) { 541 | 542 | #about .about-thumb { 543 | margin-top: 0px; 544 | } 545 | 546 | .about-thumb .social-icon { 547 | margin-bottom: 15px; 548 | } 549 | 550 | } 551 | 552 | 553 | @media (max-width: 357px) { 554 | 555 | h1 { 556 | font-size: 28px; 557 | } 558 | 559 | #about .col-md-4 a { 560 | width: 85px; 561 | height: 85px; 562 | } 563 | 564 | } 565 | -------------------------------------------------------------------------------- /resources/error.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MARK-error 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 61 | 62 | 63 | 64 |
65 | 66 |
67 |
68 | 69 |
70 | about image 72 |
73 | 74 |
75 |

错误!

76 | 77 |
78 | 79 |
80 |
81 |
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /resources/fonts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/fonts/.DS_Store -------------------------------------------------------------------------------- /resources/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /resources/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /resources/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /resources/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /resources/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /resources/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/images/.DS_Store -------------------------------------------------------------------------------- /resources/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/images/favicon.ico -------------------------------------------------------------------------------- /resources/images/instagram-image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/images/instagram-image1.jpg -------------------------------------------------------------------------------- /resources/images/instagram-image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/images/instagram-image2.jpg -------------------------------------------------------------------------------- /resources/images/instagram-image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/images/instagram-image3.jpg -------------------------------------------------------------------------------- /resources/images/instagram-image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/images/instagram-image4.jpg -------------------------------------------------------------------------------- /resources/images/instagram-image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/images/instagram-image5.jpg -------------------------------------------------------------------------------- /resources/images/profile-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/images/profile-image.jpg -------------------------------------------------------------------------------- /resources/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MARK-首页 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 60 | 61 |
62 |
63 |
64 | 65 |
66 | about image 68 |
69 |
70 |

你好,这是首页。

71 |
72 |
73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /resources/js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/js/.DS_Store -------------------------------------------------------------------------------- /resources/js/custom.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 3 | "use strict"; 4 | 5 | // PRE loader 6 | $(window).load(function(){ 7 | $('.preloader').fadeOut(1000); // set duration in brackets 8 | }); 9 | 10 | 11 | //Navigation Section 12 | $('.navbar-collapse a').on('click',function(){ 13 | $(".navbar-collapse").collapse('hide'); 14 | }); 15 | 16 | $(window).scroll(function() { 17 | if ($(".navbar").offset().top > 50) { 18 | $(".navbar-fixed-top").addClass("top-nav-collapse"); 19 | } else { 20 | $(".navbar-fixed-top").removeClass("top-nav-collapse"); 21 | } 22 | }); 23 | 24 | 25 | // Smoothscroll js 26 | $(function() { 27 | $('.custom-navbar a, #home a').bind('click', function(event) { 28 | var $anchor = $(this); 29 | $('html, body').stop().animate({ 30 | scrollTop: $($anchor.attr('href')).offset().top - 49 31 | }, 1000); 32 | event.preventDefault(); 33 | }); 34 | }); 35 | 36 | 37 | // WOW Animation js 38 | new WOW({ mobile: false }).init(); 39 | 40 | })(jQuery); 41 | -------------------------------------------------------------------------------- /resources/js/jquery.magnific-popup.min.js: -------------------------------------------------------------------------------- 1 | /*! Magnific Popup - v0.9.9 - 2014-09-06 2 | * http://dimsemenov.com/plugins/magnific-popup/ 3 | * Copyright (c) 2014 Dmitry Semenov; */ 4 | (function(e){var t,n,i,o,r,a,s,l="Close",c="BeforeClose",d="AfterClose",u="BeforeAppend",p="MarkupParse",f="Open",m="Change",g="mfp",h="."+g,v="mfp-ready",C="mfp-removing",y="mfp-prevent-close",w=function(){},b=!!window.jQuery,I=e(window),x=function(e,n){t.ev.on(g+e+h,n)},k=function(t,n,i,o){var r=document.createElement("div");return r.className="mfp-"+t,i&&(r.innerHTML=i),o?n&&n.appendChild(r):(r=e(r),n&&r.appendTo(n)),r},T=function(n,i){t.ev.triggerHandler(g+n,i),t.st.callbacks&&(n=n.charAt(0).toLowerCase()+n.slice(1),t.st.callbacks[n]&&t.st.callbacks[n].apply(t,e.isArray(i)?i:[i]))},E=function(n){return n===s&&t.currTemplate.closeBtn||(t.currTemplate.closeBtn=e(t.st.closeMarkup.replace("%title%",t.st.tClose)),s=n),t.currTemplate.closeBtn},_=function(){e.magnificPopup.instance||(t=new w,t.init(),e.magnificPopup.instance=t)},S=function(){var e=document.createElement("p").style,t=["ms","O","Moz","Webkit"];if(void 0!==e.transition)return!0;for(;t.length;)if(t.pop()+"Transition"in e)return!0;return!1};w.prototype={constructor:w,init:function(){var n=navigator.appVersion;t.isIE7=-1!==n.indexOf("MSIE 7."),t.isIE8=-1!==n.indexOf("MSIE 8."),t.isLowIE=t.isIE7||t.isIE8,t.isAndroid=/android/gi.test(n),t.isIOS=/iphone|ipad|ipod/gi.test(n),t.supportsTransition=S(),t.probablyMobile=t.isAndroid||t.isIOS||/(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent),o=e(document),t.popupsCache={}},open:function(n){i||(i=e(document.body));var r;if(n.isObj===!1){t.items=n.items.toArray(),t.index=0;var s,l=n.items;for(r=0;l.length>r;r++)if(s=l[r],s.parsed&&(s=s.el[0]),s===n.el[0]){t.index=r;break}}else t.items=e.isArray(n.items)?n.items:[n.items],t.index=n.index||0;if(t.isOpen)return t.updateItemHTML(),void 0;t.types=[],a="",t.ev=n.mainEl&&n.mainEl.length?n.mainEl.eq(0):o,n.key?(t.popupsCache[n.key]||(t.popupsCache[n.key]={}),t.currTemplate=t.popupsCache[n.key]):t.currTemplate={},t.st=e.extend(!0,{},e.magnificPopup.defaults,n),t.fixedContentPos="auto"===t.st.fixedContentPos?!t.probablyMobile:t.st.fixedContentPos,t.st.modal&&(t.st.closeOnContentClick=!1,t.st.closeOnBgClick=!1,t.st.showCloseBtn=!1,t.st.enableEscapeKey=!1),t.bgOverlay||(t.bgOverlay=k("bg").on("click"+h,function(){t.close()}),t.wrap=k("wrap").attr("tabindex",-1).on("click"+h,function(e){t._checkIfClose(e.target)&&t.close()}),t.container=k("container",t.wrap)),t.contentContainer=k("content"),t.st.preloader&&(t.preloader=k("preloader",t.container,t.st.tLoading));var c=e.magnificPopup.modules;for(r=0;c.length>r;r++){var d=c[r];d=d.charAt(0).toUpperCase()+d.slice(1),t["init"+d].call(t)}T("BeforeOpen"),t.st.showCloseBtn&&(t.st.closeBtnInside?(x(p,function(e,t,n,i){n.close_replaceWith=E(i.type)}),a+=" mfp-close-btn-in"):t.wrap.append(E())),t.st.alignTop&&(a+=" mfp-align-top"),t.fixedContentPos?t.wrap.css({overflow:t.st.overflowY,overflowX:"hidden",overflowY:t.st.overflowY}):t.wrap.css({top:I.scrollTop(),position:"absolute"}),(t.st.fixedBgPos===!1||"auto"===t.st.fixedBgPos&&!t.fixedContentPos)&&t.bgOverlay.css({height:o.height(),position:"absolute"}),t.st.enableEscapeKey&&o.on("keyup"+h,function(e){27===e.keyCode&&t.close()}),I.on("resize"+h,function(){t.updateSize()}),t.st.closeOnContentClick||(a+=" mfp-auto-cursor"),a&&t.wrap.addClass(a);var u=t.wH=I.height(),m={};if(t.fixedContentPos&&t._hasScrollBar(u)){var g=t._getScrollbarSize();g&&(m.marginRight=g)}t.fixedContentPos&&(t.isIE7?e("body, html").css("overflow","hidden"):m.overflow="hidden");var C=t.st.mainClass;return t.isIE7&&(C+=" mfp-ie7"),C&&t._addClassToMFP(C),t.updateItemHTML(),T("BuildControls"),e("html").css(m),t.bgOverlay.add(t.wrap).prependTo(t.st.prependTo||i),t._lastFocusedEl=document.activeElement,setTimeout(function(){t.content?(t._addClassToMFP(v),t._setFocus()):t.bgOverlay.addClass(v),o.on("focusin"+h,t._onFocusIn)},16),t.isOpen=!0,t.updateSize(u),T(f),n},close:function(){t.isOpen&&(T(c),t.isOpen=!1,t.st.removalDelay&&!t.isLowIE&&t.supportsTransition?(t._addClassToMFP(C),setTimeout(function(){t._close()},t.st.removalDelay)):t._close())},_close:function(){T(l);var n=C+" "+v+" ";if(t.bgOverlay.detach(),t.wrap.detach(),t.container.empty(),t.st.mainClass&&(n+=t.st.mainClass+" "),t._removeClassFromMFP(n),t.fixedContentPos){var i={marginRight:""};t.isIE7?e("body, html").css("overflow",""):i.overflow="",e("html").css(i)}o.off("keyup"+h+" focusin"+h),t.ev.off(h),t.wrap.attr("class","mfp-wrap").removeAttr("style"),t.bgOverlay.attr("class","mfp-bg"),t.container.attr("class","mfp-container"),!t.st.showCloseBtn||t.st.closeBtnInside&&t.currTemplate[t.currItem.type]!==!0||t.currTemplate.closeBtn&&t.currTemplate.closeBtn.detach(),t._lastFocusedEl&&e(t._lastFocusedEl).focus(),t.currItem=null,t.content=null,t.currTemplate=null,t.prevHeight=0,T(d)},updateSize:function(e){if(t.isIOS){var n=document.documentElement.clientWidth/window.innerWidth,i=window.innerHeight*n;t.wrap.css("height",i),t.wH=i}else t.wH=e||I.height();t.fixedContentPos||t.wrap.css("height",t.wH),T("Resize")},updateItemHTML:function(){var n=t.items[t.index];t.contentContainer.detach(),t.content&&t.content.detach(),n.parsed||(n=t.parseEl(t.index));var i=n.type;if(T("BeforeChange",[t.currItem?t.currItem.type:"",i]),t.currItem=n,!t.currTemplate[i]){var o=t.st[i]?t.st[i].markup:!1;T("FirstMarkupParse",o),t.currTemplate[i]=o?e(o):!0}r&&r!==n.type&&t.container.removeClass("mfp-"+r+"-holder");var a=t["get"+i.charAt(0).toUpperCase()+i.slice(1)](n,t.currTemplate[i]);t.appendContent(a,i),n.preloaded=!0,T(m,n),r=n.type,t.container.prepend(t.contentContainer),T("AfterChange")},appendContent:function(e,n){t.content=e,e?t.st.showCloseBtn&&t.st.closeBtnInside&&t.currTemplate[n]===!0?t.content.find(".mfp-close").length||t.content.append(E()):t.content=e:t.content="",T(u),t.container.addClass("mfp-"+n+"-holder"),t.contentContainer.append(t.content)},parseEl:function(n){var i,o=t.items[n];if(o.tagName?o={el:e(o)}:(i=o.type,o={data:o,src:o.src}),o.el){for(var r=t.types,a=0;r.length>a;a++)if(o.el.hasClass("mfp-"+r[a])){i=r[a];break}o.src=o.el.attr("data-mfp-src"),o.src||(o.src=o.el.attr("href"))}return o.type=i||t.st.type||"inline",o.index=n,o.parsed=!0,t.items[n]=o,T("ElementParse",o),t.items[n]},addGroup:function(e,n){var i=function(i){i.mfpEl=this,t._openClick(i,e,n)};n||(n={});var o="click.magnificPopup";n.mainEl=e,n.items?(n.isObj=!0,e.off(o).on(o,i)):(n.isObj=!1,n.delegate?e.off(o).on(o,n.delegate,i):(n.items=e,e.off(o).on(o,i)))},_openClick:function(n,i,o){var r=void 0!==o.midClick?o.midClick:e.magnificPopup.defaults.midClick;if(r||2!==n.which&&!n.ctrlKey&&!n.metaKey){var a=void 0!==o.disableOn?o.disableOn:e.magnificPopup.defaults.disableOn;if(a)if(e.isFunction(a)){if(!a.call(t))return!0}else if(a>I.width())return!0;n.type&&(n.preventDefault(),t.isOpen&&n.stopPropagation()),o.el=e(n.mfpEl),o.delegate&&(o.items=i.find(o.delegate)),t.open(o)}},updateStatus:function(e,i){if(t.preloader){n!==e&&t.container.removeClass("mfp-s-"+n),i||"loading"!==e||(i=t.st.tLoading);var o={status:e,text:i};T("UpdateStatus",o),e=o.status,i=o.text,t.preloader.html(i),t.preloader.find("a").on("click",function(e){e.stopImmediatePropagation()}),t.container.addClass("mfp-s-"+e),n=e}},_checkIfClose:function(n){if(!e(n).hasClass(y)){var i=t.st.closeOnContentClick,o=t.st.closeOnBgClick;if(i&&o)return!0;if(!t.content||e(n).hasClass("mfp-close")||t.preloader&&n===t.preloader[0])return!0;if(n===t.content[0]||e.contains(t.content[0],n)){if(i)return!0}else if(o&&e.contains(document,n))return!0;return!1}},_addClassToMFP:function(e){t.bgOverlay.addClass(e),t.wrap.addClass(e)},_removeClassFromMFP:function(e){this.bgOverlay.removeClass(e),t.wrap.removeClass(e)},_hasScrollBar:function(e){return(t.isIE7?o.height():document.body.scrollHeight)>(e||I.height())},_setFocus:function(){(t.st.focus?t.content.find(t.st.focus).eq(0):t.wrap).focus()},_onFocusIn:function(n){return n.target===t.wrap[0]||e.contains(t.wrap[0],n.target)?void 0:(t._setFocus(),!1)},_parseMarkup:function(t,n,i){var o;i.data&&(n=e.extend(i.data,n)),T(p,[t,n,i]),e.each(n,function(e,n){if(void 0===n||n===!1)return!0;if(o=e.split("_"),o.length>1){var i=t.find(h+"-"+o[0]);if(i.length>0){var r=o[1];"replaceWith"===r?i[0]!==n[0]&&i.replaceWith(n):"img"===r?i.is("img")?i.attr("src",n):i.replaceWith(''):i.attr(o[1],n)}}else t.find(h+"-"+e).html(n)})},_getScrollbarSize:function(){if(void 0===t.scrollbarSize){var e=document.createElement("div");e.style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(e),t.scrollbarSize=e.offsetWidth-e.clientWidth,document.body.removeChild(e)}return t.scrollbarSize}},e.magnificPopup={instance:null,proto:w.prototype,modules:[],open:function(t,n){return _(),t=t?e.extend(!0,{},t):{},t.isObj=!0,t.index=n||0,this.instance.open(t)},close:function(){return e.magnificPopup.instance&&e.magnificPopup.instance.close()},registerModule:function(t,n){n.options&&(e.magnificPopup.defaults[t]=n.options),e.extend(this.proto,n.proto),this.modules.push(t)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,prependTo:null,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'',tClose:"Close (Esc)",tLoading:"Loading..."}},e.fn.magnificPopup=function(n){_();var i=e(this);if("string"==typeof n)if("open"===n){var o,r=b?i.data("magnificPopup"):i[0].magnificPopup,a=parseInt(arguments[1],10)||0;r.items?o=r.items[a]:(o=i,r.delegate&&(o=o.find(r.delegate)),o=o.eq(a)),t._openClick({mfpEl:o},i,r)}else t.isOpen&&t[n].apply(t,Array.prototype.slice.call(arguments,1));else n=e.extend(!0,{},n),b?i.data("magnificPopup",n):i[0].magnificPopup=n,t.addGroup(i,n);return i};var P,O,z,M="inline",B=function(){z&&(O.after(z.addClass(P)).detach(),z=null)};e.magnificPopup.registerModule(M,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){t.types.push(M),x(l+"."+M,function(){B()})},getInline:function(n,i){if(B(),n.src){var o=t.st.inline,r=e(n.src);if(r.length){var a=r[0].parentNode;a&&a.tagName&&(O||(P=o.hiddenClass,O=k(P),P="mfp-"+P),z=r.after(O).detach().removeClass(P)),t.updateStatus("ready")}else t.updateStatus("error",o.tNotFound),r=e("
");return n.inlineElement=r,r}return t.updateStatus("ready"),t._parseMarkup(i,{},n),i}}});var F,H="ajax",L=function(){F&&i.removeClass(F)},A=function(){L(),t.req&&t.req.abort()};e.magnificPopup.registerModule(H,{options:{settings:null,cursor:"mfp-ajax-cur",tError:'The content could not be loaded.'},proto:{initAjax:function(){t.types.push(H),F=t.st.ajax.cursor,x(l+"."+H,A),x("BeforeChange."+H,A)},getAjax:function(n){F&&i.addClass(F),t.updateStatus("loading");var o=e.extend({url:n.src,success:function(i,o,r){var a={data:i,xhr:r};T("ParseAjax",a),t.appendContent(e(a.data),H),n.finished=!0,L(),t._setFocus(),setTimeout(function(){t.wrap.addClass(v)},16),t.updateStatus("ready"),T("AjaxContentAdded")},error:function(){L(),n.finished=n.loadError=!0,t.updateStatus("error",t.st.ajax.tError.replace("%url%",n.src))}},t.st.ajax.settings);return t.req=e.ajax(o),""}}});var j,N=function(n){if(n.data&&void 0!==n.data.title)return n.data.title;var i=t.st.image.titleSrc;if(i){if(e.isFunction(i))return i.call(t,n);if(n.el)return n.el.attr(i)||""}return""};e.magnificPopup.registerModule("image",{options:{markup:'
',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'The image could not be loaded.'},proto:{initImage:function(){var e=t.st.image,n=".image";t.types.push("image"),x(f+n,function(){"image"===t.currItem.type&&e.cursor&&i.addClass(e.cursor)}),x(l+n,function(){e.cursor&&i.removeClass(e.cursor),I.off("resize"+h)}),x("Resize"+n,t.resizeImage),t.isLowIE&&x("AfterChange",t.resizeImage)},resizeImage:function(){var e=t.currItem;if(e&&e.img&&t.st.image.verticalFit){var n=0;t.isLowIE&&(n=parseInt(e.img.css("padding-top"),10)+parseInt(e.img.css("padding-bottom"),10)),e.img.css("max-height",t.wH-n)}},_onImageHasSize:function(e){e.img&&(e.hasSize=!0,j&&clearInterval(j),e.isCheckingImgSize=!1,T("ImageHasSize",e),e.imgHidden&&(t.content&&t.content.removeClass("mfp-loading"),e.imgHidden=!1))},findImageSize:function(e){var n=0,i=e.img[0],o=function(r){j&&clearInterval(j),j=setInterval(function(){return i.naturalWidth>0?(t._onImageHasSize(e),void 0):(n>200&&clearInterval(j),n++,3===n?o(10):40===n?o(50):100===n&&o(500),void 0)},r)};o(1)},getImage:function(n,i){var o=0,r=function(){n&&(n.img[0].complete?(n.img.off(".mfploader"),n===t.currItem&&(t._onImageHasSize(n),t.updateStatus("ready")),n.hasSize=!0,n.loaded=!0,T("ImageLoadComplete")):(o++,200>o?setTimeout(r,100):a()))},a=function(){n&&(n.img.off(".mfploader"),n===t.currItem&&(t._onImageHasSize(n),t.updateStatus("error",s.tError.replace("%url%",n.src))),n.hasSize=!0,n.loaded=!0,n.loadError=!0)},s=t.st.image,l=i.find(".mfp-img");if(l.length){var c=document.createElement("img");c.className="mfp-img",n.img=e(c).on("load.mfploader",r).on("error.mfploader",a),c.src=n.src,l.is("img")&&(n.img=n.img.clone()),c=n.img[0],c.naturalWidth>0?n.hasSize=!0:c.width||(n.hasSize=!1)}return t._parseMarkup(i,{title:N(n),img_replaceWith:n.img},n),t.resizeImage(),n.hasSize?(j&&clearInterval(j),n.loadError?(i.addClass("mfp-loading"),t.updateStatus("error",s.tError.replace("%url%",n.src))):(i.removeClass("mfp-loading"),t.updateStatus("ready")),i):(t.updateStatus("loading"),n.loading=!0,n.hasSize||(n.imgHidden=!0,i.addClass("mfp-loading"),t.findImageSize(n)),i)}}});var W,R=function(){return void 0===W&&(W=void 0!==document.createElement("p").style.MozTransform),W};e.magnificPopup.registerModule("zoom",{options:{enabled:!1,easing:"ease-in-out",duration:300,opener:function(e){return e.is("img")?e:e.find("img")}},proto:{initZoom:function(){var e,n=t.st.zoom,i=".zoom";if(n.enabled&&t.supportsTransition){var o,r,a=n.duration,s=function(e){var t=e.clone().removeAttr("style").removeAttr("class").addClass("mfp-animated-image"),i="all "+n.duration/1e3+"s "+n.easing,o={position:"fixed",zIndex:9999,left:0,top:0,"-webkit-backface-visibility":"hidden"},r="transition";return o["-webkit-"+r]=o["-moz-"+r]=o["-o-"+r]=o[r]=i,t.css(o),t},d=function(){t.content.css("visibility","visible")};x("BuildControls"+i,function(){if(t._allowZoom()){if(clearTimeout(o),t.content.css("visibility","hidden"),e=t._getItemToZoom(),!e)return d(),void 0;r=s(e),r.css(t._getOffset()),t.wrap.append(r),o=setTimeout(function(){r.css(t._getOffset(!0)),o=setTimeout(function(){d(),setTimeout(function(){r.remove(),e=r=null,T("ZoomAnimationEnded")},16)},a)},16)}}),x(c+i,function(){if(t._allowZoom()){if(clearTimeout(o),t.st.removalDelay=a,!e){if(e=t._getItemToZoom(),!e)return;r=s(e)}r.css(t._getOffset(!0)),t.wrap.append(r),t.content.css("visibility","hidden"),setTimeout(function(){r.css(t._getOffset())},16)}}),x(l+i,function(){t._allowZoom()&&(d(),r&&r.remove(),e=null)})}},_allowZoom:function(){return"image"===t.currItem.type},_getItemToZoom:function(){return t.currItem.hasSize?t.currItem.img:!1},_getOffset:function(n){var i;i=n?t.currItem.img:t.st.zoom.opener(t.currItem.el||t.currItem);var o=i.offset(),r=parseInt(i.css("padding-top"),10),a=parseInt(i.css("padding-bottom"),10);o.top-=e(window).scrollTop()-r;var s={width:i.width(),height:(b?i.innerHeight():i[0].offsetHeight)-a-r};return R()?s["-moz-transform"]=s.transform="translate("+o.left+"px,"+o.top+"px)":(s.left=o.left,s.top=o.top),s}}});var Z="iframe",q="//about:blank",D=function(e){if(t.currTemplate[Z]){var n=t.currTemplate[Z].find("iframe");n.length&&(e||(n[0].src=q),t.isIE8&&n.css("display",e?"block":"none"))}};e.magnificPopup.registerModule(Z,{options:{markup:'
',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){t.types.push(Z),x("BeforeChange",function(e,t,n){t!==n&&(t===Z?D():n===Z&&D(!0))}),x(l+"."+Z,function(){D()})},getIframe:function(n,i){var o=n.src,r=t.st.iframe;e.each(r.patterns,function(){return o.indexOf(this.index)>-1?(this.id&&(o="string"==typeof this.id?o.substr(o.lastIndexOf(this.id)+this.id.length,o.length):this.id.call(this,o)),o=this.src.replace("%id%",o),!1):void 0});var a={};return r.srcAction&&(a[r.srcAction]=o),t._parseMarkup(i,a,n),t.updateStatus("ready"),i}}});var K=function(e){var n=t.items.length;return e>n-1?e-n:0>e?n+e:e},Y=function(e,t,n){return e.replace(/%curr%/gi,t+1).replace(/%total%/gi,n)};e.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var n=t.st.gallery,i=".mfp-gallery",r=Boolean(e.fn.mfpFastClick);return t.direction=!0,n&&n.enabled?(a+=" mfp-gallery",x(f+i,function(){n.navigateByImgClick&&t.wrap.on("click"+i,".mfp-img",function(){return t.items.length>1?(t.next(),!1):void 0}),o.on("keydown"+i,function(e){37===e.keyCode?t.prev():39===e.keyCode&&t.next()})}),x("UpdateStatus"+i,function(e,n){n.text&&(n.text=Y(n.text,t.currItem.index,t.items.length))}),x(p+i,function(e,i,o,r){var a=t.items.length;o.counter=a>1?Y(n.tCounter,r.index,a):""}),x("BuildControls"+i,function(){if(t.items.length>1&&n.arrows&&!t.arrowLeft){var i=n.arrowMarkup,o=t.arrowLeft=e(i.replace(/%title%/gi,n.tPrev).replace(/%dir%/gi,"left")).addClass(y),a=t.arrowRight=e(i.replace(/%title%/gi,n.tNext).replace(/%dir%/gi,"right")).addClass(y),s=r?"mfpFastClick":"click";o[s](function(){t.prev()}),a[s](function(){t.next()}),t.isIE7&&(k("b",o[0],!1,!0),k("a",o[0],!1,!0),k("b",a[0],!1,!0),k("a",a[0],!1,!0)),t.container.append(o.add(a))}}),x(m+i,function(){t._preloadTimeout&&clearTimeout(t._preloadTimeout),t._preloadTimeout=setTimeout(function(){t.preloadNearbyImages(),t._preloadTimeout=null},16)}),x(l+i,function(){o.off(i),t.wrap.off("click"+i),t.arrowLeft&&r&&t.arrowLeft.add(t.arrowRight).destroyMfpFastClick(),t.arrowRight=t.arrowLeft=null}),void 0):!1},next:function(){t.direction=!0,t.index=K(t.index+1),t.updateItemHTML()},prev:function(){t.direction=!1,t.index=K(t.index-1),t.updateItemHTML()},goTo:function(e){t.direction=e>=t.index,t.index=e,t.updateItemHTML()},preloadNearbyImages:function(){var e,n=t.st.gallery.preload,i=Math.min(n[0],t.items.length),o=Math.min(n[1],t.items.length);for(e=1;(t.direction?o:i)>=e;e++)t._preloadItem(t.index+e);for(e=1;(t.direction?i:o)>=e;e++)t._preloadItem(t.index-e)},_preloadItem:function(n){if(n=K(n),!t.items[n].preloaded){var i=t.items[n];i.parsed||(i=t.parseEl(n)),T("LazyLoad",i),"image"===i.type&&(i.img=e('').on("load.mfploader",function(){i.hasSize=!0}).on("error.mfploader",function(){i.hasSize=!0,i.loadError=!0,T("LazyLoadError",i)}).attr("src",i.src)),i.preloaded=!0}}}});var U="retina";e.magnificPopup.registerModule(U,{options:{replaceSrc:function(e){return e.src.replace(/\.\w+$/,function(e){return"@2x"+e})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var e=t.st.retina,n=e.ratio;n=isNaN(n)?n():n,n>1&&(x("ImageHasSize."+U,function(e,t){t.img.css({"max-width":t.img[0].naturalWidth/n,width:"100%"})}),x("ElementParse."+U,function(t,i){i.src=e.replaceSrc(i,n)}))}}}}),function(){var t=1e3,n="ontouchstart"in window,i=function(){I.off("touchmove"+r+" touchend"+r)},o="mfpFastClick",r="."+o;e.fn.mfpFastClick=function(o){return e(this).each(function(){var a,s=e(this);if(n){var l,c,d,u,p,f;s.on("touchstart"+r,function(e){u=!1,f=1,p=e.originalEvent?e.originalEvent.touches[0]:e.touches[0],c=p.clientX,d=p.clientY,I.on("touchmove"+r,function(e){p=e.originalEvent?e.originalEvent.touches:e.touches,f=p.length,p=p[0],(Math.abs(p.clientX-c)>10||Math.abs(p.clientY-d)>10)&&(u=!0,i())}).on("touchend"+r,function(e){i(),u||f>1||(a=!0,e.preventDefault(),clearTimeout(l),l=setTimeout(function(){a=!1},t),o())})})}s.on("click"+r,function(){a||o()})})},e.fn.destroyMfpFastClick=function(){e(this).off("touchstart"+r+" click"+r),n&&I.off("touchmove"+r+" touchend"+r)}}(),_()})(window.jQuery||window.Zepto); -------------------------------------------------------------------------------- /resources/js/magnific-popup-options.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | // MagnificPopup 3 | var magnifPopup = function() { 4 | $('.image-popup').magnificPopup({ 5 | type: 'image', 6 | removalDelay: 300, 7 | mainClass: 'mfp-with-zoom', 8 | gallery:{ 9 | enabled:true 10 | }, 11 | zoom: { 12 | enabled: true, // By default it's false, so don't forget to enable it 13 | 14 | duration: 300, // duration of the effect, in milliseconds 15 | easing: 'ease-in-out', // CSS transition easing function 16 | 17 | // The "opener" function should return the element from which popup will be zoomed in 18 | // and to which popup will be scaled down 19 | // By defailt it looks for an image tag: 20 | opener: function(openerElement) { 21 | // openerElement is the element on which popup was initialized, in this case its tag 22 | // you don't need to add "opener" option if this code matches your needs, it's defailt one. 23 | return openerElement.is('img') ? openerElement : openerElement.find('img'); 24 | } 25 | } 26 | }); 27 | }; 28 | 29 | 30 | // Call the functions 31 | magnifPopup(); 32 | 33 | }); -------------------------------------------------------------------------------- /resources/js/smoothscroll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SmoothScroll 3 | * This helper script created by DWUser.com. Copyright 2013 DWUser.com. 4 | * Dual-licensed under the GPL and MIT licenses. 5 | * All individual scripts remain property of their copyrighters. 6 | * Date: 10-Sep-2013 7 | * Version: 1.0.1 8 | */ 9 | if (!window['jQuery']) alert('The jQuery library must be included before the smoothscroll.js file. The plugin will not work propery.'); 10 | 11 | /** 12 | * jQuery.ScrollTo - Easy element scrolling using jQuery. 13 | * Copyright (c) 2007-2013 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 14 | * Dual licensed under MIT and GPL. 15 | * @author Ariel Flesler 16 | * @version 1.4.3.1 17 | */ 18 | ;(function($){var h=$.scrollTo=function(a,b,c){$(window).scrollTo(a,b,c)};h.defaults={axis:'xy',duration:parseFloat($.fn.jquery)>=1.3?0:1,limit:true};h.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(e,f,g){if(typeof f=='object'){g=f;f=0}if(typeof g=='function')g={onAfter:g};if(e=='max')e=9e9;g=$.extend({},h.defaults,g);f=f||g.duration;g.queue=g.queue&&g.axis.length>1;if(g.queue)f/=2;g.offset=both(g.offset);g.over=both(g.over);return this._scrollable().each(function(){if(e==null)return;var d=this,$elem=$(d),targ=e,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}$.each(g.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=h.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(g.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=g.offset[pos]||0;if(g.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*g.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(g.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&g.queue){if(old!=attr[key])animate(g.onAfterFirst);delete attr[key]}});animate(g.onAfter);function animate(a){$elem.animate(attr,f,g.easing,a&&function(){a.call(this,e,g)})}}).end()};h.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery); 19 | 20 | /** 21 | * jQuery.LocalScroll 22 | * Copyright (c) 2007-2010 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 23 | * Dual licensed under MIT and GPL. 24 | * Date: 05/31/2010 25 | * @author Ariel Flesler 26 | * @version 1.2.8b 27 | **/ 28 | ;(function(b){function g(a,e,d){var h=e.hash.slice(1),f=document.getElementById(h)||document.getElementsByName(h)[0];if(f){a&&a.preventDefault();var c=b(d.target);if(!(d.lock&&c.is(":animated")||d.onBefore&&!1===d.onBefore(a,f,c))){d.stop&&c._scrollable().stop(!0);if(d.hash){var a=f.id==h?"id":"name",g=b(" ").attr(a,h).css({position:"absolute",top:b(window).scrollTop(),left:b(window).scrollLeft()});f[a]="";b("body").prepend(g);location=e.hash;g.remove();f[a]=h}c.scrollTo(f,d).trigger("notify.serialScroll", 29 | [f])}}}var i=location.href.replace(/#.*/,""),c=b.localScroll=function(a){b("body").localScroll(a)};c.defaults={duration:1E3,axis:"y",event:"click",stop:!0,target:window,reset:!0};c.hash=function(a){if(location.hash){a=b.extend({},c.defaults,a);a.hash=!1;if(a.reset){var e=a.duration;delete a.duration;b(a.target).scrollTo(0,a);a.duration=e}g(0,location,a)}};b.fn.localScroll=function(a){function e(){return!!this.href&&!!this.hash&&this.href.replace(this.hash,"")==i&&(!a.filter||b(this).is(a.filter))} 30 | a=b.extend({},c.defaults,a);return a.lazy?this.bind(a.event,function(d){var c=b([d.target,d.target.parentNode]).filter(e)[0];c&&g(d,c,a)}):this.find("a,area").filter(e).bind(a.event,function(b){g(b,this,a)}).end().end()}})(jQuery); 31 | 32 | // Initialize all .smoothScroll links 33 | jQuery(function($){ $.localScroll({filter:'.smoothScroll'}); }); 34 | -------------------------------------------------------------------------------- /resources/js/wow.min.js: -------------------------------------------------------------------------------- 1 | /*! WOW - v1.0.2 - 2014-10-28 2 | * Copyright (c) 2014 Matthieu Aussaguel; Licensed MIT */(function(){var a,b,c,d,e,f=function(a,b){return function(){return a.apply(b,arguments)}},g=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};b=function(){function a(){}return a.prototype.extend=function(a,b){var c,d;for(c in b)d=b[c],null==a[c]&&(a[c]=d);return a},a.prototype.isMobile=function(a){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(a)},a.prototype.addEvent=function(a,b,c){return null!=a.addEventListener?a.addEventListener(b,c,!1):null!=a.attachEvent?a.attachEvent("on"+b,c):a[b]=c},a.prototype.removeEvent=function(a,b,c){return null!=a.removeEventListener?a.removeEventListener(b,c,!1):null!=a.detachEvent?a.detachEvent("on"+b,c):delete a[b]},a.prototype.innerHeight=function(){return"innerHeight"in window?window.innerHeight:document.documentElement.clientHeight},a}(),c=this.WeakMap||this.MozWeakMap||(c=function(){function a(){this.keys=[],this.values=[]}return a.prototype.get=function(a){var b,c,d,e,f;for(f=this.keys,b=d=0,e=f.length;e>d;b=++d)if(c=f[b],c===a)return this.values[b]},a.prototype.set=function(a,b){var c,d,e,f,g;for(g=this.keys,c=e=0,f=g.length;f>e;c=++e)if(d=g[c],d===a)return void(this.values[c]=b);return this.keys.push(a),this.values.push(b)},a}()),a=this.MutationObserver||this.WebkitMutationObserver||this.MozMutationObserver||(a=function(){function a(){"undefined"!=typeof console&&null!==console&&console.warn("MutationObserver is not supported by your browser."),"undefined"!=typeof console&&null!==console&&console.warn("WOW.js cannot detect dom mutations, please call .sync() after loading new content.")}return a.notSupported=!0,a.prototype.observe=function(){},a}()),d=this.getComputedStyle||function(a){return this.getPropertyValue=function(b){var c;return"float"===b&&(b="styleFloat"),e.test(b)&&b.replace(e,function(a,b){return b.toUpperCase()}),(null!=(c=a.currentStyle)?c[b]:void 0)||null},this},e=/(\-([a-z]){1})/g,this.WOW=function(){function e(a){null==a&&(a={}),this.scrollCallback=f(this.scrollCallback,this),this.scrollHandler=f(this.scrollHandler,this),this.start=f(this.start,this),this.scrolled=!0,this.config=this.util().extend(a,this.defaults),this.animationNameCache=new c}return e.prototype.defaults={boxClass:"wow",animateClass:"animated",offset:0,mobile:!0,live:!0},e.prototype.init=function(){var a;return this.element=window.document.documentElement,"interactive"===(a=document.readyState)||"complete"===a?this.start():this.util().addEvent(document,"DOMContentLoaded",this.start),this.finished=[]},e.prototype.start=function(){var b,c,d,e;if(this.stopped=!1,this.boxes=function(){var a,c,d,e;for(d=this.element.querySelectorAll("."+this.config.boxClass),e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b);return e}.call(this),this.all=function(){var a,c,d,e;for(d=this.boxes,e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b);return e}.call(this),this.boxes.length)if(this.disabled())this.resetStyle();else for(e=this.boxes,c=0,d=e.length;d>c;c++)b=e[c],this.applyStyle(b,!0);return this.disabled()||(this.util().addEvent(window,"scroll",this.scrollHandler),this.util().addEvent(window,"resize",this.scrollHandler),this.interval=setInterval(this.scrollCallback,50)),this.config.live?new a(function(a){return function(b){var c,d,e,f,g;for(g=[],e=0,f=b.length;f>e;e++)d=b[e],g.push(function(){var a,b,e,f;for(e=d.addedNodes||[],f=[],a=0,b=e.length;b>a;a++)c=e[a],f.push(this.doSync(c));return f}.call(a));return g}}(this)).observe(document.body,{childList:!0,subtree:!0}):void 0},e.prototype.stop=function(){return this.stopped=!0,this.util().removeEvent(window,"scroll",this.scrollHandler),this.util().removeEvent(window,"resize",this.scrollHandler),null!=this.interval?clearInterval(this.interval):void 0},e.prototype.sync=function(){return a.notSupported?this.doSync(this.element):void 0},e.prototype.doSync=function(a){var b,c,d,e,f;if(null==a&&(a=this.element),1===a.nodeType){for(a=a.parentNode||a,e=a.querySelectorAll("."+this.config.boxClass),f=[],c=0,d=e.length;d>c;c++)b=e[c],g.call(this.all,b)<0?(this.boxes.push(b),this.all.push(b),this.stopped||this.disabled()?this.resetStyle():this.applyStyle(b,!0),f.push(this.scrolled=!0)):f.push(void 0);return f}},e.prototype.show=function(a){return this.applyStyle(a),a.className=""+a.className+" "+this.config.animateClass},e.prototype.applyStyle=function(a,b){var c,d,e;return d=a.getAttribute("data-wow-duration"),c=a.getAttribute("data-wow-delay"),e=a.getAttribute("data-wow-iteration"),this.animate(function(f){return function(){return f.customStyle(a,b,d,c,e)}}(this))},e.prototype.animate=function(){return"requestAnimationFrame"in window?function(a){return window.requestAnimationFrame(a)}:function(a){return a()}}(),e.prototype.resetStyle=function(){var a,b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(a.style.visibility="visible");return e},e.prototype.customStyle=function(a,b,c,d,e){return b&&this.cacheAnimationName(a),a.style.visibility=b?"hidden":"visible",c&&this.vendorSet(a.style,{animationDuration:c}),d&&this.vendorSet(a.style,{animationDelay:d}),e&&this.vendorSet(a.style,{animationIterationCount:e}),this.vendorSet(a.style,{animationName:b?"none":this.cachedAnimationName(a)}),a},e.prototype.vendors=["moz","webkit"],e.prototype.vendorSet=function(a,b){var c,d,e,f;f=[];for(c in b)d=b[c],a[""+c]=d,f.push(function(){var b,f,g,h;for(g=this.vendors,h=[],b=0,f=g.length;f>b;b++)e=g[b],h.push(a[""+e+c.charAt(0).toUpperCase()+c.substr(1)]=d);return h}.call(this));return f},e.prototype.vendorCSS=function(a,b){var c,e,f,g,h,i;for(e=d(a),c=e.getPropertyCSSValue(b),i=this.vendors,g=0,h=i.length;h>g;g++)f=i[g],c=c||e.getPropertyCSSValue("-"+f+"-"+b);return c},e.prototype.animationName=function(a){var b;try{b=this.vendorCSS(a,"animation-name").cssText}catch(c){b=d(a).getPropertyValue("animation-name")}return"none"===b?"":b},e.prototype.cacheAnimationName=function(a){return this.animationNameCache.set(a,this.animationName(a))},e.prototype.cachedAnimationName=function(a){return this.animationNameCache.get(a)},e.prototype.scrollHandler=function(){return this.scrolled=!0},e.prototype.scrollCallback=function(){var a;return!this.scrolled||(this.scrolled=!1,this.boxes=function(){var b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],a&&(this.isVisible(a)?this.show(a):e.push(a));return e}.call(this),this.boxes.length||this.config.live)?void 0:this.stop()},e.prototype.offsetTop=function(a){for(var b;void 0===a.offsetTop;)a=a.parentNode;for(b=a.offsetTop;a=a.offsetParent;)b+=a.offsetTop;return b},e.prototype.isVisible=function(a){var b,c,d,e,f;return c=a.getAttribute("data-wow-offset")||this.config.offset,f=window.pageYOffset,e=f+Math.min(this.element.clientHeight,this.util().innerHeight())-c,d=this.offsetTop(a),b=d+a.clientHeight,e>=d&&b>=f},e.prototype.util=function(){return null!=this._util?this._util:this._util=new b},e.prototype.disabled=function(){return!this.config.mobile&&this.util().isMobile(navigator.userAgent)},e}()}).call(this); -------------------------------------------------------------------------------- /resources/login.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MARK-登录 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 60 | 61 |
62 |
63 |
64 |
65 |

登录

66 |
67 |

69 |

71 |
72 |
73 |
74 |
75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /resources/picture.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MARK-图片 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | 59 | 60 | 61 |
62 |
63 |
64 | 65 |
66 | about image 68 |
69 | 70 |
71 |

图片测试

72 |
73 | 74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 |
82 |
83 | 84 |
85 |
86 | 87 |
88 |
89 | 90 |
91 |
92 | 93 |
94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /resources/register.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | MARK-注册 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 | 31 | 32 | 56 | 57 |
58 |
59 |
60 |
61 |

注册

62 |
63 |

65 |

67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /resources/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MARK-视频 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 | 46 | 47 |
48 |
49 |
50 |
51 | about image 53 |
54 |
55 |

视频测试

56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 |
64 |
67 |
68 |
69 |
70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /resources/video/xxx.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuwenchao99/Tiny-WebServer/36a78c5b121115913c6d9fd7ae4df59217504e8a/resources/video/xxx.mp4 -------------------------------------------------------------------------------- /resources/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MARK-欢迎 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 | 52 | 53 |
54 | 55 |
56 |
57 | 58 |
59 | about image 61 |
62 | 63 |
64 |

欢迎您!

65 | 66 |
67 | 68 |
69 |
70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CFLAGS = -std=c++14 -O2 -Wall -g 3 | 4 | TARGET = test 5 | OBJS = ../code/log/*.cpp ../code/pool/*.cpp ../code/timer/*.cpp \ 6 | ../code/http/*.cpp ../code/server/*.cpp \ 7 | ../code/buffer/*.cpp ../test/test.cpp 8 | 9 | all: $(OBJS) 10 | $(CXX) $(CFLAGS) $(OBJS) -o $(TARGET) -pthread -lmysqlclient 11 | 12 | clean: 13 | rm -rf ../bin/$(OBJS) $(TARGET) 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/readme.md: -------------------------------------------------------------------------------- 1 | 单元测试 -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author : mark 3 | * @Date : 2020-06-20 4 | * @copyleft Apache 2.0 5 | */ 6 | #include "../code/log/log.h" 7 | #include "../code/pool/threadpool.h" 8 | #include 9 | 10 | #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30 11 | #include 12 | #define gettid() syscall(SYS_gettid) 13 | #endif 14 | 15 | void TestLog() { 16 | int cnt = 0, level = 0; 17 | Log::Instance()->init(level, "./testlog1", ".log", 0); 18 | for(level = 3; level >= 0; level--) { 19 | Log::Instance()->SetLevel(level); 20 | for(int j = 0; j < 10000; j++ ){ 21 | for(int i = 0; i < 4; i++) { 22 | LOG_BASE(i,"%s 111111111 %d ============= ", "Test", cnt++); 23 | } 24 | } 25 | } 26 | cnt = 0; 27 | Log::Instance()->init(level, "./testlog2", ".log", 5000); 28 | for(level = 0; level < 4; level++) { 29 | Log::Instance()->SetLevel(level); 30 | for(int j = 0; j < 10000; j++ ){ 31 | for(int i = 0; i < 4; i++) { 32 | LOG_BASE(i,"%s 222222222 %d ============= ", "Test", cnt++); 33 | } 34 | } 35 | } 36 | } 37 | 38 | void ThreadLogTask(int i, int cnt) { 39 | for(int j = 0; j < 10000; j++ ){ 40 | LOG_BASE(i,"PID:[%04d]======= %05d ========= ", gettid(), cnt++); 41 | } 42 | } 43 | 44 | void TestThreadPool() { 45 | Log::Instance()->init(0, "./testThreadpool", ".log", 5000); 46 | ThreadPool threadpool(6); 47 | for(int i = 0; i < 18; i++) { 48 | threadpool.AddTask(std::bind(ThreadLogTask, i % 4, i * 10000)); 49 | } 50 | getchar(); 51 | } 52 | 53 | int main() { 54 | TestLog(); 55 | TestThreadPool(); 56 | } -------------------------------------------------------------------------------- /webbench-1.5/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS?= -Wall -ggdb -W -O 2 | CC?= gcc 3 | LIBS?= 4 | LDFLAGS?= 5 | PREFIX?= /usr/local 6 | VERSION=1.5 7 | TMPDIR=/tmp/webbench-$(VERSION) 8 | 9 | all: webbench tags 10 | 11 | tags: *.c 12 | -ctags *.c 13 | 14 | install: webbench 15 | install -s webbench $(DESTDIR)$(PREFIX)/bin 16 | install -m 644 webbench.1 $(DESTDIR)$(PREFIX)/man/man1 17 | install -d $(DESTDIR)$(PREFIX)/share/doc/webbench 18 | install -m 644 debian/copyright $(DESTDIR)$(PREFIX)/share/doc/webbench 19 | install -m 644 debian/changelog $(DESTDIR)$(PREFIX)/share/doc/webbench 20 | 21 | webbench: webbench.o Makefile 22 | $(CC) $(CFLAGS) $(LDFLAGS) -o webbench webbench.o $(LIBS) 23 | 24 | clean: 25 | -rm -f *.o webbench *~ core *.core tags 26 | 27 | tar: clean 28 | -debian/rules clean 29 | rm -rf $(TMPDIR) 30 | install -d $(TMPDIR) 31 | cp -p Makefile webbench.c socket.c webbench.1 $(TMPDIR) 32 | install -d $(TMPDIR)/debian 33 | -cp -p debian/* $(TMPDIR)/debian 34 | ln -sf debian/copyright $(TMPDIR)/COPYRIGHT 35 | ln -sf debian/changelog $(TMPDIR)/ChangeLog 36 | -cd $(TMPDIR) && cd .. && tar cozf webbench-$(VERSION).tar.gz webbench-$(VERSION) 37 | 38 | webbench.o: webbench.c socket.c Makefile 39 | 40 | .PHONY: clean install all tar 41 | -------------------------------------------------------------------------------- /webbench-1.5/socket.c: -------------------------------------------------------------------------------- 1 | /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ 2 | * 3 | * This module has been modified by Radim Kolar for OS/2 emx 4 | */ 5 | 6 | /*********************************************************************** 7 | module: socket.c 8 | program: popclient 9 | SCCS ID: @(#)socket.c 1.5 4/1/94 10 | programmer: Virginia Tech Computing Center 11 | compiler: DEC RISC C compiler (Ultrix 4.1) 12 | environment: DEC Ultrix 4.3 13 | description: UNIX sockets code. 14 | ***********************************************************************/ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | int Socket(const char *host, int clientPort) 30 | { 31 | int sock; 32 | unsigned long inaddr; 33 | struct sockaddr_in ad; 34 | struct hostent *hp; 35 | 36 | memset(&ad, 0, sizeof(ad)); 37 | ad.sin_family = AF_INET; 38 | 39 | inaddr = inet_addr(host); 40 | if (inaddr != INADDR_NONE) 41 | memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); 42 | else 43 | { 44 | hp = gethostbyname(host); 45 | if (hp == NULL) 46 | return -1; 47 | memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); 48 | } 49 | ad.sin_port = htons(clientPort); 50 | 51 | sock = socket(AF_INET, SOCK_STREAM, 0); 52 | if (sock < 0) 53 | return sock; 54 | if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) 55 | { 56 | close(sock); 57 | return -1; 58 | } 59 | return sock; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /webbench-1.5/webbench.c: -------------------------------------------------------------------------------- 1 | /* 2 | * (C) Radim Kolar 1997-2004 3 | * This is free software, see GNU Public License version 2 for 4 | * details. 5 | * 6 | * Simple forking WWW Server benchmark: 7 | * 8 | * Usage: 9 | * webbench --help 10 | * 11 | * Return codes: 12 | * 0 - sucess 13 | * 1 - benchmark failed (server is not on-line) 14 | * 2 - bad param 15 | * 3 - internal error, fork failed 16 | * 17 | */ 18 | #include "socket.c" 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /* values */ 28 | volatile int timerexpired=0; 29 | int speed=0; 30 | int failed=0; 31 | int bytes=0; 32 | /* globals */ 33 | int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ 34 | /* Allow: GET, HEAD, OPTIONS, TRACE */ 35 | #define METHOD_GET 0 36 | #define METHOD_HEAD 1 37 | #define METHOD_OPTIONS 2 38 | #define METHOD_TRACE 3 39 | #define PROGRAM_VERSION "1.5" 40 | int method=METHOD_GET; 41 | int clients=1; 42 | int force=0; 43 | int force_reload=0; 44 | int proxyport=80; 45 | char *proxyhost=NULL; 46 | int benchtime=30; 47 | /* internal */ 48 | int mypipe[2]; 49 | char host[MAXHOSTNAMELEN]; 50 | #define REQUEST_SIZE 2048 51 | char request[REQUEST_SIZE]; 52 | 53 | static const struct option long_options[]= 54 | { 55 | {"force",no_argument,&force,1}, 56 | {"reload",no_argument,&force_reload,1}, 57 | {"time",required_argument,NULL,'t'}, 58 | {"help",no_argument,NULL,'?'}, 59 | {"http09",no_argument,NULL,'9'}, 60 | {"http10",no_argument,NULL,'1'}, 61 | {"http11",no_argument,NULL,'2'}, 62 | {"get",no_argument,&method,METHOD_GET}, 63 | {"head",no_argument,&method,METHOD_HEAD}, 64 | {"options",no_argument,&method,METHOD_OPTIONS}, 65 | {"trace",no_argument,&method,METHOD_TRACE}, 66 | {"version",no_argument,NULL,'V'}, 67 | {"proxy",required_argument,NULL,'p'}, 68 | {"clients",required_argument,NULL,'c'}, 69 | {NULL,0,NULL,0} 70 | }; 71 | 72 | /* prototypes */ 73 | static void benchcore(const char* host,const int port, const char *request); 74 | static int bench(void); 75 | static void build_request(const char *url); 76 | 77 | static void alarm_handler(int signal) 78 | { 79 | timerexpired=1; 80 | } 81 | 82 | static void usage(void) 83 | { 84 | fprintf(stderr, 85 | "webbench [option]... URL\n" 86 | " -f|--force Don't wait for reply from server.\n" 87 | " -r|--reload Send reload request - Pragma: no-cache.\n" 88 | " -t|--time Run benchmark for seconds. Default 30.\n" 89 | " -p|--proxy Use proxy server for request.\n" 90 | " -c|--clients Run HTTP clients at once. Default one.\n" 91 | " -9|--http09 Use HTTP/0.9 style requests.\n" 92 | " -1|--http10 Use HTTP/1.0 protocol.\n" 93 | " -2|--http11 Use HTTP/1.1 protocol.\n" 94 | " --get Use GET request method.\n" 95 | " --head Use HEAD request method.\n" 96 | " --options Use OPTIONS request method.\n" 97 | " --trace Use TRACE request method.\n" 98 | " -?|-h|--help This information.\n" 99 | " -V|--version Display program version.\n" 100 | ); 101 | }; 102 | int main(int argc, char *argv[]) 103 | { 104 | int opt=0; 105 | int options_index=0; 106 | char *tmp=NULL; 107 | 108 | if(argc==1) 109 | { 110 | usage(); 111 | return 2; 112 | } 113 | 114 | while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ) 115 | { 116 | switch(opt) 117 | { 118 | case 0 : break; 119 | case 'f': force=1;break; 120 | case 'r': force_reload=1;break; 121 | case '9': http10=0;break; 122 | case '1': http10=1;break; 123 | case '2': http10=2;break; 124 | case 'V': printf(PROGRAM_VERSION"\n");exit(0); 125 | case 't': benchtime=atoi(optarg);break; 126 | case 'p': 127 | /* proxy server parsing server:port */ 128 | tmp=strrchr(optarg,':'); 129 | proxyhost=optarg; 130 | if(tmp==NULL) 131 | { 132 | break; 133 | } 134 | if(tmp==optarg) 135 | { 136 | fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); 137 | return 2; 138 | } 139 | if(tmp==optarg+strlen(optarg)-1) 140 | { 141 | fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); 142 | return 2; 143 | } 144 | *tmp='\0'; 145 | proxyport=atoi(tmp+1);break; 146 | case ':': 147 | case 'h': 148 | case '?': usage();return 2;break; 149 | case 'c': clients=atoi(optarg);break; 150 | } 151 | } 152 | 153 | if(optind==argc) { 154 | fprintf(stderr,"webbench: Missing URL!\n"); 155 | usage(); 156 | return 2; 157 | } 158 | 159 | if(clients==0) clients=1; 160 | if(benchtime==0) benchtime=60; 161 | /* Copyright */ 162 | fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" 163 | "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" 164 | ); 165 | build_request(argv[optind]); 166 | /* print bench info */ 167 | printf("\nBenchmarking: "); 168 | switch(method) 169 | { 170 | case METHOD_GET: 171 | default: 172 | printf("GET");break; 173 | case METHOD_OPTIONS: 174 | printf("OPTIONS");break; 175 | case METHOD_HEAD: 176 | printf("HEAD");break; 177 | case METHOD_TRACE: 178 | printf("TRACE");break; 179 | } 180 | printf(" %s",argv[optind]); 181 | switch(http10) 182 | { 183 | case 0: printf(" (using HTTP/0.9)");break; 184 | case 2: printf(" (using HTTP/1.1)");break; 185 | } 186 | printf("\n"); 187 | if(clients==1) printf("1 client"); 188 | else 189 | printf("%d clients",clients); 190 | 191 | printf(", running %d sec", benchtime); 192 | if(force) printf(", early socket close"); 193 | if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); 194 | if(force_reload) printf(", forcing reload"); 195 | printf(".\n"); 196 | return bench(); 197 | } 198 | 199 | void build_request(const char *url) 200 | { 201 | char tmp[10]; 202 | int i; 203 | 204 | bzero(host,MAXHOSTNAMELEN); 205 | bzero(request,REQUEST_SIZE); 206 | 207 | if(force_reload && proxyhost!=NULL && http10<1) http10=1; 208 | if(method==METHOD_HEAD && http10<1) http10=1; 209 | if(method==METHOD_OPTIONS && http10<2) http10=2; 210 | if(method==METHOD_TRACE && http10<2) http10=2; 211 | 212 | switch(method) 213 | { 214 | default: 215 | case METHOD_GET: strcpy(request,"GET");break; 216 | case METHOD_HEAD: strcpy(request,"HEAD");break; 217 | case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; 218 | case METHOD_TRACE: strcpy(request,"TRACE");break; 219 | } 220 | 221 | strcat(request," "); 222 | 223 | if(NULL==strstr(url,"://")) 224 | { 225 | fprintf(stderr, "\n%s: is not a valid URL.\n",url); 226 | exit(2); 227 | } 228 | if(strlen(url)>1500) 229 | { 230 | fprintf(stderr,"URL is too long.\n"); 231 | exit(2); 232 | } 233 | if(proxyhost==NULL) 234 | if (0!=strncasecmp("http://",url,7)) 235 | { fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); 236 | exit(2); 237 | } 238 | /* protocol/host delimiter */ 239 | i=strstr(url,"://")-url+3; 240 | /* printf("%d\n",i); */ 241 | 242 | if(strchr(url+i,'/')==NULL) { 243 | fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); 244 | exit(2); 245 | } 246 | if(proxyhost==NULL) 247 | { 248 | /* get port from hostname */ 249 | if(index(url+i,':')!=NULL && 250 | index(url+i,':')0) 275 | strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); 276 | if(proxyhost==NULL && http10>0) 277 | { 278 | strcat(request,"Host: "); 279 | strcat(request,host); 280 | strcat(request,"\r\n"); 281 | } 282 | if(force_reload && proxyhost!=NULL) 283 | { 284 | strcat(request,"Pragma: no-cache\r\n"); 285 | } 286 | if(http10>1) 287 | strcat(request,"Connection: close\r\n"); 288 | /* add empty line at end */ 289 | if(http10>0) strcat(request,"\r\n"); 290 | // printf("Req=%s\n",request); 291 | } 292 | 293 | /* vraci system rc error kod */ 294 | static int bench(void) 295 | { 296 | int i,j,k; 297 | pid_t pid=0; 298 | FILE *f; 299 | 300 | /* check avaibility of target server */ 301 | i=Socket(proxyhost==NULL?host:proxyhost,proxyport); 302 | if(i<0) { 303 | fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); 304 | return 1; 305 | } 306 | close(i); 307 | /* create pipe */ 308 | if(pipe(mypipe)) 309 | { 310 | perror("pipe failed."); 311 | return 3; 312 | } 313 | 314 | /* not needed, since we have alarm() in childrens */ 315 | /* wait 4 next system clock tick */ 316 | /* 317 | cas=time(NULL); 318 | while(time(NULL)==cas) 319 | sched_yield(); 320 | */ 321 | 322 | /* fork childs */ 323 | for(i=0;i0) 418 | { 419 | /* fprintf(stderr,"Correcting failed by signal\n"); */ 420 | failed--; 421 | } 422 | return; 423 | } 424 | s=Socket(host,port); 425 | if(s<0) { failed++;continue;} 426 | if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;} 427 | if(http10==0) 428 | if(shutdown(s,1)) { failed++;close(s);continue;} 429 | if(force==0) 430 | { 431 | /* read all available data from socket */ 432 | while(1) 433 | { 434 | if(timerexpired) break; 435 | i=read(s,buf,1500); 436 | /* fprintf(stderr,"%d\n",i); */ 437 | if(i<0) 438 | { 439 | failed++; 440 | close(s); 441 | goto nexttry; 442 | } 443 | else 444 | if(i==0) break; 445 | else 446 | bytes+=i; 447 | } 448 | } 449 | if(close(s)) {failed++;continue;} 450 | speed++; 451 | } 452 | } 453 | --------------------------------------------------------------------------------