├── .gitignore ├── LICENSE ├── Makefile ├── build └── Makefile ├── code ├── buffer │ ├── buffer.cpp │ ├── buffer.h │ └── readme.md ├── http │ ├── httpconn.cpp │ ├── httpconn.h │ ├── httprequest.cpp │ ├── httprequest.h │ ├── httpresponse.cpp │ ├── httpresponse.h │ └── readme.md ├── log │ ├── blockqueue.h │ ├── log.cpp │ ├── log.h │ └── readme.md ├── main.cpp ├── pool │ ├── readme.md │ ├── sqlconnpool.cpp │ ├── sqlconnpool.h │ └── threadpool.h ├── readme.md ├── server │ ├── epoller.cpp │ ├── epoller.h │ ├── readme.md │ ├── webserver.cpp │ └── webserver.h └── timer │ ├── heaptimer.cpp │ ├── heaptimer.h │ └── readme.md ├── imgs └── pressure.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 | // 读写下标初始化,vector初始化 4 | Buffer::Buffer(int initBuffSize) : buffer_(initBuffSize), readPos_(0), writePos_(0) {} 5 | 6 | // 可写的数量:buffer大小 - 写下标 7 | size_t Buffer::WritableBytes() const { 8 | return buffer_.size() - writePos_; 9 | } 10 | 11 | // 可读的数量:写下标 - 读下标 12 | size_t Buffer::ReadableBytes() const { 13 | return writePos_ - readPos_; 14 | } 15 | 16 | // 可预留空间:已经读过的就没用了,等于读下标 17 | size_t Buffer::PrependableBytes() const { 18 | return readPos_; 19 | } 20 | 21 | const char* Buffer::Peek() const { 22 | 23 | return &buffer_[readPos_]; 24 | } 25 | 26 | // 确保可写的长度 27 | void Buffer::EnsureWriteable(size_t len) { 28 | if(len > WritableBytes()) { 29 | MakeSpace_(len); 30 | } 31 | assert(len <= WritableBytes()); 32 | } 33 | 34 | // 移动写下标,在Append中使用 35 | void Buffer::HasWritten(size_t len) { 36 | writePos_ += len; 37 | } 38 | 39 | // 读取len长度,移动读下标 40 | void Buffer::Retrieve(size_t len) { 41 | readPos_ += len; 42 | } 43 | 44 | // 读取到end位置 45 | void Buffer::RetrieveUntil(const char* end) { 46 | assert(Peek() <= end ); 47 | Retrieve(end - Peek()); // end指针 - 读指针 长度 48 | } 49 | 50 | // 取出所有数据,buffer归零,读写下标归零,在别的函数中会用到 51 | void Buffer::RetrieveAll() { 52 | bzero(&buffer_[0], buffer_.size()); // 覆盖原本数据 53 | readPos_ = writePos_ = 0; 54 | } 55 | 56 | // 取出剩余可读的str 57 | std::string Buffer::RetrieveAllToStr() { 58 | std::string str(Peek(), ReadableBytes()); 59 | RetrieveAll(); 60 | return str; 61 | } 62 | 63 | // 写指针的位置 64 | const char* Buffer::BeginWriteConst() const { 65 | return &buffer_[writePos_]; 66 | } 67 | 68 | char* Buffer::BeginWrite() { 69 | return &buffer_[writePos_]; 70 | } 71 | 72 | // 添加str到缓冲区 73 | void Buffer::Append(const char* str, size_t len) { 74 | assert(str); 75 | EnsureWriteable(len); // 确保可写的长度 76 | std::copy(str, str + len, BeginWrite()); // 将str放到写下标开始的地方 77 | HasWritten(len); // 移动写下标 78 | } 79 | 80 | void Buffer::Append(const std::string& str) { 81 | Append(str.c_str(), str.size()); 82 | } 83 | 84 | void Append(const void* data, size_t len) { 85 | Append(static_cast(data), len); 86 | } 87 | 88 | // 将buffer中的读下标的地方放到该buffer中的写下标位置 89 | void Append(const Buffer& buff) { 90 | Append(buff.Peek(), buff.ReadableBytes()); 91 | } 92 | 93 | // 将fd的内容读到缓冲区,即writable的位置 94 | ssize_t Buffer::ReadFd(int fd, int* Errno) { 95 | char buff[65535]; // 栈区 96 | struct iovec iov[2]; 97 | size_t writeable = WritableBytes(); // 先记录能写多少 98 | // 分散读, 保证数据全部读完 99 | iov[0].iov_base = BeginWrite(); 100 | iov[0].iov_len = writeable; 101 | iov[1].iov_base = buff; 102 | iov[1].iov_len = sizeof(buff); 103 | 104 | ssize_t len = readv(fd, iov, 2); 105 | if(len < 0) { 106 | *Errno = errno; 107 | } else if(static_cast(len) <= writeable) { // 若len小于writable,说明写区可以容纳len 108 | writePos_ += len; // 直接移动写下标 109 | } else { 110 | writePos_ = buffer_.size(); // 写区写满了,下标移到最后 111 | Append(buff, static_cast(len - writeable)); // 剩余的长度 112 | } 113 | return len; 114 | } 115 | 116 | // 将buffer中可读的区域写入fd中 117 | ssize_t Buffer::WriteFd(int fd, int* Errno) { 118 | ssize_t len = write(fd, Peek(), ReadableBytes()); 119 | if(len < 0) { 120 | *Errno = errno; 121 | return len; 122 | } 123 | Retrieve(len); 124 | return len; 125 | } 126 | 127 | char* Buffer::BeginPtr_() { 128 | return &buffer_[0]; 129 | } 130 | 131 | const char* Buffer::BeginPtr_() const{ 132 | return &buffer_[0]; 133 | } 134 | 135 | // 扩展空间 136 | void Buffer::MakeSpace_(size_t len) { 137 | if(WritableBytes() + PrependableBytes() < len) { 138 | buffer_.resize(writePos_ + len + 1); 139 | } else { 140 | size_t readable = ReadableBytes(); 141 | std::copy(BeginPtr_() + readPos_, BeginPtr_() + writePos_, BeginPtr_()); 142 | readPos_ = 0; 143 | writePos_ = readable; 144 | assert(readable == ReadableBytes()); 145 | } 146 | } 147 | 148 | -------------------------------------------------------------------------------- /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_(); // buffer开头 42 | const char* BeginPtr_() const; 43 | void MakeSpace_(size_t len); 44 | 45 | std::vector buffer_; 46 | std::atomic readPos_; // 读的下标 47 | std::atomic writePos_; // 写的下标 48 | }; 49 | 50 | #endif //BUFFER_H -------------------------------------------------------------------------------- /code/buffer/readme.md: -------------------------------------------------------------------------------- 1 | # BUFFER缓冲区 2 | 这里的BUFFER,仿照的是陈硕老师的muduo库,具体可看文档底部的博客连接,讲的非常通透。这里主要讲以下他的主要实现步骤和创新点。 3 | 4 | ## 主要实现方法 5 | 在WebServer中,客户端连接发来的HTTP请求(放到conn的读缓冲区)以及回复给客户端所请求的响应报文(放到conn的写缓冲区),都需要通过缓冲区来进行。我们以vector容器作为底层实体,在它的上面封装自己所需要的方法来实现一个自己的buffer缓冲区,满足读写的需要。 6 | + **buffer的存储实体** 7 | 缓冲区的最主要需要是读写数据的存储,也就是需要一个存储的实体。自己去写太繁琐了,直接用vector来完成。也就是buffer缓冲区里面需要一个: 8 | ```c++ 9 | std::vectorbuffer_; 10 | ``` 11 | + **buffer所需要的变量** 12 | 由于buffer缓冲区既要作为读缓冲区,也要作为写缓冲区,所以我们既需要指示当前读到哪里了,也需要指示当前写到哪里了。所以在buffer缓冲区里面设置变量: 13 | ```c++ 14 | std::atomicreadPos_; 15 | std::atomicwritePos_; 16 | ``` 17 | 分别指示当前读写位置的下标。其中atomic是一种原子类型,可以保证在多线的情况下,安全高性能得执行程序,更新变量。 18 | + **buffer所需要的方法** 19 | 20 | 读写接口 21 | 22 | 缓冲区最重要的就是读写接口,主要可以分为与客户端直接IO交互所需要的读写接口,以及收到客户端HTTP请求后,我们在处理过程中需要对缓冲区的读写接口。 23 | 24 | 与客户端直接I/O得读写接口(httpconn中就是调用的该接口。): 25 | ```c++ 26 | ssize_t ReadFd(); 27 | ssize_t WriteFd(); 28 | ``` 29 | 这个功能直接用read()/write()、readv()/writev()函数来实现。从某个连接接受数据的时候,有可能会超过vector的容量,所以我们用readv()来分散接受来的数据。 30 | 31 | ## 创新点 32 | **问题的提出**:在非阻塞网络编程中,如何设计并使用缓冲区?一方面我们希望减少系统调用,一次读的数据越多越划算,那么似乎应该准备一个大的缓冲区。另一方面,我们系统减少内存占用。如果有 10k 个连接,每个连接一建立就分配 64k 的读缓冲的话,将占用 640M 内存,而大多数时候这些缓冲区的使用率很低。muduo 用 readv 结合栈上空间巧妙地解决了这个问题。 33 | 34 | 在栈上准备一个 65536 字节的 stackbuf,然后利用 readv() 来读取数据,iovec 有两块,第一块指向 muduo Buffer 中的 writable 字节,另一块指向栈上的 stackbuf。这样如果读入的数据不多,那么全部都读到 Buffer 中去了;如果长度超过 Buffer 的 writable 字节数,就会读到栈上的 stackbuf 里,然后程序再把 stackbuf 里的数据 append 到 Buffer 中。 35 | 36 | 这么做利用了临时栈上空间,避免开巨大 Buffer 造成的内存浪费,也避免反复调用 read() 的系统开销(通常一次 readv() 系统调用就能读完全部数据)。 37 | 38 | > 参考博客: 39 | > 40 | > https://blog.csdn.net/Solstice/article/details/6329080 41 | > 42 | > https://blog.csdn.net/wanggao_1990/article/details/119426351 -------------------------------------------------------------------------------- /code/http/httpconn.cpp: -------------------------------------------------------------------------------- 1 | #include "httpconn.h" 2 | using namespace std; 3 | 4 | const char* HttpConn::srcDir; 5 | std::atomic HttpConn::userCount; 6 | bool HttpConn::isET; 7 | 8 | HttpConn::HttpConn() { 9 | fd_ = -1; 10 | addr_ = { 0 }; 11 | isClose_ = true; 12 | }; 13 | 14 | HttpConn::~HttpConn() { 15 | Close(); 16 | }; 17 | 18 | void HttpConn::init(int fd, const sockaddr_in& addr) { 19 | assert(fd > 0); 20 | userCount++; 21 | addr_ = addr; 22 | fd_ = fd; 23 | writeBuff_.RetrieveAll(); 24 | readBuff_.RetrieveAll(); 25 | isClose_ = false; 26 | LOG_INFO("Client[%d](%s:%d) in, userCount:%d", fd_, GetIP(), GetPort(), (int)userCount); 27 | } 28 | 29 | void HttpConn::Close() { 30 | response_.UnmapFile(); 31 | if(isClose_ == false){ 32 | isClose_ = true; 33 | userCount--; 34 | close(fd_); 35 | LOG_INFO("Client[%d](%s:%d) quit, UserCount:%d", fd_, GetIP(), GetPort(), (int)userCount); 36 | } 37 | } 38 | 39 | int HttpConn::GetFd() const { 40 | return fd_; 41 | }; 42 | 43 | struct sockaddr_in HttpConn::GetAddr() const { 44 | return addr_; 45 | } 46 | 47 | const char* HttpConn::GetIP() const { 48 | return inet_ntoa(addr_.sin_addr); 49 | } 50 | 51 | int HttpConn::GetPort() const { 52 | return addr_.sin_port; 53 | } 54 | 55 | ssize_t HttpConn::read(int* saveErrno) { 56 | ssize_t len = -1; 57 | do { 58 | len = readBuff_.ReadFd(fd_, saveErrno); 59 | if (len <= 0) { 60 | break; 61 | } 62 | } while (isET); // ET:边沿触发要一次性全部读出 63 | return len; 64 | } 65 | 66 | // 主要采用writev连续写函数 67 | ssize_t HttpConn::write(int* saveErrno) { 68 | ssize_t len = -1; 69 | do { 70 | len = writev(fd_, iov_, iovCnt_); // 将iov的内容写到fd中 71 | if(len <= 0) { 72 | *saveErrno = errno; 73 | break; 74 | } 75 | if(iov_[0].iov_len + iov_[1].iov_len == 0) { break; } /* 传输结束 */ 76 | else if(static_cast(len) > iov_[0].iov_len) { 77 | iov_[1].iov_base = (uint8_t*) iov_[1].iov_base + (len - iov_[0].iov_len); 78 | iov_[1].iov_len -= (len - iov_[0].iov_len); 79 | if(iov_[0].iov_len) { 80 | writeBuff_.RetrieveAll(); 81 | iov_[0].iov_len = 0; 82 | } 83 | } 84 | else { 85 | iov_[0].iov_base = (uint8_t*)iov_[0].iov_base + len; 86 | iov_[0].iov_len -= len; 87 | writeBuff_.Retrieve(len); 88 | } 89 | } while(isET || ToWriteBytes() > 10240); 90 | return len; 91 | } 92 | 93 | bool HttpConn::process() { 94 | request_.Init(); 95 | if(readBuff_.ReadableBytes() <= 0) { 96 | return false; 97 | } 98 | else if(request_.parse(readBuff_)) { // 解析成功 99 | LOG_DEBUG("%s", request_.path().c_str()); 100 | response_.Init(srcDir, request_.path(), request_.IsKeepAlive(), 200); 101 | } else { 102 | response_.Init(srcDir, request_.path(), false, 400); 103 | } 104 | 105 | response_.MakeResponse(writeBuff_); // 生成响应报文放入writeBuff_中 106 | // 响应头 107 | iov_[0].iov_base = const_cast(writeBuff_.Peek()); 108 | iov_[0].iov_len = writeBuff_.ReadableBytes(); 109 | iovCnt_ = 1; 110 | 111 | // 文件 112 | if(response_.FileLen() > 0 && response_.File()) { 113 | iov_[1].iov_base = response_.File(); 114 | iov_[1].iov_len = response_.FileLen(); 115 | iovCnt_ = 2; 116 | } 117 | LOG_DEBUG("filesize:%d, %d to %d", response_.FileLen() , iovCnt_, ToWriteBytes()); 118 | return true; 119 | } 120 | -------------------------------------------------------------------------------- /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 "../buffer/buffer.h" 12 | #include "httprequest.h" 13 | #include "httpresponse.h" 14 | /* 15 | 进行读写数据并调用httprequest 来解析数据以及httpresponse来生成响应 16 | */ 17 | class HttpConn { 18 | public: 19 | HttpConn(); 20 | ~HttpConn(); 21 | 22 | void init(int sockFd, const sockaddr_in& addr); 23 | ssize_t read(int* saveErrno); 24 | ssize_t write(int* saveErrno); 25 | void Close(); 26 | int GetFd() const; 27 | int GetPort() const; 28 | const char* GetIP() const; 29 | sockaddr_in GetAddr() const; 30 | bool process(); 31 | 32 | // 写的总长度 33 | int ToWriteBytes() { 34 | return iov_[0].iov_len + iov_[1].iov_len; 35 | } 36 | 37 | bool IsKeepAlive() const { 38 | return request_.IsKeepAlive(); 39 | } 40 | 41 | static bool isET; 42 | static const char* srcDir; 43 | static std::atomic userCount; // 原子,支持锁 44 | 45 | private: 46 | 47 | int fd_; 48 | struct sockaddr_in addr_; 49 | 50 | bool isClose_; 51 | 52 | int iovCnt_; 53 | struct iovec iov_[2]; 54 | 55 | Buffer readBuff_; // 读缓冲区 56 | Buffer writeBuff_; // 写缓冲区 57 | 58 | HttpRequest request_; 59 | HttpResponse response_; 60 | }; 61 | 62 | 63 | #endif //HTTP_CONN_H -------------------------------------------------------------------------------- /code/http/httprequest.cpp: -------------------------------------------------------------------------------- 1 | #include "httprequest.h" 2 | using namespace std; 3 | 4 | // 网页名称,和一般的前端跳转不同,这里需要将请求信息放到后端来验证一遍再上传(和小组成员还起过争执) 5 | const unordered_set HttpRequest::DEFAULT_HTML { 6 | "/index", "/register", "/login", "/welcome", "/video", "/picture", 7 | }; 8 | 9 | // 登录/注册 10 | const unordered_map HttpRequest::DEFAULT_HTML_TAG { 11 | {"/login.html", 1}, {"/register.html", 0} 12 | }; 13 | 14 | // 初始化操作,一些清零操作 15 | void HttpRequest::Init() { 16 | state_ = REQUEST_LINE; // 初始状态 17 | method_ = path_ = version_= body_ = ""; 18 | header_.clear(); 19 | post_.clear(); 20 | } 21 | 22 | // 解析处理 23 | bool HttpRequest::parse(Buffer& buff) { 24 | const char END[] = "\r\n"; 25 | if(buff.ReadableBytes() == 0) // 没有可读的字节 26 | return false; 27 | // 读取数据开始 28 | while(buff.ReadableBytes() && state_ != FINISH) { 29 | // 从buff中的读指针开始到读指针结束,这块区域是未读取得数据并去处"\r\n",返回有效数据得行末指针 30 | const char* lineend = search(buff.Peek(), buff.BeginWriteConst(), END, END+2); 31 | string line(buff.Peek(), lineend); 32 | switch (state_) 33 | { 34 | case REQUEST_LINE: 35 | // 解析错误 36 | if(!ParseRequestLine_(line)) { 37 | return false; 38 | } 39 | ParsePath_(); // 解析路径 40 | break; 41 | case HEADERS: 42 | ParseHeader_(line); 43 | if(buff.ReadableBytes() <= 2) { // 说明是get请求,后面为\r\n 44 | state_ = FINISH; // 提前结束 45 | } 46 | break; 47 | case BODY: 48 | ParseBody_(line); 49 | break; 50 | default: 51 | break; 52 | } 53 | if(lineend == buff.BeginWrite()) { // 读完了 54 | buff.RetrieveAll(); 55 | break; 56 | } 57 | buff.RetrieveUntil(lineend + 2); // 跳过回车换行 58 | } 59 | LOG_DEBUG("[%s], [%s], [%s]", method_.c_str(), path_.c_str(), version_.c_str()); 60 | return true; 61 | } 62 | 63 | bool HttpRequest::ParseRequestLine_(const string& line) { 64 | regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$"); 65 | smatch Match; // 用来匹配patten得到结果 66 | // 在匹配规则中,以括号()的方式来划分组别 一共三个括号 [0]表示整体 67 | if(regex_match(line, Match, patten)) { // 匹配指定字符串整体是否符合 68 | method_ = Match[1]; 69 | path_ = Match[2]; 70 | version_ = Match[3]; 71 | state_ = HEADERS; 72 | return true; 73 | } 74 | LOG_ERROR("RequestLine Error"); 75 | return false; 76 | } 77 | 78 | // 解析路径,统一一下path名称,方便后面解析资源 79 | void HttpRequest::ParsePath_() { 80 | if(path_ == "/") { 81 | path_ = "/index.html"; 82 | } else { 83 | if(DEFAULT_HTML.find(path_) != DEFAULT_HTML.end()) { 84 | path_ += ".html"; 85 | } 86 | } 87 | } 88 | 89 | void HttpRequest::ParseHeader_(const string& line) { 90 | regex patten("^([^:]*): ?(.*)$"); 91 | smatch Match; 92 | if(regex_match(line, Match, patten)) { 93 | header_[Match[1]] = Match[2]; 94 | } else { // 匹配失败说明首部行匹配完了,状态变化 95 | state_ = BODY; 96 | } 97 | } 98 | 99 | void HttpRequest::ParseBody_(const string& line) { 100 | body_ = line; 101 | ParsePost_(); 102 | state_ = FINISH; // 状态转换为下一个状态 103 | LOG_DEBUG("Body:%s, len:%d", line.c_str(), line.size()); 104 | } 105 | 106 | 107 | // 16进制转化为10进制 108 | int HttpRequest::ConverHex(char ch) { 109 | if(ch >= 'A' && ch <= 'F') 110 | return ch -'A' + 10; 111 | if(ch >= 'a' && ch <= 'f') 112 | return ch -'a' + 10; 113 | return ch; 114 | } 115 | 116 | // 处理post请求 117 | void HttpRequest::ParsePost_() { 118 | if(method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") { 119 | ParseFromUrlencoded_(); // POST请求体示例 120 | if(DEFAULT_HTML_TAG.count(path_)) { // 如果是登录/注册的path 121 | int tag = DEFAULT_HTML_TAG.find(path_)->second; 122 | LOG_DEBUG("Tag:%d", tag); 123 | if(tag == 0 || tag == 1) { 124 | bool isLogin = (tag == 1); // 为1则是登录 125 | if(UserVerify(post_["username"], post_["password"], isLogin)) { 126 | path_ = "/welcome.html"; 127 | } 128 | else { 129 | path_ = "/error.html"; 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | // 从url中解析编码 137 | void HttpRequest::ParseFromUrlencoded_() { 138 | if(body_.size() == 0) { return; } 139 | 140 | string key, value; 141 | int num = 0; 142 | int n = body_.size(); 143 | int i = 0, j = 0; 144 | 145 | for(; i < n; i++) { 146 | char ch = body_[i]; 147 | switch (ch) { 148 | case '=': 149 | key = body_.substr(j, i - j); 150 | j = i + 1; 151 | break; 152 | case '+': 153 | body_[i] = ' '; 154 | break; 155 | case '%': 156 | num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]); 157 | body_[i + 2] = num % 10 + '0'; 158 | body_[i + 1] = num / 10 + '0'; 159 | i += 2; 160 | break; 161 | case '&': 162 | value = body_.substr(j, i - j); 163 | j = i + 1; 164 | post_[key] = value; 165 | LOG_DEBUG("%s = %s", key.c_str(), value.c_str()); 166 | break; 167 | default: 168 | break; 169 | } 170 | } 171 | assert(j <= i); 172 | if(post_.count(key) == 0 && j < i) { 173 | value = body_.substr(j, i - j); 174 | post_[key] = value; 175 | } 176 | } 177 | 178 | bool HttpRequest::UserVerify(const string &name, const string &pwd, bool isLogin) { 179 | if(name == "" || pwd == "") { return false; } 180 | LOG_INFO("Verify name:%s pwd:%s", name.c_str(), pwd.c_str()); 181 | MYSQL* sql; 182 | SqlConnRAII(&sql, SqlConnPool::Instance()); 183 | assert(sql); 184 | 185 | bool flag = false; 186 | unsigned int j = 0; 187 | char order[256] = { 0 }; 188 | MYSQL_FIELD *fields = nullptr; 189 | MYSQL_RES *res = nullptr; 190 | 191 | if(!isLogin) { flag = true; } 192 | /* 查询用户及密码 */ 193 | snprintf(order, 256, "SELECT username, password FROM user WHERE username='%s' LIMIT 1", name.c_str()); 194 | LOG_DEBUG("%s", order); 195 | 196 | if(mysql_query(sql, order)) { 197 | mysql_free_result(res); 198 | return false; 199 | } 200 | res = mysql_store_result(sql); 201 | j = mysql_num_fields(res); 202 | fields = mysql_fetch_fields(res); 203 | 204 | while(MYSQL_ROW row = mysql_fetch_row(res)) { 205 | LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]); 206 | string password(row[1]); 207 | /* 注册行为 且 用户名未被使用*/ 208 | if(isLogin) { 209 | if(pwd == password) { flag = true; } 210 | else { 211 | flag = false; 212 | LOG_INFO("pwd error!"); 213 | } 214 | } 215 | else { 216 | flag = false; 217 | LOG_INFO("user used!"); 218 | } 219 | } 220 | mysql_free_result(res); 221 | 222 | /* 注册行为 且 用户名未被使用*/ 223 | if(!isLogin && flag == true) { 224 | LOG_DEBUG("regirster!"); 225 | bzero(order, 256); 226 | snprintf(order, 256,"INSERT INTO user(username, password) VALUES('%s','%s')", name.c_str(), pwd.c_str()); 227 | LOG_DEBUG( "%s", order); 228 | if(mysql_query(sql, order)) { 229 | LOG_DEBUG( "Insert error!"); 230 | flag = false; 231 | } 232 | flag = true; 233 | } 234 | // SqlConnPool::Instance()->FreeConn(sql); 235 | LOG_DEBUG( "UserVerify success!!"); 236 | return flag; 237 | } 238 | 239 | std::string HttpRequest::path() const{ 240 | return path_; 241 | } 242 | 243 | std::string& HttpRequest::path(){ 244 | return path_; 245 | } 246 | std::string HttpRequest::method() const { 247 | return method_; 248 | } 249 | 250 | std::string HttpRequest::version() const { 251 | return version_; 252 | } 253 | 254 | std::string HttpRequest::GetPost(const std::string& key) const { 255 | assert(key != ""); 256 | if(post_.count(key) == 1) { 257 | return post_.find(key)->second; 258 | } 259 | return ""; 260 | } 261 | 262 | std::string HttpRequest::GetPost(const char* key) const { 263 | assert(key != nullptr); 264 | if(post_.count(key) == 1) { 265 | return post_.find(key)->second; 266 | } 267 | return ""; 268 | } 269 | 270 | bool HttpRequest::IsKeepAlive() const { 271 | if(header_.count("Connection") == 1) { 272 | return header_.find("Connection")->second == "keep-alive" && version_ == "1.1"; 273 | } 274 | return false; 275 | } -------------------------------------------------------------------------------- /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 | 15 | class HttpRequest { 16 | public: 17 | enum PARSE_STATE { 18 | REQUEST_LINE, 19 | HEADERS, 20 | BODY, 21 | FINISH, 22 | }; 23 | 24 | HttpRequest() { Init(); } 25 | ~HttpRequest() = default; 26 | 27 | void Init(); 28 | bool parse(Buffer& buff); 29 | 30 | std::string path() const; 31 | std::string& path(); 32 | std::string method() const; 33 | std::string version() const; 34 | std::string GetPost(const std::string& key) const; 35 | std::string GetPost(const char* key) const; 36 | 37 | bool IsKeepAlive() const; 38 | 39 | private: 40 | bool ParseRequestLine_(const std::string& line); // 处理请求行 41 | void ParseHeader_(const std::string& line); // 处理请求头 42 | void ParseBody_(const std::string& line); // 处理请求体 43 | 44 | void ParsePath_(); // 处理请求路径 45 | void ParsePost_(); // 处理Post事件 46 | void ParseFromUrlencoded_(); // 从url种解析编码 47 | 48 | static bool UserVerify(const std::string& name, const std::string& pwd, bool isLogin); // 用户验证 49 | 50 | PARSE_STATE state_; 51 | std::string method_, path_, version_, body_; 52 | std::unordered_map header_; 53 | std::unordered_map post_; 54 | 55 | static const std::unordered_set DEFAULT_HTML; 56 | static const std::unordered_map DEFAULT_HTML_TAG; 57 | static int ConverHex(char ch); // 16进制转换为10进制 58 | }; 59 | 60 | #endif -------------------------------------------------------------------------------- /code/http/httpresponse.cpp: -------------------------------------------------------------------------------- 1 | #include "httpresponse.h" 2 | 3 | using namespace std; 4 | 5 | const unordered_map HttpResponse::SUFFIX_TYPE = { 6 | { ".html", "text/html" }, 7 | { ".xml", "text/xml" }, 8 | { ".xhtml", "application/xhtml+xml" }, 9 | { ".txt", "text/plain" }, 10 | { ".rtf", "application/rtf" }, 11 | { ".pdf", "application/pdf" }, 12 | { ".word", "application/nsword" }, 13 | { ".png", "image/png" }, 14 | { ".gif", "image/gif" }, 15 | { ".jpg", "image/jpeg" }, 16 | { ".jpeg", "image/jpeg" }, 17 | { ".au", "audio/basic" }, 18 | { ".mpeg", "video/mpeg" }, 19 | { ".mpg", "video/mpeg" }, 20 | { ".avi", "video/x-msvideo" }, 21 | { ".gz", "application/x-gzip" }, 22 | { ".tar", "application/x-tar" }, 23 | { ".css", "text/css "}, 24 | { ".js", "text/javascript "}, 25 | }; 26 | 27 | const unordered_map HttpResponse::CODE_STATUS = { 28 | { 200, "OK" }, 29 | { 400, "Bad Request" }, 30 | { 403, "Forbidden" }, 31 | { 404, "Not Found" }, 32 | }; 33 | 34 | const unordered_map HttpResponse::CODE_PATH = { 35 | { 400, "/400.html" }, 36 | { 403, "/403.html" }, 37 | { 404, "/404.html" }, 38 | }; 39 | 40 | HttpResponse::HttpResponse() { 41 | code_ = -1; 42 | path_ = srcDir_ = ""; 43 | isKeepAlive_ = false; 44 | mmFile_ = nullptr; 45 | mmFileStat_ = { 0 }; 46 | }; 47 | 48 | HttpResponse::~HttpResponse() { 49 | UnmapFile(); 50 | } 51 | 52 | void HttpResponse::Init(const string& srcDir, string& path, bool isKeepAlive, int code){ 53 | assert(srcDir != ""); 54 | if(mmFile_) { UnmapFile(); } 55 | code_ = code; 56 | isKeepAlive_ = isKeepAlive; 57 | path_ = path; 58 | srcDir_ = srcDir; 59 | mmFile_ = nullptr; 60 | mmFileStat_ = { 0 }; 61 | } 62 | 63 | void HttpResponse::MakeResponse(Buffer& buff) { 64 | /* 判断请求的资源文件 */ 65 | if(stat((srcDir_ + path_).data(), &mmFileStat_) < 0 || S_ISDIR(mmFileStat_.st_mode)) { 66 | code_ = 404; 67 | } 68 | else if(!(mmFileStat_.st_mode & S_IROTH)) { 69 | code_ = 403; 70 | } 71 | else if(code_ == -1) { 72 | code_ = 200; 73 | } 74 | ErrorHtml_(); 75 | AddStateLine_(buff); 76 | AddHeader_(buff); 77 | AddContent_(buff); 78 | } 79 | 80 | char* HttpResponse::File() { 81 | return mmFile_; 82 | } 83 | 84 | size_t HttpResponse::FileLen() const { 85 | return mmFileStat_.st_size; 86 | } 87 | 88 | void HttpResponse::ErrorHtml_() { 89 | if(CODE_PATH.count(code_) == 1) { 90 | path_ = CODE_PATH.find(code_)->second; 91 | stat((srcDir_ + path_).data(), &mmFileStat_); 92 | } 93 | } 94 | 95 | void HttpResponse::AddStateLine_(Buffer& buff) { 96 | string status; 97 | if(CODE_STATUS.count(code_) == 1) { 98 | status = CODE_STATUS.find(code_)->second; 99 | } 100 | else { 101 | code_ = 400; 102 | status = CODE_STATUS.find(400)->second; 103 | } 104 | buff.Append("HTTP/1.1 " + to_string(code_) + " " + status + "\r\n"); 105 | } 106 | 107 | void HttpResponse::AddHeader_(Buffer& buff) { 108 | buff.Append("Connection: "); 109 | if(isKeepAlive_) { 110 | buff.Append("keep-alive\r\n"); 111 | buff.Append("keep-alive: max=6, timeout=120\r\n"); 112 | } else{ 113 | buff.Append("close\r\n"); 114 | } 115 | buff.Append("Content-type: " + GetFileType_() + "\r\n"); 116 | } 117 | 118 | void HttpResponse::AddContent_(Buffer& buff) { 119 | int srcFd = open((srcDir_ + path_).data(), O_RDONLY); 120 | if(srcFd < 0) { 121 | ErrorContent(buff, "File NotFound!"); 122 | return; 123 | } 124 | 125 | //将文件映射到内存提高文件的访问速度 MAP_PRIVATE 建立一个写入时拷贝的私有映射 126 | LOG_DEBUG("file path %s", (srcDir_ + path_).data()); 127 | int* mmRet = (int*)mmap(0, mmFileStat_.st_size, PROT_READ, MAP_PRIVATE, srcFd, 0); 128 | if(*mmRet == -1) { 129 | ErrorContent(buff, "File NotFound!"); 130 | return; 131 | } 132 | mmFile_ = (char*)mmRet; 133 | close(srcFd); 134 | buff.Append("Content-length: " + to_string(mmFileStat_.st_size) + "\r\n\r\n"); 135 | } 136 | 137 | void HttpResponse::UnmapFile() { 138 | if(mmFile_) { 139 | munmap(mmFile_, mmFileStat_.st_size); 140 | mmFile_ = nullptr; 141 | } 142 | } 143 | 144 | // 判断文件类型 145 | string HttpResponse::GetFileType_() { 146 | string::size_type idx = path_.find_last_of('.'); 147 | if(idx == string::npos) { // 最大值 find函数在找不到指定值得情况下会返回string::npos 148 | return "text/plain"; 149 | } 150 | string suffix = path_.substr(idx); 151 | if(SUFFIX_TYPE.count(suffix) == 1) { 152 | return SUFFIX_TYPE.find(suffix)->second; 153 | } 154 | return "text/plain"; 155 | } 156 | 157 | void HttpResponse::ErrorContent(Buffer& buff, string message) 158 | { 159 | string body; 160 | string status; 161 | body += "Error"; 162 | body += ""; 163 | if(CODE_STATUS.count(code_) == 1) { 164 | status = CODE_STATUS.find(code_)->second; 165 | } else { 166 | status = "Bad Request"; 167 | } 168 | body += to_string(code_) + " : " + status + "\n"; 169 | body += "

