├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc ├── BENCHMARK.md ├── CONCURRENCY_MODEL.md ├── DEBUG.md ├── DEBUG_LOG.md ├── DESIGN.md ├── PURPOSE.md ├── dot │ ├── dict_structure.dot │ └── dict_structure.png └── flamegraph │ ├── 149bcb68 │ ├── ab-perf-kernel.svg │ └── wrk-perf-kernel.svg │ └── 8717b1a3 │ ├── ab-perf-kernel.svg │ └── wrk-perf-kernel.svg ├── src ├── CMakeLists.txt ├── Makefile ├── buffer.c ├── buffer.h ├── connection.c ├── connection.h ├── dict.c ├── dict.h ├── http_parser.c ├── http_parser.h ├── lotos_epoll.c ├── lotos_epoll.h ├── main.c ├── mem_pool.c ├── mem_pool.h ├── misc.c ├── misc.h ├── request.c ├── request.h ├── response.c ├── response.h ├── server.c ├── server.h ├── ssstr.c ├── ssstr.h └── test │ ├── Makefile │ ├── buffer_test.c │ ├── dict_test.c │ ├── heap_test.c │ ├── mem_pool_test.c │ ├── minctest.h │ ├── parse_test.c │ ├── slow_client.c │ └── ssstr_test.c └── www ├── error.html └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | lotos 39 | 40 | # Debug files 41 | *.dSYM/ 42 | *.su 43 | *.idb 44 | *.pdb 45 | 46 | # Kernel Module Compile Results 47 | *.mod* 48 | *.cmd 49 | .tmp_versions/ 50 | modules.order 51 | Module.symvers 52 | Mkfile.old 53 | dkms.conf 54 | 55 | #own files 56 | minctest/* 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | dist: trusty 4 | 5 | before_install: 6 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 7 | - sudo apt-get update -qq 8 | - sudo apt-get install -qq g++-6 9 | - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 90 10 | 11 | compiler: 12 | - clang 13 | - gcc 14 | 15 | script: 16 | - cd src/ && make && make test 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Chen YaQi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lotos WebServer 2 | 3 | [![Build Status](https://travis-ci.org/chendotjs/lotos.svg?branch=master)](https://travis-ci.org/chendotjs/lotos) 4 | 5 | **Lotos is a tiny but high-performance HTTP WebServer following the Reactor model, using non-blocking IO and IO multiplexing(epoll ET) to handle concurrency. Lotos is written in pure c and well tested. Several HTTP headers (Connection, Content-Length, etc.) is supported and more will be added in the future.** 6 | 7 | ``` 8 | ------------------------------------------------------------------------------- 9 | Language files blank comment code 10 | ------------------------------------------------------------------------------- 11 | C 19 367 275 2428 12 | C/C++ Header 12 120 131 514 13 | make 2 17 0 42 14 | CMake 1 7 0 18 15 | ------------------------------------------------------------------------------- 16 | SUM: 34 511 406 3002 17 | ------------------------------------------------------------------------------- 18 | ``` 19 | 20 | ## Documents 21 | 22 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 23 | ------------------------ | ---------------------------------- | ----------------------- | ---------------------- | -------------------------- | -------------------------- 24 | [项目目的](./doc/PURPOSE.md) | [并发模型](./doc/CONCURRENCY_MODEL.md) | [设计实现](./doc/DESIGN.md) | [测试调试](./doc/DEBUG.md) | [性能测试](./doc/BENCHMARK.md) | [调试记录](./doc/DEBUG_LOG.md) 25 | 26 | ## Environment 27 | 28 | - gcc >= 5.4 or clang >= 3.5 (gcc4.9 is not supported) 29 | - Linux only, kernel version >= 3.9 30 | 31 | ## Usage 32 | 33 | ### Build 34 | 35 | ``` 36 | $ git clone https://github.com/chendotjs/lotos.git 37 | $ cd lotos/src/ 38 | $ make && make test 39 | ``` 40 | 41 | ### Run 42 | 43 | Usage: lotos -r html_root_dir [-p port] [-t timeout] [-w worker_num] [-d (debug mode)] 44 | 45 | ``` 46 | $ ./lotos -r ../www -t 60 -w 4 -p 8888 47 | ``` 48 | 49 | then you can visit . 50 | 51 | ## Feature 52 | 53 | - EPOLL Edge Trigger mode, more efficient. 54 | - Nonblocking IO. 55 | - Multiprocessing, port reuse. 56 | - TCP connections managed by min-heap data structure. 57 | - HTTP persistent connection support. Close TCP connection when connection expires. 58 | - Parse HTTP requests using FSM. 59 | - Handle errors and exceptions. 60 | - Memory pool is optional. 61 | 62 | ## Test 63 | 64 | Unit tests are based on [minctest](https://github.com/codeplea/minctest). It is simple, lightweight, and flexible. 65 | 66 | Moreover, I contributed some codes to it. 67 | 68 | ## Benchmark 69 | 70 | Please refer to [BENCHMARK.md](./doc/BENCHMARK.md). 71 | 72 | ## Reference 73 | 74 | [nginx](https://github.com/nginx/nginx) 75 | 76 | [node.js http parser](https://github.com/nodejs/http-parser) 77 | 78 | [Tkeed](https://github.com/linw7/TKeed) 79 | -------------------------------------------------------------------------------- /doc/BENCHMARK.md: -------------------------------------------------------------------------------- 1 | # Benchmark 2 | 3 | 常见的压力测试工具有ab,[wrk](https://github.com/wg/wrk)。HTTP/1.1的长连接已经很普及,wrk默认支持长连接,ab需要加上`-k`选项, 否则ab的压力测试会默认采用HTTP/1.0,即每一个请求建立一个TCP连接。 4 | 5 | ## 测试环境 6 | 7 | - 测试环境为本地,配置4核心`Intel(R) Core(TM) i5-2320 CPU @ 3.00GHz` 8 | - 测试工具为wrk和ab,测试时间为60s,worker数设置为4 9 | - nginx的worker_processes配置为4 10 | - 标准1KB静态页面测试 11 | 12 | ## 测试内容 13 | 14 | ### 1.使用wrk测试长连接Req/Sec 15 | 16 | Concurrency | lotos | nginx 17 | ----------- | ------ | ------ 18 | 10 | 21.94k | 15.42k 19 | 100 | 28.21k | 16.95k 20 | 1000 | 26.01k | 16.62k 21 | 22 | ### 2.使用ab测试长连接Req/Sec 23 | 24 | Concurrency | lotos | nginx 25 | ----------- | ------- | ------ 26 | 10 | 129.11k | 81.89k 27 | 100 | 136.20k | 77.56k 28 | 1000 | 97.56k | 53.69k 29 | 30 | ### 3.使用ab测试短连接Req/Sec 31 | 32 | Concurrency | lotos | nginx 33 | ----------- | ------ | ------ 34 | 10 | 25.96k | 25.92k 35 | 100 | 26.52k | 26.63k 36 | 1000 | 22.53k | 23.29k 37 | 38 | ### 4.使用ab短连接测试内存池 39 | 40 | Concurrency | USE_MEM_POOL | NO_USE_MEM_POOL 41 | ----------- | ------------ | --------------- 42 | 10 | 25.78k | 25.75k 43 | 100 | 26.43k | 26.39k 44 | 1000 | 23.17k | 23.24k 45 | 46 | ## 测试总结 47 | 48 | 我不知道ab的结果为什么比wrk大很多,可能是两者的计算方式不同吧,但起码Lotos确实表现比nginx要好(毕竟功能简单,实现得也很简单)。 49 | 50 | 并发的瓶颈不在于内存的malloc和free,现代的内存分配器性能已经很强,但是如果可以用内存池在初始化阶段就分配一大片内存,对于短连接的测试,性能应该还是有些许提升的。 51 | 52 | 从ab短连接和长连接的测试结果来看,连接的建立和关闭才是真的瓶颈所在, TCP的三次握手和四次挥手比较耗费时间。 53 | 54 | 如果采用内存池,效率基本没有提升。事实上glibc的malloc已经够快了。 55 | -------------------------------------------------------------------------------- /doc/CONCURRENCY_MODEL.md: -------------------------------------------------------------------------------- 1 | # 并发模型 2 | 3 | ## 0x01 介绍 4 | 5 | 常见的并发模型主要有Fork-Exec模型和event driven模型。 Lotos采用的就是多进程Reactor模型,属于event driven模型。 6 | 7 | 传统的网络服务器的构建中,IO模式会按照Blocking/Non-Blocking、Synchronous/Asynchronous这两个标准进行分类,其中Blocking与Synchronous基本上一个意思,而NIO与Async的区别在于NIO强调的是Polling(即用户进程需要时常询问IO操作是否就绪),而Async强调的是Notification(kernel将IO数据拷贝入用户空间给用户进程使用)。 8 | 9 | 将同步与否、阻塞与否组合一下,可以得到3种主要的IO模式: 10 | 11 | - **同步阻塞**:在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。 12 | 13 | - **同步非阻塞**:在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。 14 | 15 | - **异步非阻塞**:在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。 16 | 17 | 前些年在IO并发领域有个很著名的[C10K](http://www.kegel.com/c10k.html)问题,即有10000个客户端需要连上一个服务器并保持TCP连接,客户端会不定时的发送请求给服务器,服务器收到请求后需及时处理并返回结果。 18 | 19 | ## 0x02 Unix下5种IO模式 20 | 21 | ### 1.阻塞 I/O(blocking IO) 22 | 23 | 在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样: ![](https://lukangping.gitbooks.io/java-nio/content/resources/blocking_io.jpg) 24 | 25 | 当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。 26 | 27 | 所以,blocking IO的特点就是在IO执行的两个阶段都被block了。 28 | 29 | ### 2.非阻塞 I/O(nonblocking IO) 30 | 31 | linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子: ![](https://sfault-image.b0.upaiyun.com/961/916/961916360-570a10b5a0ea9_articlex) 32 | 33 | 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。 34 | 35 | 所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。 36 | 37 | ### 3.I/O 多路复用( IO multiplexing) 38 | 39 | IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。 ![](https://sfault-image.b0.upaiyun.com/304/440/3044406194-570a10b9efcb2_articlex) 40 | 41 | 当用户进程调用了select,那么整个进程会被block,而同时,kernel会"监视"所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。 42 | 43 | 所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。 44 | 45 | 这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。 46 | 47 | 所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。) 48 | 49 | 在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。 50 | 51 | ### 4.信号驱动式IO 52 | 53 | ![](https://sfault-image.b0.upaiyun.com/221/712/2217129294-570a10c14d83b_articlex) 54 | 55 | ### 5.异步 I/O(asynchronous IO) 56 | 57 | inux下的asynchronous IO其实用得很少。先看一下它的流程: ![](https://sfault-image.b0.upaiyun.com/401/185/4011854437-570a10c3b6e2c_articlex) 58 | 59 | 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。 60 | 61 | ## 0x03 Fork-Exec模型 62 | 63 | Fork-Exec模型多采用同步阻塞IO,对每一个客户端的socket连接,都需要一个线程来处理,而且在此期间这个线程一直被占用,直到socket关闭。通常由一个独立的Acceptor线程负责监听客户端的连接,接收到客户端连接之后为客户端连接创建一个新的线程处理请求消息,处理完成之后,返回应答消息给客户端,线程销毁,这就是典型的一请求一应答模型。该架构最大的问题就是不具备弹性伸缩能力,当并发访问量增加后,服务端的线程个数和并发访问数成线性正比。创建线程多了,数据频繁拷贝(I/O,内核数据拷贝到用户进程空间、阻塞),进程/线程上下文切换消耗大,从而导致操作系统崩溃。 64 | 65 | 面对这种问题,相应的改进方式,可以设计一个线程池,复用线程资源,减少线程新建、销毁的开销。但仍然是一种浪费资源的方式。 66 | 67 | 面对即时聊天(IM)程序这种连接时间长、载体消息短的应用场景,一台服务器可能要撑起几十万的连接量(C100k问题),Fork-Exec模型是很难应对的。如果业务逻辑中,线程需要进行时间较长的IO操作(例如跨机房访问接口),则线程大部分时间都在等待IO的返回,白白浪费了大量CPU时间片。 68 | 69 | ## 0x04 Reactor模型 70 | 71 | 什么是Reactor? 换个名词"non-blocking IO + IO multiplexing",意思就显而易见了。Reactor模式用非阻塞IO + IO复用函数来处理并发,程序的基本结构是一个事件循环,以事件驱动和事件回调的方式实现业务逻辑。 72 | 73 | Lotos就是采用NIO + epoll的方式处理并发。Lotos使用epoll作为同步事件多路分解器(Synchronous Event Demultiplexer),等待IO事件的发生。当可读或者可写事件发生于某个文件描述符时,事件分解器会去调用之前注册的相应的事件处理器。 74 | 75 | 对应到Lotos的代码中,[main.c](../src/mainl.c)中`request_handle`和`response_handle`就是对读、写操作的相应handler。 76 | 77 | ## 0x05 Proactor模型 78 | 79 | 与Reactor模式对应的就是Proactor模式。Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而 Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备。Proactor的实现依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,实现优秀的如windows IOCP。由于Unix/Linux系统对纯异步的支持有限,应用事件驱动的主流还是通过select/epoll来实现。 80 | 81 | ## 参考 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /doc/DEBUG.md: -------------------------------------------------------------------------------- 1 | # 测试调试 2 | 3 | ## 调试工具 4 | 5 | - nc 6 | - curl 7 | - wrk 8 | - gdb 9 | - valgrind 10 | - perf 11 | 12 | ## 测试 13 | 14 | 对每一个数据结构都写了简单的单元测试。我不敢说测试很充分,但是主要的功能点还是测试到了在开发过程中多次大块修改代码结构,测试能跑过就可以比较安心,有单元测试对于重构代码帮助很大! 15 | 16 | 在代码设计中,考虑到测试的便捷性,需要把每个模块独立起来,尽量减少模块之间的耦合。对于利用recv这样的系统调用获得输入,从而进行测试的过程,会比较麻烦。我在[parse_test.c](../src/test/parse_test.c)使用`buffer_cat`函数模拟recv得到的数据,从而进行parser的测试。在这里,`buffer_t`和parser是较为紧密的耦合关系,不过总体而言,相比较另写客户端提供数据,这种方式还是便捷得多。 17 | 18 | [设计实现](./DESIGN.md)里面说过,NIO决定了每一个IO操作的状态包含三种:OK, ERROR, AGAIN。健壮的服务器应该能处理这种慢请求,因此测试代码也实现了一个[慢速client](../src/test/slow_client.c),每隔30ms发送一个字节给服务器,测试服务器能否正确解析。client里面已经内置了一些请求体。 19 | -------------------------------------------------------------------------------- /doc/DEBUG_LOG.md: -------------------------------------------------------------------------------- 1 | # 问题1 2 | 3 | ## 问题描述 4 | 5 | 使用`wrk`进行压力测试,发现`Avg Req/Sec`只有10^2数量级,显然不符合预期。请求返回的`Avg Latency` 也在40ms左右,在单机测试的环境下,这也是很长的响应时间了。 6 | 7 | 下面是wrk的测试报告: 8 | ``` 9 | $ wrk -t5 -c10 -d10s http://localhost:8888/ -vvv 10 | wrk [epoll] Copyright (C) 2012 Will Glozer 11 | wrk [epoll] Copyright (C) 2012 Will Glozer 12 | wrk [epoll] Copyright (C) 2012 Will Glozer 13 | Running 10s test @ http://localhost:8888/ 14 | 5 threads and 10 connections 15 | Thread Stats Avg Stdev Max +/- Stdev 16 | Latency 43.72ms 3.16ms 49.97ms 99.21% 17 | Req/Sec 45.68 6.97 60.00 88.00% 18 | 2284 requests in 10.01s, 333.74KB read 19 | Requests/sec: 228.12 20 | Transfer/sec: 33.33KB 21 | 22 | ``` 23 | 24 | ## Debug记录 25 | 26 | 一开始猜测是在某个函数上开销比较大,但是函数太多,具体到某个函数又不容易定位。于是想起可以用`on-cpu 火焰图`分析函数执行耗时。 27 | 28 | 针对是否使用http keep-alive选项,对`lotos`使用两种模式的压力测试。 29 | 30 | - `ab`版本 31 | 32 | ab版本不加keep-alive,纯http1.0模式,得到![149bcb68/ab-perf-kernel.svg](flamegraph/149bcb68/ab-perf-kernel.svg) 33 | 34 | - `wrk`版本 35 | 36 | 采用http/1.1协议,支持keep-alive,得到![149bcb68/wrk-perf-kernel.svg](flamegraph/149bcb68/wrk-perf-kernel.svg) 37 | 38 | ab版本的火焰图很符合预期,每次都会建立、断开tcp连接,请求、回复的handler占有的比重也很正常,总之是个很漂亮的火焰图😉。 39 | 40 | 相比ab版本,发现wrk版本在epoll_wait上等待较多,cpu的采样点也少得可怜,可以大胆猜测应该是在某处IO上阻塞了。 41 | 42 | 我给`lotos`的主事件循环中加入了一些调试信息,采用`curl`命令发起keep-alive请求,命令如下 43 | ``` 44 | curl localhost:8000 localhost:8000 -vvv 45 | ``` 46 | 命令的返回如下: 47 | ``` 48 | $ curl localhost:8888 -o /dev/null localhost:8888 -o /dev/null -vvv 49 | * Rebuilt URL to: localhost:8888/ 50 | % Total % Received % Xferd Average Speed Time Time Time Current 51 | Dload Upload Total Spent Left Speed 52 | 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying ::1... 53 | * TCP_NODELAY set 54 | * connect to ::1 port 8888 failed: 連線被拒絕 55 | * Trying 127.0.0.1... 56 | * TCP_NODELAY set 57 | * Connected to localhost (127.0.0.1) port 8888 (#0) 58 | > GET / HTTP/1.1 59 | > Host: localhost:8888 60 | > User-Agent: curl/7.57.0 61 | > Accept: */* 62 | > 63 | < HTTP/1.1 200 OK 64 | < Date: Tue, 30 Jan 2018 07:08:59 GMT 65 | < Server: lotos/0.1 66 | < Content-Type: text/html 67 | < Content-Length: 6 68 | < Connection: keep-alive 69 | < 70 | { [6 bytes data] 71 | 100 6 100 6 0 0 6 0 0:00:01 --:--:-- 0:00:01 666 72 | * Connection #0 to host localhost left intact 73 | * Rebuilt URL to: localhost:8888/ 74 | * Found bundle for host localhost: 0x55fc045f4460 [can pipeline] 75 | * Re-using existing connection! (#0) with host localhost 76 | * Connected to localhost (127.0.0.1) port 8888 (#0) 77 | > GET / HTTP/1.1 78 | > Host: localhost:8888 79 | > User-Agent: curl/7.57.0 80 | > Accept: */* 81 | > 82 | < HTTP/1.1 200 OK 83 | < Date: Tue, 30 Jan 2018 07:08:59 GMT 84 | < Server: lotos/0.1 85 | < Content-Type: text/html 86 | < Content-Length: 6 87 | < Connection: keep-alive 88 | < 89 | { [6 bytes data] 90 | 100 6 100 6 0 0 6 0 0:00:01 --:--:-- 0:00:01 6 91 | * Connection #0 to host localhost left intact 92 | 93 | ``` 94 | 95 | 输出中的`Re-using existing connection! (#0) with host localhost`确实表明了curl确实重用了这条tcp连接。 96 | 97 | 对应的`lotos`的调试信息: 98 | ``` 99 | epoll: 0 100 | epoll: 0 101 | epoll: 1 102 | [2018-01-30 10:12:25] fd: 6 127.0.0.1:43556 103 | 104 | [2018-01-30 10:12:25] malloc 0x56418c766c70 1 105 | 106 | [2018-01-30 10:12:25] ---------------accept 107 | 108 | epoll: 1 109 | 0x56418c766c70 in 6 110 | [2018-01-30 10:12:25] 0---------------in 108 us 111 | 112 | [2018-01-30 10:12:25] 0---------------out 0 us 113 | 114 | epoll: 1 115 | [2018-01-30 10:12:25] 0---------------in 0 us 116 | 117 | 0x56418c766c70 out 6 118 | send 143 bytes 119 | send 0 bytes 120 | [2018-01-30 10:12:25] 0---------------out 130 us 121 | 122 | epoll: 1 123 | 0x56418c766c70 in 6 124 | [2018-01-30 10:12:25] 0---------------in 51 us 125 | 126 | [2018-01-30 10:12:25] 0---------------out 0 us 127 | 128 | epoll: 1 129 | [2018-01-30 10:12:25] 0---------------in 0 us 130 | 131 | 0x56418c766c70 out 6 132 | send 143 bytes 133 | send 0 bytes 134 | [2018-01-30 10:12:25] 0---------------out 87 us 135 | 136 | epoll: 0 137 | epoll: 0 138 | epoll: 1 139 | 0x56418c766c70 in 6 140 | [2018-01-30 10:12:25] -1---------------in 27 us 141 | 142 | [2018-01-30 10:12:25] -1---------------out 0 us 143 | 144 | [2018-01-30 10:12:25] prune 0x56418c766c70 1 145 | 146 | epoll: 0 147 | epoll: 0 148 | epoll: 0 149 | epoll: 0 150 | ``` 151 | 152 | 问题出现了, 153 | ``` 154 | 0x56418c766c70 out 6 155 | send 143 bytes 156 | send 0 bytes 157 | [2018-01-30 10:12:25] 0---------------out 87 us 158 | 159 | epoll: 0 160 | epoll: 0 161 | epoll: 1 162 | 0x56418c766c70 in 6 163 | [2018-01-30 10:12:25] -1---------------in 27 us 164 | ``` 165 | 166 | 在最后一次给客户端发送完之后,`epoll_wait`出现了两次等待超时!!!很神奇!!!压力测试的工具竟然会延迟给我返回数据? 或者,是我的程序发送数据存在延迟??? 可是我已经设置了禁用Nagle算法!!!(大四的网络程序设计课吃过瘪,很有印象)。 167 | 168 | 掏出神器strace执行`strace -tt ./lotos -r /tmp -t 10 -w 4 -d`,发现确实存在两次`epoll_wait`的超时,正好是40ms的阻塞。 169 | ``` 170 | 00:39:34.964433 sendfile(7, 8, NULL, 6) = 6 171 | 00:39:34.964535 sendfile(7, 8, NULL, 6) = 0 172 | 00:39:34.964616 close(8) = 0 173 | 00:39:34.964692 epoll_ctl(5, EPOLL_CTL_MOD, 7, {EPOLLET, {u32=717054400, u64=94206529724864}}) = 0 174 | 00:39:34.964776 epoll_ctl(5, EPOLL_CTL_MOD, 7, {EPOLLIN|EPOLLET, {u32=717054400, u64=94206529724864}}) = 0 175 | 00:39:34.964858 epoll_pwait(5, [], 10240, 20, NULL, 8) = 0 176 | 00:39:34.985093 epoll_pwait(5, [], 10240, 20, NULL, 8) = 0 177 | 00:39:35.005413 epoll_pwait(5, [{EPOLLIN, {u32=717054400, u64=94206529724864}}], 10240, 20, NULL, 8) = 1 178 | 00:39:35.006764 recvfrom(7, "GET / HTTP/1.1\r\nHost: 192.168.1."..., 8192, 0, NULL, NULL) = 44 179 | 00:39:35.006875 recvfrom(7, 0x7ffe23f19810, 8192, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable) 180 | 00:39:35.006929 openat(3, "./", O_RDONLY) = 6 181 | 00:39:35.007000 fstat(6, {st_mode=S_IFDIR|S_ISVTX|0777, st_size=840, ...}) = 0 182 | 00:39:35.007104 openat(6, "index.html", O_RDONLY) = 8 183 | 184 | ``` 185 | 掏出wireshark抓包看也是一切正常。囧了。google搜索也没给出答案(关键词不对:sweat: 186 | 187 | 后来我发现不论`epoll_wait`的超时值设为多少,`Avg Latency` 总是在40ms左右。于是把`40ms`作为关键词google之,发现还真是Nagle算法的问题。估计是我代码写错了吧,去排查一下,发现果真对文件描述符0做了设置TCP_NODELAY的操作!!!我真的是写bug的程序员... 188 | 189 | 修改了代码之后,问题瞬间就不存在了。 190 | 重新生成火焰图, 191 | 192 | - `ab`版本 193 | 194 | ab版本不加keep-alive,纯http1.0模式,得到![8717b1a3/ab-perf-kernel.svg](flamegraph/8717b1a3/ab-perf-kernel.svg) 195 | 196 | - `wrk`版本 197 | 198 | 采用http/1.1协议,支持keep-alive,得到![8717b1a3/wrk-perf-kernel.svg](flamegraph/8717b1a3/wrk-perf-kernel.svg) 199 | 200 | 关于Nagle算法,我觉得这篇文章已经讲得很清晰[《神秘的40毫秒延迟与 TCP_NODELAY》](http://jerrypeng.me/2013/08/mythical-40ms-delay-and-tcp-nodelay/)。 201 | 202 | lotos的设计上确实也是http headers和body分开发送,所以headers被立即发送("send data immediately"),body则被放在缓冲区里面("enqueue data in the buffer until an acknowledge is received"),直到对面40ms的超时ACK来临,才会把body发送出去。 203 | 204 | 好吧,虽然我知道很多服务器都设置TCP_NODELAY,包括nginx,以前也只是经验性的设置一下该选项,直到今天踩了坑,才对这玩意有更深的理解。调试过程也是一个学习成长的过程! 205 | 206 | 207 | # 问题2 208 | 209 | ## 问题描述 210 | 211 | 使用wrk压力测试时候,程序莫名退出,也没有打印出任何错误信息。 212 | 213 | ## Debug记录 214 | 215 | 掏出gdb,在gdb中运行,然后使用wrk压力测试。 216 | 217 | ``` 218 | Program received signal SIGPIPE, Broken pipe. 219 | 0x00007ffff7b1751d in send () from /usr/lib/libc.so.6 220 | ``` 221 | 222 | 恍然大悟,原来是没有处理SIGPIPE。[`man 7 pipe`](https://linux.die.net/man/7/pipe)中写的很清楚。 223 | 224 | > If all file descriptors referring to the write end of a pipe have been closed, then an attempt to read(2) from the pipe will see end-of-file (read(2) will return 0). If all file descriptors referring to the read end of a pipe have been closed, then a write(2) will cause a SIGPIPE signal to be generated for the calling process. If the calling process is ignoring this signal, then write(2) fails with the error EPIPE. An application that uses pipe(2) and fork(2) should use suitable close(2) calls to close unnecessary duplicate file descriptors; this ensures that end-of-file and SIGPIPE/EPIPE are delivered when appropriate. 225 | 226 | 当对端关闭了连接,并且本端忽略了SIGPIPE信号,那么`write`系统调用会失败并且设置errno为EPIPE。 227 | -------------------------------------------------------------------------------- /doc/DESIGN.md: -------------------------------------------------------------------------------- 1 | # 设计与实现 2 | 3 | ## 0x01 重要的结构体 4 | 5 | 1.配置信息结构(server.h) 6 | 7 | ```c 8 | typedef struct { 9 | uint16_t port; /* listen port */ 10 | bool debug; /* debug mode */ 11 | int timeout; /* connection expired time */ 12 | uint32_t worker; /* worker num */ 13 | char *rootdir; /* html root directory */ 14 | int rootdir_fd; /* fildes of rootdir */ 15 | } config_t; 16 | ``` 17 | 18 | 2.请求/响应缓冲结构(buffer.h) 19 | 20 | ```c 21 | typedef struct { 22 | int len; /* used space length in buf */ 23 | int free; /* free space length in buf */ 24 | char buf[]; /* store data */ 25 | } buffer_t; 26 | ``` 27 | 28 | 采用柔性数组,可以动态增长,围绕该结构设计了一系列操作函数。 29 | 30 | 3.简单静态字符串结构(ssstr.h) 31 | 32 | ```c 33 | typedef struct { 34 | char *str; 35 | int len; 36 | } ssstr_t; 37 | ``` 38 | 39 | 对于字符串字面常量以及`buffer_t`结构的子串,都可以用该结构描述而无需开启新的缓冲区存储。有效节省了空间。 40 | 41 | 4.HTTP连接结构(connection.h) 42 | 43 | ```c 44 | struct connection { 45 | int fd; /* connection fildes */ 46 | struct epoll_event event; /* epoll event */ 47 | struct sockaddr_in saddr; /* IP socket address */ 48 | time_t active_time; /* connection accpet time */ 49 | int heap_idx; /* idx at lotos_connections */ 50 | request_t req; /* request */ 51 | }; 52 | typedef struct connection connection_t; 53 | ``` 54 | 55 | 5.请求信息结构(request.h) 56 | 57 | ```c 58 | struct request { 59 | struct connection *c; /* belonged connection */ 60 | buffer_t *ib; /* request buffer */ 61 | buffer_t *ob; /* response buffer */ 62 | parse_archive par; /* parse_archive */ 63 | int resource_fd; /* resource fildes */ 64 | int resource_size; /* resource size */ 65 | int status_code; /* response status code */ 66 | int (*req_handler)(struct request *); /* request handler for rl, hd, bd */ 67 | int (*res_handler)(struct request *); /* response handler for hd bd */ 68 | }; 69 | typedef struct request request_t; 70 | ``` 71 | 72 | 6.HTTP请求解析结构(http_parser.h) 73 | 74 | ```c 75 | typedef struct { 76 | /* parsed request line result */ 77 | http_method method; 78 | http_version version; 79 | ssstr_t request_url_string; 80 | req_url url; 81 | 82 | /* parsed header lines result */ 83 | bool keep_alive; /* connection keep alive */ 84 | int content_length; /* request body content_length */ 85 | int transfer_encoding; /* affect body recv strategy */ 86 | request_headers_t req_headers; 87 | 88 | int num_headers; 89 | ssstr_t header[2]; /* store header every time `parse_header_line` */ 90 | 91 | /* preserve buffer_t state, so when recv new data, we can keep parsing */ 92 | char *next_parse_pos; /* parser position in buffer_t */ 93 | int state; /* parser state */ 94 | 95 | /* private members, do not modify !!! */ 96 | char *method_begin; 97 | char *url_begin; 98 | char *header_line_begin; 99 | char *header_colon_pos; 100 | char *header_val_begin; 101 | char *header_val_end; 102 | size_t body_received; 103 | int buffer_sent; 104 | bool isCRLF_LINE; 105 | bool response_done; 106 | bool err_req; 107 | } parse_archive; 108 | ``` 109 | 110 | 7.错误页面结构(response.h) 111 | 112 | ```c 113 | typedef struct { 114 | int err_page_fd; /* fildes of err page */ 115 | const char *raw_err_page; /* raw data of err page file */ 116 | size_t raw_page_size; /* size of err page file */ 117 | buffer_t *rendered_err_page; /* buffer contains err msg */ 118 | size_t rendered_page_size; /* size of err page file */ 119 | } err_page_t; 120 | ``` 121 | 122 | ## 0x02 数据结构 123 | 124 | ### 1.最小堆(connection.c) 125 | 126 | connection.c实现了一个最小二叉堆, 依据每个connection的active_time比较大小。因为二叉堆是一个完全二叉树的形态,为了简化编程,可以使用数组来存储堆结点。假设堆顶的position为0,按照层次遍历(BFS)的顺序编号,那么position为`i`的结点,左孩子的position为`2*i+1`, 右孩子的position为`2*i+2`。有了这层关系,可以通过position很快定位到孩子或者父结点的位置。 127 | 128 | 在这样的基础上,实现了以下操作: 129 | 130 | - heap_bubble_up 131 | - heap_bubble_down 132 | - heap_insert 133 | 134 | ### 2.HashMap(dict.c) 135 | 136 | 实现了一个简单的HashMap。将其结构画出来,应该也是很一目了然的。 137 | 138 | ![](./dot/dict_structure.png) 139 | 140 | 有一个小细节需要注意,通常我们需要把hash函数算出来的hash值映射回一个HashMap数组的对应位置,使其可以被加入索引。虽然最简单直接的想法是通过取模运算(%),但是%运算比较低效,在大规模的查询/插入操作时很费CPU时间。换一个方式,我们可以规定的HashMap数组的长度为2的幂(如16,32,64...),这样数组的范围就是[0, 2^n-1],映射回HashMap数组的对应方法可以是 `index = Hash(key) & (Length - 1)`。这样`Length - 1`的二进制低位就全是1,如此可以均匀地把key映射到数组中。Lotos的实现中,HashMap的数组长度定为256,可以通过修改`DICT_MASK_SIZE`宏来改变数组长度。 141 | 142 | 在Lotos中,HashMap的使用场景数据量比较小,就没有考虑负载因子、rehash等因素,仅仅实现了最简单的功能。 143 | 144 | - dict_init 145 | - dict_put 146 | - dict_get 147 | - dict_free 148 | 149 | ## 0x03 NIO配合epoll 150 | 151 | 所有关于epoll的问题几乎都可以在[`man 7 epoll`](https://linux.die.net/man/7/epoll)中找到。manual写的很详细了,也给了服务器处理事件循环的样例代码,大部分采用epoll的服务器结构无外乎如此。 152 | 153 | ```c 154 | 155 | /* Set up listening socket, 'listen_sock' (socket(), 156 | bind(), listen()) */ 157 | 158 | epollfd = epoll_create(10); 159 | if (epollfd == -1) { 160 | perror("epoll_create"); 161 | exit(EXIT_FAILURE); 162 | } 163 | 164 | ev.events = EPOLLIN; 165 | ev.data.fd = listen_sock; 166 | if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) { 167 | perror("epoll_ctl: listen_sock"); 168 | exit(EXIT_FAILURE); 169 | } 170 | 171 | for (;;) { 172 | nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); 173 | if (nfds == -1) { 174 | perror("epoll_pwait"); 175 | exit(EXIT_FAILURE); 176 | } 177 | 178 | for (n = 0; n < nfds; ++n) { 179 | if (events[n].data.fd == listen_sock) { 180 | conn_sock = accept(listen_sock, 181 | (struct sockaddr *) &local, &addrlen); 182 | if (conn_sock == -1) { 183 | perror("accept"); 184 | exit(EXIT_FAILURE); 185 | } 186 | setnonblocking(conn_sock); 187 | ev.events = EPOLLIN | EPOLLET; 188 | ev.data.fd = conn_sock; 189 | if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, 190 | &ev) == -1) { 191 | perror("epoll_ctl: conn_sock"); 192 | exit(EXIT_FAILURE); 193 | } 194 | } else { 195 | do_use_fd(events[n].data.fd); 196 | } 197 | } 198 | } 199 | ``` 200 | 201 | epoll中很重要的一个结构体类型是`struct epoll_event`, 它包含了一个`epoll_data_t`类型的联合对象`data`。 202 | 203 | ```c 204 | typedef union epoll_data { 205 | void *ptr; 206 | int fd; 207 | uint32_t u32; 208 | uint64_t u64; 209 | } epoll_data_t; 210 | 211 | struct epoll_event { 212 | uint32_t events; /* Epoll events */ 213 | epoll_data_t data; /* User data variable */ 214 | }; 215 | ``` 216 | 217 | 样例代码中`ev.data.fd = conn_sock;`,直接使用`fd`成员存储新建立连接的文件描述符,这样做简洁明了,但是需要额外的代码去描述一个连接的状态。在Lotos中使用了`connection_t`类型来描述一个连接的属性和状态,所以Lotos使用了`ptr`成员来保存`connection_t`实例的地址。 218 | 219 | ## 0x04 长连接与超时关闭 220 | 221 | 相比于HTTP/1.0,在服务器端发送完数据后关闭文件描述符即可,HTTP/1.1支持长连接,这就需要考虑连接的超时关闭问题,否则大量的非活动连接会消耗尽系统资源。 222 | 223 | Lotos将所有连接注册进一个最小堆,active_time表示该连接上次活动的Epoch时间,active_time越小,表明该连接上次活动时间越早,越有可能超时。当连接建立或者有IO操作时,active_time会被更新,并且在堆中的位置会做相应调整。在每次的事件循环中,都会检查一下堆顶的连接是否超时(connection_prune函数),若超时则关闭连接、移出最小堆。对连接的操作中假若出现了错误,需要关闭连接,最简单的办法是将其active_time设为很小的一个值(比如0),然后等待connection_prune函数将其移除。 224 | 225 | ## 0x05 HTTP请求解析 226 | 227 | 对于HTTP请求体的解析,可以采用有穷状态机(FSM)逐个字母匹配,也可以采用简单的字符串匹配方式。 由于Lotos采用了NIO,不一定可以一次得到完整的请求体(这点在[测试调试](./DEBUG.md)部分也有体现),所以保存连接请求的解析状态是必不可少的工作,否则每次请求体到来之后从头解析就显得愚钝了。状态机恰好可以可以满足这种需求,写起来也不是特别复杂,[RFC2016](https://www.w3.org/Protocols/rfc2616/rfc2616.html)已经给出了BNF范式,照着BNF范式逐字匹配即可,遇到对不上的请求体返回错误即可。Lotos目前实现了Request Line、Header和部分Body的解析,解析代码都在[http_parser.c](../src/http_parser.c)中,解析的结果保存在`parse_archive`类型的结构体中,`request_t`类型有一个`parse_archive`类型的成员`par`,用来记录每个请求解析的状态以及结果。 228 | 229 | ## 0x06 状态管理 230 | 231 | NIO决定了每一个IO操作的状态包含三种:OK, ERROR, AGAIN。Lotos中有两个函数`int request_recv(request_t *r)`和`int response_send(request_t *r)`用于接受和发送数据。在这里三个状态对应的语义应该是: 232 | 233 | 对于`request_recv`: 234 | 235 | - OK: 读到EOF,对端正常关闭连接,无需再读 236 | - ERROR: 错误,需要进入错误处理环节,如断开连接 237 | - AGAIN: 还有数据等待读取,等待下次再读 238 | 239 | 对于`response_send`: 240 | 241 | - OK: 全部数据已经发送(并不代表对端收到),无需再发 242 | - ERROR: 错误,需要进入错误处理环节,如断开连接 243 | - AGAIN: 还有数据等待发送,等待下次再发 244 | 245 | 请求体的状态判定不仅和IO操作的状态相关,也与HTTP协议解析是耦合的。比如对端发出`GE`,在HTTP请求解析模块里,这一部分是合法的请求,但并不完整,我们很大程度上相信这将会是一个HTTP GET请求,所以我们需要再次recv获得更多请求体才能确定。如果接下又收到`T / HTTP/1.0`,那么认为该次请求的Request Line是OK的,否则就是ERROR。所以需要赋予请求的每个状态更明确的语义。 246 | 247 | - OK: 请求是合法的 248 | - ERROR: 错误,需要进入错误处理环节,如断开连接 249 | - AGAIN: 请求体目前是合法的,但不完整,需要再读 250 | 251 | ## 0x07 错误处理 252 | 253 | 作为一个长时间跑在后台的程序而言,需要足够健壮,需要对错误处理做足功夫。调试时就遇到[使用wrk压力测试时,程序退出没有任何错误的假象](./DEBUG_LOG.md),原因是对于SIGPIPE没有做正确的处理。中给出了触发网络编程中两个常见错误`Connection reset by peer` 和`Broken pipe`的示例代码。在编写代码时候需要对每个syscall做错误检查,否则调试时定位bug则会困难许多。保证内存没有泄露也是很重要的一点,用valgrind测试是最简单的方式。 254 | -------------------------------------------------------------------------------- /doc/PURPOSE.md: -------------------------------------------------------------------------------- 1 | # 项目目的 2 | 3 | Lotos是个人的业余网络编程项目,也是自己将理论付诸于实践的一个过程。初学c的socket编程是在本科三年级,从那时起就梦想写一个HTTP Server,虽然之后陆陆续续也写过几个和网络编程有关的小项目,比如网络扫描器和[Tinyhttpd](https://github.com/EZLippi/Tinyhttpd),但也仅限于几百行的规模。 4 | 5 | 2017年花了约两个月断断续续读完了[《Unix/Linux编程实践教程》](https://book.douban.com/subject/1219329/),可以说这是一本相见恨晚的书,既适合入门,也适合重新梳理知识体系,我觉得自己有能力重新开一个HTTP Server的坑了。 6 | 7 | 我对Lotos的期望是“能用”,这就要求能够正确处理syscall error、signal以及很多极端情况下的用户请求。更进一步,在能用的基础上争取做到“高性能”,高兴的是,在[Benchmark](./BENCHMARK.md)中,Lotos的性能还是很不错的。 8 | 9 | Lotos的开发几乎占用了我一个多月的所有业余时间,私下里认为代码还算工整规范,现在再把开发调试的思考和总结记录成文档,方便与大家交流学习。 10 | 11 | #### 开发规范 12 | 13 | 开发流程比较完整,确定需求 -> 服务器模型选型 -> 数据结构选择 -> 单元测试 -> 开发 -> 集成测试 -> 性能测试。核心的数据结构都配套有单元测试。 14 | 15 | #### 数据结构 16 | 17 | 通过对场景需求和将来扩展性的考量,需要设计合理的、高效的数据结构。Lotos中用了最小堆来淘汰超时过期的HTTP连接,以及使用了HashMap这种KV结构加速对诸如`Mime Type`和 `HTTP Header Handler`的查找。 18 | 19 | #### 语言特性 20 | 21 | 项目中涉及C语言中很多特性,比如条件编译,嵌套宏定义、柔性数组、静态全局变量、函数指针、位运算以及利用内存空间全局区等。 22 | 23 | #### 抽象能力 24 | 25 | 在用户空间进行c编程,由于没有天然的面向对象支持,需要程序员自己封装各种结构体。系统编程中,IO操作基本上都是对文件描述符fd进行操作,在本项目中,一个文件描述符对应着一个TCP连接,把fd封装进`connection_t`作为一个成员变量是一个自然而然的抽象过程。 26 | 27 | #### 协议理解 28 | 29 | 开发HTTP服务器从宏观上来说会对网络协议TCP及其各个状态理解更深,会对HTTP协议主要字段的功能理解更深,会对网络I/O模型认识更深。 30 | -------------------------------------------------------------------------------- /doc/dot/dict_structure.dot: -------------------------------------------------------------------------------- 1 | digraph structs { 2 | rankdir=LR 3 | node [shape=record]; 4 | struct1 [label=" dict_t| table| size_mask| used"]; 5 | struct2 [label=" dict_node_t| 0| 1| 2| ...| 254| 255"]; 6 | struct3 [label="NULL"] 7 | struct4 [label="NULL"] 8 | struct5 [label="NULL"] 9 | struct6 [label="NULL"] 10 | struct7[label=" dict_node_t| { k | v | next }"] 11 | struct8[label=" dict_node_t| { k | v | next }"] 12 | struct9 [label="NULL"] 13 | 14 | struct1:f1 -> struct2:f0; 15 | struct2:f1 -> struct3; 16 | struct2:f2 -> struct4; 17 | struct2:f5 -> struct5; 18 | struct2:f6 -> struct6; 19 | 20 | struct2:f3 -> struct7; 21 | struct7:next -> struct8; 22 | struct8:next -> struct9; 23 | } 24 | -------------------------------------------------------------------------------- /doc/dot/dict_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chendotjs/lotos/3eb36cc3723a1dc9bb737505f0c8a3538ee16347/doc/dot/dict_structure.png -------------------------------------------------------------------------------- /doc/flamegraph/149bcb68/wrk-perf-kernel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 332 | 333 | Flame Graph 334 | 335 | Reset Zoom 336 | Search 337 | 338 | 339 | ep_poll (10 samples, 26.32%) 340 | ep_poll 341 | 342 | 343 | tty_ldisc_ref_wait (1 samples, 2.63%) 344 | tt.. 345 | 346 | 347 | __send (1 samples, 2.63%) 348 | __.. 349 | 350 | 351 | ip_finish_output2 (4 samples, 10.53%) 352 | ip_finish_output2 353 | 354 | 355 | main (27 samples, 71.05%) 356 | main 357 | 358 | 359 | x86_pmu_enable_all (9 samples, 23.68%) 360 | x86_pmu_enable_all 361 | 362 | 363 | try_to_wake_up (1 samples, 2.63%) 364 | tr.. 365 | 366 | 367 | ip_output (4 samples, 10.53%) 368 | ip_output 369 | 370 | 371 | sys_sendfile64 (1 samples, 2.63%) 372 | sy.. 373 | 374 | 375 | x86_pmu_enable_all (13 samples, 34.21%) 376 | x86_pmu_enable_all 377 | 378 | 379 | finish_task_switch (9 samples, 23.68%) 380 | finish_task_switch 381 | 382 | 383 | epoll_pwait (10 samples, 26.32%) 384 | epoll_pwait 385 | 386 | 387 | schedule_hrtimeout_range_clock (15 samples, 39.47%) 388 | schedule_hrtimeout_range_clock 389 | 390 | 391 | entry_SYSCALL_64_fastpath (10 samples, 26.32%) 392 | entry_SYSCALL_64_fastpath 393 | 394 | 395 | tcp_transmit_skb (5 samples, 13.16%) 396 | tcp_transmit_skb 397 | 398 | 399 | smp_apic_timer_interrupt (1 samples, 2.63%) 400 | sm.. 401 | 402 | 403 | vfs_write (1 samples, 2.63%) 404 | vf.. 405 | 406 | 407 | _IO_vsprintf (1 samples, 2.63%) 408 | _I.. 409 | 410 | 411 | connecion_set_reactivated (1 samples, 2.63%) 412 | co.. 413 | 414 | 415 | ip_rcv (3 samples, 7.89%) 416 | ip_rcv 417 | 418 | 419 | __tcp_push_pending_frames (5 samples, 13.16%) 420 | __tcp_push_pending_.. 421 | 422 | 423 | ep_poll (15 samples, 39.47%) 424 | ep_poll 425 | 426 | 427 | do_softirq_own_stack (3 samples, 7.89%) 428 | do_softirq_.. 429 | 430 | 431 | __GI___libc_write (1 samples, 2.63%) 432 | __.. 433 | 434 | 435 | exit_to_usermode_loop (2 samples, 5.26%) 436 | exit_t.. 437 | 438 | 439 | __libc_start_main (27 samples, 71.05%) 440 | __libc_start_main 441 | 442 | 443 | __schedule (14 samples, 36.84%) 444 | __schedule 445 | 446 | 447 | __perf_event_task_sched_in (9 samples, 23.68%) 448 | __perf_event_task_sched_in 449 | 450 | 451 | __schedule (9 samples, 23.68%) 452 | __schedule 453 | 454 | 455 | ldsem_down_read (1 samples, 2.63%) 456 | ld.. 457 | 458 | 459 | do_sendfile (1 samples, 2.63%) 460 | do.. 461 | 462 | 463 | [unknown] (28 samples, 73.68%) 464 | [unknown] 465 | 466 | 467 | enqueue_task_fair (1 samples, 2.63%) 468 | en.. 469 | 470 | 471 | entry_SYSCALL_64_fastpath (5 samples, 13.16%) 472 | entry_SYSCALL_64_fa.. 473 | 474 | 475 | ip_queue_xmit (5 samples, 13.16%) 476 | ip_queue_xmit 477 | 478 | 479 | __wake_up_common_lock (1 samples, 2.63%) 480 | __.. 481 | 482 | 483 | tcp_v4_do_rcv (1 samples, 2.63%) 484 | tc.. 485 | 486 | 487 | __send (5 samples, 13.16%) 488 | __send 489 | 490 | 491 | hrtimer_interrupt (1 samples, 2.63%) 492 | hr.. 493 | 494 | 495 | __vfs_write (1 samples, 2.63%) 496 | __.. 497 | 498 | 499 | tcp_write_xmit (5 samples, 13.16%) 500 | tcp_write_xmit 501 | 502 | 503 | _raw_spin_unlock_irq (1 samples, 2.63%) 504 | _r.. 505 | 506 | 507 | schedule (9 samples, 23.68%) 508 | schedule 509 | 510 | 511 | __perf_event_task_sched_in (13 samples, 34.21%) 512 | __perf_event_task_sched_in 513 | 514 | 515 | __wake_up_common (1 samples, 2.63%) 516 | __.. 517 | 518 | 519 | tcp_filter (1 samples, 2.63%) 520 | tc.. 521 | 522 | 523 | ttwu_do_activate (1 samples, 2.63%) 524 | tt.. 525 | 526 | 527 | syscall_return_slowpath (2 samples, 5.26%) 528 | syscal.. 529 | 530 | 531 | __cgroup_bpf_run_filter_skb (1 samples, 2.63%) 532 | __.. 533 | 534 | 535 | ip_local_deliver_finish (3 samples, 7.89%) 536 | ip_local_de.. 537 | 538 | 539 | tcp_rcv_established (1 samples, 2.63%) 540 | tc.. 541 | 542 | 543 | native_write_msr (9 samples, 23.68%) 544 | native_write_msr 545 | 546 | 547 | __hrtimer_run_queues (1 samples, 2.63%) 548 | __.. 549 | 550 | 551 | __remove_hrtimer (1 samples, 2.63%) 552 | __.. 553 | 554 | 555 | hrtimer_try_to_cancel (1 samples, 2.63%) 556 | hr.. 557 | 558 | 559 | response_handle_send_buffer (1 samples, 2.63%) 560 | re.. 561 | 562 | 563 | ip_local_deliver (3 samples, 7.89%) 564 | ip_local_de.. 565 | 566 | 567 | sendfile (1 samples, 2.63%) 568 | se.. 569 | 570 | 571 | tcp_sendmsg_locked (5 samples, 13.16%) 572 | tcp_sendmsg_locked 573 | 574 | 575 | process_backlog (3 samples, 7.89%) 576 | process_bac.. 577 | 578 | 579 | sock_def_readable (1 samples, 2.63%) 580 | so.. 581 | 582 | 583 | net_rx_action (3 samples, 7.89%) 584 | net_rx_action 585 | 586 | 587 | __netif_receive_skb_core (3 samples, 7.89%) 588 | __netif_rec.. 589 | 590 | 591 | _raw_spin_lock_irqsave (1 samples, 2.63%) 592 | _r.. 593 | 594 | 595 | schedule (14 samples, 36.84%) 596 | schedule 597 | 598 | 599 | tcp_sendmsg (5 samples, 13.16%) 600 | tcp_sendmsg 601 | 602 | 603 | account_entity_enqueue (1 samples, 2.63%) 604 | ac.. 605 | 606 | 607 | request_handle_headers (1 samples, 2.63%) 608 | re.. 609 | 610 | 611 | all (38 samples, 100%) 612 | 613 | 614 | 615 | SYSC_sendto (5 samples, 13.16%) 616 | SYSC_sendto 617 | 618 | 619 | sys_epoll_pwait (15 samples, 39.47%) 620 | sys_epoll_pwait 621 | 622 | 623 | validate_xmit_skb (1 samples, 2.63%) 624 | va.. 625 | 626 | 627 | lotos_epoll_wait (15 samples, 39.47%) 628 | lotos_epoll_wait 629 | 630 | 631 | ep_poll_callback (1 samples, 2.63%) 632 | ep.. 633 | 634 | 635 | do_softirq.part.17 (3 samples, 7.89%) 636 | do_softirq... 637 | 638 | 639 | entry_SYSCALL_64_fastpath (15 samples, 39.47%) 640 | entry_SYSCALL_64_fastpath 641 | 642 | 643 | tcp_data_queue (1 samples, 2.63%) 644 | tc.. 645 | 646 | 647 | tty_write (1 samples, 2.63%) 648 | tt.. 649 | 650 | 651 | request_handle (3 samples, 7.89%) 652 | request_han.. 653 | 654 | 655 | __close (2 samples, 5.26%) 656 | __close 657 | 658 | 659 | __dev_queue_xmit (1 samples, 2.63%) 660 | __.. 661 | 662 | 663 | response_handle (8 samples, 21.05%) 664 | response_handle 665 | 666 | 667 | epoll_pwait (15 samples, 39.47%) 668 | epoll_pwait 669 | 670 | 671 | schedule_hrtimeout_range_clock (9 samples, 23.68%) 672 | schedule_hrtimeout_range_clock 673 | 674 | 675 | __local_bh_enable_ip (3 samples, 7.89%) 676 | __local_bh_.. 677 | 678 | 679 | task_work_run (2 samples, 5.26%) 680 | task_w.. 681 | 682 | 683 | lotos (38 samples, 100.00%) 684 | lotos 685 | 686 | 687 | tcp_v4_rcv (2 samples, 5.26%) 688 | tcp_v4.. 689 | 690 | 691 | vfprintf (1 samples, 2.63%) 692 | vf.. 693 | 694 | 695 | sk_filter_trim_cap (1 samples, 2.63%) 696 | sk.. 697 | 698 | 699 | __do_softirq (3 samples, 7.89%) 700 | __do_softirq 701 | 702 | 703 | ssstr_tolower (1 samples, 2.63%) 704 | ss.. 705 | 706 | 707 | native_write_msr (13 samples, 34.21%) 708 | native_write_msr 709 | 710 | 711 | enqueue_entity (1 samples, 2.63%) 712 | en.. 713 | 714 | 715 | __wake_up_common (1 samples, 2.63%) 716 | __.. 717 | 718 | 719 | entry_SYSCALL_64_fastpath (2 samples, 5.26%) 720 | entry_.. 721 | 722 | 723 | entry_SYSCALL_64_fastpath (1 samples, 2.63%) 724 | en.. 725 | 726 | 727 | finish_task_switch (14 samples, 36.84%) 728 | finish_task_switch 729 | 730 | 731 | lotos_epoll_wait (10 samples, 26.32%) 732 | lotos_epoll_wait 733 | 734 | 735 | apic_timer_interrupt (1 samples, 2.63%) 736 | ap.. 737 | 738 | 739 | sys_epoll_pwait (10 samples, 26.32%) 740 | sys_epoll_pwait 741 | 742 | 743 | entry_SYSCALL_64_fastpath (1 samples, 2.63%) 744 | en.. 745 | 746 | 747 | sys_write (1 samples, 2.63%) 748 | sy.. 749 | 750 | 751 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6 FATAL_ERROR) 2 | 3 | project(lotos) 4 | 5 | if (NOT CMAKE_BUILD_TYPE) 6 | message(STATUS "No build type defined; defaulting to 'Debug'") 7 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING 8 | "The type of build. Possible values are: Debug, Release, RelWithDebInfo and MinSizeRel.") 9 | endif() 10 | 11 | message(STATUS "Host is: ${CMAKE_HOST_SYSTEM}. Build target is: ${CMAKE_SYSTEM}") 12 | 13 | set(SRC 14 | buffer.c connection.c dict.c http_parser.c lotos_epoll.c main.c misc.c request.c response.c server.c ssstr.c mem_pool.c 15 | ) 16 | 17 | 18 | message(STATUS "OS type: ${CMAKE_SYSTEM_NAME}") 19 | if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Linux") 20 | message( FATAL_ERROR "Only Linux is supported, CMake will exit." ) 21 | endif() 22 | 23 | add_executable(lotos ${SRC}) 24 | add_definitions(-DNDEBUG) 25 | set_property(TARGET lotos PROPERTY C_STANDARD 99) 26 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-std=c99 -Wall -O3 -DNDEBUG -DUSE_MEM_POOL=1 2 | OPTFLAGS= 3 | 4 | OBJS=misc.o ssstr.o dict.o lotos_epoll.o buffer.o request.o response.o \ 5 | connection.o http_parser.o server.o mem_pool.o main.o 6 | 7 | lotos : $(OBJS) 8 | $(CC) $(CFLAGS) $^ -o $@ $(OPTFLAGS) 9 | 10 | test : 11 | make -C ./test 12 | make test -C ./test 13 | 14 | format : 15 | find . -iname '*.[ch]' -exec clang-format -i -style="{ColumnLimit: 80}" {} + 16 | 17 | clean : 18 | rm -f *.o lotos 19 | 20 | .PHONY : test clean format 21 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | #include "misc.h" 3 | #include 4 | #include 5 | #include 6 | 7 | buffer_t *buffer_init() { return buffer_new(BUFSIZ); } 8 | 9 | buffer_t *buffer_new(size_t initlen) { 10 | buffer_t *pb = malloc(sizeof(buffer_t) + initlen + 1); // reserve space for \0 11 | assert(pb != NULL); 12 | if (pb == NULL) { 13 | return NULL; 14 | } 15 | pb->len = 0; 16 | pb->free = initlen; 17 | pb->buf[pb->len] = '\0'; 18 | return pb; 19 | } 20 | 21 | void buffer_free(buffer_t *pb) { 22 | if (pb != NULL) { 23 | free(pb); 24 | } 25 | } 26 | 27 | inline void buffer_clear(buffer_t *pb) { 28 | pb->free += pb->len; 29 | pb->len = 0; 30 | } 31 | 32 | /** 33 | * newbuf = buffer_cat(oldbuf, "abc", 3); 34 | * After the call, oldbuf is no longer valid, and must be substituted with 35 | * newbuf 36 | * 37 | * @param pb [description] 38 | * @param buf [description] 39 | * @param nbyte [description] 40 | */ 41 | buffer_t *buffer_cat(buffer_t *pb, const char *buf, size_t nbyte) { 42 | buffer_t *npb = NULL; 43 | 44 | if (nbyte <= buffer_avail(pb)) { // no need to realloc 45 | memcpy(pb->buf + pb->len, buf, nbyte); 46 | pb->len += nbyte; 47 | pb->free -= nbyte; 48 | pb->buf[pb->len] = '\0'; 49 | return pb; 50 | } 51 | /* realloc */ 52 | size_t cur_len = buffer_len(pb); 53 | size_t new_len = cur_len + nbyte; 54 | /* realloc strategy */ 55 | if (new_len < BUFFER_LIMIT) 56 | new_len *= 2; 57 | else 58 | new_len += BUFFER_LIMIT; 59 | 60 | npb = realloc(pb, sizeof(buffer_t) + new_len + 1); 61 | if (npb == NULL) 62 | return NULL; 63 | memcpy(npb->buf + npb->len, buf, nbyte); 64 | npb->len += nbyte; 65 | npb->free = new_len - npb->len; 66 | npb->buf[npb->len] = '\0'; 67 | return npb; 68 | } 69 | 70 | buffer_t *buffer_cat_cstr(buffer_t *pb, const char *cstr) { 71 | return buffer_cat(pb, cstr, strlen(cstr)); 72 | } 73 | 74 | void buffer_print(buffer_t *pb) { 75 | char *p = pb->buf; 76 | for (; p != pb->buf + pb->len; p++) { 77 | printf("%c", *p); 78 | fflush(stdout); 79 | } 80 | printf("\n"); 81 | } 82 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef _BUFFER_H__ 2 | #define _BUFFER_H__ 3 | #include "misc.h" 4 | #include 5 | 6 | /* usually 2M, a request buffer may increase refering to this */ 7 | #define BUFFER_LIMIT (BUFSIZ * 250) 8 | 9 | typedef struct { 10 | int len; /* used space length in buf */ 11 | int free; /* free space length in buf */ 12 | char buf[]; /* store data */ 13 | } buffer_t; 14 | 15 | extern buffer_t *buffer_init(); 16 | extern buffer_t *buffer_new(size_t initlen); 17 | extern void buffer_free(buffer_t *pb); 18 | extern void buffer_clear(buffer_t *pb); 19 | extern buffer_t *buffer_cat(buffer_t *pb, const char *buf, size_t nbyte); 20 | extern buffer_t *buffer_cat_cstr(buffer_t *pb, const char *cstr); 21 | extern void buffer_print(buffer_t *pb); 22 | 23 | static inline size_t buffer_len(const buffer_t *pb) { return pb->len; } 24 | 25 | static inline size_t buffer_avail(const buffer_t *pb) { return pb->free; } 26 | 27 | static inline buffer_t *buffer_buffer(const char *buf) { 28 | return (buffer_t *)(buf - (sizeof(buffer_t))); 29 | } 30 | 31 | static inline char *buffer_end(const buffer_t *pb) { 32 | return ((char *)(pb->buf) + pb->len); 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/connection.c: -------------------------------------------------------------------------------- 1 | #include "connection.h" 2 | #include "lotos_epoll.h" 3 | #include "misc.h" 4 | #include "server.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /************************** heap operation start ****************************/ 14 | 15 | /* lotos_connections is seen as a binary min heap */ 16 | connection_t *lotos_connections[MAX_CONNECTION] = {0}; 17 | static int heap_size = 0; 18 | 19 | #define LCHILD(x) (((x) << 1) + 1) 20 | #define RCHILD(x) (LCHILD(x) + 1) 21 | #define PARENT(x) ((x - 1) >> 1) 22 | #define INHEAP(n, x) (((-1) < (x)) && ((x) < (n))) 23 | 24 | inline static void c_swap(int x, int y) { 25 | assert(x >= 0 && x < heap_size && y >= 0 && y < heap_size); 26 | connection_t *tmp = lotos_connections[x]; 27 | lotos_connections[x] = lotos_connections[y]; 28 | lotos_connections[y] = tmp; 29 | // update heap_idx 30 | lotos_connections[x]->heap_idx = x; 31 | lotos_connections[y]->heap_idx = y; 32 | } 33 | 34 | /* used for inserting */ 35 | static void heap_bubble_up(int idx) { 36 | while (PARENT(idx) >= 0) { 37 | int fidx = PARENT(idx); // fidx is father of idx; 38 | connection_t *c = lotos_connections[idx]; 39 | connection_t *fc = lotos_connections[fidx]; 40 | if (c->active_time >= fc->active_time) 41 | break; 42 | c_swap(idx, fidx); 43 | idx = fidx; 44 | } 45 | } 46 | 47 | /* used for extracting or active_time update larger */ 48 | static void heap_bubble_down(int idx) { 49 | while (TRUE) { 50 | int proper_child; 51 | int lchild = INHEAP(heap_size, LCHILD(idx)) ? LCHILD(idx) : (heap_size + 1); 52 | int rchild = INHEAP(heap_size, RCHILD(idx)) ? RCHILD(idx) : (heap_size + 1); 53 | if (lchild > heap_size && rchild > heap_size) { // no children 54 | break; 55 | } else if (INHEAP(heap_size, lchild) && INHEAP(heap_size, rchild)) { 56 | proper_child = lotos_connections[lchild]->active_time < 57 | lotos_connections[rchild]->active_time 58 | ? lchild 59 | : rchild; 60 | } else if (lchild > heap_size) { 61 | proper_child = rchild; 62 | } else { 63 | proper_child = lchild; 64 | } 65 | // idx is the smaller than children 66 | if (lotos_connections[idx]->active_time <= 67 | lotos_connections[proper_child]->active_time) 68 | break; 69 | assert(INHEAP(heap_size, proper_child)); 70 | c_swap(idx, proper_child); 71 | idx = proper_child; 72 | } 73 | } 74 | 75 | static int heap_insert(connection_t *c) { 76 | if (heap_size >= MAX_CONNECTION) { 77 | return ERROR; 78 | } 79 | lotos_connections[heap_size++] = c; 80 | c->heap_idx = heap_size - 1; 81 | heap_bubble_up(heap_size - 1); 82 | return 0; 83 | } 84 | 85 | static void heap_print() { 86 | connection_t *c; 87 | int i; 88 | printf("----------------heap---------------\n"); 89 | for (i = 0; i < heap_size; i++) { 90 | c = lotos_connections[i]; 91 | printf("[%2d] %p fd: %2d heap_idx: %2d active_time: %lu\n", i, c, c->fd, 92 | c->heap_idx, c->active_time); 93 | } 94 | printf("----------------heap---------------\n"); 95 | } 96 | 97 | /************************** heap operation end ******************************/ 98 | 99 | /* ref `man 7 tcp`. disable Nagle Algorithm, make send(2) flush */ 100 | static inline void connection_set_nodelay(connection_t *c) { 101 | static int enable = 1; 102 | setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); 103 | } 104 | 105 | connection_t *connection_init() { 106 | // Too many malloc would be slow, but mem pool seems not popular right now. 107 | #if USE_MEM_POOL 108 | connection_t *c = pool_alloc(&connection_pool); 109 | #else 110 | connection_t *c = malloc(sizeof(connection_t)); 111 | #endif 112 | // init request 113 | if (c) { 114 | if (request_init(&c->req, c) == ERROR) { 115 | #if USE_MEM_POOL 116 | pool_free(&connection_pool, c); 117 | #else 118 | free(c); 119 | #endif 120 | c = NULL; 121 | } 122 | } 123 | return c; 124 | } 125 | 126 | connection_t *connection_accept(int fd, struct sockaddr_in *paddr) { 127 | connection_t *c = connection_init(); 128 | assert(c != NULL); 129 | if (c == NULL) { // malloc fail 130 | connection_close(c); 131 | return NULL; 132 | } 133 | 134 | /* fill in connection_t */ 135 | c->fd = fd; 136 | if (paddr) 137 | c->saddr = *paddr; 138 | c->active_time = time(NULL); 139 | 140 | set_fd_nonblocking(c->fd); 141 | connection_set_nodelay(c); 142 | 143 | if (connection_register(c) == ERROR) { 144 | connection_close(c); 145 | return NULL; 146 | } 147 | 148 | if (lotos_epoll_add(epoll_fd, c, EPOLLIN | EPOLLET, &c->event) == ERROR) { 149 | connection_close(c); 150 | return NULL; 151 | } 152 | 153 | #ifndef NDEBUG 154 | char ip_addr[32]; 155 | uint16_t port; 156 | get_internet_address(ip_addr, 32, &port, &c->saddr); 157 | lotos_log(LOG_INFO, "fd: %2d %s:%u\n", fd, ip_addr, port); 158 | lotos_log(LOG_INFO, "malloc %p %d\n", c, heap_size); 159 | (void)heap_print; /* Unused. Silent compiler warning. */ 160 | #endif 161 | 162 | return c; 163 | } 164 | 165 | int connection_register(connection_t *c) { 166 | if (heap_size >= MAX_CONNECTION) { 167 | return ERROR; 168 | } 169 | return heap_insert(c); 170 | } 171 | 172 | void connection_unregister(connection_t *c) { 173 | assert(heap_size >= 1); 174 | lotos_connections[c->heap_idx] = lotos_connections[heap_size - 1]; 175 | lotos_connections[c->heap_idx]->heap_idx = c->heap_idx; 176 | heap_size--; 177 | heap_bubble_down(c->heap_idx); 178 | } 179 | 180 | static inline void connection_free(connection_t *c) { 181 | if (c) { 182 | buffer_free(c->req.ib); 183 | c->req.ib = NULL; 184 | buffer_free(c->req.ob); 185 | c->req.ob = NULL; 186 | #if USE_MEM_POOL 187 | pool_free(&connection_pool, c); 188 | #else 189 | free(c); 190 | #endif 191 | } 192 | } 193 | 194 | /* close connection, free memory */ 195 | int connection_close(connection_t *c) { 196 | if (c == NULL) 197 | return OK; 198 | /* 199 | * explicitly delete fd from epoll_set, see `man 7 epoll` Q6 200 | * Q6 Will closing a file descriptor cause it to be removed from all epoll 201 | * sets automatically? 202 | */ 203 | lotos_epoll_del(epoll_fd, c, 0, NULL); 204 | close(c->fd); 205 | connection_unregister(c); 206 | connection_free(c); 207 | return OK; 208 | } 209 | 210 | void connection_prune() { 211 | while (heap_size > 0) { 212 | connection_t *c = lotos_connections[0]; 213 | if (time(NULL) - c->active_time >= server_config.timeout) { 214 | #ifndef NDEBUG 215 | // heap_print(); 216 | lotos_log(LOG_INFO, "prune %p %d\n", c, heap_size); 217 | #endif 218 | connection_close(c); 219 | } else 220 | break; 221 | } 222 | } 223 | 224 | inline bool connecion_is_expired(connection_t *c) { 225 | return (time(NULL) - c->active_time > server_config.timeout); 226 | } 227 | 228 | void connecion_set_reactivated(connection_t *c) { 229 | c->active_time = time(NULL); 230 | heap_bubble_down(c->heap_idx); 231 | } 232 | 233 | void connecion_set_expired(connection_t *c) { 234 | c->active_time = 0; // very old time 235 | heap_bubble_up(c->heap_idx); 236 | } 237 | 238 | int set_fd_nonblocking(int fd) { 239 | int flag = fcntl(fd, F_GETFL, 0); 240 | ABORT_ON(flag == ERROR, "fcntl: F_GETFL"); 241 | flag |= O_NONBLOCK; 242 | ABORT_ON(fcntl(fd, F_SETFL, flag) == ERROR, "fcntl: FSETFL"); 243 | return 0; 244 | } 245 | -------------------------------------------------------------------------------- /src/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONNECTION_H__ 2 | #define _CONNECTION_H__ 3 | #include "buffer.h" 4 | #include "misc.h" 5 | #include "request.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define MAX_CONNECTION (10240) 12 | 13 | struct connection { 14 | int fd; /* connection fildes */ 15 | struct epoll_event event; /* epoll event */ 16 | struct sockaddr_in saddr; /* IP socket address */ 17 | time_t active_time; /* connection accpet time */ 18 | int heap_idx; /* idx at lotos_connections */ 19 | request_t req; /* request */ 20 | }; 21 | typedef struct connection connection_t; 22 | 23 | extern connection_t *connection_accept(int fd, struct sockaddr_in *paddr); 24 | extern connection_t *connection_init(); 25 | extern int connection_register(connection_t *c); 26 | extern void connection_unregister(connection_t *c); 27 | extern int connection_close(connection_t *c); 28 | extern void connection_prune(); 29 | extern bool connecion_is_expired(connection_t *c); 30 | extern void connecion_set_reactivated(connection_t *c); 31 | extern void connecion_set_expired(connection_t *c); 32 | 33 | extern int set_fd_nonblocking(int fd); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/dict.c: -------------------------------------------------------------------------------- 1 | #include "dict.h" 2 | #include "misc.h" 3 | #include "ssstr.h" 4 | #include 5 | #include 6 | #include 7 | 8 | // https://www.byvoid.com/zhs/blog/string-hash-compare 9 | static unsigned int ssstr_hash_sdbm(const ssstr_t *key) { 10 | const char *str = key->str; 11 | unsigned int hash = 0; 12 | while (str != (key->str + key->len)) { 13 | // equivalent to: hash = 65599*hash + (*str++); 14 | hash = (*str++) + (hash << 6) + (hash << 16) - hash; 15 | } 16 | return (hash & 0x7FFFFFFF); 17 | } 18 | 19 | #define LOTOS_HASH(key) (ssstr_hash_sdbm(key) & DICT_MASK_SIZE) 20 | 21 | void dict_put(dict_t *dict, const ssstr_t *key, void *val) { 22 | unsigned int hash = LOTOS_HASH(key); 23 | dict_node_t *p = dict->table[hash]; 24 | dict_node_t *q = malloc(sizeof(dict_node_t)); 25 | 26 | // p == NULL or p != NULL, the same 27 | dict_node_init(q, (ssstr_t *)key, val, p); 28 | dict->table[hash] = q; 29 | 30 | dict->used++; 31 | } 32 | 33 | /* cause we can put NULL as a valid value, so found_flag is necessary */ 34 | void *dict_get(dict_t *dict, const ssstr_t *key, bool *found_flag) { 35 | if (found_flag != NULL) 36 | *found_flag = FALSE; 37 | unsigned int hash = LOTOS_HASH(key); 38 | dict_node_t *p = dict->table[hash]; 39 | 40 | while (p != NULL) { 41 | if (ssstr_cmp(&p->k, key) == 0) { 42 | if (found_flag != NULL) 43 | *found_flag = TRUE; 44 | return p->v; 45 | } 46 | p = p->next; 47 | } 48 | 49 | return NULL; 50 | } 51 | 52 | void dict_free(dict_t *d) { 53 | if (d == NULL) 54 | return; 55 | int i; 56 | for (i = 0; i < sizeof(d->table) / sizeof(dict_node_t *); i++) { 57 | dict_node_t *p = d->table[i]; 58 | while (p != NULL) { 59 | dict_node_t *q = p->next; 60 | free(p); 61 | d->used--; 62 | p = q; 63 | } 64 | } 65 | assert(d->used == 0); 66 | } 67 | -------------------------------------------------------------------------------- /src/dict.h: -------------------------------------------------------------------------------- 1 | #ifndef _DICT_H__ 2 | #define _DICT_H__ 3 | 4 | /** 5 | * dict used with a small amount of data, no need to rehash 6 | */ 7 | 8 | #include "misc.h" 9 | #include "ssstr.h" 10 | #include 11 | 12 | #define DICT_MASK_SIZE (0xFF) 13 | 14 | typedef struct dict_node { 15 | ssstr_t k; 16 | void *v; 17 | struct dict_node *next; 18 | } dict_node_t; 19 | 20 | typedef struct { 21 | dict_node_t *table[DICT_MASK_SIZE + 1]; 22 | unsigned int size_mask; 23 | unsigned int used; 24 | } dict_t; 25 | 26 | static inline void dict_init(dict_t *d) { 27 | memset(d, 0, sizeof(dict_t)); 28 | d->size_mask = DICT_MASK_SIZE; 29 | } 30 | 31 | static inline void dict_node_init(dict_node_t *node, ssstr_t *s, void *v, 32 | dict_node_t *next) { 33 | node->k = *s; 34 | node->v = v; 35 | node->next = next; 36 | } 37 | 38 | extern void dict_put(dict_t *dict, const ssstr_t *key, void *val); 39 | extern void *dict_get(dict_t *dict, const ssstr_t *key, bool *found_flag); 40 | extern void dict_free(dict_t *d); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/http_parser.c: -------------------------------------------------------------------------------- 1 | #include "http_parser.h" 2 | #include "buffer.h" 3 | #include "misc.h" 4 | #include 5 | #include 6 | 7 | #define STR2_EQ(p, q) ((p)[0] == (q)[0] && (p)[1] == (q)[1]) 8 | #define STR3_EQ(p, q) (STR2_EQ(p, q) && (p)[2] == (q)[2]) 9 | #define STR4_EQ(p, q) (STR2_EQ(p, q) && STR2_EQ(p + 2, q + 2)) 10 | #define STR5_EQ(p, q) (STR2_EQ(p, q) && STR3_EQ(p + 2, q + 2)) 11 | #define STR6_EQ(p, q) (STR3_EQ(p, q) && STR3_EQ(p + 3, q + 3)) 12 | #define STR7_EQ(p, q) (STR3_EQ(p, q) && STR4_EQ(p + 3, q + 3)) 13 | 14 | #define HEADER_SET(header, str_beg, str_end) \ 15 | do { \ 16 | assert(str_beg <= str_end); \ 17 | (header)->str = str_beg; \ 18 | (header)->len = (str_end) - (str_beg); \ 19 | } while (0) 20 | 21 | static int parse_method(char *begin, char *end); 22 | static int parse_url(char *begin, char *end, parse_archive *ar); 23 | 24 | /* parse request line */ 25 | /** 26 | * @return 27 | * OK: request line OK 28 | * AGAIN: parse to the end of buffer, but no complete request line 29 | * INVALID_REQUEST request not valid 30 | */ 31 | int parse_request_line(buffer_t *b, parse_archive *ar) { 32 | char ch; 33 | char *p; 34 | for (p = ar->next_parse_pos; p < buffer_end(b); p++) { 35 | ch = *p; 36 | switch (ar->state) { 37 | case S_RL_BEGIN: 38 | switch (ch) { 39 | case 'a' ... 'z': 40 | case 'A' ... 'Z': 41 | /* save current pos, which is METHOD beginning */ 42 | ar->method_begin = p; 43 | ar->state = S_RL_METHOD; 44 | break; 45 | default: 46 | return INVALID_REQUEST; 47 | } // end S_RL_BEGIN 48 | 49 | case S_RL_METHOD: 50 | switch (ch) { 51 | case 'a' ... 'z': 52 | case 'A' ... 'Z': 53 | break; 54 | case ' ': { 55 | ar->method = parse_method(ar->method_begin, p); 56 | if (ar->method == HTTP_INVALID) 57 | return INVALID_REQUEST; 58 | ar->state = S_RL_SP_BEFORE_URL; 59 | break; 60 | default: 61 | return INVALID_REQUEST; 62 | } 63 | } // end S_RL_METHOD 64 | break; 65 | case S_RL_SP_BEFORE_URL: 66 | switch (ch) { 67 | case ' ': 68 | case '\t': /* ease parser, '\t' is also considered valid */ 69 | break; 70 | case '\r': 71 | case '\n': 72 | return INVALID_REQUEST; 73 | default: 74 | ar->state = S_RL_URL; 75 | ar->url_begin = p; 76 | } 77 | break; 78 | 79 | case S_RL_URL: 80 | switch (ch) { 81 | case ' ': 82 | case '\t': 83 | // assume url part has been received completely 84 | ar->state = S_RL_SP_BEFORE_VERSION; 85 | int url_status = parse_url(ar->url_begin, p, ar); 86 | if (url_status) 87 | return url_status; 88 | break; 89 | case '\r': 90 | case '\n': 91 | return INVALID_REQUEST; 92 | default: 93 | break; 94 | } // end S_RL_URL 95 | break; 96 | case S_RL_SP_BEFORE_VERSION: 97 | switch (ch) { 98 | case ' ': 99 | case '\t': 100 | break; 101 | case 'H': 102 | case 'h': 103 | ar->state = S_RL_VERSION_H; 104 | break; 105 | default: 106 | return INVALID_REQUEST; 107 | } // end S_RL_SP_BEFORE_RL_VERSION 108 | break; 109 | case S_RL_VERSION_H: 110 | switch (ch) { 111 | case 'T': 112 | case 't': 113 | ar->state = S_RL_VERSION_HT; 114 | break; 115 | default: 116 | return INVALID_REQUEST; 117 | } // end S_RL_VERSION_H 118 | break; 119 | case S_RL_VERSION_HT: 120 | switch (ch) { 121 | case 'T': 122 | case 't': 123 | ar->state = S_RL_VERSION_HTT; 124 | break; 125 | default: 126 | return INVALID_REQUEST; 127 | } // end S_RL_VERSION_HT 128 | break; 129 | case S_RL_VERSION_HTT: 130 | switch (ch) { 131 | case 'P': 132 | case 'p': 133 | ar->state = S_RL_VERSION_HTTP; 134 | break; 135 | default: 136 | return INVALID_REQUEST; 137 | } // end S_RL_VERSION_HTT 138 | break; 139 | case S_RL_VERSION_HTTP: 140 | switch (ch) { 141 | case '/': 142 | ar->state = S_RL_VERSION_HTTP_SLASH; 143 | break; 144 | default: 145 | return INVALID_REQUEST; 146 | } // end S_RL_VERSION_HTTP 147 | break; 148 | case S_RL_VERSION_HTTP_SLASH: 149 | switch (ch) { 150 | case '0' ... '9': 151 | ar->version.http_major = ar->version.http_major * 10 + ch - '0'; 152 | ar->state = S_RL_VERSION_MAJOR; 153 | break; 154 | default: 155 | return INVALID_REQUEST; 156 | } // end S_RL_VERSION_HTTP_SLASH 157 | break; 158 | case S_RL_VERSION_MAJOR: 159 | switch (ch) { 160 | case '0' ... '9': 161 | ar->version.http_major = ar->version.http_major * 10 + ch - '0'; 162 | if (ar->version.http_major > 1) 163 | return INVALID_REQUEST; 164 | break; 165 | case '.': 166 | ar->state = S_RL_VERSION_DOT; 167 | break; 168 | default: 169 | return INVALID_REQUEST; 170 | } // end S_RL_VERSION_MAJOR 171 | break; 172 | case S_RL_VERSION_DOT: 173 | switch (ch) { 174 | case '0' ... '9': 175 | ar->version.http_minor = ar->version.http_minor * 10 + ch - '0'; 176 | ar->state = S_RL_VERSION_MINOR; 177 | break; 178 | default: 179 | return INVALID_REQUEST; 180 | } // end S_RL_VERSION_DOT 181 | break; 182 | case S_RL_VERSION_MINOR: 183 | switch (ch) { 184 | case '0' ... '9': 185 | ar->version.http_minor = ar->version.http_minor * 10 + ch - '0'; 186 | if (ar->version.http_minor > 1) 187 | return INVALID_REQUEST; 188 | break; 189 | case '\r': 190 | ar->state = S_RL_CR_AFTER_VERSION; 191 | break; 192 | default: 193 | return INVALID_REQUEST; 194 | } // end S_RL_VERSION_MINOR 195 | break; 196 | case S_RL_CR_AFTER_VERSION: 197 | switch (ch) { 198 | case '\n': 199 | ar->state = S_RL_LF_AFTER_VERSION; 200 | /* parse request line done*/ 201 | goto done; 202 | default: 203 | return INVALID_REQUEST; 204 | } // end S_RL_CR_AFTER_VERSION 205 | break; 206 | } // end switch(state) 207 | } // end for 208 | ar->next_parse_pos = buffer_end(b); 209 | return AGAIN; 210 | done:; 211 | ar->next_parse_pos = p + 1; 212 | ar->state = S_HD_BEGIN; 213 | return OK; 214 | } 215 | 216 | /* parse header line */ 217 | /** 218 | * @return 219 | * OK: one header line has been parsed 220 | * AGAIN: parse to the end of buffer, but no complete header 221 | * INVALID_REQUEST request not valid 222 | * CRLF_LINE: `\r\n`, which means all headers have been parsed 223 | * 224 | */ 225 | int parse_header_line(buffer_t *b, parse_archive *ar) { 226 | char ch, *p; 227 | // NOTE: isCRLF_LINE must be an attribute of ar, cannot be a local variable. 228 | // see the change in fix commit. 229 | // bool isCRLF_LINE = TRUE; 230 | for (p = ar->next_parse_pos; p < buffer_end(b); p++) { 231 | ch = *p; 232 | switch (ar->state) { 233 | case S_HD_BEGIN: 234 | switch (ch) { 235 | case 'A' ... 'Z': 236 | case 'a' ... 'z': 237 | case '0' ... '9': 238 | case '-': 239 | ar->state = S_HD_NAME; 240 | ar->header_line_begin = p; 241 | ar->isCRLF_LINE = FALSE; 242 | break; 243 | case '\r': 244 | ar->state = S_HD_CR_AFTER_VAL; 245 | ar->isCRLF_LINE = TRUE; 246 | break; 247 | case ' ': 248 | case '\t': 249 | break; 250 | default: 251 | return INVALID_REQUEST; 252 | } 253 | break; 254 | 255 | case S_HD_NAME: 256 | switch (ch) { 257 | case 'A' ... 'Z': 258 | case 'a' ... 'z': 259 | case '0' ... '9': 260 | case '-': 261 | break; 262 | case ':': 263 | ar->state = S_HD_COLON; 264 | ar->header_colon_pos = p; 265 | break; 266 | default: 267 | return INVALID_REQUEST; 268 | } 269 | break; 270 | 271 | case S_HD_COLON: 272 | switch (ch) { 273 | case ' ': 274 | case '\t': 275 | ar->state = S_HD_SP_BEFORE_VAL; 276 | break; 277 | case '\r': 278 | case '\n': 279 | return INVALID_REQUEST; 280 | default: 281 | ar->state = S_HD_VAL; 282 | ar->header_val_begin = p; 283 | break; 284 | } 285 | break; 286 | 287 | case S_HD_SP_BEFORE_VAL: 288 | switch (ch) { 289 | case ' ': 290 | case '\t': 291 | break; 292 | case '\r': 293 | case '\n': 294 | return INVALID_REQUEST; 295 | default: 296 | ar->state = S_HD_VAL; 297 | ar->header_val_begin = p; 298 | break; 299 | } 300 | break; 301 | 302 | case S_HD_VAL: 303 | switch (ch) { 304 | case '\r': 305 | ar->header_val_end = p; 306 | ar->state = S_HD_CR_AFTER_VAL; 307 | break; 308 | case '\n': 309 | ar->state = S_HD_LF_AFTER_VAL; 310 | break; 311 | default: 312 | break; 313 | } 314 | break; 315 | 316 | case S_HD_CR_AFTER_VAL: 317 | switch (ch) { 318 | case '\n': 319 | ar->state = S_HD_LF_AFTER_VAL; 320 | goto done; 321 | default: 322 | return INVALID_REQUEST; 323 | } 324 | break; 325 | } // end switch state 326 | } // end for 327 | ar->next_parse_pos = buffer_end(b); 328 | return AGAIN; 329 | done:; 330 | ar->next_parse_pos = p + 1; 331 | ar->state = S_HD_BEGIN; 332 | ar->num_headers++; 333 | 334 | /* put header name and val into header[2] */ 335 | HEADER_SET(&ar->header[0], ar->header_line_begin, ar->header_colon_pos); 336 | HEADER_SET(&ar->header[1], ar->header_val_begin, ar->header_val_end); 337 | return ar->isCRLF_LINE ? CRLF_LINE : OK; 338 | } 339 | 340 | static int parse_method(char *begin, char *end) { 341 | int len = end - begin; 342 | switch (len) { 343 | case 3: 344 | if (STR3_EQ(begin, "GET")) { 345 | return HTTP_GET; 346 | } else if (STR3_EQ(begin, "PUT")) { 347 | return HTTP_PUT; 348 | } else { 349 | return HTTP_INVALID; 350 | } 351 | break; 352 | case 4: 353 | if (STR4_EQ(begin, "POST")) { 354 | return HTTP_POST; 355 | } else if (STR4_EQ(begin, "HEAD")) { 356 | return HTTP_HEAD; 357 | } else { 358 | return HTTP_INVALID; 359 | } 360 | break; 361 | case 6: 362 | if (STR6_EQ(begin, "DELETE")) { 363 | return HTTP_DELETE; 364 | } else { 365 | return HTTP_INVALID; 366 | } 367 | break; 368 | default: 369 | return HTTP_INVALID; 370 | } 371 | return HTTP_INVALID; 372 | } 373 | 374 | /** 375 | * Some Samples: 376 | * 377 | * /abc/def/ 378 | * /unp.pdf 379 | * /unp.pdf/ `dir` 380 | * /abc.def/set?name=chen&val=newbie 381 | * /video/life.of.pi.BlueRay.rmvb 382 | * /video/life.of.pi.BlueRay `dir` 383 | */ 384 | /* simple parse url */ 385 | static int parse_url(char *begin, char *end, parse_archive *ar) { 386 | ar->request_url_string.str = begin; 387 | ar->request_url_string.len = end - begin; 388 | assert(ar->request_url_string.len >= 0); 389 | 390 | int curr_state = S_URL_BEGIN; 391 | 392 | char ch; 393 | char *p = begin; 394 | for (; p != end + 1; p++) { 395 | ch = *p; 396 | switch (curr_state) { 397 | case S_URL_BEGIN: 398 | switch (ch) { 399 | case '/': 400 | curr_state = S_URL_ABS_PATH; 401 | break; 402 | default: 403 | return ERROR; 404 | } 405 | break; 406 | 407 | case S_URL_ABS_PATH: 408 | switch (ch) { 409 | case ' ': 410 | ar->url.abs_path.str = begin; 411 | ar->url.abs_path.len = p - begin; 412 | 413 | ar->url.query_string.str = p; 414 | ar->url.query_string.len = 0; 415 | curr_state = S_URL_END; 416 | break; 417 | case '?': 418 | ar->url.abs_path.str = begin; 419 | ar->url.abs_path.len = p - begin; 420 | begin = p + 1; 421 | curr_state = S_URL_QUERY; 422 | break; 423 | default: 424 | break; 425 | } 426 | break; 427 | 428 | case S_URL_QUERY: 429 | switch (ch) { 430 | case ' ': 431 | ar->url.query_string.str = begin; 432 | ar->url.query_string.len = p - begin; 433 | curr_state = S_URL_END; 434 | default: 435 | break; 436 | } 437 | break; 438 | 439 | case S_URL_END: 440 | goto parse_extension; 441 | } // end switch(curr_state) 442 | } // end for 443 | 444 | parse_extension:; 445 | // directory extension will be corrected in `request_handle_request_line` 446 | char *abs_path_end = ar->url.abs_path.str + ar->url.abs_path.len; 447 | 448 | for (p = abs_path_end; p != ar->url.abs_path.str; p--) { 449 | if (*p == '.') { 450 | ar->url.mime_extension.str = p + 1; 451 | ar->url.mime_extension.len = abs_path_end - p - 1; 452 | break; 453 | } else if (*p == '/') 454 | break; 455 | } 456 | 457 | return OK; 458 | } 459 | 460 | int parse_header_body_identity(buffer_t *b, parse_archive *ar) { 461 | if (ar->content_length <= 0) 462 | return OK; 463 | // not that complicated, using `next_parse_pos` to indicate where to parse 464 | size_t received = buffer_end(b) - ar->next_parse_pos; 465 | ar->body_received += received; 466 | #ifndef NDEBUG 467 | printf("%s %d\n", __FUNCTION__, __LINE__); 468 | printf("%s %lu\n", ar->next_parse_pos, received); 469 | #endif 470 | ar->next_parse_pos = buffer_end(b); 471 | 472 | if (ar->body_received >= ar->content_length) { // full data recv 473 | return OK; 474 | } 475 | return AGAIN; // will conitinue to recv until full data recv or conn timeout 476 | } 477 | -------------------------------------------------------------------------------- /src/http_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef _PARSER_H__ 2 | #define _PARSER_H__ 3 | 4 | #include "buffer.h" 5 | #include "misc.h" 6 | #include "ssstr.h" 7 | #include 8 | 9 | /* RFC2616 */ 10 | /* Ref: https://www.w3.org/Protocols/rfc2616/rfc2616.html */ 11 | /* This is a simple implementation */ 12 | 13 | /** 14 | * 15 | * Request = Request-Line ; Section 5.1 16 | (( general-header ; Section 4.5 17 | | request-header ; Section 5.3 18 | | entity-header ) CRLF) ; Section 7.1 19 | CRLF 20 | [ message-body ] ; Section 4.3 21 | */ 22 | 23 | /** 24 | * Request-Line = Method SP Request-URI SP HTTP-Version CRLF 25 | */ 26 | 27 | /** 28 | * Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2 29 | * http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]] 30 | */ 31 | 32 | /** 33 | * request-header = Accept ; Section 14.1 34 | | Accept-Charset ; Section 14.2 35 | | Accept-Encoding ; Section 14.3 36 | | Accept-Language ; Section 14.4 37 | | Authorization ; Section 14.8 38 | | Expect ; Section 14.20 39 | | From ; Section 14.22 40 | | Host ; Section 14.23 41 | | If-Match ; Section 14.24 42 | | If-Modified-Since ; Section 14.25 43 | | If-None-Match ; Section 14.26 44 | | If-Range ; Section 14.27 45 | | If-Unmodified-Since ; Section 14.28 46 | | Max-Forwards ; Section 14.31 47 | | Proxy-Authorization ; Section 14.34 48 | | Range ; Section 14.35 49 | | Referer ; Section 14.36 50 | | TE ; Section 14.39 51 | | User-Agent ; Section 14.43 52 | */ 53 | 54 | #define INVALID_REQUEST (-1) 55 | #define CRLF_LINE (2) 56 | 57 | #define MAX_ELEMENT_SIZE (2048) 58 | 59 | /* basic http method */ 60 | typedef enum { 61 | HTTP_DELETE, 62 | HTTP_GET, 63 | HTTP_HEAD, 64 | HTTP_POST, 65 | HTTP_PUT, 66 | HTTP_INVALID, 67 | } http_method; 68 | 69 | /* HTTP protocol version */ 70 | typedef struct { 71 | unsigned short http_major; 72 | unsigned short http_minor; 73 | } http_version; 74 | 75 | /* Request URL */ 76 | typedef struct { 77 | ssstr_t abs_path; 78 | ssstr_t query_string; 79 | ssstr_t mime_extension; 80 | } req_url; 81 | 82 | /* parser state used in fsm */ 83 | typedef enum { 84 | /* request line states */ 85 | S_RL_BEGIN = 0, 86 | S_RL_METHOD, 87 | S_RL_SP_BEFORE_URL, 88 | S_RL_URL, 89 | S_RL_SP_BEFORE_VERSION, 90 | S_RL_VERSION_H, 91 | S_RL_VERSION_HT, 92 | S_RL_VERSION_HTT, 93 | S_RL_VERSION_HTTP, 94 | S_RL_VERSION_HTTP_SLASH, 95 | S_RL_VERSION_MAJOR, 96 | S_RL_VERSION_DOT, 97 | S_RL_VERSION_MINOR, 98 | S_RL_CR_AFTER_VERSION, 99 | S_RL_LF_AFTER_VERSION, 100 | 101 | /* header states */ 102 | S_HD_BEGIN, 103 | S_HD_NAME, 104 | S_HD_COLON, 105 | S_HD_SP_BEFORE_VAL, 106 | S_HD_VAL, 107 | S_HD_CR_AFTER_VAL, 108 | S_HD_LF_AFTER_VAL, 109 | 110 | /* url states */ 111 | S_URL_BEGIN, 112 | S_URL_ABS_PATH, 113 | S_URL_QUERY, 114 | S_URL_END, 115 | } parser_state; 116 | 117 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding 118 | typedef enum { 119 | TE_IDENTITY = 0, 120 | TE_CHUNKED, 121 | TE_COMPRESS, 122 | TE_DEFLATE, 123 | TE_GZIP, 124 | } transfer_encoding_t; 125 | 126 | /* some of the request headers we may parse */ 127 | typedef struct { 128 | ssstr_t cache_control; 129 | ssstr_t connection; 130 | ssstr_t date; 131 | ssstr_t transfer_encoding; 132 | ssstr_t accept; 133 | ssstr_t accept_charset; 134 | ssstr_t accept_encoding; 135 | ssstr_t accept_language; 136 | ssstr_t cookie; 137 | ssstr_t host; 138 | ssstr_t if_modified_since; 139 | ssstr_t if_unmodified_since; 140 | ssstr_t max_forwards; 141 | ssstr_t range; 142 | ssstr_t referer; 143 | ssstr_t user_agent; 144 | ssstr_t content_length; 145 | } request_headers_t; 146 | 147 | typedef struct { 148 | /* parsed request line result */ 149 | http_method method; 150 | http_version version; 151 | ssstr_t request_url_string; 152 | req_url url; 153 | 154 | /* parsed header lines result */ 155 | bool keep_alive; /* connection keep alive */ 156 | int content_length; /* request body content_length */ 157 | int transfer_encoding; /* affect body recv strategy */ 158 | request_headers_t req_headers; 159 | 160 | int num_headers; 161 | ssstr_t header[2]; /* store header every time `parse_header_line` */ 162 | 163 | /* preserve buffer_t state, so when recv new data, we can keep parsing */ 164 | char *next_parse_pos; /* parser position in buffer_t */ 165 | int state; /* parser state */ 166 | 167 | /* private members, do not modify !!! */ 168 | char *method_begin; 169 | char *url_begin; 170 | char *header_line_begin; 171 | char *header_colon_pos; 172 | char *header_val_begin; 173 | char *header_val_end; 174 | size_t body_received; 175 | int buffer_sent; 176 | bool isCRLF_LINE; 177 | bool response_done; 178 | bool err_req; 179 | } parse_archive; 180 | 181 | static inline void parse_archive_init(parse_archive *ar, buffer_t *b) { 182 | memset(ar, 0, sizeof(parse_archive)); 183 | ar->next_parse_pos = b->buf; 184 | ar->isCRLF_LINE = TRUE; 185 | ar->content_length = -1; // no Content-Length header 186 | } 187 | 188 | extern int parse_request_line(buffer_t *b, parse_archive *ar); 189 | extern int parse_header_line(buffer_t *b, parse_archive *ar); 190 | extern int parse_header_body_identity(buffer_t *b, parse_archive *ar); 191 | 192 | #endif 193 | -------------------------------------------------------------------------------- /src/lotos_epoll.c: -------------------------------------------------------------------------------- 1 | #include "lotos_epoll.h" 2 | #include "connection.h" 3 | #include "misc.h" 4 | #include 5 | 6 | struct epoll_event lotos_events[MAX_EVENTS]; 7 | 8 | int lotos_epoll_create(int flags) { return epoll_create1(flags); } 9 | 10 | int lotos_epoll_add(int epoll_fd, connection_t *c, uint32_t events, 11 | struct epoll_event *pev) { 12 | FILL_EPOLL_EVENT(pev, c, events); 13 | return epoll_ctl(epoll_fd, EPOLL_CTL_ADD, c->fd, pev); 14 | } 15 | 16 | int lotos_epoll_mod(int epoll_fd, connection_t *c, uint32_t events, 17 | struct epoll_event *pev) { 18 | FILL_EPOLL_EVENT(pev, c, events); 19 | return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, pev); 20 | } 21 | 22 | int lotos_epoll_del(int epoll_fd, connection_t *c, uint32_t events, 23 | struct epoll_event *pev) { 24 | (void)pev; /* Unused. Silent compiler warning. */ 25 | return epoll_ctl(epoll_fd, EPOLL_CTL_DEL, c->fd, NULL); 26 | } 27 | 28 | inline int lotos_epoll_wait(int epoll_fd, struct epoll_event *events, 29 | int max_events, int timeout) { 30 | return epoll_wait(epoll_fd, events, max_events, timeout); 31 | } 32 | -------------------------------------------------------------------------------- /src/lotos_epoll.h: -------------------------------------------------------------------------------- 1 | #ifndef _LOTOS_EPOLL_H__ 2 | #define _LOTOS_EPOLL_H__ 3 | #include "connection.h" 4 | #include "misc.h" 5 | #include 6 | 7 | #define MAX_EVENTS (10240) 8 | #define FILL_EPOLL_EVENT(pev, pconn, e_events) \ 9 | do { \ 10 | struct epoll_event *ev = pev; \ 11 | ev->data.ptr = pconn; \ 12 | ev->events = e_events; \ 13 | } while (0) 14 | #define CONN_IS_IN(c) ((c)->event.events & EPOLLIN) 15 | #define CONN_IS_OUT(c) ((c)->event.events & EPOLLOUT) 16 | 17 | extern struct epoll_event lotos_events[MAX_EVENTS]; // global 18 | 19 | extern int lotos_epoll_create(int flags); 20 | extern int lotos_epoll_add(int epoll_fd, connection_t *restrict c, 21 | uint32_t events, struct epoll_event *pev); 22 | extern int lotos_epoll_mod(int epoll_fd, connection_t *restrict c, 23 | uint32_t events, struct epoll_event *pev); 24 | extern int lotos_epoll_del(int epoll_fd, connection_t *restrict c, 25 | uint32_t events, struct epoll_event *pev); 26 | extern int lotos_epoll_wait(int epoll_fd, struct epoll_event *events, 27 | int max_events, int timeout); 28 | 29 | static inline int connection_enable_in(int epoll_fd, connection_t *c) { 30 | if (CONN_IS_IN(c)) 31 | return OK; 32 | c->event.events |= EPOLLIN; 33 | return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event); 34 | } 35 | 36 | static inline int connection_disable_in(int epoll_fd, connection_t *c) { 37 | if (!CONN_IS_IN(c)) 38 | return OK; 39 | c->event.events &= ~EPOLLIN; 40 | return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event); 41 | } 42 | 43 | static inline int connection_enable_out(int epoll_fd, connection_t *c) { 44 | if (CONN_IS_OUT(c)) 45 | return OK; 46 | c->event.events |= EPOLLOUT; 47 | return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event); 48 | } 49 | 50 | static inline int connection_disable_out(int epoll_fd, connection_t *c) { 51 | if (!CONN_IS_OUT(c)) 52 | return OK; 53 | c->event.events &= ~EPOLLOUT; 54 | return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event); 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "lotos_epoll.h" 2 | #include "misc.h" 3 | #include "server.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static void usage(const char *executable) { 14 | printf("Usage: %s -r html_root_dir [-p port] " 15 | "[-t timeout] [-w worker_num] [-d (debug mode)]\n", 16 | executable); 17 | } 18 | 19 | int main(int argc, char *argv[]) { 20 | if (argc < 2 || config_parse(argc, argv) != OK) { 21 | usage(argv[0]); 22 | exit(ERROR); 23 | } 24 | 25 | if (server_config.debug) { 26 | goto work; 27 | } 28 | 29 | int nworker = 0; 30 | while (TRUE) { 31 | if (nworker >= server_config.worker) { 32 | int status; 33 | waitpid(-1, &status, 0); // wait all children 34 | if (WIFEXITED(status)) 35 | raise(SIGINT); 36 | lotos_log(LOG_ERR, "a worker exit, please restart..."); 37 | raise(SIGINT); 38 | } 39 | pid_t pid = fork(); 40 | ABORT_ON(pid == -1, "fork"); 41 | if (pid == 0) { // child 42 | break; // child ends up in loop and directly goto `work` 43 | } 44 | nworker++; 45 | } 46 | 47 | work:; 48 | int nfds; 49 | int i; 50 | 51 | server_setup(server_config.port); 52 | 53 | while (TRUE) { 54 | /** 55 | * nfds is number of file descriptors ready for the requested I/O or zero 56 | * if timeout 57 | */ 58 | nfds = lotos_epoll_wait(epoll_fd, lotos_events, MAX_EVENTS, 20); 59 | if (nfds == ERROR) { 60 | // if not caused by signal, cannot recover 61 | ERR_ON(errno != EINTR, "lotos_epoll_wait"); 62 | } 63 | 64 | for (i = 0; i < nfds; i++) { 65 | struct epoll_event *curr_event = lotos_events + i; 66 | int fd = *((int *)(curr_event->data.ptr)); 67 | if (fd == listen_fd) { 68 | // accept connection 69 | server_accept(listen_fd); 70 | } else { 71 | // handle connection 72 | connection_t *c = curr_event->data.ptr; 73 | int status; 74 | assert(c != NULL); 75 | 76 | if (connecion_is_expired(c)) 77 | continue; 78 | 79 | if (curr_event->events & EPOLLIN) { 80 | // recv 81 | status = request_handle(c); 82 | if (status == ERROR) 83 | connecion_set_expired(c); 84 | else 85 | connecion_set_reactivated(c); 86 | } 87 | if (curr_event->events & EPOLLOUT) { 88 | // send 89 | status = response_handle(c); 90 | if (status == ERROR) 91 | connecion_set_expired(c); 92 | else 93 | connecion_set_reactivated(c); 94 | } 95 | } // else 96 | } // for loop 97 | /* prune expired connections */ 98 | connection_prune(); 99 | } // while 100 | 101 | close(epoll_fd); 102 | server_shutdown(); 103 | return OK; 104 | } 105 | -------------------------------------------------------------------------------- /src/mem_pool.c: -------------------------------------------------------------------------------- 1 | #include "mem_pool.h" 2 | #include "misc.h" 3 | #include 4 | #include 5 | 6 | static inline void *last_block(mem_pool_t *pool) { 7 | return ((char *)pool->blocks) + (pool->block_num - 1) * pool->block_size; 8 | } 9 | 10 | int pool_create(mem_pool_t *pool, size_t nmemb, size_t size) { 11 | memset(pool, 0, sizeof(mem_pool_t)); 12 | 13 | pool->block_num = nmemb; 14 | pool->block_size = size > sizeof(void *) ? size : sizeof(void *); 15 | 16 | pool->blocks = calloc(pool->block_num, pool->block_size); 17 | if (pool->blocks == NULL) { 18 | lotos_log(LOG_ERR, "memory pool creating failed...exit"); 19 | exit(1); 20 | } 21 | 22 | pool->next = pool->blocks; 23 | 24 | /** 25 | * similar to what we do in ACM, pseudo linkedlist 26 | * 27 | * 28 | * suppose block_size = 30 and block_num = 4, the `blocks` memory: 29 | * 0 30 60 90 120 30 | * ——————————————————————————————————————————— 31 | * |30 |60 |90 |NULL | 32 | * ——————————————————————————————————————————— 33 | * 34 | */ 35 | char *char_ptr = pool->blocks; 36 | void **ptr_ptr; 37 | for (; char_ptr < (char *)last_block(pool);) { 38 | ptr_ptr = (void **)char_ptr; 39 | *ptr_ptr = (char_ptr += pool->block_size); 40 | } 41 | ptr_ptr = (void **)char_ptr; 42 | *ptr_ptr = NULL; 43 | return OK; 44 | } 45 | 46 | void pool_destroy(mem_pool_t *pool) { 47 | if (pool->blocks) { 48 | free(pool->blocks); 49 | pool->blocks = NULL; 50 | } 51 | } 52 | 53 | void *pool_alloc(mem_pool_t *pool) { 54 | if (pool->block_allocated >= pool->block_num) 55 | return NULL; 56 | 57 | void *cur = pool->next; 58 | void **ptr_ptr = cur; 59 | if (ptr_ptr) { 60 | pool->next = *ptr_ptr; 61 | pool->block_allocated++; 62 | } 63 | return cur; 64 | } 65 | 66 | void pool_free(mem_pool_t *pool, void *ptr) { 67 | if (ptr == NULL) 68 | return; 69 | 70 | void **ptr_ptr = ptr; 71 | *ptr_ptr = pool->next; 72 | pool->next = ptr; 73 | pool->block_allocated--; 74 | } 75 | -------------------------------------------------------------------------------- /src/mem_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef _MEM_POOL_H__ 2 | #define _MEM_POOL_H__ 3 | #include 4 | 5 | typedef struct { 6 | size_t block_num; /* num of blocks expected to alloc */ 7 | size_t block_size; /* size of each block */ 8 | size_t block_allocated; /* num of allocated blocks */ 9 | void *next; /* next block to be allocated */ 10 | void *blocks; /* store data */ 11 | } mem_pool_t; 12 | 13 | int pool_create(mem_pool_t *pool, size_t nmemb, size_t size); 14 | void pool_destroy(mem_pool_t *pool); 15 | void *pool_alloc(mem_pool_t *pool); 16 | void pool_free(mem_pool_t *pool, void *ptr); 17 | #endif 18 | -------------------------------------------------------------------------------- /src/misc.c: -------------------------------------------------------------------------------- 1 | #include "misc.h" 2 | #include 3 | #include 4 | #include 5 | 6 | void lotos_log(int priority, const char *format, ...) { 7 | FILE *fp; 8 | const char *color_prefix = NULL; 9 | 10 | switch (priority) { 11 | case LOG_ERR: 12 | fp = stderr; 13 | color_prefix = "\033[1;31m"; 14 | break; 15 | case LOG_INFO: 16 | fp = stdout; 17 | color_prefix = "\033[1;32m"; 18 | break; 19 | default: 20 | fp = stdout; 21 | } 22 | time_t t = time(NULL); 23 | struct tm tm = *localtime(&t); 24 | fprintf(fp, "[%4d-%02d-%02d %02d:%02d:%02d] ", tm.tm_year + 1900, 25 | tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); 26 | 27 | color_prefix == NULL ? 0 : fprintf(fp, "%s", color_prefix); 28 | va_list args; 29 | va_start(args, format); 30 | vfprintf(fp, format, args); 31 | fprintf(fp, "\033[0m\n"); 32 | fflush(fp); 33 | va_end(args); 34 | } 35 | -------------------------------------------------------------------------------- /src/misc.h: -------------------------------------------------------------------------------- 1 | #ifndef _MISC_H__ 2 | #define _MISC_H__ 3 | #include 4 | #include 5 | 6 | typedef enum { TRUE = 1, FALSE = 0 } bool; 7 | 8 | #define ERROR (-1) 9 | #define OK (0) 10 | #define AGAIN (1) 11 | 12 | #define CRLF "\r\n" 13 | 14 | #define ABORT_ON(cond, msg) \ 15 | do { \ 16 | if (cond) { \ 17 | fprintf(stderr, "%s: %d: ", __FILE__, __LINE__); \ 18 | perror(msg); \ 19 | abort(); \ 20 | } \ 21 | } while (0) 22 | 23 | #define ERR_ON(cond, msg) \ 24 | do { \ 25 | if (cond) { \ 26 | fprintf(stderr, "%s: %d: ", __FILE__, __LINE__); \ 27 | perror(msg); \ 28 | } \ 29 | } while (0) 30 | 31 | enum { LOG_ERR, LOG_INFO }; 32 | 33 | extern void lotos_log(int priority, const char *format, ...); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/request.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "buffer.h" 3 | #include "connection.h" 4 | #include "dict.h" 5 | #include "http_parser.h" 6 | #include "lotos_epoll.h" 7 | #include "misc.h" 8 | #include "response.h" 9 | #include "server.h" 10 | #include "ssstr.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | static int request_handle_request_line(request_t *r); 24 | static int request_handle_headers(request_t *r); 25 | static int request_handle_body(request_t *r); 26 | 27 | static int response_handle_send_buffer(struct request *r); 28 | static int response_handle_send_file(struct request *r); 29 | static int response_assemble_buffer(struct request *r); 30 | static int response_assemble_err_buffer(struct request *r, int status_code); 31 | 32 | typedef int (*header_handle_method)(request_t *, size_t); 33 | /* handlers for specific http headers */ 34 | static int request_handle_hd_base(request_t *r, size_t offset); 35 | static int request_handle_hd_connection(request_t *r, size_t offset); 36 | static int request_handle_hd_content_length(request_t *r, size_t offset); 37 | static int request_handle_hd_transfer_encoding(request_t *r, size_t offset); 38 | 39 | typedef struct { 40 | ssstr_t hd; 41 | header_handle_method func; 42 | size_t offset; 43 | } header_func; 44 | 45 | #define XX(hd, hd_mn, func) \ 46 | { SSSTR(hd), func, offsetof(request_headers_t, hd_mn) } 47 | 48 | static header_func hf_list[] = { 49 | XX("accept", accept, request_handle_hd_base), 50 | XX("accept-charset", accept_charset, request_handle_hd_base), 51 | XX("accept-encoding", accept_encoding, request_handle_hd_base), 52 | XX("accept-language", accept_language, request_handle_hd_base), 53 | XX("cache-control", cache_control, request_handle_hd_base), 54 | XX("content-length", content_length, request_handle_hd_content_length), 55 | XX("connection", connection, request_handle_hd_connection), 56 | XX("cookie", cookie, request_handle_hd_base), 57 | XX("date", date, request_handle_hd_base), 58 | XX("host", host, request_handle_hd_base), 59 | XX("if-modified-since", if_modified_since, request_handle_hd_base), 60 | XX("if-unmodified-since", if_unmodified_since, request_handle_hd_base), 61 | XX("max-forwards", max_forwards, request_handle_hd_base), 62 | XX("range", range, request_handle_hd_base), 63 | XX("referer", referer, request_handle_hd_base), 64 | XX("transfer-encoding", transfer_encoding, 65 | request_handle_hd_transfer_encoding), 66 | XX("user-agent", user_agent, request_handle_hd_base), 67 | }; 68 | #undef XX 69 | 70 | dict_t header_handler_dict; 71 | 72 | void header_handler_dict_init() { 73 | dict_init(&header_handler_dict); 74 | size_t nsize = sizeof(hf_list) / sizeof(hf_list[0]); 75 | int i; 76 | for (i = 0; i < nsize; i++) { 77 | dict_put(&header_handler_dict, &hf_list[i].hd, (void *)&hf_list[i]); 78 | } 79 | } 80 | 81 | void header_handler_dict_free() { dict_free(&header_handler_dict); } 82 | 83 | int request_init(request_t *r, connection_t *c) { 84 | assert(r != NULL); 85 | memset(r, 0, sizeof(request_t)); 86 | r->c = c; 87 | r->ib = buffer_init(); 88 | r->ob = buffer_init(); 89 | if (r->ib == NULL || r->ob == NULL) 90 | return ERROR; 91 | parse_archive_init(&r->par, r->ib); 92 | r->resource_fd = -1; 93 | r->status_code = 200; 94 | 95 | r->req_handler = request_handle_request_line; 96 | r->res_handler = response_handle_send_buffer; 97 | return OK; 98 | } 99 | 100 | /* when connection keep-alive, reuse request_t in connection_t */ 101 | int request_reset(request_t *r) { 102 | buffer_t *ib = r->ib; 103 | buffer_t *ob = r->ob; 104 | connection_t *c = r->c; 105 | 106 | memset(r, 0, sizeof(request_t)); 107 | r->ib = ib; 108 | r->ob = ob; 109 | r->c = c; 110 | buffer_clear(ib); 111 | buffer_clear(ob); 112 | parse_archive_init(&r->par, r->ib); 113 | r->resource_fd = -1; 114 | r->status_code = 200; 115 | 116 | r->req_handler = request_handle_request_line; 117 | r->res_handler = response_handle_send_buffer; 118 | return OK; 119 | } 120 | 121 | static int request_recv(request_t *r) { 122 | char buf[BUFSIZ]; 123 | int len; 124 | while (TRUE) { 125 | len = recv(r->c->fd, buf, sizeof(buf), 0); 126 | // https://stackoverflow.com/questions/2416944/can-read-function-on-a-connected-socket-return-zero-bytes 127 | if (len == 0) { /* if client close, will read EOF */ 128 | return OK; 129 | } 130 | if (len == ERROR) { 131 | if (errno != EAGAIN) { 132 | lotos_log(LOG_ERR, "recv: %s", strerror(errno)); 133 | return ERROR; 134 | } else 135 | return AGAIN; /* does not have data now */ 136 | } 137 | buffer_cat(r->ib, buf, len); /* append new data to buffer */ 138 | } 139 | return AGAIN; 140 | } 141 | 142 | int request_handle(connection_t *c) { 143 | request_t *r = &c->req; 144 | int status = request_recv(r); 145 | if (status == ERROR || status == OK) /* error or client close */ 146 | return ERROR; 147 | /** 148 | * parse request main process: 149 | * 150 | * do { 151 | * status = parse_request_line() 152 | * status = parse_header() 153 | * *status = parse_body() 154 | * } while (status != error && !parse_done) 155 | * 156 | * if status == AGAIN, then exit `request_handle` and try to recv in the next 157 | * loop. 158 | * if status == ERROR, then exit `request_handle` and expire this connection 159 | * 160 | */ 161 | do { 162 | status = r->req_handler(r); 163 | } while (r->req_handler != NULL && status == OK); 164 | 165 | return status; 166 | } 167 | 168 | static int request_handle_request_line(request_t *r) { 169 | int status; 170 | status = parse_request_line(r->ib, &r->par); 171 | if (status == AGAIN) // not a complete request line 172 | return AGAIN; 173 | if (status != OK) { // INVALID_REQUEST 174 | // send error response to client 175 | return response_assemble_err_buffer(r, 400); 176 | } 177 | // status = OK now 178 | parse_archive *ar = &(r->par); 179 | /* check http version */ 180 | if (ar->version.http_major > 1 || ar->version.http_minor > 1) { 181 | // send 505 error response to client 182 | return response_assemble_err_buffer(r, 505); 183 | } 184 | ar->keep_alive = (ar->version.http_major == 1 && ar->version.http_minor == 1); 185 | 186 | // make `relative_path` a c-style string, really ugly.... 187 | char *p = ar->url.abs_path.str; 188 | while (*p != ' ' && *p != '?') 189 | p++; 190 | *p = '\0'; 191 | 192 | /* check abs_path */ 193 | const char *relative_path = NULL; 194 | relative_path = ar->url.abs_path.len == 1 && ar->url.abs_path.str[0] == '/' 195 | ? "./" 196 | : ar->url.abs_path.str + 1; 197 | 198 | int fd = openat(server_config.rootdir_fd, relative_path, O_RDONLY); 199 | if (fd == ERROR) { 200 | // send 404 error response to client 201 | return response_assemble_err_buffer(r, 404); 202 | } 203 | struct stat st; 204 | fstat(fd, &st); 205 | 206 | if (S_ISDIR(st.st_mode)) { // substitute dir to index.html 207 | // fd is a dir fildes 208 | int html_fd = openat(fd, "index.html", O_RDONLY); 209 | close(fd); 210 | if (fd == ERROR) { 211 | // send 404 error response to client 212 | return response_assemble_err_buffer(r, 404); 213 | } 214 | fd = html_fd; 215 | fstat(fd, &st); 216 | ssstr_set(&ar->url.mime_extension, "html"); 217 | } 218 | r->resource_fd = fd; 219 | r->resource_size = st.st_size; 220 | r->req_handler = request_handle_headers; 221 | return OK; 222 | } 223 | 224 | static int request_handle_headers(request_t *r) { 225 | int status; 226 | buffer_t *b = r->ib; 227 | parse_archive *ar = &r->par; 228 | while (TRUE) { 229 | status = parse_header_line(b, ar); 230 | switch (status) { 231 | /* not a complete header */ 232 | case AGAIN: 233 | return AGAIN; 234 | /* header invalid */ 235 | case INVALID_REQUEST: 236 | // send error response to client 237 | return response_assemble_err_buffer(r, 400); 238 | /* all headers completed */ 239 | case CRLF_LINE: 240 | goto header_done; 241 | /* a header completed */ 242 | case OK: 243 | ssstr_tolower(&r->par.header[0]); 244 | 245 | // handle header individually 246 | header_func *hf = dict_get(&header_handler_dict, &r->par.header[0], NULL); 247 | if (hf == NULL) 248 | break; 249 | header_handle_method func = hf->func; 250 | size_t offset = hf->offset; 251 | if (func != NULL) { 252 | status = func(r, offset); 253 | if (status != OK) 254 | return OK; 255 | } 256 | break; 257 | } 258 | } 259 | header_done:; 260 | r->req_handler = request_handle_body; 261 | return OK; 262 | } 263 | 264 | static int request_handle_body(request_t *r) { 265 | int status; 266 | buffer_t *b = r->ib; 267 | parse_archive *ar = &r->par; 268 | switch (r->par.transfer_encoding) { 269 | case TE_IDENTITY: 270 | status = parse_header_body_identity(b, ar); 271 | break; 272 | default: 273 | status = ERROR; 274 | break; 275 | } 276 | 277 | switch (status) { 278 | case AGAIN: 279 | return AGAIN; 280 | case OK: 281 | connection_disable_in(epoll_fd, r->c); 282 | connection_enable_out(epoll_fd, r->c); 283 | r->req_handler = NULL; // body parse done !!! no more handlers 284 | response_assemble_buffer(r); 285 | return OK; 286 | default: 287 | // send error response to client 288 | return response_assemble_err_buffer(r, 501); 289 | } 290 | return OK; 291 | } 292 | 293 | /* save header value into the proper position of parse_archive.req_headers */ 294 | int request_handle_hd_base(request_t *r, size_t offset) { 295 | parse_archive *ar = &r->par; 296 | ssstr_t *header = (ssstr_t *)(((char *)(&ar->req_headers)) + offset); 297 | *header = ar->header[1]; 298 | return OK; 299 | } 300 | 301 | int request_handle_hd_connection(request_t *r, size_t offset) { 302 | request_handle_hd_base(r, offset); 303 | ssstr_t *connection = &(r->par.req_headers.connection); 304 | if (ssstr_caseequal(connection, "keep-alive")) { 305 | r->par.keep_alive = TRUE; 306 | } else if (ssstr_caseequal(connection, "close")) { 307 | r->par.keep_alive = FALSE; 308 | } else { 309 | // send error response to client 310 | return response_assemble_err_buffer(r, 400); 311 | } 312 | return OK; 313 | } 314 | 315 | int request_handle_hd_content_length(request_t *r, size_t offset) { 316 | request_handle_hd_base(r, offset); 317 | ssstr_t *content_length = &(r->par.req_headers.content_length); 318 | int len = atoi(content_length->str); 319 | if (len <= 0) { 320 | // send error response to client 321 | return response_assemble_err_buffer(r, 400); 322 | } 323 | r->par.content_length = len; 324 | return OK; 325 | } 326 | 327 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding 328 | // https://imququ.com/post/content-encoding-header-in-http.html 329 | int request_handle_hd_transfer_encoding(request_t *r, size_t offset) { 330 | request_handle_hd_base(r, offset); 331 | ssstr_t *transfer_encoding = &(r->par.req_headers.transfer_encoding); 332 | if (ssstr_caseequal(transfer_encoding, "chunked")) { 333 | // may implement, send error response to client 334 | r->par.transfer_encoding = TE_CHUNKED; 335 | return response_assemble_err_buffer(r, 501); 336 | } else if (ssstr_caseequal(transfer_encoding, "compress")) { 337 | // send error response to client 338 | r->par.transfer_encoding = TE_COMPRESS; 339 | return response_assemble_err_buffer(r, 501); 340 | } else if (ssstr_caseequal(transfer_encoding, "deflate")) { 341 | // send error response to client 342 | r->par.transfer_encoding = TE_DEFLATE; 343 | return response_assemble_err_buffer(r, 501); 344 | } else if (ssstr_caseequal(transfer_encoding, "gzip")) { 345 | // send error response to client 346 | r->par.transfer_encoding = TE_GZIP; 347 | return response_assemble_err_buffer(r, 501); 348 | } else if (ssstr_caseequal(transfer_encoding, "identity")) { 349 | r->par.transfer_encoding = TE_IDENTITY; 350 | return response_assemble_err_buffer(r, 501); 351 | } else { 352 | // send error response to client 353 | return response_assemble_err_buffer(r, 400); 354 | } 355 | return OK; 356 | } 357 | 358 | /** 359 | * Return: 360 | * OK: all data sent 361 | * AGAIN: haven't sent all data 362 | * ERROR: error 363 | */ 364 | static int response_send(request_t *r) { 365 | int len = 0; 366 | buffer_t *b = r->ob; 367 | char *buf_beg; 368 | while (TRUE) { 369 | buf_beg = b->buf + r->par.buffer_sent; 370 | len = send(r->c->fd, buf_beg, buffer_end(b) - buf_beg, 0); 371 | if (len == 0) { 372 | buffer_clear(b); 373 | r->par.buffer_sent = 0; 374 | return OK; 375 | } else if (len < 0) { 376 | if (errno == EAGAIN) 377 | return AGAIN; 378 | lotos_log(LOG_ERR, "send: %s", strerror(errno)); 379 | return ERROR; 380 | } 381 | r->par.buffer_sent += len; 382 | } 383 | return OK; 384 | } 385 | 386 | int response_handle(struct connection *c) { 387 | request_t *r = &c->req; 388 | int status; 389 | do { 390 | status = r->res_handler(r); 391 | } while (status == OK && r->par.response_done != TRUE); 392 | if (r->par.response_done) { // response done 393 | if (r->par.keep_alive) { 394 | request_reset(r); 395 | connection_disable_out(epoll_fd, c); 396 | connection_enable_in(epoll_fd, c); 397 | } else 398 | return ERROR; // make connection expired 399 | } 400 | return status; 401 | } 402 | 403 | int response_handle_send_buffer(struct request *r) { 404 | int status; 405 | status = response_send(r); 406 | if (status != OK) { 407 | return status; 408 | } else { 409 | if (r->resource_fd != -1) { 410 | r->res_handler = response_handle_send_file; 411 | return OK; 412 | } 413 | r->par.response_done = TRUE; 414 | connection_disable_out(epoll_fd, r->c); 415 | return OK; 416 | } 417 | return OK; 418 | } 419 | 420 | int response_handle_send_file(struct request *r) { 421 | int len; 422 | connection_t *c = r->c; 423 | while (TRUE) { 424 | // zero copy, make it faster 425 | len = sendfile(c->fd, r->resource_fd, NULL, r->resource_size); 426 | if (len == 0) { 427 | r->par.response_done = TRUE; 428 | close(r->resource_fd); 429 | return OK; 430 | } else if (len < 0) { 431 | if (errno == EAGAIN) { 432 | return AGAIN; 433 | } 434 | lotos_log(LOG_ERR, "sendfile: %s", strerror(errno)); 435 | return ERROR; 436 | } 437 | } 438 | return OK; 439 | } 440 | 441 | int response_assemble_buffer(struct request *r) { 442 | response_append_status_line(r); 443 | response_append_date(r); 444 | response_append_server(r); 445 | response_append_content_type(r); 446 | response_append_content_length(r); 447 | response_append_connection(r); 448 | response_append_crlf(r); 449 | return OK; 450 | } 451 | 452 | int response_assemble_err_buffer(struct request *r, int status_code) { 453 | r->req_handler = NULL; 454 | r->par.err_req = TRUE; 455 | r->status_code = status_code; 456 | 457 | response_append_status_line(r); 458 | response_append_date(r); 459 | response_append_server(r); 460 | response_append_content_type(r); 461 | response_append_content_length(r); 462 | r->par.keep_alive = FALSE; 463 | response_append_connection(r); 464 | response_append_crlf(r); 465 | 466 | // add err page html 467 | r->ob = buffer_cat_cstr(r->ob, err_page_render_buf()); 468 | 469 | connection_disable_in(epoll_fd, r->c); 470 | connection_enable_out(epoll_fd, r->c); 471 | r->par.response_done = TRUE; 472 | return OK; 473 | } 474 | -------------------------------------------------------------------------------- /src/request.h: -------------------------------------------------------------------------------- 1 | #ifndef _REQUEST_H__ 2 | #define _REQUEST_H__ 3 | 4 | #include "buffer.h" 5 | #include "connection.h" 6 | #include "http_parser.h" 7 | #include "misc.h" 8 | 9 | struct request { 10 | struct connection *c; /* belonged connection */ 11 | buffer_t *ib; /* request buffer */ 12 | buffer_t *ob; /* response buffer */ 13 | parse_archive par; /* parse_archive */ 14 | int resource_fd; /* resource fildes */ 15 | int resource_size; /* resource size */ 16 | int status_code; /* response status code */ 17 | int (*req_handler)(struct request *); /* request handler for rl, hd, bd */ 18 | int (*res_handler)(struct request *); /* response handler for hd bd */ 19 | }; 20 | typedef struct request request_t; 21 | 22 | extern int request_init(request_t *r, struct connection *c); 23 | extern int request_reset(request_t *r); 24 | extern int request_handle(struct connection *c); 25 | 26 | extern int response_handle(struct connection *c); 27 | 28 | void header_handler_dict_init(); 29 | void header_handler_dict_free(); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/response.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "response.h" 3 | #include "buffer.h" 4 | #include "connection.h" 5 | #include "dict.h" 6 | #include "misc.h" 7 | #include "request.h" 8 | #include "server.h" 9 | #include "ssstr.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define ERR_HEADER_MAX_LEN (BUFSIZ) 18 | 19 | static const char *status_table[512]; 20 | 21 | static ssstr_t mime_list[][2] = { 22 | {SSSTR("word"), SSSTR("application/msword")}, 23 | {SSSTR("pdf"), SSSTR("application/pdf")}, 24 | {SSSTR("zip"), SSSTR("application/zip")}, 25 | {SSSTR("js"), SSSTR("application/javascript")}, 26 | {SSSTR("gif"), SSSTR("image/gif")}, 27 | {SSSTR("jpeg"), SSSTR("image/jpeg")}, 28 | {SSSTR("jpg"), SSSTR("image/jpeg")}, 29 | {SSSTR("png"), SSSTR("image/png")}, 30 | {SSSTR("css"), SSSTR("text/css")}, 31 | {SSSTR("html"), SSSTR("text/html")}, 32 | {SSSTR("htm"), SSSTR("text/html")}, 33 | {SSSTR("txt"), SSSTR("text/plain")}, 34 | {SSSTR("xml"), SSSTR("text/xml")}, 35 | {SSSTR("svg"), SSSTR("image/svg+xml")}, 36 | {SSSTR("mp4"), SSSTR("video/mp4")}, 37 | }; 38 | 39 | static dict_t mime_dict; 40 | 41 | void mime_dict_init() { 42 | size_t nsize = sizeof(mime_list) / sizeof(mime_list[0]); 43 | int i; 44 | dict_init(&mime_dict); 45 | for (i = 0; i < nsize; i++) { 46 | dict_put(&mime_dict, &mime_list[i][0], &mime_list[i][1]); 47 | } 48 | } 49 | 50 | void mime_dict_free() { dict_free(&mime_dict); } 51 | 52 | void status_table_init() { 53 | memset(status_table, 0, sizeof(status_table)); 54 | #define XX(num, name, string) status_table[num] = #num " " #string; 55 | HTTP_STATUS_MAP(XX); 56 | #undef XX 57 | } 58 | 59 | static err_page_t err_page; 60 | 61 | int err_page_init() { 62 | err_page_t *ep = &err_page; 63 | // open error.html 64 | ep->err_page_fd = openat(server_config.rootdir_fd, "error.html", O_RDONLY); 65 | ABORT_ON(ep->err_page_fd == ERROR, "openat"); 66 | struct stat st; 67 | fstat(ep->err_page_fd, &st); 68 | ep->raw_page_size = st.st_size; 69 | 70 | ep->rendered_err_page = buffer_init(ep->raw_page_size + ERR_HEADER_MAX_LEN); 71 | ABORT_ON(ep->rendered_err_page == NULL, "buffer_init"); 72 | 73 | // mmap file to memory 74 | ep->raw_err_page = 75 | mmap(NULL, ep->raw_page_size, PROT_READ, MAP_SHARED, ep->err_page_fd, 0); 76 | ABORT_ON(ep->raw_err_page == NULL, "mmap"); 77 | return OK; 78 | } 79 | 80 | void err_page_free() { 81 | err_page_t *ep = &err_page; 82 | buffer_free(ep->rendered_err_page); 83 | // munmap 84 | munmap((void *)ep->raw_err_page, ep->raw_page_size); 85 | close(ep->err_page_fd); 86 | } 87 | 88 | inline char *err_page_render_buf() { return err_page.rendered_err_page->buf; } 89 | 90 | void response_append_status_line(struct request *r) { 91 | buffer_t *b = r->ob; 92 | if (r->par.version.http_minor == 1) { 93 | r->ob = buffer_cat_cstr(b, "HTTP/1.1 "); 94 | } else { 95 | r->ob = buffer_cat_cstr(b, "HTTP/1.0 "); 96 | } 97 | // status 98 | const char *status_str = status_table[r->status_code]; 99 | if (status_str != NULL) 100 | r->ob = buffer_cat_cstr(b, status_str); 101 | r->ob = buffer_cat_cstr(b, CRLF); 102 | } 103 | 104 | void response_append_date(struct request *r) { 105 | buffer_t *b = r->ob; 106 | time_t now = time(NULL); 107 | struct tm *tm = gmtime(&now); 108 | size_t len = strftime(b->buf + buffer_len(b), b->free, 109 | "Date: %a, %d %b %Y %H:%M:%S GMT" CRLF, tm); 110 | b->len += len; 111 | b->free -= len; 112 | } 113 | 114 | void response_append_server(struct request *r) { 115 | buffer_t *b = r->ob; 116 | r->ob = buffer_cat_cstr(b, "Server: "); 117 | r->ob = buffer_cat_cstr(b, SERVER_NAME CRLF); 118 | } 119 | 120 | void response_append_content_type(struct request *r) { 121 | buffer_t *b = r->ob; 122 | parse_archive *ar = &r->par; 123 | 124 | ssstr_t content_type; 125 | if (ar->err_req) { 126 | content_type = SSSTR("text/html"); 127 | goto done; 128 | } 129 | ssstr_t *v = dict_get(&mime_dict, &ar->url.mime_extension, NULL); 130 | if (v != NULL) { 131 | content_type = *v; 132 | } else { 133 | content_type = SSSTR("text/html"); 134 | } 135 | done:; 136 | r->ob = buffer_cat_cstr(b, "Content-Type: "); 137 | r->ob = buffer_cat_cstr(b, content_type.str); 138 | r->ob = buffer_cat_cstr(b, CRLF); 139 | } 140 | 141 | void response_append_content_length(struct request *r) { 142 | buffer_t *b = r->ob; 143 | char cl[128]; 144 | int len; 145 | buffer_t *rendered_err_page = err_page.rendered_err_page; 146 | // modify content_length when sending err page 147 | if (r->par.err_req) { 148 | len = snprintf(rendered_err_page->buf, 149 | rendered_err_page->free + rendered_err_page->len, 150 | err_page.raw_err_page, status_table[r->status_code]); 151 | err_page.rendered_page_size = len; 152 | } else { 153 | len = r->resource_size; 154 | } 155 | sprintf(cl, "Content-Length: %d" CRLF, len); 156 | r->ob = buffer_cat_cstr(b, cl); 157 | } 158 | 159 | void response_append_connection(struct request *r) { 160 | buffer_t *b = r->ob; 161 | ssstr_t connection; 162 | if (r->par.keep_alive) { 163 | connection = SSSTR("Connection: keep-alive"); 164 | } else { 165 | connection = SSSTR("Connection: close"); 166 | } 167 | r->ob = buffer_cat_cstr(b, connection.str); 168 | r->ob = buffer_cat_cstr(b, CRLF); 169 | } 170 | 171 | void response_append_crlf(struct request *r) { 172 | buffer_t *b = r->ob; 173 | r->ob = buffer_cat_cstr(b, CRLF); 174 | } 175 | -------------------------------------------------------------------------------- /src/response.h: -------------------------------------------------------------------------------- 1 | #ifndef _RESPONSE_H__ 2 | #define _RESPONSE_H__ 3 | 4 | #include "buffer.h" 5 | #include "connection.h" 6 | #include "request.h" 7 | 8 | #define SERVER_NAME "lotos/0.1" 9 | 10 | // https://github.com/nodejs/http-parser/blob/b11de0f5c65bcc1b906f85f4df58883b0c133e7b/http_parser.h#L233 11 | /* status code */ 12 | #define HTTP_STATUS_MAP(XX) \ 13 | XX(100, CONTINUE, Continue) \ 14 | XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ 15 | XX(102, PROCESSING, Processing) \ 16 | XX(200, OK, OK) \ 17 | XX(201, CREATED, Created) \ 18 | XX(202, ACCEPTED, Accepted) \ 19 | XX(203, NON_AUTHORITATIVE_INFORMATION, Non - Authoritative Information) \ 20 | XX(204, NO_CONTENT, No Content) \ 21 | XX(205, RESET_CONTENT, Reset Content) \ 22 | XX(206, PARTIAL_CONTENT, Partial Content) \ 23 | XX(207, MULTI_STATUS, Multi - Status) \ 24 | XX(208, ALREADY_REPORTED, Already Reported) \ 25 | XX(226, IM_USED, IM Used) \ 26 | XX(300, MULTIPLE_CHOICES, Multiple Choices) \ 27 | XX(301, MOVED_PERMANENTLY, Moved Permanently) \ 28 | XX(302, FOUND, Found) \ 29 | XX(303, SEE_OTHER, See Other) \ 30 | XX(304, NOT_MODIFIED, Not Modified) \ 31 | XX(305, USE_PROXY, Use Proxy) \ 32 | XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ 33 | XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ 34 | XX(400, BAD_REQUEST, Bad Request) \ 35 | XX(401, UNAUTHORIZED, Unauthorized) \ 36 | XX(402, PAYMENT_REQUIRED, Payment Required) \ 37 | XX(403, FORBIDDEN, Forbidden) \ 38 | XX(404, NOT_FOUND, Not Found) \ 39 | XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ 40 | XX(406, NOT_ACCEPTABLE, Not Acceptable) \ 41 | XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ 42 | XX(408, REQUEST_TIMEOUT, Request Timeout) \ 43 | XX(409, CONFLICT, Conflict) \ 44 | XX(410, GONE, Gone) \ 45 | XX(411, LENGTH_REQUIRED, Length Required) \ 46 | XX(412, PRECONDITION_FAILED, Precondition Failed) \ 47 | XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ 48 | XX(414, URI_TOO_LONG, URI Too Long) \ 49 | XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ 50 | XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ 51 | XX(417, EXPECTATION_FAILED, Expectation Failed) \ 52 | XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ 53 | XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ 54 | XX(423, LOCKED, Locked) \ 55 | XX(424, FAILED_DEPENDENCY, Failed Dependency) \ 56 | XX(426, UPGRADE_REQUIRED, Upgrade Required) \ 57 | XX(428, PRECONDITION_REQUIRED, Precondition Required) \ 58 | XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ 59 | XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ 60 | XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ 61 | XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ 62 | XX(501, NOT_IMPLEMENTED, Not Implemented) \ 63 | XX(502, BAD_GATEWAY, Bad Gateway) \ 64 | XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ 65 | XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ 66 | XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ 67 | XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ 68 | XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ 69 | XX(508, LOOP_DETECTED, Loop Detected) \ 70 | XX(510, NOT_EXTENDED, Not Extended) \ 71 | XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) 72 | 73 | typedef enum { 74 | #define XX(num, name, string) HTTP_STATUS_##name = num, 75 | HTTP_STATUS_MAP(XX) 76 | #undef XX 77 | } http_status; 78 | 79 | extern void mime_dict_init(); 80 | extern void mime_dict_free(); 81 | 82 | extern void status_table_init(); 83 | 84 | typedef struct { 85 | int err_page_fd; /* fildes of err page */ 86 | const char *raw_err_page; /* raw data of err page file */ 87 | size_t raw_page_size; /* size of err page file */ 88 | buffer_t *rendered_err_page; /* buffer contains err msg */ 89 | size_t rendered_page_size; /* size of err page file */ 90 | } err_page_t; 91 | 92 | extern int err_page_init(); 93 | extern void err_page_free(); 94 | extern char *err_page_render_buf(); 95 | 96 | extern void response_append_status_line(struct request *r); 97 | extern void response_append_date(struct request *r); 98 | extern void response_append_server(struct request *r); 99 | extern void response_append_content_type(struct request *r); 100 | extern void response_append_content_length(struct request *r); 101 | extern void response_append_connection(struct request *r); 102 | extern void response_append_crlf(struct request *r); 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /src/server.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "connection.h" 3 | #include "lotos_epoll.h" 4 | #include "misc.h" 5 | #include "response.h" 6 | #include "server.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | config_t server_config = { 24 | .port = 8888, 25 | .debug = FALSE, 26 | .timeout = 60, 27 | .worker = 4, 28 | .rootdir = NULL, 29 | .rootdir_fd = -1, 30 | }; 31 | 32 | int epoll_fd = -1; 33 | int listen_fd = -1; 34 | 35 | #if USE_MEM_POOL 36 | mem_pool_t connection_pool; 37 | #endif 38 | 39 | static void sigint_handler(int signum); 40 | static int make_server_socket(uint16_t port, int backlog); 41 | static int add_listen_fd(); 42 | 43 | int config_parse(int argc, char *argv[]) { 44 | int c; 45 | while ((c = getopt(argc, argv, "p:dt:w:r:")) != -1) { 46 | switch (c) { 47 | case 'p': 48 | server_config.port = atoi(optarg); 49 | break; 50 | case 'd': 51 | server_config.debug = TRUE; 52 | break; 53 | case 't': 54 | server_config.timeout = atoi(optarg); 55 | break; 56 | case 'w': 57 | server_config.worker = atoi(optarg); 58 | if (server_config.worker > sysconf(_SC_NPROCESSORS_ONLN)) { 59 | fprintf(stderr, 60 | "Config ERROR: worker num greater than cpu available cores.\n"); 61 | return ERROR; 62 | } 63 | break; 64 | case 'r': 65 | server_config.rootdir = optarg; 66 | break; 67 | default: 68 | return ERROR; 69 | } 70 | } 71 | DIR *dirp = NULL; 72 | if (server_config.rootdir != NULL && 73 | (dirp = opendir(server_config.rootdir)) != NULL) { 74 | closedir(dirp); 75 | server_config.rootdir_fd = open(server_config.rootdir, O_RDONLY); 76 | ERR_ON(server_config.rootdir_fd == ERROR, server_config.rootdir); 77 | return OK; 78 | } else { 79 | perror(server_config.rootdir); 80 | return ERROR; 81 | } 82 | } 83 | 84 | static void sigint_handler(int signum) { 85 | if (signum == SIGINT) { 86 | mime_dict_free(); 87 | header_handler_dict_free(); 88 | err_page_free(); 89 | #if USE_MEM_POOL 90 | pool_destroy(&connection_pool); 91 | #endif 92 | lotos_log(LOG_INFO, "lotos(PID: %u) exit...", getpid()); 93 | kill(-getpid(), SIGINT); 94 | exit(0); 95 | } 96 | } 97 | 98 | int server_setup(uint16_t port) { 99 | signal(SIGINT, sigint_handler); 100 | signal(SIGPIPE, SIG_IGN); // client close, server write will recv sigpipe 101 | 102 | mime_dict_init(); 103 | header_handler_dict_init(); 104 | status_table_init(); 105 | err_page_init(); 106 | #if USE_MEM_POOL 107 | pool_create(&connection_pool, 1024, sizeof(connection_t)); 108 | #endif 109 | 110 | listen_fd = make_server_socket(port, 1024); 111 | ABORT_ON(listen_fd == ERROR, "make_server_socket"); 112 | 113 | epoll_fd = lotos_epoll_create(0); 114 | ABORT_ON(epoll_fd == ERROR, "lotos_epoll_create"); 115 | 116 | ABORT_ON(add_listen_fd() == ERROR, "add_listen_fd"); 117 | return OK; 118 | } 119 | 120 | int server_shutdown() { return close(listen_fd); } 121 | 122 | int server_accept(int listen_fd) { 123 | int conn_fd; 124 | static struct sockaddr_in saddr; 125 | socklen_t saddrlen = sizeof(struct sockaddr_in); 126 | while ((conn_fd = accept(listen_fd, &saddr, &saddrlen)) != ERROR) { 127 | connection_accept(conn_fd, &saddr); 128 | } 129 | return 0; 130 | } 131 | 132 | static int make_server_socket(uint16_t port, int backlog) { 133 | int listen_fd; 134 | struct sockaddr_in saddr; 135 | 136 | listen_fd = socket(AF_INET, SOCK_STREAM, 0); 137 | if (listen_fd == ERROR) 138 | return ERROR; 139 | 140 | int enable = 1; 141 | setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); 142 | if (server_config.worker > 1) { 143 | // since linux 3.9 144 | setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)); 145 | } 146 | 147 | memset((void *)&saddr, 0, sizeof(saddr)); 148 | 149 | saddr.sin_family = AF_INET; 150 | saddr.sin_port = htons(port); 151 | saddr.sin_addr.s_addr = htonl(INADDR_ANY); 152 | 153 | if (bind(listen_fd, (struct sockaddr *)&saddr, sizeof(saddr)) != OK) 154 | return ERROR; 155 | if (listen(listen_fd, backlog) != OK) 156 | return ERROR; 157 | return listen_fd; 158 | } 159 | 160 | static int add_listen_fd() { 161 | set_fd_nonblocking(listen_fd); 162 | struct epoll_event ev; 163 | ev.data.ptr = &listen_fd; 164 | ev.events = EPOLLIN; 165 | return epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev); 166 | } 167 | 168 | int get_internet_address(char *host, int len, uint16_t *pport, 169 | struct sockaddr_in *paddr) { 170 | strncpy(host, inet_ntoa(paddr->sin_addr), len); 171 | *pport = ntohs(paddr->sin_port); 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /src/server.h: -------------------------------------------------------------------------------- 1 | #ifndef _SERVER_H__ 2 | #define _SERVER_H__ 3 | #include "mem_pool.h" 4 | #include "misc.h" 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct { 10 | uint16_t port; /* listen port */ 11 | bool debug; /* debug mode */ 12 | int timeout; /* connection expired time */ 13 | uint32_t worker; /* worker num */ 14 | char *rootdir; /* html root directory */ 15 | int rootdir_fd; /* fildes of rootdir */ 16 | } config_t; 17 | 18 | extern config_t server_config; 19 | extern int epoll_fd; /* epoll fd */ 20 | extern int listen_fd; /* server listen fd */ 21 | 22 | extern int config_parse(int argc, 23 | char *argv[]); /* parse command line options */ 24 | extern int server_setup(uint16_t port); /* bind and listen */ 25 | extern int server_shutdown(); /* server shutdown */ 26 | extern int server_accept(int listen_fd); /* accpet all connections */ 27 | 28 | extern int 29 | get_internet_address(char *host, int len, uint16_t *pport, 30 | struct sockaddr_in *paddr); /* get ip/port info */ 31 | 32 | #if USE_MEM_POOL 33 | extern mem_pool_t connection_pool; 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/ssstr.c: -------------------------------------------------------------------------------- 1 | #include "ssstr.h" 2 | #include "misc.h" 3 | #include 4 | #include 5 | #include 6 | 7 | void ssstr_print(const ssstr_t *s) { 8 | if (s == NULL || s->str == NULL) 9 | return; 10 | int i; 11 | for (i = 0; i < s->len; i++) { 12 | printf("%c", s->str[i]); 13 | fflush(stdout); 14 | } 15 | printf("\n"); 16 | } 17 | 18 | void ssstr_tolower(ssstr_t *s) { 19 | int i; 20 | for (i = 0; i < s->len; i++) { 21 | s->str[i] = tolower(s->str[i]); 22 | } 23 | } 24 | 25 | int ssstr_cmp(const ssstr_t *l, const ssstr_t *r) { 26 | if (l == r || (l->str == r->str && l->len == r->len)) 27 | return 0; 28 | if (l->str == NULL) 29 | return -1; 30 | if (r->str == NULL) 31 | return 1; 32 | 33 | int llen = l->len; 34 | int rlen = r->len; 35 | int minlen = llen > rlen ? rlen : llen; 36 | 37 | int i; 38 | for (i = 0; i < minlen; i++) { 39 | if (l->str[i] < r->str[i]) 40 | return -1; 41 | else if (l->str[i] > r->str[i]) 42 | return 1; 43 | } 44 | 45 | return (rlen == llen) ? 0 : ((llen < rlen) ? -1 : 1); 46 | } 47 | 48 | bool ssstr_equal(const ssstr_t *s, const char *cstr) { 49 | ssstr_t s2; 50 | ssstr_set(&s2, cstr); 51 | return ssstr_cmp(s, &s2) == 0 ? TRUE : FALSE; 52 | } 53 | -------------------------------------------------------------------------------- /src/ssstr.h: -------------------------------------------------------------------------------- 1 | /** 2 | * simple static c-style string, used with buffer_t or constant c string, 3 | * no need to copy memory, just save time 4 | */ 5 | #ifndef _SSSTR_H__ 6 | #define _SSSTR_H__ 7 | 8 | #include "misc.h" 9 | #include 10 | #include 11 | 12 | typedef struct { 13 | char *str; 14 | int len; 15 | } ssstr_t; 16 | 17 | /** 18 | * const char *p = "hello"; 19 | * SSSTR("hello") ✅ 20 | * SSSTR(p)❌ 21 | */ 22 | #define SSSTR(cstr) \ 23 | (ssstr_t) { cstr, sizeof(cstr) - 1 } 24 | 25 | static inline void ssstr_init(ssstr_t *s) { 26 | s->str = NULL; 27 | s->len = 0; 28 | } 29 | 30 | static inline void ssstr_set(ssstr_t *s, const char *cstr) { 31 | s->str = (char *)cstr; 32 | s->len = strlen(cstr); 33 | } 34 | 35 | extern void ssstr_print(const ssstr_t *s); 36 | extern void ssstr_tolower(ssstr_t *s); 37 | extern int ssstr_cmp(const ssstr_t *l, const ssstr_t *r); 38 | extern bool ssstr_equal(const ssstr_t *s, const char *cstr); 39 | 40 | static inline bool ssstr_caseequal(ssstr_t *s, const char *cstr) { 41 | return strncasecmp(s->str, cstr, strlen(cstr)) == 0 ? TRUE : FALSE; 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/test/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-g -std=c99 -Wall 2 | OPTFLAGS= 3 | 4 | OBJS=slow_client heap_test buffer_test ssstr_test dict_test parse_test\ 5 | mem_pool_test 6 | 7 | all : $(OBJS) 8 | 9 | slow_client : slow_client.c ../misc.c 10 | $(CC) $(CFLAGS) $^ -o $@ 11 | 12 | heap_test : heap_test.c 13 | $(CC) $(CFLAGS) $^ -o $@ 14 | 15 | buffer_test : buffer_test.c ../buffer.c 16 | $(CC) $(CFLAGS) $^ -o $@ 17 | 18 | ssstr_test : ssstr_test.c ../ssstr.c 19 | $(CC) $(CFLAGS) $^ -o $@ 20 | 21 | dict_test : dict_test.c ../dict.c ../ssstr.c 22 | $(CC) $(CFLAGS) $^ -o $@ 23 | 24 | parse_test : parse_test.c ../http_parser.c ../buffer.c ../ssstr.c 25 | $(CC) $(CFLAGS) $^ -o $@ 26 | 27 | mem_pool_test : mem_pool_test.c ../mem_pool.c ../misc.c 28 | $(CC) $(CFLAGS) $^ -o $@ 29 | 30 | test : 31 | ./heap_test 32 | ./buffer_test 33 | ./ssstr_test 34 | ./dict_test 35 | ./parse_test 36 | ./mem_pool_test 37 | 38 | clean : 39 | rm -f $(OBJS) 40 | -------------------------------------------------------------------------------- /src/test/buffer_test.c: -------------------------------------------------------------------------------- 1 | #include "../buffer.h" 2 | #include "minctest.h" 3 | #include 4 | #include 5 | 6 | void test1() { 7 | buffer_t *buffer = buffer_new(10); 8 | lok(buffer->len == 0 && buffer->free == 10); 9 | 10 | buffer = buffer_cat(buffer, "abc", 3); 11 | lok(buffer->len == 3 && buffer->free == 7); 12 | 13 | lsequal(buffer->buf, "abc"); 14 | buffer_free(buffer); 15 | } 16 | 17 | void test2() { 18 | buffer_t *buffer = buffer_new(10); 19 | lok(buffer->len == 0 && buffer->free == 10); 20 | 21 | buffer = buffer_cat(buffer, "aaaaabbbbb", 10); 22 | lok(buffer->len == 10 && buffer->free == 0); 23 | 24 | lsequal(buffer->buf, "aaaaabbbbb"); 25 | buffer_free(buffer); 26 | } 27 | 28 | void test3() { 29 | buffer_t *buffer = buffer_new(10); 30 | lok(buffer->len == 0 && buffer->free == 10); 31 | 32 | buffer = buffer_cat(buffer, "", 0); 33 | lok(buffer->len == 0 && buffer->free == 10); 34 | 35 | lsequal(buffer->buf, ""); 36 | buffer_free(buffer); 37 | } 38 | 39 | void test4() { 40 | buffer_t *buffer = buffer_new(10); 41 | lok(buffer->len == 0 && buffer->free == 10); 42 | 43 | buffer = buffer_cat(buffer, "abc", 3); 44 | lok(buffer->len == 3 && buffer->free == 7); 45 | buffer = buffer_cat(buffer, "defgh", 5); 46 | lok(buffer->len == 8 && buffer->free == 2); 47 | 48 | lsequal(buffer->buf, "abcdefgh"); 49 | buffer_free(buffer); 50 | } 51 | 52 | void test5() { 53 | buffer_t *buffer = buffer_new(10); 54 | lok(buffer->len == 0 && buffer->free == 10); 55 | 56 | buffer = buffer_cat(buffer, "abc", 3); 57 | lok(buffer->len == 3 && buffer->free == 7); 58 | buffer = buffer_cat(buffer, "defghijk", 8); 59 | lok(buffer->len == 11 && buffer->free == 11); 60 | 61 | lsequal(buffer->buf, "abcdefghijk"); 62 | 63 | buffer = buffer_cat_cstr(buffer, "cstr"); 64 | lok(buffer->len == 15 && buffer->free == 7); 65 | lsequal(buffer->buf, "abcdefghijkcstr"); 66 | 67 | buffer_free(buffer); 68 | } 69 | 70 | void test6() { 71 | buffer_t *buffer = buffer_init(); 72 | lok(buffer->len == 0 && buffer->free == BUFSIZ); 73 | 74 | static char buf[BUFFER_LIMIT]; 75 | memset(buf, 'x', sizeof(buf)); 76 | 77 | buffer = buffer_cat(buffer, "abc", 3); 78 | lok(buffer->len == 3 && buffer->free == BUFSIZ - 3); 79 | buffer = buffer_cat(buffer, buf, sizeof(buf)); 80 | lequal(buffer->len, BUFFER_LIMIT + 3); 81 | lequal(buffer->free, BUFFER_LIMIT); 82 | 83 | buffer_free(buffer); 84 | } 85 | 86 | void test7() { 87 | buffer_t *buffer = buffer_new(10); 88 | lok(buffer->len == 0 && buffer->free == 10); 89 | lok(buffer->buf == buffer_end(buffer)); 90 | 91 | buffer = buffer_cat(buffer, "abc", 3); 92 | lok(buffer->len == 3 && buffer->free == 7); 93 | 94 | lsequal(buffer->buf, "abc"); 95 | lequal('c', *(buffer_end(buffer) - 1)); 96 | lequal('b', *(buffer_end(buffer) - 2)); 97 | buffer_free(buffer); 98 | } 99 | 100 | int main(int argc, char const *argv[]) { 101 | lrun("test1", test1); 102 | lrun("test2", test2); 103 | lrun("test3", test3); 104 | lrun("test4", test4); 105 | lrun("test5", test5); 106 | lrun("test6", test6); 107 | lrun("test7", test7); 108 | lresults(); 109 | printf("\n\n"); 110 | return lfails != 0; 111 | } 112 | -------------------------------------------------------------------------------- /src/test/dict_test.c: -------------------------------------------------------------------------------- 1 | #include "../dict.h" 2 | #include "../ssstr.h" 3 | #include "minctest.h" 4 | #include 5 | #include 6 | 7 | // https://github.com/nodejs/http-parser/blob/b11de0f5c65bcc1b906f85f4df58883b0c133e7b/http_parser.h#L233 8 | #define HTTP_STATUS_MAP(XX) \ 9 | XX(100, CONTINUE, Continue) \ 10 | XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ 11 | XX(102, PROCESSING, Processing) \ 12 | XX(200, OK, OK) \ 13 | XX(201, CREATED, Created) \ 14 | XX(202, ACCEPTED, Accepted) \ 15 | XX(203, NON_AUTHORITATIVE_INFORMATION, Non - Authoritative Information) \ 16 | XX(204, NO_CONTENT, No Content) \ 17 | XX(205, RESET_CONTENT, Reset Content) \ 18 | XX(206, PARTIAL_CONTENT, Partial Content) \ 19 | XX(207, MULTI_STATUS, Multi - Status) \ 20 | XX(208, ALREADY_REPORTED, Already Reported) \ 21 | XX(226, IM_USED, IM Used) \ 22 | XX(300, MULTIPLE_CHOICES, Multiple Choices) \ 23 | XX(301, MOVED_PERMANENTLY, Moved Permanently) \ 24 | XX(302, FOUND, Found) \ 25 | XX(303, SEE_OTHER, See Other) \ 26 | XX(304, NOT_MODIFIED, Not Modified) \ 27 | XX(305, USE_PROXY, Use Proxy) \ 28 | XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ 29 | XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ 30 | XX(400, BAD_REQUEST, Bad Request) \ 31 | XX(401, UNAUTHORIZED, Unauthorized) \ 32 | XX(402, PAYMENT_REQUIRED, Payment Required) \ 33 | XX(403, FORBIDDEN, Forbidden) \ 34 | XX(404, NOT_FOUND, Not Found) \ 35 | XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ 36 | XX(406, NOT_ACCEPTABLE, Not Acceptable) \ 37 | XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ 38 | XX(408, REQUEST_TIMEOUT, Request Timeout) \ 39 | XX(409, CONFLICT, Conflict) \ 40 | XX(410, GONE, Gone) \ 41 | XX(411, LENGTH_REQUIRED, Length Required) \ 42 | XX(412, PRECONDITION_FAILED, Precondition Failed) \ 43 | XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ 44 | XX(414, URI_TOO_LONG, URI Too Long) \ 45 | XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ 46 | XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ 47 | XX(417, EXPECTATION_FAILED, Expectation Failed) \ 48 | XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ 49 | XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ 50 | XX(423, LOCKED, Locked) \ 51 | XX(424, FAILED_DEPENDENCY, Failed Dependency) \ 52 | XX(426, UPGRADE_REQUIRED, Upgrade Required) \ 53 | XX(428, PRECONDITION_REQUIRED, Precondition Required) \ 54 | XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ 55 | XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ 56 | XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ 57 | XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ 58 | XX(501, NOT_IMPLEMENTED, Not Implemented) \ 59 | XX(502, BAD_GATEWAY, Bad Gateway) \ 60 | XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ 61 | XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ 62 | XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ 63 | XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ 64 | XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ 65 | XX(508, LOOP_DETECTED, Loop Detected) \ 66 | XX(510, NOT_EXTENDED, Not Extended) \ 67 | XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) 68 | 69 | #define XX(num, name, string) num, 70 | int HTTP_STATUS_CODE[] = {HTTP_STATUS_MAP(XX)}; 71 | #undef XX 72 | 73 | #define XX(num, name, string) #string, 74 | const char *HTTP_STATUS_STRING[] = {HTTP_STATUS_MAP(XX)}; 75 | #undef XX 76 | 77 | size_t nsize = sizeof(HTTP_STATUS_CODE) / sizeof(int); 78 | 79 | dict_t dict; 80 | 81 | void make_dict() { 82 | dict_init(&dict); 83 | int i; 84 | for (i = 0; i < nsize; i++) { 85 | ssstr_t key; 86 | ssstr_set(&key, HTTP_STATUS_STRING[i]); 87 | 88 | dict_put(&dict, &key, (void *)(HTTP_STATUS_CODE + i)); 89 | } 90 | } 91 | 92 | void test1() { 93 | lequal(0, dict.size_mask & (dict.size_mask + 1)); 94 | lok(dict.used == nsize); 95 | } 96 | 97 | void test2() { 98 | ssstr_t key = SSSTR("Network Authentication Required"); 99 | bool found; 100 | int *val = dict_get(&dict, &key, &found); 101 | 102 | lequal(found, TRUE); 103 | lequal(511, *val); 104 | } 105 | 106 | void test3() { 107 | ssstr_t key = SSSTR("Permanent Redirect"); 108 | bool found; 109 | int *val = dict_get(&dict, &key, &found); 110 | 111 | lequal(found, TRUE); 112 | lequal(308, *val); 113 | } 114 | 115 | /** 116 | * dict->table[244] -> "Unauthorized" -> "Bad Request" -> 0x0 117 | */ 118 | void test4() { 119 | ssstr_t key = SSSTR("Bad Request"); 120 | bool found; 121 | int *val = dict_get(&dict, &key, &found); 122 | 123 | lequal(found, TRUE); 124 | lequal(400, *val); 125 | } 126 | 127 | void test5() { 128 | ssstr_t key = SSSTR("Internal Server Error"); 129 | bool found; 130 | int *val = dict_get(&dict, &key, &found); 131 | 132 | lequal(found, TRUE); 133 | lequal(500, *val); 134 | } 135 | 136 | void test6() { 137 | ssstr_t key = SSSTR("Intrnal Server Error"); 138 | bool found; 139 | int *val = dict_get(&dict, &key, &found); 140 | 141 | lequal(found, FALSE); 142 | lok(NULL == val); 143 | } 144 | 145 | void test7() { 146 | ssstr_t key = SSSTR(""); 147 | bool found; 148 | int *val = dict_get(&dict, &key, &found); 149 | 150 | lequal(found, FALSE); 151 | lok(NULL == val); 152 | } 153 | 154 | void test8() { 155 | ssstr_t key = SSSTR("take it easy, chendotjs"); 156 | bool found; 157 | int *val = dict_get(&dict, &key, &found); 158 | 159 | lequal(found, FALSE); 160 | lok(NULL == val); 161 | } 162 | 163 | int main(int argc, char const *argv[]) { 164 | make_dict(); 165 | lrun("test1", test1); 166 | lrun("test2", test2); 167 | lrun("test3", test3); 168 | lrun("test4", test4); 169 | lrun("test5", test5); 170 | lrun("test6", test6); 171 | lrun("test7", test7); 172 | lrun("test8", test8); 173 | lresults(); 174 | dict_free(&dict); 175 | printf("\n\n"); 176 | return lfails != 0; 177 | } 178 | -------------------------------------------------------------------------------- /src/test/heap_test.c: -------------------------------------------------------------------------------- 1 | #include "../connection.h" 2 | #include "../misc.h" 3 | #include "minctest.h" 4 | #include 5 | #include 6 | 7 | /* lotos_connections is seen as a binary min heap */ 8 | connection_t *lotos_connections[MAX_CONNECTION] = {0}; 9 | static int heap_size = 0; 10 | 11 | #define LCHILD(x) (((x) << 1) + 1) 12 | #define RCHILD(x) (LCHILD(x) + 1) 13 | #define PARENT(x) ((x - 1) >> 1) 14 | #define INHEAP(n, x) (((-1) < (x)) && ((x) < (n))) 15 | 16 | inline static void c_swap(int x, int y) { 17 | lok(x >= 0 && x < heap_size && y >= 0 && y < heap_size); 18 | connection_t *tmp = lotos_connections[x]; 19 | lotos_connections[x] = lotos_connections[y]; 20 | lotos_connections[y] = tmp; 21 | // update heap_idx 22 | lotos_connections[x]->heap_idx = x; 23 | lotos_connections[y]->heap_idx = y; 24 | } 25 | 26 | /* used for inserting */ 27 | static void heap_bubble_up(int idx) { 28 | while (PARENT(idx) >= 0) { 29 | int fidx = PARENT(idx); // fidx is father of idx; 30 | connection_t *c = lotos_connections[idx]; 31 | connection_t *fc = lotos_connections[fidx]; 32 | if (c->active_time >= fc->active_time) 33 | break; 34 | c_swap(idx, fidx); 35 | idx = fidx; 36 | } 37 | } 38 | 39 | /* used for extracting or active_time update larger */ 40 | static void heap_bubble_down(int idx) { 41 | while (TRUE) { 42 | int proper_child; 43 | int lchild = INHEAP(heap_size, LCHILD(idx)) ? LCHILD(idx) : (heap_size + 1); 44 | int rchild = INHEAP(heap_size, RCHILD(idx)) ? RCHILD(idx) : (heap_size + 1); 45 | if (lchild > heap_size && rchild > heap_size) { // no children 46 | break; 47 | } else if (INHEAP(heap_size, lchild) && INHEAP(heap_size, rchild)) { 48 | proper_child = lotos_connections[lchild]->active_time < 49 | lotos_connections[rchild]->active_time 50 | ? lchild 51 | : rchild; 52 | } else if (lchild > heap_size) { 53 | proper_child = rchild; 54 | } else { 55 | proper_child = lchild; 56 | } 57 | // idx is the smaller than children 58 | if (lotos_connections[idx]->active_time <= 59 | lotos_connections[proper_child]->active_time) 60 | break; 61 | lok(INHEAP(heap_size, proper_child)); 62 | c_swap(idx, proper_child); 63 | idx = proper_child; 64 | } 65 | } 66 | 67 | static int heap_insert(connection_t *c) { 68 | if (heap_size >= MAX_CONNECTION) { 69 | return ERROR; 70 | } 71 | lotos_connections[heap_size++] = c; 72 | c->heap_idx = heap_size - 1; 73 | heap_bubble_up(heap_size - 1); 74 | return 0; 75 | } 76 | 77 | /****************************** TEST CASES **********************************/ 78 | static int test_bubble_up = 0; 79 | void TEST_BUBBLE_UP(int input[], int ans[], size_t n) { 80 | // init 81 | memset(lotos_connections, 0, sizeof(lotos_connections)); 82 | heap_size = 0; 83 | int *arr = input; 84 | 85 | connection_t *conn_arr = malloc(sizeof(connection_t) * n); 86 | ERR_ON(conn_arr == NULL, "malloc"); 87 | for (int i = 0; i < n; i++) { 88 | connection_t *c = conn_arr + i; 89 | c->active_time = arr[i]; 90 | heap_insert(c); 91 | } 92 | 93 | // TEST 94 | printf("%s %d:\n", "TEST_BUBBLE_UP CASE", test_bubble_up++); 95 | lok(heap_size == n); 96 | for (int i = 0; i < n; i++) { 97 | printf("{%d %lu} ", i, lotos_connections[i]->active_time); 98 | lok(ans[i] == lotos_connections[i]->active_time); 99 | } 100 | printf("\n"); 101 | free(conn_arr); 102 | } 103 | 104 | static int test_bubble_down = 0; 105 | void TEST_BUBBLE_DOWN(int input[], int ans[], size_t n, int pos, int nval) { 106 | // init 107 | memset(lotos_connections, 0, sizeof(lotos_connections)); 108 | heap_size = 0; 109 | int *arr = input; 110 | 111 | connection_t *conn_arr = malloc(sizeof(connection_t) * n); 112 | ERR_ON(conn_arr == NULL, "malloc"); 113 | for (int i = 0; i < n; i++) { 114 | connection_t *c = conn_arr + i; 115 | c->active_time = arr[i]; 116 | heap_insert(c); 117 | } 118 | 119 | // update value 120 | lotos_connections[pos]->active_time = nval; 121 | heap_bubble_down(pos); 122 | 123 | // TEST 124 | printf("%s %d:\n", "TEST_BUBBLE_DOWN CASE", test_bubble_down++); 125 | lok(heap_size == n); 126 | for (int i = 0; i < n; i++) { 127 | printf("{%d %lu} ", i, lotos_connections[i]->active_time); 128 | lok(ans[i] == lotos_connections[i]->active_time); 129 | } 130 | printf("\n"); 131 | free(conn_arr); 132 | } 133 | 134 | int main(int argc, char const *argv[]) { 135 | { 136 | int arr[] = {4, 1, 5, 9, 6, 0}; 137 | int ans[] = {0, 4, 1, 9, 6, 5}; 138 | TEST_BUBBLE_UP(arr, ans, sizeof(arr) / sizeof(int)); 139 | } 140 | 141 | { 142 | int arr[] = {5, 8, 12, 19, 28, 20, 15, 22, 3}; 143 | int ans[] = {3, 5, 12, 8, 28, 20, 15, 22, 19}; 144 | TEST_BUBBLE_UP(arr, ans, sizeof(arr) / sizeof(int)); 145 | } 146 | 147 | { 148 | int arr[] = {9, 12, 17, 30, 50, 20, 60, 65, 4, 19}; 149 | int ans[] = {4, 9, 17, 12, 19, 20, 60, 65, 30, 50}; 150 | TEST_BUBBLE_UP(arr, ans, sizeof(arr) / sizeof(int)); 151 | } 152 | 153 | printf("\n\n"); 154 | 155 | { 156 | int arr[] = {4, 1, 5, 9, 6, 0}; 157 | int ans[] = {1, 4, 5, 9, 6, 100}; 158 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 0, 100); 159 | } 160 | 161 | { 162 | int arr[] = {4, 1, 5, 9, 6, 0}; 163 | int ans[] = {0, 6, 1, 9, 100, 5}; 164 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 1, 100); 165 | } 166 | 167 | { 168 | int arr[] = {4, 1, 5, 9, 6, 0}; 169 | int ans[] = {0, 6, 1, 9, 8, 5}; 170 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 1, 8); 171 | } 172 | 173 | { 174 | int arr[] = {4, 1, 5, 9, 6, 0}; 175 | int ans[] = {0, 5, 1, 9, 6, 5}; 176 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 1, 5); 177 | } 178 | 179 | { 180 | int arr[] = {4, 1, 5, 9, 6, 0}; 181 | int ans[] = {0, 4, 1, 9, 7, 5}; 182 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 4, 7); 183 | } 184 | 185 | { 186 | int arr[] = {4, 1, 5, 9, 6, 0}; 187 | int ans[] = {0, 4, 1, 9, 6, 10}; 188 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 5, 10); 189 | } 190 | 191 | { 192 | int arr[] = {4, 1, 5, 9, 6, 0}; 193 | int ans[] = {0, 4, 3, 9, 6, 5}; 194 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 2, 3); 195 | } 196 | 197 | { 198 | int arr[] = {4, 1, 5, 9, 6, 0}; 199 | int ans[] = {0, 4, 5, 9, 6, 10}; 200 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 2, 10); 201 | } 202 | 203 | { 204 | int arr[] = {1}; 205 | int ans[] = {10}; 206 | TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 0, 10); 207 | } 208 | lresults(); 209 | printf("\n\n"); 210 | return lfails != 0; 211 | } 212 | -------------------------------------------------------------------------------- /src/test/mem_pool_test.c: -------------------------------------------------------------------------------- 1 | #include "../mem_pool.h" 2 | #include "minctest.h" 3 | #include 4 | #include 5 | 6 | void test1() { 7 | mem_pool_t pool; 8 | pool_create(&pool, 4, 20); 9 | 10 | void **q = pool.blocks; 11 | lok(*q == pool.blocks + 20); 12 | 13 | q = pool.blocks + 20; 14 | lok(*q == pool.blocks + 40); 15 | 16 | q = pool.blocks + 60; 17 | lok(*q == 0); 18 | 19 | pool_destroy(&pool); 20 | } 21 | 22 | void test2() { 23 | mem_pool_t pool; 24 | pool_create(&pool, 4, 20); 25 | lok(pool.block_num == 4); 26 | lok(pool.block_size == 20); 27 | lok(pool.block_allocated == 0); 28 | 29 | void *p; 30 | 31 | p = pool_alloc(&pool); 32 | lok(p == pool.blocks); 33 | lok(pool.block_allocated == 1); 34 | 35 | p = pool_alloc(&pool); 36 | lok(p == pool.blocks + 20); 37 | 38 | p = pool_alloc(&pool); 39 | lok(p == pool.blocks + 40); 40 | 41 | p = pool_alloc(&pool); 42 | lok(p == pool.blocks + 60); 43 | lok(pool.block_allocated == 4); 44 | 45 | p = pool_alloc(&pool); 46 | lok(p == NULL); 47 | lok(pool.block_allocated == 4); 48 | 49 | p = pool_alloc(&pool); 50 | lok(p == NULL); 51 | lok(pool.block_allocated == 4); 52 | 53 | pool_destroy(&pool); 54 | } 55 | 56 | void test3() { 57 | mem_pool_t pool; 58 | pool_create(&pool, 4, 20); 59 | lok(pool.block_num == 4); 60 | lok(pool.block_size == 20); 61 | lok(pool.block_allocated == 0); 62 | 63 | void *p; 64 | 65 | p = pool_alloc(&pool); 66 | lok(p == pool.blocks); 67 | lok(pool.block_allocated == 1); 68 | 69 | p = pool_alloc(&pool); 70 | lok(p == pool.blocks + 20); 71 | 72 | p = pool_alloc(&pool); 73 | lok(p == pool.blocks + 40); 74 | 75 | pool_free(&pool, p); 76 | lok(pool.block_allocated == 2); 77 | 78 | p = pool_alloc(&pool); 79 | lok(p == pool.blocks + 40); 80 | lok(pool.block_allocated == 3); 81 | 82 | p = pool_alloc(&pool); 83 | lok(p == pool.blocks + 60); 84 | lok(pool.block_allocated == 4); 85 | 86 | p = pool_alloc(&pool); 87 | lok(p == NULL); 88 | lok(pool.block_allocated == 4); 89 | 90 | p = pool_alloc(&pool); 91 | lok(p == NULL); 92 | lok(pool.block_allocated == 4); 93 | 94 | pool_destroy(&pool); 95 | } 96 | 97 | int main(int argc, char const *argv[]) { 98 | lrun("test1", test1); 99 | lrun("test2", test2); 100 | lrun("test3", test3); 101 | lresults(); 102 | printf("\n\n"); 103 | return lfails != 0; 104 | } 105 | -------------------------------------------------------------------------------- /src/test/minctest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * MINCTEST - Minimal C Test Library - 0.2.0 4 | * 5 | * Copyright (c) 2014-2017 Lewis Van Winkle 6 | * 7 | * http://CodePlea.com 8 | * 9 | * This software is provided 'as-is', without any express or implied 10 | * warranty. In no event will the authors be held liable for any damages 11 | * arising from the use of this software. 12 | * 13 | * Permission is granted to anyone to use this software for any purpose, 14 | * including commercial applications, and to alter it and redistribute it 15 | * freely, subject to the following restrictions: 16 | * 17 | * 1. The origin of this software must not be misrepresented; you must not 18 | * claim that you wrote the original software. If you use this software 19 | * in a product, an acknowledgement in the product documentation would be 20 | * appreciated but is not required. 21 | * 2. Altered source versions must be plainly marked as such, and must not be 22 | * misrepresented as being the original software. 23 | * 3. This notice may not be removed or altered from any source distribution. 24 | * 25 | */ 26 | 27 | 28 | 29 | /* 30 | * MINCTEST - Minimal testing library for C 31 | * 32 | * 33 | * Example: 34 | * 35 | * void test1() { 36 | * lok('a' == 'a'); 37 | * } 38 | * 39 | * void test2() { 40 | * lequal(5, 6); 41 | * lfequal(5.5, 5.6); 42 | * } 43 | * 44 | * int main() { 45 | * lrun("test1", test1); 46 | * lrun("test2", test2); 47 | * lresults(); 48 | * return lfails != 0; 49 | * } 50 | * 51 | * 52 | * 53 | * Hints: 54 | * All functions/variables start with the letter 'l'. 55 | * 56 | */ 57 | 58 | 59 | #ifndef __MINCTEST_H__ 60 | #define __MINCTEST_H__ 61 | 62 | #include 63 | #include 64 | #include 65 | #include 66 | 67 | 68 | /* How far apart can floats be before we consider them unequal. */ 69 | #ifndef LTEST_FLOAT_TOLERANCE 70 | #define LTEST_FLOAT_TOLERANCE 0.001 71 | #endif 72 | 73 | 74 | /* Track the number of passes, fails. */ 75 | /* NB this is made for all tests to be in one file. */ 76 | static int ltests = 0; 77 | static int lfails = 0; 78 | 79 | 80 | /* Display the test results. */ 81 | #define lresults() do {\ 82 | if (lfails == 0) {\ 83 | printf("ALL TESTS PASSED (%d/%d)\n", ltests, ltests);\ 84 | } else {\ 85 | printf("SOME TESTS FAILED (%d/%d)\n", ltests-lfails, ltests);\ 86 | }\ 87 | } while (0) 88 | 89 | 90 | /* Run a test. Name can be any string to print out, test is the function name to call. */ 91 | #define lrun(name, test) do {\ 92 | const int ts = ltests;\ 93 | const int fs = lfails;\ 94 | const clock_t start = clock();\ 95 | printf("\t%-14s", name);\ 96 | test();\ 97 | printf("pass:%2d fail:%2d %4dms\n",\ 98 | (ltests-ts)-(lfails-fs), lfails-fs,\ 99 | (int)((clock() - start) * 1000 / CLOCKS_PER_SEC));\ 100 | } while (0) 101 | 102 | 103 | /* Assert a true statement. */ 104 | #define lok(test) do {\ 105 | ++ltests;\ 106 | if (!(test)) {\ 107 | ++lfails;\ 108 | printf("%s:%d error \n", __FILE__, __LINE__);\ 109 | }} while (0) 110 | 111 | 112 | /* Prototype to assert equal. */ 113 | #define lequal_base(equality, a, b, format) do {\ 114 | ++ltests;\ 115 | if (!(equality)) {\ 116 | ++lfails;\ 117 | printf("%s:%d ("format " != " format")\n", __FILE__, __LINE__, (a), (b));\ 118 | }} while (0) 119 | 120 | 121 | /* Assert two integers are equal. */ 122 | #define lequal(a, b)\ 123 | lequal_base((a) == (b), a, b, "%d") 124 | 125 | 126 | /* Assert two floats are equal (Within LTEST_FLOAT_TOLERANCE). */ 127 | #define lfequal(a, b)\ 128 | lequal_base(fabs((double)(a)-(double)(b)) <= LTEST_FLOAT_TOLERANCE\ 129 | && fabs((double)(a)-(double)(b)) == fabs((double)(a)-(double)(b)), (double)(a), (double)(b), "%f") 130 | 131 | 132 | /* Assert two strings are equal. */ 133 | #define lsequal(a, b)\ 134 | lequal_base(strcmp(a, b) == 0, a, b, "%s") 135 | 136 | 137 | #endif /*__MINCTEST_H__*/ 138 | -------------------------------------------------------------------------------- /src/test/parse_test.c: -------------------------------------------------------------------------------- 1 | #include "../buffer.h" 2 | #include "../http_parser.h" 3 | #include "../misc.h" 4 | #include "../ssstr.h" 5 | #include "minctest.h" 6 | #include 7 | #include 8 | 9 | void test_method1() { 10 | buffer_t *buffer = buffer_init(); 11 | parse_archive ar; 12 | parse_archive_init(&ar, buffer); 13 | 14 | buffer_cat(buffer, "POST / HTTP/1.0", 10); 15 | parse_request_line(buffer, &ar); 16 | lok(ar.method_begin == buffer->buf); 17 | lok(ar.next_parse_pos == buffer_end(buffer)); 18 | lequal(HTTP_POST, ar.method); 19 | lok(ssstr_equal(&ar.request_url_string, "/")); 20 | lok(ssstr_equal(&ar.url.abs_path, "/")); 21 | lok(ssstr_equal(&ar.url.query_string, "")); 22 | } 23 | 24 | /* so parse_request_line can be called many times when recv new data */ 25 | void test_method2() { 26 | buffer_t *buffer = buffer_init(); 27 | parse_archive ar; 28 | parse_archive_init(&ar, buffer); 29 | 30 | int status = -1; 31 | buffer_cat(buffer, "GE", 2); 32 | status = parse_request_line(buffer, &ar); 33 | lequal(AGAIN, status); 34 | lok(ar.method_begin == buffer->buf); 35 | lok(ar.next_parse_pos == buffer_end(buffer)); 36 | 37 | char next_buf[] = "T /s?wd=hello%20world"; 38 | buffer_cat(buffer, next_buf, strlen(next_buf)); 39 | status = parse_request_line(buffer, &ar); 40 | lequal(HTTP_GET, ar.method); 41 | lequal(AGAIN, status); 42 | 43 | buffer_cat(buffer, " ", 1); 44 | status = parse_request_line(buffer, &ar); 45 | lok(ssstr_equal(&ar.url.abs_path, "/s")); 46 | lok(ssstr_equal(&ar.url.query_string, "wd=hello%20world")); 47 | } 48 | 49 | /* valid request line */ 50 | void test_method3() { 51 | buffer_t *buffer = buffer_init(); 52 | parse_archive ar; 53 | parse_archive_init(&ar, buffer); 54 | 55 | int status = -1; 56 | char req_line[] = "GET /api/set/?wd=123abc HTTP/1.1\r\nHost:localhost:8888"; 57 | buffer_cat(buffer, req_line, strlen(req_line)); 58 | status = parse_request_line(buffer, &ar); 59 | 60 | lequal(HTTP_GET, ar.method); 61 | lequal(ar.version.http_major, 1); 62 | lequal(ar.version.http_minor, 1); 63 | lok(ssstr_equal(&ar.request_url_string, "/api/set/?wd=123abc")); 64 | lok(ssstr_equal(&ar.url.abs_path, "/api/set/")); 65 | lok(ssstr_equal(&ar.url.query_string, "wd=123abc")); 66 | lequal(OK, status); 67 | lok(ar.next_parse_pos[0] = 'H'); 68 | lok(ar.next_parse_pos[1] = 'o'); 69 | } 70 | 71 | /* invalid request line */ 72 | void test_method4() { 73 | buffer_t *buffer = buffer_init(); 74 | parse_archive ar; 75 | parse_archive_init(&ar, buffer); 76 | 77 | int status = -1; 78 | char req_line[] = 79 | "POST /api/set/?wd=123abc HTTP/01.10\r\nHost:localhost:8888"; 80 | buffer_cat(buffer, req_line, strlen(req_line)); 81 | status = parse_request_line(buffer, &ar); 82 | 83 | lequal(HTTP_POST, ar.method); 84 | lequal(ar.version.http_major, 1); 85 | lequal(ar.version.http_minor, 10); 86 | lok(ssstr_equal(&ar.request_url_string, "/api/set/?wd=123abc")); 87 | lequal(ERROR, status); 88 | } 89 | 90 | /* curl GET */ 91 | void test_method5() { 92 | buffer_t *buffer = buffer_init(); 93 | parse_archive ar; 94 | parse_archive_init(&ar, buffer); 95 | ar.state = S_HD_BEGIN; 96 | 97 | int status = -1; 98 | char req_line[] = "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) " 99 | "libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" 100 | "Host: 0.0.0.0=5000\r\n" 101 | "Accept: */*\r\n" 102 | "\r\n"; 103 | buffer_cat(buffer, req_line, strlen(req_line)); 104 | 105 | status = parse_header_line(buffer, &ar); 106 | lok(ssstr_equal(&ar.header[0], "User-Agent")); 107 | lok(ssstr_equal(&ar.header[1], 108 | "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 " 109 | "OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1")); 110 | lequal(OK, status); 111 | 112 | status = parse_header_line(buffer, &ar); 113 | lok(ssstr_equal(&ar.header[0], "Host")); 114 | lok(ssstr_equal(&ar.header[1], "0.0.0.0=5000")); 115 | lequal(OK, status); 116 | 117 | status = parse_header_line(buffer, &ar); 118 | lok(ssstr_equal(&ar.header[0], "Accept")); 119 | lok(ssstr_equal(&ar.header[1], "*/*")); 120 | lequal(OK, status); 121 | 122 | status = parse_header_line(buffer, &ar); 123 | lequal(CRLF_LINE, status); 124 | } 125 | 126 | /* firefox GET */ 127 | void test_method6() { 128 | buffer_t *buffer = buffer_init(); 129 | parse_archive ar; 130 | parse_archive_init(&ar, buffer); 131 | 132 | int status = -1; 133 | char req_line[] = 134 | "GET /favicon.ico HTTP/1.1\r\n" 135 | "Host: 0.0.0.0=5000\r\n" 136 | "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) " 137 | "Gecko/2008061015 Firefox/3.0\r\n" 138 | "Accept: " 139 | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" 140 | "Accept-Language: en-us,en;q=0.5\r\n" 141 | "Accept-Encoding: gzip,deflate\r\n" 142 | "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" 143 | "Keep-Alive: 300\r\n" 144 | "Connection: keep-alive\r\n" 145 | "\r\n"; 146 | buffer_cat(buffer, req_line, strlen(req_line)); 147 | 148 | status = parse_request_line(buffer, &ar); 149 | 150 | lequal(HTTP_GET, ar.method); 151 | lequal(ar.version.http_major, 1); 152 | lequal(ar.version.http_minor, 1); 153 | lok(ssstr_equal(&ar.request_url_string, "/favicon.ico")); 154 | lok(ssstr_equal(&ar.url.abs_path, "/favicon.ico")); 155 | lok(ssstr_equal(&ar.url.query_string, "")); 156 | lok(ssstr_equal(&ar.url.mime_extension, "ico")); 157 | lequal(OK, status); 158 | 159 | status = parse_header_line(buffer, &ar); 160 | lok(ssstr_equal(&ar.header[0], "Host")); 161 | lok(ssstr_equal(&ar.header[1], "0.0.0.0=5000")); 162 | lequal(OK, status); 163 | 164 | status = parse_header_line(buffer, &ar); 165 | lok(ssstr_equal(&ar.header[0], "User-Agent")); 166 | lok(ssstr_equal(&ar.header[1], 167 | "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) " 168 | "Gecko/2008061015 Firefox/3.0")); 169 | lequal(OK, status); 170 | 171 | status = parse_header_line(buffer, &ar); 172 | lok(ssstr_equal(&ar.header[0], "Accept")); 173 | lok(ssstr_equal( 174 | &ar.header[1], 175 | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); 176 | lequal(OK, status); 177 | 178 | status = parse_header_line(buffer, &ar); 179 | lok(ssstr_equal(&ar.header[0], "Accept-Language")); 180 | lok(ssstr_equal(&ar.header[1], "en-us,en;q=0.5")); 181 | lequal(OK, status); 182 | 183 | status = parse_header_line(buffer, &ar); 184 | lok(ssstr_equal(&ar.header[0], "Accept-Encoding")); 185 | lok(ssstr_equal(&ar.header[1], "gzip,deflate")); 186 | lequal(OK, status); 187 | 188 | status = parse_header_line(buffer, &ar); 189 | lok(ssstr_equal(&ar.header[0], "Accept-Charset")); 190 | lok(ssstr_equal(&ar.header[1], "ISO-8859-1,utf-8;q=0.7,*;q=0.7")); 191 | lequal(OK, status); 192 | 193 | status = parse_header_line(buffer, &ar); 194 | lok(ssstr_equal(&ar.header[0], "Keep-Alive")); 195 | lok(ssstr_equal(&ar.header[1], "300")); 196 | lequal(OK, status); 197 | 198 | status = parse_header_line(buffer, &ar); 199 | lok(ssstr_equal(&ar.header[0], "Connection")); 200 | lok(ssstr_equal(&ar.header[1], "keep-alive")); 201 | lequal(OK, status); 202 | 203 | status = parse_header_line(buffer, &ar); 204 | lequal(CRLF_LINE, status); 205 | } 206 | 207 | int main(int argc, char const *argv[]) { 208 | lrun("test_method1", test_method1); 209 | lrun("test_method2", test_method2); 210 | lrun("test_method3", test_method3); 211 | lrun("test_method4", test_method4); 212 | lrun("test_method5", test_method5); 213 | lrun("test_method6", test_method6); 214 | 215 | lresults(); 216 | printf("\n\n"); 217 | return lfails != 0; 218 | } 219 | -------------------------------------------------------------------------------- /src/test/slow_client.c: -------------------------------------------------------------------------------- 1 | /** 2 | * ./slow_client 8888 [0-9] 3 | */ 4 | 5 | #define _GNU_SOURCE 6 | #include "../misc.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 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 | #include 29 | #include 30 | 31 | const char *requests[] = { 32 | #define SLOW_CLIENT_GET 0 33 | "GET / HTTP/1.1\r\n" 34 | "Host:127.0.0.1:8888\r\n" 35 | "User-Agent:SLOW_CLIENT\r\n" 36 | "\r\n", 37 | 38 | #define CURL_GET 1 39 | "GET / HTTP/1.1\r\n" 40 | "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) " 41 | "libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 " 42 | "libidn/1.1\r\n" 43 | "Host: 0.0.0.0=5000\r\n" 44 | "Accept: */*\r\n" 45 | "\r\n", 46 | 47 | #define FIREFOX_GET 2 48 | "GET /favicon.ico HTTP/1.1\r\n" 49 | "Host: 0.0.0.0=5000\r\n" 50 | "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) " 51 | "Gecko/2008061015 Firefox/3.0\r\n" 52 | "Accept: " 53 | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" 54 | "Accept-Language: en-us,en;q=0.5\r\n" 55 | "Accept-Encoding: gzip,deflate\r\n" 56 | "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" 57 | "Keep-Alive: 300\r\n" 58 | "Connection: keep-alive\r\n" 59 | "\r\n", 60 | 61 | #define POST_IDENTITY_BODY_WORLD 3 62 | "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" 63 | "Accept: */*\r\n" 64 | "Transfer-Encoding: identity\r\n" 65 | "Content-Length: 5\r\n" 66 | "\r\n" 67 | "World", 68 | 69 | #define GET_FUNKY_CONTENT_LENGTH 4 70 | "GET / HTTP/1.0\r\n" 71 | "conTENT-Length: 5\r\n" 72 | "\r\n" 73 | "HELLO", 74 | 75 | #define POST_CHUNKED_ALL_YOUR_BASE 5 76 | "POST / HTTP/1.1\r\n" 77 | "Transfer-Encoding: chunked\r\n" 78 | "\r\n" 79 | "1e\r\nall your base are belong to us\r\n" 80 | "0\r\n" 81 | "\r\n", 82 | 83 | }; 84 | 85 | int connect_to_server(uint16_t port) { 86 | static const char *host = "127.0.0.1"; 87 | int fd = socket(AF_INET, SOCK_STREAM, 0); 88 | ABORT_ON(fd == -1, "socket"); 89 | 90 | struct sockaddr_in addr; 91 | addr.sin_family = AF_INET; 92 | addr.sin_port = htons(port); 93 | int status = inet_pton(AF_INET, host, &addr.sin_addr); 94 | ABORT_ON(status <= 0, "inet_pton"); 95 | 96 | status = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); 97 | ABORT_ON(status != 0, "connect"); 98 | 99 | return fd; 100 | } 101 | 102 | int main(int argc, char *argv[]) { 103 | int fd, opt; 104 | if (argc < 3) { 105 | printf("Usage: %s port [0-5]\n", argv[0]); 106 | return 1; 107 | } 108 | signal(SIGPIPE, SIG_IGN); 109 | fd = connect_to_server(atoi(argv[1])); 110 | opt = atoi(argv[2]); 111 | 112 | if (opt >= sizeof(requests) / sizeof(requests[0])) 113 | return 1; 114 | 115 | /* send begin */ 116 | for (size_t i = 0; i < strlen(requests[opt]); i++) { 117 | int ch = requests[opt][i]; 118 | if (-1 == send(fd, &ch, 1, 0)) { 119 | perror("send"); // probably broken pipe 120 | break; 121 | } 122 | printf("%c", ch); 123 | fflush(stdout); 124 | usleep(30 * 1000); 125 | } 126 | /* send end */ 127 | 128 | /* recv begin */ 129 | printf("\n\n***************Recv**************\n\n"); 130 | while (1) { 131 | int ch; 132 | int len = recv(fd, &ch, 1, 0); 133 | if (len != 1) { 134 | break; 135 | } 136 | printf("%c", ch); 137 | fflush(stdout); 138 | } 139 | /* recv end */ 140 | 141 | close(fd); 142 | return 0; 143 | } 144 | -------------------------------------------------------------------------------- /src/test/ssstr_test.c: -------------------------------------------------------------------------------- 1 | #include "../ssstr.h" 2 | #include "minctest.h" 3 | #include 4 | #include 5 | 6 | void test1() { 7 | ssstr_t s = SSSTR("hello"); 8 | lequal(5, s.len); 9 | lsequal("hello", s.str); 10 | } 11 | 12 | void test2() { 13 | ssstr_t s1 = SSSTR("hello"); 14 | ssstr_t s2 = SSSTR("hello"); 15 | lequal(0, ssstr_cmp(&s1, &s2)); 16 | 17 | s1 = SSSTR(""); 18 | s2 = SSSTR(""); 19 | lequal(0, ssstr_cmp(&s1, &s2)); 20 | } 21 | 22 | void test3() { 23 | char str1[] = "hello"; 24 | char str2[] = "hello"; 25 | 26 | ssstr_t s1 = SSSTR(str1); 27 | ssstr_t s2 = SSSTR(str2); 28 | lequal(0, ssstr_cmp(&s1, &s2)); 29 | } 30 | 31 | void test4() { 32 | char str1[] = "hello"; 33 | char str2[] = "hullo"; 34 | 35 | ssstr_t s1 = SSSTR(str1); 36 | ssstr_t s2 = SSSTR(str2); 37 | lequal(-1, ssstr_cmp(&s1, &s2)); 38 | } 39 | 40 | void test5() { 41 | 42 | char str1[] = "hello_world"; 43 | char str2[] = "hello"; 44 | 45 | ssstr_t s1 = SSSTR(str1); 46 | ssstr_t s2 = SSSTR(str2); 47 | 48 | lequal(1, ssstr_cmp(&s1, &s2)); 49 | lequal(11, s1.len); 50 | lequal(5, s2.len); 51 | } 52 | 53 | void test6() { 54 | char str1[] = "hello_world"; 55 | char str2[] = "hullo"; 56 | 57 | ssstr_t s1 = SSSTR(str1); 58 | ssstr_t s2 = SSSTR(str2); 59 | lequal(-1, ssstr_cmp(&s1, &s2)); 60 | } 61 | 62 | void test7() { 63 | char str1[] = ""; 64 | char str2[] = ""; 65 | 66 | ssstr_t s1 = SSSTR(str1); 67 | ssstr_t s2 = SSSTR(str2); 68 | 69 | lok(s1.str != s2.str); 70 | lequal(0, ssstr_cmp(&s1, &s2)); 71 | } 72 | 73 | void test8() { 74 | char str1[] = "hello"; 75 | char str2[] = "hello"; 76 | 77 | ssstr_t s1 = SSSTR(str1); 78 | 79 | lok(ssstr_equal(&s1, str2)); 80 | lok(ssstr_equal(&s1, "hello")); 81 | lok(!ssstr_equal(&s1, "hello_world")); 82 | lok(!ssstr_equal(&s1, "hullo")); 83 | } 84 | 85 | void test9() { 86 | char str1[] = "HellO"; 87 | char str2[] = "heLLo-World"; 88 | 89 | ssstr_t s1 = SSSTR(str1); 90 | ssstr_t s2 = SSSTR(str2); 91 | 92 | ssstr_tolower(&s1); 93 | ssstr_tolower(&s2); 94 | 95 | lok(ssstr_equal(&s1, "hello")); 96 | lok(ssstr_equal(&s2, "hello-world")); 97 | } 98 | 99 | void test10() { 100 | char str1[] = "HellO"; 101 | char str2[] = "heLLo-World"; 102 | 103 | ssstr_t s1 = SSSTR(str1); 104 | ssstr_t s2 = SSSTR(str2); 105 | 106 | lok(ssstr_caseequal(&s1, "hello")); 107 | lok(ssstr_caseequal(&s2, "hello-world")); 108 | } 109 | 110 | int main(int argc, char const *argv[]) { 111 | lrun("test1", test1); 112 | lrun("test2", test2); 113 | lrun("test3", test3); 114 | lrun("test4", test4); 115 | lrun("test5", test5); 116 | lrun("test6", test6); 117 | lrun("test7", test7); 118 | lrun("test8", test8); 119 | lrun("test9", test9); 120 | lrun("test10", test10); 121 | lresults(); 122 | printf("\n\n"); 123 | return lfails != 0; 124 | } 125 | -------------------------------------------------------------------------------- /www/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Error 6 | 13 | 14 | 15 | 16 |

%s

17 |

Sorry, the page you are looking for is currently unavailable.
Please try again later.

18 |

If you are the system administrator of this resource then you should check the error log for details.

19 |

Faithfully yours, lotos.

20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome to lotos! 6 | 13 | 14 | 15 | 16 |

Welcome to lotos!

17 |

If you see this page, the lotos web server is successfully installed and working. Further configuration is required.

18 | 19 |

For online documentation and support please refer to 20 | https://github.com/chendotjs/lotos.

21 | 22 |

Thank you for using lotos.

23 | 24 | 25 | 26 | --------------------------------------------------------------------------------