" + message + "

"; 170 | body += "
TinyWebServer"; 171 | 172 | buff.Append("Content-length: " + to_string(body.size()) + "\r\n\r\n"); 173 | buff.Append(body); 174 | } 175 | -------------------------------------------------------------------------------- /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 50 | -------------------------------------------------------------------------------- /code/http/readme.md: -------------------------------------------------------------------------------- 1 | # HTTP 2 | 3 | ## HTTP请求报文解析与响应报文生成 4 | 5 | ### 请求报文 6 | 7 | HTTP请求报文的结构如下: 8 | 9 | 包括请求行、请求头部、空行和请求数据四个部分。 10 | 11 | ![](https://img-blog.csdnimg.cn/6141ab5159cb4fbaa09d249bdd7201c4.png) 12 | 13 | 14 | 15 | 以下是百度的请求包 16 | 17 | > GET / HTTP/1.1 18 | > Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 19 | > Accept-Encoding: gzip, deflate, br 20 | > Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 21 | > Connection: keep-alive 22 | > Host: www.baidu.com 23 | > Sec-Fetch-Dest: document 24 | > Sec-Fetch-Mode: navigate 25 | > Sec-Fetch-Site: none 26 | > Sec-Fetch-User: ?1 27 | > Upgrade-Insecure-Requests: 1 28 | > User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36 Edg/101.0.1210.32 29 | > sec-ch-ua: " Not A;Brand";v=“99”, “Chromium”;v=“101”, “Microsoft Edge”;v=“101” 30 | > sec-ch-ua-mobile: ?0 31 | > sec-ch-ua-platform: “Windows” 32 | 33 | 上面只包括请求行、请求头和空行,请求数据为空。请求方法是GET,协议版本是HTTP/1.1;请求头是键值对的形式。 34 | 35 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/da23459cb29243068e2118ae8b79534d.png) 36 | 37 | 解析过程由`parse()`函数完成;函数根据状态分别调用了 38 | 39 | ```c++ 40 | ParseRequestLine_();//解析请求行 41 | ParseHeader_();//解析请求头 42 | ParseBody_();//解析请求体 43 | ``` 44 | 45 | 三个函数对请求行、请求头和数据体进行解析。当然解析请求体的函数还会调用`ParsePost_()`,因为Post请求会携带请求体。 46 | 47 | ### 响应报文 48 | 49 | 50 | ```HTML 51 | HTTP/1.1 200 OK 52 | Date: Fri, 22 May 2009 06:07:21 GMT 53 | Content-Type: text/html; charset=UTF-8 54 | 空行 55 | 56 | 57 | 58 | 59 | 60 | 61 | ``` 62 | + 状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。 63 | 第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为OK。 64 | 65 | + 消息报头,用来说明客户端要使用的一些附加信息。 66 | 第二行和第三行为消息报头,Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8。 67 | 68 | + 空行,消息报头后面的空行是必须的。 69 | 70 | + 响应正文,服务器返回给客户端的文本信息。空行后面的html部分为响应正文。 71 | ___ 72 | 73 | 解析请求报文和生成响应报文都是在`HttpConn::process()`函数内完成的。并且是在解析请求报文后随即生成了响应报文。之后这个生成的响应报文便放在缓冲区等待`writev()`函数将其发送给fd。 74 | ```c++ 75 | //只为了说明逻辑,代码有删减 76 | bool HttpConn::process() { 77 | request_.Init();//初始化解析类 78 | if(readBuff_.ReadableBytes() <= 0) {//从缓冲区中读数据 79 | return false; 80 | } 81 | else if(request_.parse(readBuff_)) {//解析数据,根据解析结果进行响应类的初始化 82 | response_.Init(srcDir, request_.path(), request_.IsKeepAlive(), 200); 83 | } else { 84 | response_.Init(srcDir, request_.path(), false, 400); 85 | } 86 | response_.MakeResponse(writeBuff_);//生成响应报文放入writeBuff_中 87 | /* 响应头 iov记录了需要把数据从缓冲区发送出去的相关信息 88 | iov_base为缓冲区首地址,iov_len为缓冲区长度 */ 89 | iov_[0].iov_base = const_cast(writeBuff_.Peek()); 90 | iov_[0].iov_len = writeBuff_.ReadableBytes(); 91 | iovCnt_ = 1; 92 | 93 | /* 文件 */ 94 | if(response_.FileLen() > 0 && response_.File()) { // 95 | iov_[1].iov_base = response_.File(); 96 | iov_[1].iov_len = response_.FileLen(); 97 | iovCnt_ = 2; 98 | } 99 | return true; 100 | } 101 | ``` 102 | 103 | -------------------------------------------------------------------------------- /code/log/blockqueue.h: -------------------------------------------------------------------------------- 1 | # ifndef BLOCKQUEUE_H 2 | # define BLOCKQUEUE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace std; 9 | 10 | template 11 | class BlockQueue { 12 | public: 13 | explicit BlockQueue(size_t maxsize = 1000); 14 | ~BlockQueue(); 15 | bool empty(); 16 | bool full(); 17 | void push_back(const T& item); 18 | void push_front(const T& item); 19 | bool pop(T& item); // 弹出的任务放入item 20 | bool pop(T& item, int timeout); // 等待时间 21 | void clear(); 22 | T front(); 23 | T back(); 24 | size_t capacity(); 25 | size_t size(); 26 | 27 | void flush(); 28 | void Close(); 29 | 30 | private: 31 | deque deq_; // 底层数据结构 32 | mutex mtx_; // 锁 33 | bool isClose_; // 关闭标志 34 | size_t capacity_; // 容量 35 | condition_variable condConsumer_; // 消费者条件变量 36 | condition_variable condProducer_; // 生产者条件变量 37 | }; 38 | 39 | template 40 | BlockQueue::BlockQueue(size_t maxsize) : capacity_(maxsize) { 41 | assert(maxsize > 0); 42 | isClose_ = false; 43 | } 44 | 45 | template 46 | BlockQueue::~BlockQueue() { 47 | Close(); 48 | } 49 | 50 | template 51 | void BlockQueue::Close() { 52 | // lock_guard locker(mtx_); // 操控队列之前,都需要上锁 53 | // deq_.clear(); // 清空队列 54 | clear(); 55 | isClose_ = true; 56 | condConsumer_.notify_all(); 57 | condProducer_.notify_all(); 58 | } 59 | 60 | template 61 | void BlockQueue::clear() { 62 | lock_guard locker(mtx_); 63 | deq_.clear(); 64 | } 65 | 66 | template 67 | bool BlockQueue::empty() { 68 | lock_guard locker(mtx_); 69 | return deq_.empty(); 70 | } 71 | 72 | template 73 | bool BlockQueue::full() { 74 | lock_guard locker(mtx_); 75 | return deq_.size() >= capacity_; 76 | } 77 | 78 | template 79 | void BlockQueue::push_back(const T& item) { 80 | // 注意,条件变量需要搭配unique_lock 81 | unique_lock locker(mtx_); 82 | while(deq_.size() >= capacity_) { // 队列满了,需要等待 83 | condProducer_.wait(locker); // 暂停生产,等待消费者唤醒生产条件变量 84 | } 85 | deq_.push_back(item); 86 | condConsumer_.notify_one(); // 唤醒消费者 87 | } 88 | 89 | template 90 | void BlockQueue::push_front(const T& item) { 91 | unique_lock locker(mtx_); 92 | while(deq_.size() >= capacity_) { // 队列满了,需要等待 93 | condProducer_.wait(locker); // 暂停生产,等待消费者唤醒生产条件变量 94 | } 95 | deq_.push_front(item); 96 | condConsumer_.notify_one(); // 唤醒消费者 97 | } 98 | 99 | template 100 | bool BlockQueue::pop(T& item) { 101 | unique_lock locker(mtx_); 102 | while(deq_.empty()) { 103 | condConsumer_.wait(locker); // 队列空了,需要等待 104 | } 105 | item = deq_.front(); 106 | deq_.pop_front(); 107 | condProducer_.notify_one(); // 唤醒生产者 108 | return true; 109 | } 110 | 111 | template 112 | bool BlockQueue::pop(T &item, int timeout) { 113 | unique_lock locker(mtx_); 114 | while(deq_.empty()){ 115 | if(condConsumer_.wait_for(locker, std::chrono::seconds(timeout)) 116 | == std::cv_status::timeout){ 117 | return false; 118 | } 119 | if(isClose_){ 120 | return false; 121 | } 122 | } 123 | item = deq_.front(); 124 | deq_.pop_front(); 125 | condProducer_.notify_one(); 126 | return true; 127 | } 128 | 129 | template 130 | T BlockQueue::front() { 131 | lock_guard locker(mtx_); 132 | return deq_.front(); 133 | } 134 | 135 | template 136 | T BlockQueue::back() { 137 | lock_guard locker(mtx_); 138 | return deq_.back(); 139 | } 140 | 141 | template 142 | size_t BlockQueue::capacity() { 143 | lock_guard locker(mtx_); 144 | return capacity_; 145 | } 146 | 147 | template 148 | size_t BlockQueue::size() { 149 | lock_guard locker(mtx_); 150 | return deq_.size(); 151 | } 152 | 153 | // 唤醒消费者 154 | template 155 | void BlockQueue::flush() { 156 | condConsumer_.notify_one(); 157 | } 158 | # endif -------------------------------------------------------------------------------- /code/log/log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | // 构造函数 4 | Log::Log() { 5 | fp_ = nullptr; 6 | deque_ = nullptr; 7 | writeThread_ = nullptr; 8 | lineCount_ = 0; 9 | toDay_ = 0; 10 | isAsync_ = false; 11 | } 12 | 13 | Log::~Log() { 14 | while(!deque_->empty()) { 15 | deque_->flush(); // 唤醒消费者,处理掉剩下的任务 16 | } 17 | deque_->Close(); // 关闭队列 18 | writeThread_->join(); // 等待当前线程完成手中的任务 19 | if(fp_) { // 冲洗文件缓冲区,关闭文件描述符 20 | lock_guard locker(mtx_); 21 | flush(); // 清空缓冲区中的数据 22 | fclose(fp_); // 关闭日志文件 23 | } 24 | } 25 | 26 | // 唤醒阻塞队列消费者,开始写日志 27 | void Log::flush() { 28 | if(isAsync_) { // 只有异步日志才会用到deque 29 | deque_->flush(); 30 | } 31 | fflush(fp_); // 清空输入缓冲区 32 | } 33 | 34 | // 懒汉模式 局部静态变量法(这种方法不需要加锁和解锁操作) 35 | Log* Log::Instance() { 36 | static Log log; 37 | return &log; 38 | } 39 | 40 | // 异步日志的写线程函数 41 | void Log::FlushLogThread() { 42 | Log::Instance()->AsyncWrite_(); 43 | } 44 | 45 | // 写线程真正的执行函数 46 | void Log::AsyncWrite_() { 47 | string str = ""; 48 | while(deque_->pop(str)) { 49 | lock_guard locker(mtx_); 50 | fputs(str.c_str(), fp_); 51 | } 52 | } 53 | 54 | // 初始化日志实例 55 | void Log::init(int level, const char* path, const char* suffix, int maxQueCapacity) { 56 | isOpen_ = true; 57 | level_ = level; 58 | path_ = path; 59 | suffix_ = suffix; 60 | if(maxQueCapacity) { // 异步方式 61 | isAsync_ = true; 62 | if(!deque_) { // 为空则创建一个 63 | unique_ptr> newQue(new BlockQueue); 64 | // 因为unique_ptr不支持普通的拷贝或赋值操作,所以采用move 65 | // 将动态申请的内存权给deque,newDeque被释放 66 | deque_ = move(newQue); // 左值变右值,掏空newDeque 67 | 68 | unique_ptr newThread(new thread(FlushLogThread)); 69 | writeThread_ = move(newThread); 70 | } 71 | } else { 72 | isAsync_ = false; 73 | } 74 | 75 | lineCount_ = 0; 76 | 77 | time_t timer = time(nullptr); 78 | struct tm *sysTime = localtime(&timer); 79 | struct tm t = *sysTime; 80 | path_ = path; 81 | suffix_ = suffix; 82 | char fileName[LOG_NAME_LEN] = {0}; 83 | snprintf(fileName, LOG_NAME_LEN - 1, "%s/%04d_%02d_%02d%s", 84 | path_, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, suffix_); 85 | toDay_ = t.tm_mday; 86 | 87 | { 88 | lock_guard locker(mtx_); 89 | buff_.RetrieveAll(); 90 | if(fp_) { // 重新打开 91 | flush(); 92 | fclose(fp_); 93 | } 94 | fp_ = fopen(fileName, "a"); // 打开文件读取并附加写入 95 | if(fp_ == nullptr) { 96 | mkdir(path_, 0777); 97 | fp_ = fopen(fileName, "a"); // 生成目录文件(最大权限) 98 | } 99 | assert(fp_ != nullptr); 100 | } 101 | } 102 | 103 | void Log::write(int level, const char *format, ...) { 104 | struct timeval now = {0, 0}; 105 | gettimeofday(&now, nullptr); 106 | time_t tSec = now.tv_sec; 107 | struct tm *sysTime = localtime(&tSec); 108 | struct tm t = *sysTime; 109 | va_list vaList; 110 | 111 | // 日志日期 日志行数 如果不是今天或行数超了 112 | if (toDay_ != t.tm_mday || (lineCount_ && (lineCount_ % MAX_LINES == 0))) 113 | { 114 | unique_lock locker(mtx_); 115 | locker.unlock(); 116 | 117 | char newFile[LOG_NAME_LEN]; 118 | char tail[36] = {0}; 119 | snprintf(tail, 36, "%04d_%02d_%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday); 120 | 121 | if (toDay_ != t.tm_mday) // 时间不匹配,则替换为最新的日志文件名 122 | { 123 | snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s%s", path_, tail, suffix_); 124 | toDay_ = t.tm_mday; 125 | lineCount_ = 0; 126 | } 127 | else { 128 | snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s-%d%s", path_, tail, (lineCount_ / MAX_LINES), suffix_); 129 | } 130 | 131 | locker.lock(); 132 | flush(); 133 | fclose(fp_); 134 | fp_ = fopen(newFile, "a"); 135 | assert(fp_ != nullptr); 136 | } 137 | 138 | // 在buffer内生成一条对应的日志信息 139 | { 140 | unique_lock locker(mtx_); 141 | lineCount_++; 142 | int n = snprintf(buff_.BeginWrite(), 128, "%d-%02d-%02d %02d:%02d:%02d.%06ld ", 143 | t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, 144 | t.tm_hour, t.tm_min, t.tm_sec, now.tv_usec); 145 | 146 | buff_.HasWritten(n); 147 | AppendLogLevelTitle_(level); 148 | 149 | va_start(vaList, format); 150 | int m = vsnprintf(buff_.BeginWrite(), buff_.WritableBytes(), format, vaList); 151 | va_end(vaList); 152 | 153 | buff_.HasWritten(m); 154 | buff_.Append("\n\0", 2); 155 | 156 | if(isAsync_ && deque_ && !deque_->full()) { // 异步方式(加入阻塞队列中,等待写线程读取日志信息) 157 | deque_->push_back(buff_.RetrieveAllToStr()); 158 | } else { // 同步方式(直接向文件中写入日志信息) 159 | fputs(buff_.Peek(), fp_); // 同步就直接写入文件 160 | } 161 | buff_.RetrieveAll(); // 清空buff 162 | } 163 | } 164 | 165 | // 添加日志等级 166 | void Log::AppendLogLevelTitle_(int level) { 167 | switch(level) { 168 | case 0: 169 | buff_.Append("[debug]: ", 9); 170 | break; 171 | case 1: 172 | buff_.Append("[info] : ", 9); 173 | break; 174 | case 2: 175 | buff_.Append("[warn] : ", 9); 176 | break; 177 | case 3: 178 | buff_.Append("[error]: ", 9); 179 | break; 180 | default: 181 | buff_.Append("[info] : ", 9); 182 | break; 183 | } 184 | } 185 | 186 | int Log::GetLevel() { 187 | lock_guard locker(mtx_); 188 | return level_; 189 | } 190 | 191 | void Log::SetLevel(int level) { 192 | lock_guard locker(mtx_); 193 | level_ = level; 194 | } -------------------------------------------------------------------------------- /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 | // 初始化日志实例(阻塞队列最大容量、日志保存路径、日志文件后缀) 18 | void init(int level, const char* path = "./log", 19 | const char* suffix =".log", 20 | int maxQueueCapacity = 1024); 21 | 22 | static Log* Instance(); 23 | static void FlushLogThread(); // 异步写日志公有方法,调用私有方法asyncWrite 24 | 25 | void write(int level, const char *format,...); // 将输出内容按照标准格式整理 26 | void flush(); 27 | 28 | int GetLevel(); 29 | void SetLevel(int level); 30 | bool IsOpen() { return isOpen_; } 31 | 32 | private: 33 | Log(); 34 | void AppendLogLevelTitle_(int level); 35 | virtual ~Log(); 36 | void AsyncWrite_(); // 异步写日志方法 37 | 38 | private: 39 | static const int LOG_PATH_LEN = 256; // 日志文件最长文件名 40 | static const int LOG_NAME_LEN = 256; // 日志最长名字 41 | static const int MAX_LINES = 50000; // 日志文件内的最长日志条数 42 | 43 | const char* path_; //路径名 44 | const char* suffix_; //后缀名 45 | 46 | int MAX_LINES_; // 最大日志行数 47 | 48 | int lineCount_; //日志行数记录 49 | int toDay_; //按当天日期区分文件 50 | 51 | bool isOpen_; 52 | 53 | Buffer buff_; // 输出的内容,缓冲区 54 | int level_; // 日志等级 55 | bool isAsync_; // 是否开启异步日志 56 | 57 | FILE* fp_; //打开log的文件指针 58 | std::unique_ptr> deque_; //阻塞队列 59 | std::unique_ptr writeThread_; //写线程的指针 60 | std::mutex mtx_; //同步日志必需的互斥量 61 | }; 62 | 63 | #define LOG_BASE(level, format, ...) \ 64 | do {\ 65 | Log* log = Log::Instance();\ 66 | if (log->IsOpen() && log->GetLevel() <= level) {\ 67 | log->write(level, format, ##__VA_ARGS__); \ 68 | log->flush();\ 69 | }\ 70 | } while(0); 71 | 72 | // 四个宏定义,主要用于不同类型的日志输出,也是外部使用日志的接口 73 | // ...表示可变参数,__VA_ARGS__就是将...的值复制到这里 74 | // 前面加上##的作用是:当可变参数的个数为0时,这里的##可以把把前面多余的","去掉,否则会编译出错。 75 | #define LOG_DEBUG(format, ...) do {LOG_BASE(0, format, ##__VA_ARGS__)} while(0); 76 | #define LOG_INFO(format, ...) do {LOG_BASE(1, format, ##__VA_ARGS__)} while(0); 77 | #define LOG_WARN(format, ...) do {LOG_BASE(2, format, ##__VA_ARGS__)} while(0); 78 | #define LOG_ERROR(format, ...) do {LOG_BASE(3, format, ##__VA_ARGS__)} while(0); 79 | 80 | #endif //LOG_H -------------------------------------------------------------------------------- /code/log/readme.md: -------------------------------------------------------------------------------- 1 | # 日志系统 2 | 对于一个服务器而言,不论是在调试中还是在运行中,都需要通过打日志的方式来记录程序的运行情况。 3 | ![日志系统](https://img-blog.csdnimg.cn/img_convert/081995d0fbbcee532e60fed1c31255d8.png) 4 | + **同步日志**:日志写入函数与工作线程串行执行,由于涉及到I/O操作,当单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。 5 | 6 | + **异步日志**:将所写的日志内容先存入阻塞队列中,写线程从阻塞队列中取出内容,写入日志。 7 | ## 日志的运行流程: 8 | 1、使用单例模式(局部静态变量方法)获取实例Log::getInstance()。 9 | 10 | 2、通过实例调用Log::getInstance()->init()函数完成初始化,若设置阻塞队列大小大于0则选择异步日志,等于0则选择同步日志,更新isAysnc变量。 11 | 12 | 3、通过实例调用write_log()函数写日志,首先根据当前时刻创建日志(前缀为时间,后缀为".log",并更新日期today和当前行数lineCount。 13 | 14 | 4、在write_log()函数内部,通过isAsync变量判断写日志的方法:如果是异步,工作线程将要写的内容放进阻塞队列中,由写线程在阻塞队列中取出数据,然后写入日志;如果是同步,直接写入日志文件中。 15 | > 该函数采用了不定参数的形式,具体使用步骤如下: 16 | > 17 | > ```C++ 18 | > va_list vaList; 19 | > va_start(vaList, format); 20 | > int m = vsnprintf(buff_.BeginWrite(), buff_.WritableBytes(), format, vaList); 21 | > va_end(vaList); 22 | > ``` 23 | 24 | 关于unique_ptr的移动拷贝构造: https://blog.csdn.net/tongyi04/article/details/123405806 25 | ## blockqueue 26 | 阻塞队列采用deque实现。 27 | 若`MaxCapacity`为0,则为同步日志,不需要阻塞队列。 28 | 29 | 内部有生产者消费者模型,搭配锁、条件变量使用。 30 | 31 | 其中,消费者防止任务队列为空,生产者防止任务队列满。 32 | 33 | ## 日志的分级与分文件: 34 | **分级情况:** 35 | + Debug,调试代码时的输出,在系统实际运行时,一般不使用。 36 | + Warn,这种警告与调试时终端的warning类似,同样是调试代码时使用。 37 | + Info,报告系统当前的状态,当前执行的流程或接收的信息等。 38 | + Erro,输出系统的错误信息 39 | 40 | **分文件情况:** 41 | 42 | 1. 按天分,日志写入前会判断当前today是否为创建日志的时间,若为创建日志时间,则写入日志,否则按当前时间创建新的log文件,更新创建时间和行数。 43 | 2. 按行分,日志写入前会判断行数是否超过最大行限制,若超过,则在当前日志的末尾加lineCount / MAX_LOG_LINES为后缀创建新的log文件。 44 | -------------------------------------------------------------------------------- /code/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "server/webserver.h" 3 | 4 | int main() { 5 | // 守护进程 后台运行 6 | WebServer server( 7 | 1316, 3, 60000, // 端口 ET模式 timeoutMs 8 | 3306, "root", "123456", "webserver", /* Mysql配置 */ 9 | 12, 8, true, 1, 1024); /* 连接池数量 线程池数量 日志开关 日志等级 日志异步队列容量 */ 10 | server.Start(); 11 | } 12 | -------------------------------------------------------------------------------- /code/pool/readme.md: -------------------------------------------------------------------------------- 1 | # 线程池 & 数据库连接池 2 | ## 线程池 3 | 使用线程池可以减少线程的销毁,而且如果不使用线程池的话,来一个客户端就创建一个线程。比如有1000,这样线程的创建、线程之间的调度也会耗费很多的系统资源,所以采用线程池使程序的效率更高。 线程池就是项目启动的时候,就先把线程池准备好。 4 | 5 | 一般线程池的实现是通过生产者消费者模型来的: 6 | ![](https://img-blog.csdnimg.cn/77a4124181da4fd69d200c1b532e57aa.png) 7 | 8 | + **线程同步问题** 9 | 10 | 线程同步问题涉及到了互斥量、条件变量。 11 | 在代码中,将互斥锁、条件变量、关闭状态、工作队列封装到了一起,通过一个共享智能指针来管理这些条件。 12 | 13 | > 其实我不是很理解为啥要把他们封装到一个结构体中,可能是为了在构造函数中,创建线程时的匿名函数中捕获列表传值更方便吧。在捕获列表中,采用了this:函数体内可以使用Lambda所在类中的成员变量 14 | 15 | 鉴于本人对匿名函数不是很熟悉,所以在写的时候又去学习了一遍匿名函数。关于匿名函数的使用,参考这篇博客:https://blog.csdn.net/huangshanchun/article/details/47155859 16 | 17 | 在匿名函数中还用到了move,也是C++11的一个新特性,该函数的作用是将左值转换为右值,资产转移,该函数是为性能而生的。 18 | 19 | ## 数据库连接池 20 | 我见过的连接池有用std::list写的,也有用std::queue写的,我个人还是比较倾向于用queue写。 21 | + 为什么要使用连接池? 22 | - 由于服务器需要频繁地访问数据库,即需要频繁创建和断开数据库连接,该过程是一个很耗时的操作,也会对数据库造成安全隐患。 23 | - 在程序初始化的时候,集中创建并管理多个数据库连接,可以保证较快的数据库读写速度,更加安全可靠。 24 | + RAII机制 25 | - C++的语言机制保证,当一个对象创建时会自动调用构造函数,当对象超出作用域时会自动调用析构函数。 26 | - 所以,我们可以使用类来管理资源,在构造函数中申请分配资源,在析构函数中释放资源。 27 | - RAII的核心思想是将资源与对象的生命周期绑定。 28 | 29 | 在连接池的实现中,使用到了信号量来管理资源的数量;而锁的使用则是为了在访问公共资源的时候使用。所以说,无论是条件变量还是信号量,都需要锁。 30 | 31 | 不同的是,信号量的使用要先使用信号量sem_wait再上锁,而条件变量的使用要先上锁再使用条件变量wait。 -------------------------------------------------------------------------------- /code/pool/sqlconnpool.cpp: -------------------------------------------------------------------------------- 1 | #include "sqlconnpool.h" 2 | 3 | SqlConnPool* SqlConnPool::Instance() { 4 | static SqlConnPool pool; 5 | return &pool; 6 | } 7 | 8 | // 初始化 9 | void SqlConnPool::Init(const char* host, uint16_t port, 10 | const char* user,const char* pwd, 11 | const char* dbName, int connSize = 10) { 12 | assert(connSize > 0); 13 | for(int i = 0; i < connSize; i++) { 14 | MYSQL* conn = nullptr; 15 | conn = mysql_init(conn); 16 | if(!conn) { 17 | LOG_ERROR("MySql init error!"); 18 | assert(conn); 19 | } 20 | conn = mysql_real_connect(conn, host, user, pwd, dbName, port, nullptr, 0); 21 | if (!conn) { 22 | LOG_ERROR("MySql Connect error!"); 23 | } 24 | connQue_.emplace(conn); 25 | } 26 | MAX_CONN_ = connSize; 27 | sem_init(&semId_, 0, MAX_CONN_); 28 | } 29 | 30 | MYSQL* SqlConnPool::GetConn() { 31 | MYSQL* conn = nullptr; 32 | if(connQue_.empty()) { 33 | LOG_WARN("SqlConnPool busy!"); 34 | return nullptr; 35 | } 36 | sem_wait(&semId_); // -1 37 | lock_guard locker(mtx_); 38 | conn = connQue_.front(); 39 | connQue_.pop(); 40 | return conn; 41 | } 42 | 43 | // 存入连接池,实际上没有关闭 44 | void SqlConnPool::FreeConn(MYSQL* conn) { 45 | assert(conn); 46 | lock_guard locker(mtx_); 47 | connQue_.push(conn); 48 | sem_post(&semId_); // +1 49 | } 50 | 51 | void SqlConnPool::ClosePool() { 52 | lock_guard locker(mtx_); 53 | while(!connQue_.empty()) { 54 | auto conn = connQue_.front(); 55 | connQue_.pop(); 56 | mysql_close(conn); 57 | } 58 | mysql_library_end(); 59 | } 60 | 61 | int SqlConnPool::GetFreeConnCount() { 62 | lock_guard locker(mtx_); 63 | return connQue_.size(); 64 | } -------------------------------------------------------------------------------- /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, uint16_t port, 21 | const char* user,const char* pwd, 22 | const char* dbName, int connSize); 23 | void ClosePool(); 24 | 25 | private: 26 | SqlConnPool() = default; 27 | ~SqlConnPool() { ClosePool(); } 28 | 29 | int MAX_CONN_; 30 | 31 | std::queue connQue_; 32 | std::mutex mtx_; 33 | sem_t semId_; 34 | }; 35 | 36 | /* 资源在对象构造初始化 资源在对象析构时释放*/ 37 | class SqlConnRAII { 38 | public: 39 | SqlConnRAII(MYSQL** sql, SqlConnPool *connpool) { 40 | assert(connpool); 41 | *sql = connpool->GetConn(); 42 | sql_ = *sql; 43 | connpool_ = connpool; 44 | } 45 | 46 | ~SqlConnRAII() { 47 | if(sql_) { connpool_->FreeConn(sql_); } 48 | } 49 | 50 | private: 51 | MYSQL *sql_; 52 | SqlConnPool* connpool_; 53 | }; 54 | 55 | #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 | #include 10 | 11 | 12 | class ThreadPool { 13 | public: 14 | ThreadPool() = default; 15 | ThreadPool(ThreadPool&&) = default; 16 | // 尽量用make_shared代替new,如果通过new再传递给shared_ptr,内存是不连续的,会造成内存碎片化 17 | explicit ThreadPool(int threadCount = 8) : pool_(std::make_shared()) { // make_shared:传递右值,功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr 18 | assert(threadCount > 0); 19 | for(int i = 0; i < threadCount; i++) { 20 | std::thread([this]() { 21 | std::unique_lock locker(pool_->mtx_); 22 | while(true) { 23 | if(!pool_->tasks.empty()) { 24 | auto task = std::move(pool_->tasks.front()); // 左值变右值,资产转移 25 | pool_->tasks.pop(); 26 | locker.unlock(); // 因为已经把任务取出来了,所以可以提前解锁了 27 | task(); 28 | locker.lock(); // 马上又要取任务了,上锁 29 | } else if(pool_->isClosed) { 30 | break; 31 | } else { 32 | pool_->cond_.wait(locker); // 等待,如果任务来了就notify的 33 | } 34 | 35 | } 36 | }).detach(); 37 | } 38 | } 39 | 40 | ~ThreadPool() { 41 | if(pool_) { 42 | std::unique_lock locker(pool_->mtx_); 43 | pool_->isClosed = true; 44 | } 45 | pool_->cond_.notify_all(); // 唤醒所有的线程 46 | } 47 | 48 | template 49 | void AddTask(T&& task) { 50 | std::unique_lock locker(pool_->mtx_); 51 | pool_->tasks.emplace(std::forward(task)); 52 | pool_->cond_.notify_one(); 53 | } 54 | 55 | private: 56 | // 用一个结构体封装起来,方便调用 57 | struct Pool { 58 | std::mutex mtx_; 59 | std::condition_variable cond_; 60 | bool isClosed; 61 | std::queue> tasks; // 任务队列,函数类型为void() 62 | }; 63 | std::shared_ptr pool_; 64 | }; 65 | 66 | #endif -------------------------------------------------------------------------------- /code/readme.md: -------------------------------------------------------------------------------- 1 | 由于之前自己手动敲一遍的代码因为自己的一个失误全没了。当时我只是想删除某个文件夹下的所有文件,便在终端执行`rm -rf ./*`,但我大意了,没有打.。当我反应过来的时候已经为时已晚,崩溃了一个晚上。 2 | 3 | 后来想了想,不妨重新再写一次,第二次肯定会有新的理解。所以这次我自顶向下,重新写了一遍笔记,也希望自己能有更新的理解。 4 | 5 | 一些优质博客: 6 | > https://blog.csdn.net/weixin_47243756/article/details/129494126 -------------------------------------------------------------------------------- /code/server/epoller.cpp: -------------------------------------------------------------------------------- 1 | #include "epoller.h" 2 | 3 | Epoller::Epoller(int maxEvent):epollFd_(epoll_create(512)), events_(maxEvent){ 4 | assert(epollFd_ >= 0 && events_.size() > 0); 5 | } 6 | 7 | Epoller::~Epoller() { 8 | close(epollFd_); 9 | } 10 | 11 | bool Epoller::AddFd(int fd, uint32_t events) { 12 | if(fd < 0) return false; 13 | epoll_event ev = {0}; 14 | ev.data.fd = fd; 15 | ev.events = events; 16 | return 0 == epoll_ctl(epollFd_, EPOLL_CTL_ADD, fd, &ev); 17 | } 18 | 19 | bool Epoller::ModFd(int fd, uint32_t events) { 20 | if(fd < 0) return false; 21 | epoll_event ev = {0}; 22 | ev.data.fd = fd; 23 | ev.events = events; 24 | return 0 == epoll_ctl(epollFd_, EPOLL_CTL_MOD, fd, &ev); 25 | } 26 | 27 | bool Epoller::DelFd(int fd) { 28 | if(fd < 0) return false; 29 | return 0 == epoll_ctl(epollFd_, EPOLL_CTL_DEL, fd, 0); 30 | } 31 | 32 | // 返回事件数量 33 | int Epoller::Wait(int timeoutMs) { 34 | return epoll_wait(epollFd_, &events_[0], static_cast(events_.size()), timeoutMs); 35 | } 36 | 37 | // 获取事件的fd 38 | int Epoller::GetEventFd(size_t i) const { 39 | assert(i < events_.size() && i >= 0); 40 | return events_[i].data.fd; 41 | } 42 | 43 | // 获取事件属性 44 | uint32_t Epoller::GetEvents(size_t i) const { 45 | assert(i < events_.size() && i >= 0); 46 | return events_[i].events; 47 | } -------------------------------------------------------------------------------- /code/server/epoller.h: -------------------------------------------------------------------------------- 1 | #ifndef EPOLLER_H 2 | #define EPOLLER_H 3 | 4 | #include //epoll_ctl() 5 | #include // close() 6 | #include // close() 7 | #include 8 | #include 9 | 10 | class Epoller { 11 | public: 12 | explicit Epoller(int maxEvent = 1024); 13 | ~Epoller(); 14 | 15 | bool AddFd(int fd, uint32_t events); 16 | bool ModFd(int fd, uint32_t events); 17 | bool DelFd(int fd); 18 | int Wait(int timeoutMs = -1); 19 | int GetEventFd(size_t i) const; 20 | uint32_t GetEvents(size_t i) const; 21 | 22 | private: 23 | int epollFd_; 24 | std::vector events_; 25 | }; 26 | 27 | #endif //EPOLLER_H -------------------------------------------------------------------------------- /code/server/readme.md: -------------------------------------------------------------------------------- 1 | # WebServer 2 | 这是整个WebServer最顶层的接口,每连接进来一个用户,就有一个新的套接字创建并加入epoller中,并且给该用户创建一个时间结点,加入时间堆中,同时将该用户加入users哈希表中(key:fd,value:HttpConn) 3 | 4 | ## 软件层次的设计 5 | WebServer :服务器逻辑框架: epoller监听+线程池读写 6 | | 7 | | 8 | Epoller Timer :epoll操作封装, 定时器给连接计时 9 | | | 10 | ---------- 11 | | 12 | HttpConnection :把监听连接返回的文件描述符封装成一个连接实例, 对readv, write网络数据传输进行封装, 管理连接 13 | | | 14 | HttpRequest HttpResponse :请求操作封装,响应操作封装,业务逻辑 15 | | | 16 | -------------- 17 | | 18 | Buffer :读写缓冲区 19 | 20 | ThreadPool : 线程池,负责读写操作(上图上两层属于主线程,下三层属于线程池) 21 | Log : 日志类 22 | 23 | ## 设计 24 | 按照软件分层设计的草图,WebServer设计目标为: 25 | 26 | + 监听IO事件 27 | + 处理超时连接 28 | 数据: 29 | int port_;     //端口 30 | 31 | int timeoutMS_;     //毫秒MS,定时器的默认过期时间 32 | 33 | bool isClose_;    //服务启动标志 34 | 35 | int listenFd_;   //监听文件描述符 36 | 37 | bool openLinger_;  //优雅关闭选项 38 | 39 | char* srcDir_;   //需要获取的路径 40 | 41 | uint32_t listenEvent_; //初始监听描述符监听设置 42 | 43 | uint32_t connectionEvent_;//初始连接描述符监听设置 44 | 45 | std::unique_ptrtimer_;  //定时器 46 | 47 | std::unique_ptr threadpool_; //线程池 48 | 49 | std::unique_ptr epoller_; //反应堆 50 | 51 | std::unordered_map users_;//连接队列 52 | 53 | + 函数: 54 | 55 | 1. 构造函数: 设置服务器参数 + 初始化定时器/线程池/反应堆/连接队列 56 | 57 | 2. 析构函数: 关闭listenFd_, 销毁 连接队列/定时器/线程池/反应堆 58 | 59 | 3. 主函数start() 60 | 61 | 1. 创建端口,绑定端口,监听端口, 创建epoll反应堆, 将监听描述符加入反应堆 62 | 63 | 2. 等待事件就绪 64 | 65 | 1. 连接事件-->DealListen() 66 | 67 | 2. 写事件-->DealWrite() 68 | 69 | 3. 读事件-->DealRead() 70 | 71 | 3. 事件处理完毕,修改反应堆,再跳到2处循环执行 72 | 73 | 4. DealListen: 新初始化一个HttpConnection对象 74 | 75 | 5. DealWrite: 对应连接对象进行处理-->若处理成功,则监听事件转换成 读 事件 76 | 77 | 6. DealRead:  对应连接对象进行处理-->若处理成功,则监听事件转换成 写 事件 78 | ## Epoller 79 | 对增删查改的简单封装。 80 | ## WebServer 类详解 81 | ### 1. 初始化 82 | ```c++ 83 | threadpool_(new ThreadPool(threadNum)) 84 | InitSocket_();//初始化Socket连接 85 | InitEventMode_(trigMode);//初始化事件模式 86 | SqlConnPool::Instance()->Init();//初始化数据库连接池 87 | Log::Instance()->init(logLevel, "./log", ".log", logQueSize); 88 | ``` 89 | 创建线程池:线程池的构造函数中会创建线程并且detach() 90 | 91 | 初始化Socket的函数`InitSocket_();` C/S中,服务器套接字的初始化无非就是socket - bind - listen - accept - 发送接收数据这几个过程;函数执行到listen后,把前面得到的listenfd添加到epoller模型中,即把accept()和接收数据的操作交给epoller处理了。并且把该监听描述符设置为非阻塞。 92 | 93 | 初始化事件模式函数`InitEventMode_(trigMode);`,将`listenEvent_` 和 `connEvent_`都设置为EPOLLET模式。 94 | 95 | 初始化数据库连接池`SqlConnPool::Instance()->Init();`创造单例连接池,执行初始化函数。 96 | 97 | 初始化日志系统:在初始化函数中,创建阻塞队列和写线程,并创建日志。 98 | 99 | ### 2. 启动WebServer 100 | 接下来启动WebServer,首先需要设定`epoll_wait()`等待的时间,这里我们选择调用定时器的`GetNextTick()`函数,这个函数的作用是返回最小堆堆顶的连接设定的过期时间与现在时间的差值。这个时间的选择可以保证服务器等待事件的时间不至于太短也不至于太长。接着调用`epoll_wait()`函数,返回需要已经就绪事件的数目。这里的就绪事件分为两类:收到新的http请求和其他的读写事件。 101 | 这里设置两个变量fd和events分别用来存储就绪事件的文件描述符和事件类型。 102 | 103 | 1.收到新的HTTP请求的情况 104 | 105 | 在fd==listenFd_的时候,也就是收到新的HTTP请求的时候,调用函数DealListen_();处理监听,接受客户端连接; 106 | 107 | 2.已经建立连接的HTTP发来IO请求的情况 108 | 109 | 在events& EPOLLIN 或events & EPOLLOUT为真时,需要进行读写的处理。分别调用 DealRead_(&users_[fd])和DealWrite_(&users_[fd]) 函数。这里需要说明:DealListen_()函数并没有调用线程池中的线程,而DealRead_(&users_[fd])和DealWrite_(&users_[fd]) 则都交由线程池中的线程进行处理了。 110 | 111 | ### 3. I/O处理的具体流程 112 | `DealRead_(&users_[fd])`和`DealWrite_(&users_[fd])` 通过调用 113 | ```c++ 114 | threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client)); //读 115 | threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client)); //写 116 | ``` 117 | 函数来取出线程池中的线程继续进行读写,而主进程这时可以继续监听新来的就绪事件了。 118 | 119 | `OnRead_()`和`OnWrite_()`函数分别进行读写的处理。 120 | 121 | `OnRead_()`函数首先把数据从缓冲区中读出来(调用HttpConn的read,read调用ReadFd读取到读缓冲区BUFFER),然后交由逻辑函数`OnProcess()`处理。这里多说一句,`process()`函数在解析请求报文后随即就生成了响应报文等待OnWrite_()函数发送。 122 | 123 | 这里必须说清楚OnRead_()和OnWrite_()函数进行读写的方法,那就是:**分散读和集中写** 124 | > 分散读(scatter read)和集中写(gatherwrite)具体来说是来自读操作的输入数据被分散到多个应用缓冲区中,而来自应用缓冲区的输出数据则被集中提供给单个写操作。 125 | 这样做的好处是:它们只需一次系统调用就可以实现在文件和进程的多个缓冲区之间传送数据,免除了多次系统调用或复制数据的开销。 126 | 127 | `OnWrite_()`函数首先把之前根据请求报文生成的响应报文从缓冲区交给fd,传输完成后修改该fd的events. 128 | 129 | ``OnProcess()``就是进行业务逻辑处理(解析请求报文、生成响应报文)的函数了。具体可看http中的readme.md 130 | 131 | 参考博客:https://blog.csdn.net/ccw_922/article/details/124530436 -------------------------------------------------------------------------------- /code/server/webserver.cpp: -------------------------------------------------------------------------------- 1 | #include "webserver.h" 2 | 3 | using namespace std; 4 | 5 | WebServer::WebServer( 6 | int port, int trigMode, int timeoutMS, 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), timeoutMS_(timeoutMS), isClose_(false), 11 | timer_(new HeapTimer()), threadpool_(new ThreadPool(threadNum)), epoller_(new Epoller()) 12 | { 13 | 14 | // 是否打开日志标志 15 | if(openLog) { 16 | Log::Instance()->init(logLevel, "./log", ".log", logQueSize); 17 | if(isClose_) { LOG_ERROR("========== Server init error!=========="); } 18 | else { 19 | LOG_INFO("========== Server init =========="); 20 | LOG_INFO("Listen Mode: %s, OpenConn Mode: %s", 21 | (listenEvent_ & EPOLLET ? "ET": "LT"), 22 | (connEvent_ & EPOLLET ? "ET": "LT")); 23 | LOG_INFO("LogSys level: %d", logLevel); 24 | LOG_INFO("srcDir: %s", HttpConn::srcDir); 25 | LOG_INFO("SqlConnPool num: %d, ThreadPool num: %d", connPoolNum, threadNum); 26 | } 27 | } 28 | 29 | srcDir_ = getcwd(nullptr, 256); 30 | assert(srcDir_); 31 | strcat(srcDir_, "/resources/"); 32 | HttpConn::userCount = 0; 33 | HttpConn::srcDir = srcDir_; 34 | 35 | // 初始化操作 36 | SqlConnPool::Instance()->Init("localhost", sqlPort, sqlUser, sqlPwd, dbName, connPoolNum); // 连接池单例的初始化 37 | // 初始化事件和初始化socket(监听) 38 | InitEventMode_(trigMode); 39 | if(!InitSocket_()) { isClose_ = true;} 40 | } 41 | 42 | WebServer::~WebServer() { 43 | close(listenFd_); 44 | isClose_ = true; 45 | free(srcDir_); 46 | SqlConnPool::Instance()->ClosePool(); 47 | } 48 | 49 | void WebServer::InitEventMode_(int trigMode) { 50 | listenEvent_ = EPOLLRDHUP; // 检测socket关闭 51 | connEvent_ = EPOLLONESHOT | EPOLLRDHUP; // EPOLLONESHOT由一个线程处理 52 | switch (trigMode) 53 | { 54 | case 0: 55 | break; 56 | case 1: 57 | connEvent_ |= EPOLLET; 58 | break; 59 | case 2: 60 | listenEvent_ |= EPOLLET; 61 | break; 62 | case 3: 63 | listenEvent_ |= EPOLLET; 64 | connEvent_ |= EPOLLET; 65 | break; 66 | default: 67 | listenEvent_ |= EPOLLET; 68 | connEvent_ |= EPOLLET; 69 | break; 70 | } 71 | HttpConn::isET = (connEvent_ & EPOLLET); 72 | } 73 | 74 | void WebServer::Start() { 75 | int timeMS = -1; /* epoll wait timeout == -1 无事件将阻塞 */ 76 | if(!isClose_) { LOG_INFO("========== Server start =========="); } 77 | while(!isClose_) { 78 | if(timeoutMS_ > 0) { 79 | timeMS = timer_->GetNextTick(); // 获取下一次的超时等待事件(至少这个时间才会有用户过期,每次关闭超时连接则需要有新的请求进来) 80 | } 81 | int eventCnt = epoller_->Wait(timeMS); 82 | for(int i = 0; i < eventCnt; i++) { 83 | /* 处理事件 */ 84 | int fd = epoller_->GetEventFd(i); 85 | uint32_t events = epoller_->GetEvents(i); 86 | if(fd == listenFd_) { 87 | DealListen_(); 88 | } 89 | else if(events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { 90 | assert(users_.count(fd) > 0); 91 | CloseConn_(&users_[fd]); 92 | } 93 | else if(events & EPOLLIN) { 94 | assert(users_.count(fd) > 0); 95 | DealRead_(&users_[fd]); 96 | } 97 | else if(events & EPOLLOUT) { 98 | assert(users_.count(fd) > 0); 99 | DealWrite_(&users_[fd]); 100 | } else { 101 | LOG_ERROR("Unexpected event"); 102 | } 103 | } 104 | } 105 | } 106 | 107 | void WebServer::SendError_(int fd, const char*info) { 108 | assert(fd > 0); 109 | int ret = send(fd, info, strlen(info), 0); 110 | if(ret < 0) { 111 | LOG_WARN("send error to client[%d] error!", fd); 112 | } 113 | close(fd); 114 | } 115 | 116 | void WebServer::CloseConn_(HttpConn* client) { 117 | assert(client); 118 | LOG_INFO("Client[%d] quit!", client->GetFd()); 119 | epoller_->DelFd(client->GetFd()); 120 | client->Close(); 121 | } 122 | 123 | void WebServer::AddClient_(int fd, sockaddr_in addr) { 124 | assert(fd > 0); 125 | users_[fd].init(fd, addr); 126 | if(timeoutMS_ > 0) { 127 | timer_->add(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd])); 128 | } 129 | epoller_->AddFd(fd, EPOLLIN | connEvent_); 130 | SetFdNonblock(fd); 131 | LOG_INFO("Client[%d] in!", users_[fd].GetFd()); 132 | } 133 | 134 | // 处理监听套接字,主要逻辑是accept新的套接字,并加入timer和epoller中 135 | void WebServer::DealListen_() { 136 | struct sockaddr_in addr; 137 | socklen_t len = sizeof(addr); 138 | do { 139 | int fd = accept(listenFd_, (struct sockaddr *)&addr, &len); 140 | if(fd <= 0) { return;} 141 | else if(HttpConn::userCount >= MAX_FD) { 142 | SendError_(fd, "Server busy!"); 143 | LOG_WARN("Clients is full!"); 144 | return; 145 | } 146 | AddClient_(fd, addr); 147 | } while(listenEvent_ & EPOLLET); 148 | } 149 | 150 | // 处理读事件,主要逻辑是将OnRead加入线程池的任务队列中 151 | void WebServer::DealRead_(HttpConn* client) { 152 | assert(client); 153 | ExtentTime_(client); 154 | threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client)); // 这是一个右值,bind将参数和函数绑定 155 | } 156 | 157 | // 处理写事件,主要逻辑是将OnWrite加入线程池的任务队列中 158 | void WebServer::DealWrite_(HttpConn* client) { 159 | assert(client); 160 | ExtentTime_(client); 161 | threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client)); 162 | } 163 | 164 | void WebServer::ExtentTime_(HttpConn* client) { 165 | assert(client); 166 | if(timeoutMS_ > 0) { timer_->adjust(client->GetFd(), timeoutMS_); } 167 | } 168 | 169 | void WebServer::OnRead_(HttpConn* client) { 170 | assert(client); 171 | int ret = -1; 172 | int readErrno = 0; 173 | ret = client->read(&readErrno); // 读取客户端套接字的数据,读到httpconn的读缓存区 174 | if(ret <= 0 && readErrno != EAGAIN) { // 读异常就关闭客户端 175 | CloseConn_(client); 176 | return; 177 | } 178 | // 业务逻辑的处理(先读后处理) 179 | OnProcess(client); 180 | } 181 | 182 | /* 处理读(请求)数据的函数 */ 183 | void WebServer::OnProcess(HttpConn* client) { 184 | // 首先调用process()进行逻辑处理 185 | if(client->process()) { // 根据返回的信息重新将fd置为EPOLLOUT(写)或EPOLLIN(读) 186 | //读完事件就跟内核说可以写了 187 | epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT); // 响应成功,修改监听事件为写,等待OnWrite_()发送 188 | } else { 189 | //写完事件就跟内核说可以读了 190 | epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLIN); 191 | } 192 | } 193 | 194 | void WebServer::OnWrite_(HttpConn* client) { 195 | assert(client); 196 | int ret = -1; 197 | int writeErrno = 0; 198 | ret = client->write(&writeErrno); 199 | if(client->ToWriteBytes() == 0) { 200 | /* 传输完成 */ 201 | if(client->IsKeepAlive()) { 202 | // OnProcess(client); 203 | epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLIN); // 回归换成监测读事件 204 | return; 205 | } 206 | } 207 | else if(ret < 0) { 208 | if(writeErrno == EAGAIN) { // 缓冲区满了 209 | /* 继续传输 */ 210 | epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT); 211 | return; 212 | } 213 | } 214 | CloseConn_(client); 215 | } 216 | 217 | /* Create listenFd */ 218 | bool WebServer::InitSocket_() { 219 | int ret; 220 | struct sockaddr_in addr; 221 | addr.sin_family = AF_INET; 222 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 223 | addr.sin_port = htons(port_); 224 | 225 | listenFd_ = socket(AF_INET, SOCK_STREAM, 0); 226 | if(listenFd_ < 0) { 227 | LOG_ERROR("Create socket error!", port_); 228 | return false; 229 | } 230 | 231 | int optval = 1; 232 | /* 端口复用 */ 233 | /* 只有最后一个套接字会正常接收数据。 */ 234 | ret = setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int)); 235 | if(ret == -1) { 236 | LOG_ERROR("set socket setsockopt error !"); 237 | close(listenFd_); 238 | return false; 239 | } 240 | 241 | // 绑定 242 | ret = bind(listenFd_, (struct sockaddr *)&addr, sizeof(addr)); 243 | if(ret < 0) { 244 | LOG_ERROR("Bind Port:%d error!", port_); 245 | close(listenFd_); 246 | return false; 247 | } 248 | 249 | // 监听 250 | ret = listen(listenFd_, 8); 251 | if(ret < 0) { 252 | LOG_ERROR("Listen port:%d error!", port_); 253 | close(listenFd_); 254 | return false; 255 | } 256 | ret = epoller_->AddFd(listenFd_, listenEvent_ | EPOLLIN); // 将监听套接字加入epoller 257 | if(ret == 0) { 258 | LOG_ERROR("Add listen error!"); 259 | close(listenFd_); 260 | return false; 261 | } 262 | SetFdNonblock(listenFd_); 263 | LOG_INFO("Server port:%d", port_); 264 | return true; 265 | } 266 | 267 | // 设置非阻塞 268 | int WebServer::SetFdNonblock(int fd) { 269 | assert(fd > 0); 270 | return fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK); 271 | } 272 | 273 | 274 | -------------------------------------------------------------------------------- /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 "../timer/heaptimer.h" 15 | 16 | #include "../log/log.h" 17 | #include "../pool/sqlconnpool.h" 18 | #include "../pool/threadpool.h" 19 | 20 | #include "../http/httpconn.h" 21 | 22 | class WebServer { 23 | public: 24 | WebServer( 25 | int port, int trigMode, int timeoutMS, 26 | int sqlPort, const char* sqlUser, const char* sqlPwd, 27 | const char* dbName, int connPoolNum, int threadNum, 28 | bool openLog, int logLevel, int logQueSize); 29 | 30 | ~WebServer(); 31 | void Start(); 32 | 33 | private: 34 | bool InitSocket_(); 35 | void InitEventMode_(int trigMode); 36 | void AddClient_(int fd, sockaddr_in addr); 37 | 38 | void DealListen_(); 39 | void DealWrite_(HttpConn* client); 40 | void DealRead_(HttpConn* client); 41 | 42 | void SendError_(int fd, const char*info); 43 | void ExtentTime_(HttpConn* client); 44 | void CloseConn_(HttpConn* client); 45 | 46 | void OnRead_(HttpConn* client); 47 | void OnWrite_(HttpConn* client); 48 | void OnProcess(HttpConn* client); 49 | 50 | static const int MAX_FD = 65536; 51 | 52 | static int SetFdNonblock(int fd); 53 | 54 | int port_; 55 | bool openLinger_; 56 | int timeoutMS_; /* 毫秒MS */ 57 | bool isClose_; 58 | int listenFd_; 59 | char* srcDir_; 60 | 61 | uint32_t listenEvent_; // 监听事件 62 | uint32_t connEvent_; // 连接事件 63 | 64 | std::unique_ptr timer_; 65 | std::unique_ptr threadpool_; 66 | std::unique_ptr epoller_; 67 | std::unordered_map users_; 68 | }; 69 | 70 | 71 | #endif //WEBSERVER_H -------------------------------------------------------------------------------- /code/timer/heaptimer.cpp: -------------------------------------------------------------------------------- 1 | #include "heaptimer.h" 2 | 3 | void HeapTimer::SwapNode_(size_t i, size_t j) { 4 | assert(i >= 0 && i = 0 && j = 0 && i < heap_.size()); 13 | size_t parent = (i-1) / 2; 14 | while(parent >= 0) { 15 | if(heap_[parent] > heap_[i]) { 16 | SwapNode_(i, parent); 17 | i = parent; 18 | parent = (i-1)/2; 19 | } else { 20 | break; 21 | } 22 | } 23 | } 24 | 25 | // false:不需要下滑 true:下滑成功 26 | bool HeapTimer::siftdown_(size_t i, size_t n) { 27 | assert(i >= 0 && i < heap_.size()); 28 | assert(n >= 0 && n <= heap_.size()); // n:共几个结点 29 | auto index = i; 30 | auto child = 2*index+1; 31 | while(child < n) { 32 | if(child+1 < n && heap_[child+1] < heap_[child]) { 33 | child++; 34 | } 35 | if(heap_[child] < heap_[index]) { 36 | SwapNode_(index, child); 37 | index = child; 38 | child = 2*child+1; 39 | } 40 | break; // 需要跳出循环 41 | } 42 | return index > i; 43 | } 44 | 45 | // 删除指定位置的结点 46 | void HeapTimer::del_(size_t index) { 47 | assert(index >= 0 && index < heap_.size()); 48 | // 将要删除的结点换到队尾,然后调整堆 49 | size_t tmp = index; 50 | size_t n = heap_.size() - 1; 51 | assert(tmp <= n); 52 | // 如果就在队尾,就不用移动了 53 | if(index < heap_.size()-1) { 54 | SwapNode_(tmp, heap_.size()-1); 55 | if(!siftdown_(tmp, n)) { 56 | siftup_(tmp); 57 | } 58 | } 59 | ref_.erase(heap_.back().id); 60 | heap_.pop_back(); 61 | } 62 | 63 | // 调整指定id的结点 64 | void HeapTimer::adjust(int id, int newExpires) { 65 | assert(!heap_.empty() && ref_.count(id)); 66 | heap_[ref_[id]].expires = Clock::now() + MS(newExpires); 67 | siftdown_(ref_[id], heap_.size()); 68 | } 69 | 70 | void HeapTimer::add(int id, int timeOut, const TimeoutCallBack& cb) { 71 | assert(id >= 0); 72 | // 如果有,则调整 73 | if(ref_.count(id)) { 74 | int tmp = ref_[id]; 75 | heap_[tmp].expires = Clock::now() + MS(timeOut); 76 | heap_[tmp].cb = cb; 77 | if(!siftdown_(tmp, heap_.size())) { 78 | siftup_(tmp); 79 | } 80 | } else { 81 | size_t n = heap_.size(); 82 | ref_[id] = n; 83 | // 这里应该算是结构体的默认构造? 84 | heap_.push_back({id, Clock::now() + MS(timeOut), cb}); // 右值 85 | siftup_(n); 86 | } 87 | } 88 | 89 | // 删除指定id,并触发回调函数 90 | void HeapTimer::doWork(int id) { 91 | if(heap_.empty() || ref_.count(id) == 0) { 92 | return; 93 | } 94 | size_t i = ref_[id]; 95 | auto node = heap_[i]; 96 | node.cb(); // 触发回调函数 97 | del_(i); 98 | } 99 | 100 | void HeapTimer::tick() { 101 | /* 清除超时结点 */ 102 | if(heap_.empty()) { 103 | return; 104 | } 105 | while(!heap_.empty()) { 106 | TimerNode node = heap_.front(); 107 | if(std::chrono::duration_cast(node.expires - Clock::now()).count() > 0) { 108 | break; 109 | } 110 | node.cb(); 111 | pop(); 112 | } 113 | } 114 | 115 | void HeapTimer::pop() { 116 | assert(!heap_.empty()); 117 | del_(0); 118 | } 119 | 120 | void HeapTimer::clear() { 121 | ref_.clear(); 122 | heap_.clear(); 123 | } 124 | 125 | int HeapTimer::GetNextTick() { 126 | tick(); 127 | size_t res = -1; 128 | if(!heap_.empty()) { 129 | res = std::chrono::duration_cast(heap_.front().expires - Clock::now()).count(); 130 | if(res < 0) { res = 0; } 131 | } 132 | return res; 133 | } -------------------------------------------------------------------------------- /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; // 回调function 23 | bool operator<(const TimerNode& t) { // 重载比较运算符 24 | return expires < t.expires; 25 | } 26 | bool operator>(const TimerNode& t) { // 重载比较运算符 27 | return expires > t.expires; 28 | } 29 | }; 30 | class HeapTimer { 31 | public: 32 | HeapTimer() { heap_.reserve(64); } // 保留(扩充)容量 33 | ~HeapTimer() { clear(); } 34 | 35 | void adjust(int id, int newExpires); 36 | void add(int id, int timeOut, const TimeoutCallBack& cb); 37 | void doWork(int id); 38 | void clear(); 39 | void tick(); 40 | void pop(); 41 | int GetNextTick(); 42 | 43 | private: 44 | void del_(size_t i); 45 | void siftup_(size_t i); 46 | bool siftdown_(size_t i, size_t n); 47 | void SwapNode_(size_t i, size_t j); 48 | 49 | std::vector heap_; 50 | // key:id value:vector的下标 51 | std::unordered_map ref_; // id对应的在heap_中的下标,方便用heap_的时候查找 52 | }; 53 | 54 | #endif //HEAP_TIMER_H -------------------------------------------------------------------------------- /code/timer/readme.md: -------------------------------------------------------------------------------- 1 | # 时间堆 2 | 网络编程中除了处理IO事件之外,定时事件也同样不可或缺,如定期检测一个客户连接的活动状态、游戏中的技能冷却倒计时以及其他需要使用超时机制的功能。我们的服务器程序中往往需要处理众多的定时事件,因此有效的组织定时事件,使之能在预期时间内被触发且不影响服务器主要逻辑,对我们的服务器性能影响特别大。 3 | 4 | 一般的做法是将每个定时事件封装成定时器,并使用某种容器类数据结构将所有的定时器保存好,实现对定时事件的统一管理。常用方法有排序链表、红黑树、时间堆和时间轮。这里使用的是时间堆。 5 | 6 | 时间堆的底层实现是由小根堆实现的。小根堆可以保证堆顶元素为最小的。 7 | 8 | 9 | ## 小根堆详解 10 | 传统的定时方案是以固定频率调用起搏函数tick,进而执行定时器上的回调函数。而时间堆的做法则是将所有定时器中超时时间最小的一个定时器的超时值作为心搏间隔,当超时时间到达时,处理超时事件,然后再次从剩余定时器中找出超时时间最小的一个,依次反复即可。 11 | 12 | 当前系统时间:8:00 13 | 14 | 1号定时器超时时间:8:05 15 | 16 | 2号定时器超时时间:8:08 17 | 18 | 设置心搏间隔:8:05-8:00=5 19 | 20 | 5分钟到达后处理1号定时器事件,再根据2号超时时间设定心搏间隔. 21 | 22 | 23 | 为了后面处理过期连接的方便,我们给每一个定时器里面放置一个回调函数,用来关闭过期连接。 24 | 25 | 为了便于定时器结点的比较,主要是后续堆结构的实现方便,我们还需要重载比较运算符。 26 | ```c++ 27 | struct TimerNode{ 28 | public: 29 | int id; //用来标记定时器 30 | TimeStamp expire; //设置过期时间 31 | TimeoutCallBack cb; //设置一个回调函数用来方便删除定时器时将对应的HTTP连接关闭 32 | bool operator<(const TimerNode& t) 33 | { 34 | return expireadd(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd]));` 39 | 由于`TimeoutCallBack`的类型是`std::function`,所以这里采用bind绑定参数。 40 | 41 | ## 定时器的管理 42 | 主要有对堆节点进行增删和调整的操作。 43 | ```c++ 44 | void addTimer(int id,int timeout,const TimeoutCallBack& cb);//添加一个定时器 45 | void del_(size_t i);//删除指定定时器 46 | void siftup_(size_t i);//向上调整 47 | bool siftdown_(size_t index,size_t n);//向下调整,若不能向下则返回false 48 | void swapNode_(size_t i,size_t j);//交换两个结点位置 49 | ``` -------------------------------------------------------------------------------- /imgs/pressure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/imgs/pressure.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # A C++轻量级、高性能、高并发的Web服务器 2 | ## Introduction 3 | 用C++实现的高性能WEB服务器,经过webbenchh压力测试可以实现上万的QPS 4 | 5 | ## Function 6 | * 利用IO复用技术Epoll与线程池实现多线程的Reactor高并发模型; 7 | * 利用正则与状态机解析HTTP请求报文,实现处理静态资源的请求; 8 | * 利用标准库容器封装char,实现自动增长的缓冲区; 9 | * 基于小根堆实现的定时器,关闭超时的非活动连接; 10 | * 利用单例模式与阻塞队列实现异步的日志系统,记录服务器运行状态; 11 | * 利用RAII机制实现了数据库连接池,减少数据库连接建立与关闭的开销,同时实现了用户注册登录功能。 12 | 13 | * 增加logsys,threadpool测试单元(todo: timer, sqlconnpool, httprequest, httpresponse) 14 | 15 | ## Environment 16 | * Ubuntu 18 17 | * Modern C++ 18 | * MySql 19 | * Vscode 20 | * git 21 | 22 | ## 目录树 23 | ``` 24 | . 25 | ├── code 源代码 26 | │ ├── buffer 27 | │   ├── config 28 | │   ├── http 29 | │   ├── log 30 | │   ├── timer 31 | │   ├── pool 32 | │   ├── server 33 | │   └── main.cpp 34 | ├── test 单元测试 35 | │ ├── Makefile 36 | │   └── test.cpp 37 | ├── resources 静态资源 38 | │   ├── index.html 39 | │   ├── image 40 | │   ├── video 41 | │   ├── js 42 | │ └── css 43 | ├── bin 可执行文件 44 | │ └── server 45 | ├── log 日志文件 46 | ├── webbench-1.5 压力测试 47 | ├── build 48 | │ └── Makefile 49 | ├── Makefile 50 | ├── LICENSE 51 | └── readme.md 52 | ``` 53 | ## Build & Usage 54 | ``` 55 | make 56 | ./bin/server 57 | 58 | 需要先配置好对应的数据库 59 | bash 60 | // 建立yourdb库 61 | create database yourdb; 62 | 63 | // 创建user表 64 | USE yourdb; 65 | CREATE TABLE user( 66 | username char(50) NULL, 67 | password char(50) NULL 68 | )ENGINE=InnoDB; 69 | 70 | // 添加数据 71 | INSERT INTO user(username, password) VALUES('name', 'password'); 72 | ``` 73 | 74 | ## Test 75 | ```bash 76 | 日志、线程池测试: 77 | cd test 78 | make 79 | ./test 80 | 81 | 82 | 服务器压力测试: 83 | cd webbench-1.5 84 | make 85 | webbench -c 1000 -t 30 http://ip:port/ 86 | 87 | 参数: 88 | -c 表示客户端数 89 | -t 表示时间 90 | ``` 91 | 92 | ![](./imgs/pressure.png) 93 | 94 | 95 | ## Thanks 96 | Linux高性能服务器编程,游双著. 97 | 98 | [@qinguoyi](https://github.com/qinguoyi/TinyWebServer) 99 | 100 | [@markparticle](https://github.com/markparticle/WebServer) 101 | -------------------------------------------------------------------------------- /resources/400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JehanRio-首页 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 55 | 56 |
57 |
58 |
59 | 60 |
61 | about image 63 |
64 |
65 |

400 无法解析请求

66 |
67 |
68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /resources/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JehanRio-首页 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 55 | 56 |
57 |
58 |
59 | 60 |
61 | about image 63 |
64 |
65 |

403 禁止访问

66 |
67 |
68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /resources/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JehanRio-首页 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 55 | 56 |
57 |
58 |
59 | 60 |
61 | about image 63 |
64 |
65 |

404 没有找到该页面

66 |
67 |
68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /resources/405.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JehanRio-首页 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 55 | 56 |
57 |
58 |
59 | 60 |
61 | about image 63 |
64 |
65 |

404 没有找到该页面

66 |
67 |
68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /resources/css/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/css/.DS_Store -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JehanRio-首页 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 56 | 57 | 58 | 59 |
60 | 61 |
62 |
63 | 64 |
65 | about image 67 |
68 | 69 |
70 |

错误!

71 | 72 |
73 | 74 |
75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /resources/fonts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/fonts/.DS_Store -------------------------------------------------------------------------------- /resources/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /resources/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /resources/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /resources/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /resources/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /resources/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/images/.DS_Store -------------------------------------------------------------------------------- /resources/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/images/favicon.ico -------------------------------------------------------------------------------- /resources/images/instagram-image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/images/instagram-image1.jpg -------------------------------------------------------------------------------- /resources/images/instagram-image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/images/instagram-image2.jpg -------------------------------------------------------------------------------- /resources/images/instagram-image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/images/instagram-image3.jpg -------------------------------------------------------------------------------- /resources/images/instagram-image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/images/instagram-image4.jpg -------------------------------------------------------------------------------- /resources/images/instagram-image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/images/instagram-image5.jpg -------------------------------------------------------------------------------- /resources/images/profile-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/images/profile-image.jpg -------------------------------------------------------------------------------- /resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JehanRio-首页 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 55 | 56 |
57 |
58 |
59 | 60 |
61 | about image 63 |
64 |
65 |

你好,这是首页。

66 |
67 |
68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /resources/js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JehanRio-登录 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 55 | 56 |
57 |
58 |
59 |
60 |

登录

61 |
62 |

64 |

66 |
67 |
68 |
69 |
70 |
71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /resources/picture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | JehanRio-图片 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 54 | 55 | 56 |
57 |
58 |
59 | 60 |
61 | about image 63 |
64 | 65 |
66 |

图片测试

67 |
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 | -------------------------------------------------------------------------------- /resources/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JehanRio-注册 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 | 51 | 52 |
53 |
54 |
55 |
56 |

注册

57 |
58 |

60 |

62 |
63 |
64 |
65 |
66 |
67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /resources/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JehanRio-视频 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/JehanRio/TinyWebServer/b7b397a5ecfa404f945c8100827647b7f08257cc/resources/video/xxx.mp4 -------------------------------------------------------------------------------- /resources/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JehanRio-欢迎 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 | #include "../code/log/log.h" 2 | #include "../code/pool/threadpool.h" 3 | #include 4 | 5 | #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30 6 | #include 7 | #define gettid() syscall(SYS_gettid) 8 | #endif 9 | 10 | void TestLog() { 11 | int cnt = 0, level = 0; 12 | Log::Instance()->init(level, "./testlog1", ".log", 0); 13 | for(level = 3; level >= 0; level--) { 14 | Log::Instance()->SetLevel(level); 15 | for(int j = 0; j < 10000; j++ ){ 16 | for(int i = 0; i < 4; i++) { 17 | LOG_BASE(i,"%s 111111111 %d ============= ", "Test", cnt++); 18 | } 19 | } 20 | } 21 | cnt = 0; 22 | Log::Instance()->init(level, "./testlog2", ".log", 5000); 23 | for(level = 0; level < 4; level++) { 24 | Log::Instance()->SetLevel(level); 25 | for(int j = 0; j < 10000; j++ ){ 26 | for(int i = 0; i < 4; i++) { 27 | LOG_BASE(i,"%s 222222222 %d ============= ", "Test", cnt++); 28 | } 29 | } 30 | } 31 | } 32 | 33 | void ThreadLogTask(int i, int cnt) { 34 | for(int j = 0; j < 10000; j++ ){ 35 | LOG_BASE(i,"PID:[%04d]======= %05d ========= ", gettid(), cnt++); 36 | } 37 | } 38 | 39 | void TestThreadPool() { 40 | Log::Instance()->init(0, "./testThreadpool", ".log", 5000); 41 | ThreadPool threadpool(6); 42 | for(int i = 0; i < 18; i++) { 43 | threadpool.AddTask(std::bind(ThreadLogTask, i % 4, i * 10000)); 44 | } 45 | getchar(); 46 | } 47 | 48 | int main() { 49 | TestLog(); 50 | TestThreadPool(); 51 | } -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------