[
  {
    "path": ".gitignore",
    "content": "# Prerequisites\n*.d\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Linker output\n*.ilk\n*.map\n*.exp\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\nlotos\n\n# Debug files\n*.dSYM/\n*.su\n*.idb\n*.pdb\n\n# Kernel Module Compile Results\n*.mod*\n*.cmd\n.tmp_versions/\nmodules.order\nModule.symvers\nMkfile.old\ndkms.conf\n\n#own files\nminctest/*\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: c\n\ndist: trusty\n\nbefore_install:\n  - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test\n  - sudo apt-get update -qq\n  - sudo apt-get install -qq g++-6\n  - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 90\n\ncompiler:\n  - clang\n  - gcc\n\nscript:\n  - cd src/ && make && make test\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Chen YaQi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Lotos WebServer\n\n[![Build Status](https://travis-ci.org/chendotjs/lotos.svg?branch=master)](https://travis-ci.org/chendotjs/lotos)\n\n**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.**\n\n```\n-------------------------------------------------------------------------------\nLanguage                     files          blank        comment           code\n-------------------------------------------------------------------------------\nC                               19            367            275           2428\nC/C++ Header                    12            120            131            514\nmake                             2             17              0             42\nCMake                            1              7              0             18\n-------------------------------------------------------------------------------\nSUM:                            34            511            406           3002\n-------------------------------------------------------------------------------\n```\n\n## Documents\n\n0x01                     | 0x02                               | 0x03                    | 0x04                   | 0x05                       | 0x06\n------------------------ | ---------------------------------- | ----------------------- | ---------------------- | -------------------------- | --------------------------\n[项目目的](./doc/PURPOSE.md) | [并发模型](./doc/CONCURRENCY_MODEL.md) | [设计实现](./doc/DESIGN.md) | [测试调试](./doc/DEBUG.md) | [性能测试](./doc/BENCHMARK.md) | [调试记录](./doc/DEBUG_LOG.md)\n\n## Environment\n\n- gcc >= 5.4 or clang >= 3.5 (gcc4.9 is not supported)\n- Linux only, kernel version >= 3.9\n\n## Usage\n\n### Build\n\n```\n$ git clone https://github.com/chendotjs/lotos.git\n$ cd lotos/src/\n$ make && make test\n```\n\n### Run\n\nUsage: lotos -r html_root_dir [-p port] [-t timeout] [-w worker_num] [-d (debug mode)]\n\n```\n$ ./lotos -r ../www -t 60 -w 4 -p 8888\n```\n\nthen you can visit <http://localhost:8888/>.\n\n## Feature\n\n- EPOLL Edge Trigger mode, more efficient.\n- Nonblocking IO.\n- Multiprocessing, port reuse.\n- TCP connections managed by min-heap data structure.\n- HTTP persistent connection support. Close TCP connection when connection expires.\n- Parse HTTP requests using FSM.\n- Handle errors and exceptions.\n- Memory pool is optional.\n\n## Test\n\nUnit tests are based on [minctest](https://github.com/codeplea/minctest). It is simple, lightweight, and flexible.\n\nMoreover, I contributed some codes to it.\n\n## Benchmark\n\nPlease refer to [BENCHMARK.md](./doc/BENCHMARK.md).\n\n## Reference\n\n[nginx](https://github.com/nginx/nginx)\n\n[node.js http parser](https://github.com/nodejs/http-parser)\n\n[Tkeed](https://github.com/linw7/TKeed)\n"
  },
  {
    "path": "doc/BENCHMARK.md",
    "content": "# Benchmark\n\n常见的压力测试工具有ab，[wrk](https://github.com/wg/wrk)。HTTP/1.1的长连接已经很普及，wrk默认支持长连接，ab需要加上`-k`选项， 否则ab的压力测试会默认采用HTTP/1.0，即每一个请求建立一个TCP连接。\n\n## 测试环境\n\n- 测试环境为本地，配置4核心`Intel(R) Core(TM) i5-2320 CPU @ 3.00GHz`\n- 测试工具为wrk和ab，测试时间为60s，worker数设置为4\n- nginx的worker_processes配置为4\n- 标准1KB静态页面测试\n\n## 测试内容\n\n### 1.使用wrk测试长连接Req/Sec\n\nConcurrency | lotos  | nginx\n----------- | ------ | ------\n10          | 21.94k | 15.42k\n100         | 28.21k | 16.95k\n1000        | 26.01k | 16.62k\n\n### 2.使用ab测试长连接Req/Sec\n\nConcurrency | lotos   | nginx\n----------- | ------- | ------\n10          | 129.11k | 81.89k\n100         | 136.20k | 77.56k\n1000        | 97.56k  | 53.69k\n\n### 3.使用ab测试短连接Req/Sec\n\nConcurrency | lotos  | nginx\n----------- | ------ | ------\n10          | 25.96k | 25.92k\n100         | 26.52k | 26.63k\n1000        | 22.53k | 23.29k\n\n### 4.使用ab短连接测试内存池\n\nConcurrency | USE_MEM_POOL | NO_USE_MEM_POOL\n----------- | ------------ | ---------------\n10          | 25.78k       | 25.75k\n100         | 26.43k       | 26.39k\n1000        | 23.17k       | 23.24k\n\n## 测试总结\n\n我不知道ab的结果为什么比wrk大很多，可能是两者的计算方式不同吧，但起码Lotos确实表现比nginx要好(毕竟功能简单，实现得也很简单)。\n\n并发的瓶颈不在于内存的malloc和free，现代的内存分配器性能已经很强，但是如果可以用内存池在初始化阶段就分配一大片内存，对于短连接的测试，性能应该还是有些许提升的。\n\n从ab短连接和长连接的测试结果来看，连接的建立和关闭才是真的瓶颈所在， TCP的三次握手和四次挥手比较耗费时间。\n\n如果采用内存池，效率基本没有提升。事实上glibc的malloc已经够快了。\n"
  },
  {
    "path": "doc/CONCURRENCY_MODEL.md",
    "content": "# 并发模型\n\n## 0x01 介绍\n\n常见的并发模型主要有Fork-Exec模型和event driven模型。 Lotos采用的就是多进程Reactor模型，属于event driven模型。\n\n传统的网络服务器的构建中，IO模式会按照Blocking/Non-Blocking、Synchronous/Asynchronous这两个标准进行分类，其中Blocking与Synchronous基本上一个意思，而NIO与Async的区别在于NIO强调的是Polling(即用户进程需要时常询问IO操作是否就绪)，而Async强调的是Notification(kernel将IO数据拷贝入用户空间给用户进程使用)。\n\n将同步与否、阻塞与否组合一下，可以得到3种主要的IO模式：\n\n- **同步阻塞**：在此种方式下，用户进程在发起一个IO操作以后，必须等待IO操作的完成，只有当真正完成了IO操作以后，用户进程才能运行。\n\n- **同步非阻塞**：在此种方式下，用户进程发起一个IO操作以后边可返回做其它事情，但是用户进程需要时不时的询问IO操作是否就绪，这就要求用户进程不停的去询问，从而引入不必要的CPU资源浪费。\n\n- **异步非阻塞**：在此种模式下，用户进程只需要发起一个IO操作然后立即返回，等IO操作真正的完成以后，应用程序会得到IO操作完成的通知，此时用户进程只需要对数据进行处理就好了，不需要进行实际的IO读写操作，因为真正的IO读取或者写入操作已经由内核完成了。\n\n前些年在IO并发领域有个很著名的[C10K](http://www.kegel.com/c10k.html)问题，即有10000个客户端需要连上一个服务器并保持TCP连接，客户端会不定时的发送请求给服务器，服务器收到请求后需及时处理并返回结果。\n\n## 0x02 Unix下5种IO模式\n\n### 1.阻塞 I/O（blocking IO）\n\n在linux中，默认情况下所有的socket都是blocking，一个典型的读操作流程大概是这样： ![](https://lukangping.gitbooks.io/java-nio/content/resources/blocking_io.jpg)\n\n当用户进程调用了recvfrom这个系统调用，kernel就开始了IO的第一个阶段：准备数据（对于网络IO来说，很多时候数据在一开始还没有到达。比如，还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来）。这个过程需要等待，也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边，整个进程会被阻塞（当然，是进程自己选择的阻塞）。当kernel一直等到数据准备好了，它就会将数据从kernel中拷贝到用户内存，然后kernel返回结果，用户进程才解除block的状态，重新运行起来。\n\n所以，blocking IO的特点就是在IO执行的两个阶段都被block了。\n\n### 2.非阻塞 I/O（nonblocking IO）\n\nlinux下，可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时，流程是这个样子： ![](https://sfault-image.b0.upaiyun.com/961/916/961916360-570a10b5a0ea9_articlex)\n\n当用户进程发出read操作时，如果kernel中的数据还没有准备好，那么它并不会block用户进程，而是立刻返回一个error。从用户进程角度讲 ，它发起一个read操作后，并不需要等待，而是马上就得到了一个结果。用户进程判断结果是一个error时，它就知道数据还没有准备好，于是它可以再次发送read操作。一旦kernel中的数据准备好了，并且又再次收到了用户进程的system call，那么它马上就将数据拷贝到了用户内存，然后返回。\n\n所以，nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。\n\n### 3.I/O 多路复用（ IO multiplexing）\n\nIO 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)\n\n当用户进程调用了select，那么整个进程会被block，而同时，kernel会\"监视\"所有select负责的socket，当任何一个socket中的数据准备好了，select就会返回。这个时候用户进程再调用read操作，将数据从kernel拷贝到用户进程。\n\n所以，I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符，而这些文件描述符（套接字描述符）其中的任意一个进入读就绪状态，select()函数就可以返回。\n\n这个图和blocking IO的图其实并没有太大的不同，事实上，还更差一些。因为这里需要使用两个system call (select 和 recvfrom)，而blocking IO只调用了一个system call (recvfrom)。但是，用select的优势在于它可以同时处理多个connection。\n\n所以，如果处理的连接数不是很高的话，使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好，可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快，而是在于能处理更多的连接。）\n\n在IO multiplexing Model中，实际中，对于每一个socket，一般都设置成为non-blocking，但是，如上图所示，整个用户的process其实是一直被block的。只不过process是被select这个函数block，而不是被socket IO给block。\n\n### 4.信号驱动式IO\n\n![](https://sfault-image.b0.upaiyun.com/221/712/2217129294-570a10c14d83b_articlex)\n\n### 5.异步 I/O（asynchronous IO）\n\ninux下的asynchronous IO其实用得很少。先看一下它的流程： ![](https://sfault-image.b0.upaiyun.com/401/185/4011854437-570a10c3b6e2c_articlex)\n\n用户进程发起read操作之后，立刻就可以开始去做其它的事。而另一方面，从kernel的角度，当它受到一个asynchronous read之后，首先它会立刻返回，所以不会对用户进程产生任何block。然后，kernel会等待数据准备完成，然后将数据拷贝到用户内存，当这一切都完成之后，kernel会给用户进程发送一个signal，告诉它read操作完成了。\n\n## 0x03 Fork-Exec模型\n\nFork-Exec模型多采用同步阻塞IO，对每一个客户端的socket连接，都需要一个线程来处理，而且在此期间这个线程一直被占用，直到socket关闭。通常由一个独立的Acceptor线程负责监听客户端的连接，接收到客户端连接之后为客户端连接创建一个新的线程处理请求消息，处理完成之后，返回应答消息给客户端，线程销毁，这就是典型的一请求一应答模型。该架构最大的问题就是不具备弹性伸缩能力，当并发访问量增加后，服务端的线程个数和并发访问数成线性正比。创建线程多了，数据频繁拷贝（I/O，内核数据拷贝到用户进程空间、阻塞），进程/线程上下文切换消耗大，从而导致操作系统崩溃。\n\n面对这种问题，相应的改进方式，可以设计一个线程池，复用线程资源，减少线程新建、销毁的开销。但仍然是一种浪费资源的方式。\n\n面对即时聊天(IM)程序这种连接时间长、载体消息短的应用场景，一台服务器可能要撑起几十万的连接量(C100k问题)，Fork-Exec模型是很难应对的。如果业务逻辑中，线程需要进行时间较长的IO操作(例如跨机房访问接口)，则线程大部分时间都在等待IO的返回，白白浪费了大量CPU时间片。\n\n## 0x04 Reactor模型\n\n什么是Reactor？ 换个名词\"non-blocking IO + IO multiplexing\"，意思就显而易见了。Reactor模式用非阻塞IO + IO复用函数来处理并发，程序的基本结构是一个事件循环，以事件驱动和事件回调的方式实现业务逻辑。\n\nLotos就是采用NIO + epoll的方式处理并发。Lotos使用epoll作为同步事件多路分解器(Synchronous Event Demultiplexer)，等待IO事件的发生。当可读或者可写事件发生于某个文件描述符时，事件分解器会去调用之前注册的相应的事件处理器。\n\n对应到Lotos的代码中，[main.c](../src/mainl.c)中`request_handle`和`response_handle`就是对读、写操作的相应handler。\n\n## 0x05 Proactor模型\n\n与Reactor模式对应的就是Proactor模式。Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的，Reactor中需要应用程序自己读取或者写入数据，而 Proactor模式中，应用程序不需要进行实际的读写过程，它只需要从缓存区读取或者写入即可，操作系统会读取缓存区或者写入缓存区到真正的IO设备。Proactor的实现依赖操作系统对异步的支持，目前实现了纯异步操作的操作系统少，实现优秀的如windows IOCP。由于Unix/Linux系统对纯异步的支持有限，应用事件驱动的主流还是通过select/epoll来实现。\n\n## 参考\n\n<https://segmentfault.com/a/1190000004909797#articleHeader14>\n\n<https://segmentfault.com/a/1190000003063859>\n\n<https://segmentfault.com/a/1190000002715832>\n"
  },
  {
    "path": "doc/DEBUG.md",
    "content": "# 测试调试\n\n## 调试工具\n\n- nc\n- curl\n- wrk\n- gdb\n- valgrind\n- perf\n\n## 测试\n\n对每一个数据结构都写了简单的单元测试。我不敢说测试很充分，但是主要的功能点还是测试到了在开发过程中多次大块修改代码结构，测试能跑过就可以比较安心，有单元测试对于重构代码帮助很大！\n\n在代码设计中，考虑到测试的便捷性，需要把每个模块独立起来，尽量减少模块之间的耦合。对于利用recv这样的系统调用获得输入，从而进行测试的过程，会比较麻烦。我在[parse_test.c](../src/test/parse_test.c)使用`buffer_cat`函数模拟recv得到的数据，从而进行parser的测试。在这里，`buffer_t`和parser是较为紧密的耦合关系，不过总体而言，相比较另写客户端提供数据，这种方式还是便捷得多。\n\n[设计实现](./DESIGN.md)里面说过，NIO决定了每一个IO操作的状态包含三种：OK， ERROR， AGAIN。健壮的服务器应该能处理这种慢请求，因此测试代码也实现了一个[慢速client](../src/test/slow_client.c)，每隔30ms发送一个字节给服务器，测试服务器能否正确解析。client里面已经内置了一些请求体。\n"
  },
  {
    "path": "doc/DEBUG_LOG.md",
    "content": "# 问题1\n\n## 问题描述\n\n使用`wrk`进行压力测试，发现`Avg Req/Sec`只有10^2数量级，显然不符合预期。请求返回的`Avg Latency` 也在40ms左右，在单机测试的环境下，这也是很长的响应时间了。\n\n下面是wrk的测试报告：\n```\n$ wrk -t5 -c10 -d10s http://localhost:8888/ -vvv\nwrk  [epoll] Copyright (C) 2012 Will Glozer\nwrk  [epoll] Copyright (C) 2012 Will Glozer\nwrk  [epoll] Copyright (C) 2012 Will Glozer\nRunning 10s test @ http://localhost:8888/\n  5 threads and 10 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    43.72ms    3.16ms  49.97ms   99.21%\n    Req/Sec    45.68      6.97    60.00     88.00%\n  2284 requests in 10.01s, 333.74KB read\nRequests/sec:    228.12\nTransfer/sec:     33.33KB\n\n```\n\n## Debug记录\n\n一开始猜测是在某个函数上开销比较大，但是函数太多，具体到某个函数又不容易定位。于是想起可以用`on-cpu 火焰图`分析函数执行耗时。\n\n针对是否使用http keep-alive选项，对`lotos`使用两种模式的压力测试。\n\n- `ab`版本\n\n  ab版本不加keep-alive，纯http1.0模式，得到![149bcb68/ab-perf-kernel.svg](flamegraph/149bcb68/ab-perf-kernel.svg)\n\n- `wrk`版本\n\n  采用http/1.1协议，支持keep-alive，得到![149bcb68/wrk-perf-kernel.svg](flamegraph/149bcb68/wrk-perf-kernel.svg)\n\nab版本的火焰图很符合预期，每次都会建立、断开tcp连接，请求、回复的handler占有的比重也很正常，总之是个很漂亮的火焰图😉。\n\n相比ab版本，发现wrk版本在epoll_wait上等待较多，cpu的采样点也少得可怜，可以大胆猜测应该是在某处IO上阻塞了。\n\n我给`lotos`的主事件循环中加入了一些调试信息，采用`curl`命令发起keep-alive请求，命令如下\n```\ncurl localhost:8000 localhost:8000 -vvv\n```\n命令的返回如下：\n```\n$ curl localhost:8888 -o /dev/null localhost:8888 -o /dev/null  -vvv\n* Rebuilt URL to: localhost:8888/\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying ::1...\n* TCP_NODELAY set\n* connect to ::1 port 8888 failed: 連線被拒絕\n*   Trying 127.0.0.1...\n* TCP_NODELAY set\n* Connected to localhost (127.0.0.1) port 8888 (#0)\n> GET / HTTP/1.1\n> Host: localhost:8888\n> User-Agent: curl/7.57.0\n> Accept: */*\n>\n< HTTP/1.1 200 OK\n< Date: Tue, 30 Jan 2018 07:08:59 GMT\n< Server: lotos/0.1\n< Content-Type: text/html\n< Content-Length: 6\n< Connection: keep-alive\n<\n{ [6 bytes data]\n100     6  100     6    0     0      6      0  0:00:01 --:--:--  0:00:01   666\n* Connection #0 to host localhost left intact\n* Rebuilt URL to: localhost:8888/\n* Found bundle for host localhost: 0x55fc045f4460 [can pipeline]\n* Re-using existing connection! (#0) with host localhost\n* Connected to localhost (127.0.0.1) port 8888 (#0)\n> GET / HTTP/1.1\n> Host: localhost:8888\n> User-Agent: curl/7.57.0\n> Accept: */*\n>\n< HTTP/1.1 200 OK\n< Date: Tue, 30 Jan 2018 07:08:59 GMT\n< Server: lotos/0.1\n< Content-Type: text/html\n< Content-Length: 6\n< Connection: keep-alive\n<\n{ [6 bytes data]\n100     6  100     6    0     0      6      0  0:00:01 --:--:--  0:00:01     6\n* Connection #0 to host localhost left intact\n\n```\n\n输出中的`Re-using existing connection! (#0) with host localhost`确实表明了curl确实重用了这条tcp连接。\n\n对应的`lotos`的调试信息：\n```\nepoll: 0\nepoll: 0\nepoll: 1\n[2018-01-30 10:12:25] fd:  6 127.0.0.1:43556\n\n[2018-01-30 10:12:25] malloc 0x56418c766c70 1\n\n[2018-01-30 10:12:25] ---------------accept\n\nepoll: 1\n0x56418c766c70 in 6\n[2018-01-30 10:12:25] 0---------------in 108 us\n\n[2018-01-30 10:12:25] 0---------------out 0 us\n\nepoll: 1\n[2018-01-30 10:12:25] 0---------------in 0 us\n\n0x56418c766c70 out 6\nsend 143 bytes\nsend 0 bytes\n[2018-01-30 10:12:25] 0---------------out 130 us\n\nepoll: 1\n0x56418c766c70 in 6\n[2018-01-30 10:12:25] 0---------------in 51 us\n\n[2018-01-30 10:12:25] 0---------------out 0 us\n\nepoll: 1\n[2018-01-30 10:12:25] 0---------------in 0 us\n\n0x56418c766c70 out 6\nsend 143 bytes\nsend 0 bytes\n[2018-01-30 10:12:25] 0---------------out 87 us\n\nepoll: 0\nepoll: 0\nepoll: 1\n0x56418c766c70 in 6\n[2018-01-30 10:12:25] -1---------------in 27 us\n\n[2018-01-30 10:12:25] -1---------------out 0 us\n\n[2018-01-30 10:12:25] prune 0x56418c766c70 1\n\nepoll: 0\nepoll: 0\nepoll: 0\nepoll: 0\n```\n\n问题出现了，\n```\n0x56418c766c70 out 6\nsend 143 bytes\nsend 0 bytes\n[2018-01-30 10:12:25] 0---------------out 87 us\n\nepoll: 0\nepoll: 0\nepoll: 1\n0x56418c766c70 in 6\n[2018-01-30 10:12:25] -1---------------in 27 us\n```\n\n在最后一次给客户端发送完之后，`epoll_wait`出现了两次等待超时！！！很神奇！！！压力测试的工具竟然会延迟给我返回数据？ 或者，是我的程序发送数据存在延迟??? 可是我已经设置了禁用Nagle算法!!!(大四的网络程序设计课吃过瘪，很有印象)。\n\n掏出神器strace执行`strace -tt ./lotos -r /tmp -t 10 -w 4 -d`，发现确实存在两次`epoll_wait`的超时，正好是40ms的阻塞。\n```\n00:39:34.964433 sendfile(7, 8, NULL, 6) = 6\n00:39:34.964535 sendfile(7, 8, NULL, 6) = 0\n00:39:34.964616 close(8)                = 0\n00:39:34.964692 epoll_ctl(5, EPOLL_CTL_MOD, 7, {EPOLLET, {u32=717054400, u64=94206529724864}}) = 0\n00:39:34.964776 epoll_ctl(5, EPOLL_CTL_MOD, 7, {EPOLLIN|EPOLLET, {u32=717054400, u64=94206529724864}}) = 0\n00:39:34.964858 epoll_pwait(5, [], 10240, 20, NULL, 8) = 0\n00:39:34.985093 epoll_pwait(5, [], 10240, 20, NULL, 8) = 0\n00:39:35.005413 epoll_pwait(5, [{EPOLLIN, {u32=717054400, u64=94206529724864}}], 10240, 20, NULL, 8) = 1\n00:39:35.006764 recvfrom(7, \"GET / HTTP/1.1\\r\\nHost: 192.168.1.\"..., 8192, 0, NULL, NULL) = 44\n00:39:35.006875 recvfrom(7, 0x7ffe23f19810, 8192, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)\n00:39:35.006929 openat(3, \"./\", O_RDONLY) = 6\n00:39:35.007000 fstat(6, {st_mode=S_IFDIR|S_ISVTX|0777, st_size=840, ...}) = 0\n00:39:35.007104 openat(6, \"index.html\", O_RDONLY) = 8\n\n```\n掏出wireshark抓包看也是一切正常。囧了。google搜索也没给出答案（关键词不对:sweat:\n\n后来我发现不论`epoll_wait`的超时值设为多少，`Avg Latency` 总是在40ms左右。于是把`40ms`作为关键词google之，发现还真是Nagle算法的问题。估计是我代码写错了吧，去排查一下，发现果真对文件描述符0做了设置TCP_NODELAY的操作！！！我真的是写bug的程序员...\n\n修改了代码之后，问题瞬间就不存在了。\n重新生成火焰图，\n\n- `ab`版本\n\n  ab版本不加keep-alive，纯http1.0模式，得到![8717b1a3/ab-perf-kernel.svg](flamegraph/8717b1a3/ab-perf-kernel.svg)\n\n- `wrk`版本\n\n  采用http/1.1协议，支持keep-alive，得到![8717b1a3/wrk-perf-kernel.svg](flamegraph/8717b1a3/wrk-perf-kernel.svg)\n\n  关于Nagle算法，我觉得这篇文章已经讲得很清晰[《神秘的40毫秒延迟与 TCP_NODELAY》](http://jerrypeng.me/2013/08/mythical-40ms-delay-and-tcp-nodelay/)。\n\n  lotos的设计上确实也是http headers和body分开发送，所以headers被立即发送(\"send data immediately\")，body则被放在缓冲区里面(\"enqueue data in the buffer until an acknowledge is received\")，直到对面40ms的超时ACK来临，才会把body发送出去。\n\n  好吧，虽然我知道很多服务器都设置TCP_NODELAY，包括nginx，以前也只是经验性的设置一下该选项，直到今天踩了坑，才对这玩意有更深的理解。调试过程也是一个学习成长的过程！\n\n\n# 问题2\n\n## 问题描述\n\n使用wrk压力测试时候，程序莫名退出，也没有打印出任何错误信息。\n\n## Debug记录\n\n掏出gdb，在gdb中运行，然后使用wrk压力测试。\n\n```\nProgram received signal SIGPIPE, Broken pipe.\n0x00007ffff7b1751d in send () from /usr/lib/libc.so.6\n```\n\n恍然大悟，原来是没有处理SIGPIPE。[`man 7 pipe`](https://linux.die.net/man/7/pipe)中写的很清楚。\n\n> 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.\n\n当对端关闭了连接，并且本端忽略了SIGPIPE信号，那么`write`系统调用会失败并且设置errno为EPIPE。\n"
  },
  {
    "path": "doc/DESIGN.md",
    "content": "# 设计与实现\n\n## 0x01 重要的结构体\n\n1.配置信息结构(server.h)\n\n```c\ntypedef struct {\n  uint16_t port;   /* listen port */\n  bool debug;      /* debug mode */\n  int timeout;     /* connection expired time */\n  uint32_t worker; /* worker num */\n  char *rootdir;   /* html root directory */\n  int rootdir_fd;  /* fildes of rootdir */\n} config_t;\n```\n\n2.请求/响应缓冲结构(buffer.h)\n\n```c\ntypedef struct {\n  int len;    /* used space length in buf */\n  int free;   /* free space length in buf */\n  char buf[]; /* store data */\n} buffer_t;\n```\n\n采用柔性数组，可以动态增长，围绕该结构设计了一系列操作函数。\n\n3.简单静态字符串结构(ssstr.h)\n\n```c\ntypedef struct {\n  char *str;\n  int len;\n} ssstr_t;\n```\n\n对于字符串字面常量以及`buffer_t`结构的子串，都可以用该结构描述而无需开启新的缓冲区存储。有效节省了空间。\n\n4.HTTP连接结构(connection.h)\n\n```c\nstruct connection {\n  int fd;                   /* connection fildes */\n  struct epoll_event event; /* epoll event */\n  struct sockaddr_in saddr; /* IP socket address */\n  time_t active_time;       /* connection accpet time */\n  int heap_idx;             /* idx at lotos_connections */\n  request_t req;            /* request */\n};\ntypedef struct connection connection_t;\n```\n\n5.请求信息结构(request.h)\n\n```c\nstruct request {\n  struct connection *c;                 /* belonged connection */\n  buffer_t *ib;                         /* request buffer */\n  buffer_t *ob;                         /* response buffer */\n  parse_archive par;                    /* parse_archive */\n  int resource_fd;                      /* resource fildes */\n  int resource_size;                    /* resource size */\n  int status_code;                      /* response status code */\n  int (*req_handler)(struct request *); /* request handler for rl, hd, bd */\n  int (*res_handler)(struct request *); /* response handler for hd bd */\n};\ntypedef struct request request_t;\n```\n\n6.HTTP请求解析结构(http_parser.h)\n\n```c\ntypedef struct {\n  /* parsed request line result */\n  http_method method;\n  http_version version;\n  ssstr_t request_url_string;\n  req_url url;\n\n  /* parsed header lines result */\n  bool keep_alive;       /* connection keep alive */\n  int content_length;    /* request body content_length */\n  int transfer_encoding; /* affect body recv strategy */\n  request_headers_t req_headers;\n\n  int num_headers;\n  ssstr_t header[2]; /* store header every time `parse_header_line` */\n\n  /* preserve buffer_t state, so when recv new data, we can keep parsing */\n  char *next_parse_pos; /* parser position in buffer_t */\n  int state;            /* parser state */\n\n  /* private members, do not modify !!! */\n  char *method_begin;\n  char *url_begin;\n  char *header_line_begin;\n  char *header_colon_pos;\n  char *header_val_begin;\n  char *header_val_end;\n  size_t body_received;\n  int buffer_sent;\n  bool isCRLF_LINE;\n  bool response_done;\n  bool err_req;\n} parse_archive;\n```\n\n7.错误页面结构(response.h)\n\n```c\ntypedef struct {\n  int err_page_fd;             /* fildes of err page */\n  const char *raw_err_page;    /* raw data of err page file */\n  size_t raw_page_size;        /* size of err page file */\n  buffer_t *rendered_err_page; /* buffer contains err msg */\n  size_t rendered_page_size;   /* size of err page file */\n} err_page_t;\n```\n\n## 0x02 数据结构\n\n### 1.最小堆(connection.c)\n\nconnection.c实现了一个最小二叉堆， 依据每个connection的active_time比较大小。因为二叉堆是一个完全二叉树的形态，为了简化编程，可以使用数组来存储堆结点。假设堆顶的position为0，按照层次遍历（BFS）的顺序编号，那么position为`i`的结点，左孩子的position为`2*i+1`, 右孩子的position为`2*i+2`。有了这层关系，可以通过position很快定位到孩子或者父结点的位置。\n\n在这样的基础上，实现了以下操作：\n\n- heap_bubble_up\n- heap_bubble_down\n- heap_insert\n\n### 2.HashMap(dict.c)\n\n实现了一个简单的HashMap。将其结构画出来，应该也是很一目了然的。\n\n![](./dot/dict_structure.png)\n\n有一个小细节需要注意，通常我们需要把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`宏来改变数组长度。\n\n在Lotos中，HashMap的使用场景数据量比较小，就没有考虑负载因子、rehash等因素，仅仅实现了最简单的功能。\n\n- dict_init\n- dict_put\n- dict_get\n- dict_free\n\n## 0x03 NIO配合epoll\n\n所有关于epoll的问题几乎都可以在[`man 7 epoll`](https://linux.die.net/man/7/epoll)中找到。manual写的很详细了，也给了服务器处理事件循环的样例代码，大部分采用epoll的服务器结构无外乎如此。\n\n```c\n\n/* Set up listening socket, 'listen_sock' (socket(),\n   bind(), listen()) */\n\nepollfd = epoll_create(10);\nif (epollfd == -1) {\n    perror(\"epoll_create\");\n    exit(EXIT_FAILURE);\n}\n\nev.events = EPOLLIN;\nev.data.fd = listen_sock;\nif (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {\n    perror(\"epoll_ctl: listen_sock\");\n    exit(EXIT_FAILURE);\n}\n\nfor (;;) {\n    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);\n    if (nfds == -1) {\n        perror(\"epoll_pwait\");\n        exit(EXIT_FAILURE);\n    }\n\n   for (n = 0; n < nfds; ++n) {\n        if (events[n].data.fd == listen_sock) {\n            conn_sock = accept(listen_sock,\n                            (struct sockaddr *) &local, &addrlen);\n            if (conn_sock == -1) {\n                perror(\"accept\");\n                exit(EXIT_FAILURE);\n            }\n            setnonblocking(conn_sock);\n            ev.events = EPOLLIN | EPOLLET;\n            ev.data.fd = conn_sock;\n            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,\n                        &ev) == -1) {\n                perror(\"epoll_ctl: conn_sock\");\n                exit(EXIT_FAILURE);\n            }\n        } else {\n            do_use_fd(events[n].data.fd);\n        }\n    }\n}\n```\n\nepoll中很重要的一个结构体类型是`struct epoll_event`， 它包含了一个`epoll_data_t`类型的联合对象`data`。\n\n```c\ntypedef union epoll_data {\n  void *ptr;\n  int fd;\n  uint32_t u32;\n  uint64_t u64;\n} epoll_data_t;\n\nstruct epoll_event {\n  uint32_t events;   /* Epoll events */\n  epoll_data_t data; /* User data variable */\n};\n```\n\n样例代码中`ev.data.fd = conn_sock;`，直接使用`fd`成员存储新建立连接的文件描述符，这样做简洁明了，但是需要额外的代码去描述一个连接的状态。在Lotos中使用了`connection_t`类型来描述一个连接的属性和状态，所以Lotos使用了`ptr`成员来保存`connection_t`实例的地址。\n\n## 0x04 长连接与超时关闭\n\n相比于HTTP/1.0，在服务器端发送完数据后关闭文件描述符即可，HTTP/1.1支持长连接，这就需要考虑连接的超时关闭问题，否则大量的非活动连接会消耗尽系统资源。\n\nLotos将所有连接注册进一个最小堆，active_time表示该连接上次活动的Epoch时间，active_time越小，表明该连接上次活动时间越早，越有可能超时。当连接建立或者有IO操作时，active_time会被更新，并且在堆中的位置会做相应调整。在每次的事件循环中，都会检查一下堆顶的连接是否超时(connection_prune函数)，若超时则关闭连接、移出最小堆。对连接的操作中假若出现了错误，需要关闭连接，最简单的办法是将其active_time设为很小的一个值(比如0)，然后等待connection_prune函数将其移除。\n\n## 0x05 HTTP请求解析\n\n对于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`，用来记录每个请求解析的状态以及结果。\n\n## 0x06 状态管理\n\nNIO决定了每一个IO操作的状态包含三种：OK， ERROR， AGAIN。Lotos中有两个函数`int request_recv(request_t *r)`和`int response_send(request_t *r)`用于接受和发送数据。在这里三个状态对应的语义应该是：\n\n对于`request_recv`:\n\n- OK: 读到EOF，对端正常关闭连接，无需再读\n- ERROR: 错误，需要进入错误处理环节，如断开连接\n- AGAIN: 还有数据等待读取，等待下次再读\n\n对于`response_send`:\n\n- OK: 全部数据已经发送(并不代表对端收到)，无需再发\n- ERROR: 错误，需要进入错误处理环节，如断开连接\n- AGAIN: 还有数据等待发送，等待下次再发\n\n请求体的状态判定不仅和IO操作的状态相关，也与HTTP协议解析是耦合的。比如对端发出`GE`，在HTTP请求解析模块里，这一部分是合法的请求，但并不完整，我们很大程度上相信这将会是一个HTTP GET请求，所以我们需要再次recv获得更多请求体才能确定。如果接下又收到`T / HTTP/1.0`，那么认为该次请求的Request Line是OK的，否则就是ERROR。所以需要赋予请求的每个状态更明确的语义。\n\n- OK: 请求是合法的\n- ERROR: 错误，需要进入错误处理环节，如断开连接\n- AGAIN: 请求体目前是合法的，但不完整，需要再读\n\n## 0x07 错误处理\n\n作为一个长时间跑在后台的程序而言，需要足够健壮，需要对错误处理做足功夫。调试时就遇到[使用wrk压力测试时，程序退出没有任何错误的假象](./DEBUG_LOG.md)，原因是对于SIGPIPE没有做正确的处理。<https://github.com/chendotjs/snippets/tree/master/network>中给出了触发网络编程中两个常见错误`Connection reset by peer` 和`Broken pipe`的示例代码。在编写代码时候需要对每个syscall做错误检查，否则调试时定位bug则会困难许多。保证内存没有泄露也是很重要的一点，用valgrind测试是最简单的方式。\n"
  },
  {
    "path": "doc/PURPOSE.md",
    "content": "# 项目目的\n\nLotos是个人的业余网络编程项目，也是自己将理论付诸于实践的一个过程。初学c的socket编程是在本科三年级，从那时起就梦想写一个HTTP Server，虽然之后陆陆续续也写过几个和网络编程有关的小项目，比如网络扫描器和[Tinyhttpd](https://github.com/EZLippi/Tinyhttpd)，但也仅限于几百行的规模。\n\n2017年花了约两个月断断续续读完了[《Unix/Linux编程实践教程》](https://book.douban.com/subject/1219329/)，可以说这是一本相见恨晚的书，既适合入门，也适合重新梳理知识体系，我觉得自己有能力重新开一个HTTP Server的坑了。\n\n我对Lotos的期望是“能用”，这就要求能够正确处理syscall error、signal以及很多极端情况下的用户请求。更进一步，在能用的基础上争取做到“高性能”，高兴的是，在[Benchmark](./BENCHMARK.md)中，Lotos的性能还是很不错的。\n\nLotos的开发几乎占用了我一个多月的所有业余时间，私下里认为代码还算工整规范，现在再把开发调试的思考和总结记录成文档，方便与大家交流学习。\n\n#### 开发规范\n\n开发流程比较完整，确定需求 -> 服务器模型选型 -> 数据结构选择 -> 单元测试 -> 开发 -> 集成测试 -> 性能测试。核心的数据结构都配套有单元测试。\n\n#### 数据结构\n\n通过对场景需求和将来扩展性的考量，需要设计合理的、高效的数据结构。Lotos中用了最小堆来淘汰超时过期的HTTP连接，以及使用了HashMap这种KV结构加速对诸如`Mime Type`和 `HTTP Header Handler`的查找。\n\n#### 语言特性\n\n项目中涉及C语言中很多特性，比如条件编译，嵌套宏定义、柔性数组、静态全局变量、函数指针、位运算以及利用内存空间全局区等。\n\n#### 抽象能力\n\n在用户空间进行c编程，由于没有天然的面向对象支持，需要程序员自己封装各种结构体。系统编程中，IO操作基本上都是对文件描述符fd进行操作，在本项目中，一个文件描述符对应着一个TCP连接，把fd封装进`connection_t`作为一个成员变量是一个自然而然的抽象过程。\n\n#### 协议理解\n\n开发HTTP服务器从宏观上来说会对网络协议TCP及其各个状态理解更深，会对HTTP协议主要字段的功能理解更深，会对网络I/O模型认识更深。\n"
  },
  {
    "path": "doc/dot/dict_structure.dot",
    "content": "digraph structs {\n  rankdir=LR\n  node [shape=record];\n  struct1 [label=\"<f0> dict_t|<f1> table|<f2> size_mask|<f3> used\"];\n  struct2 [label=\"<f0> dict_node_t|<f1> 0|<f2> 1|<f3> 2|<f4> ...|<f5> 254|<f6> 255\"];\n  struct3 [label=\"NULL\"]\n  struct4 [label=\"NULL\"]\n  struct5 [label=\"NULL\"]\n  struct6 [label=\"NULL\"]\n  struct7[label=\"<f0> dict_node_t| { k | v | <next>next }\"]\n  struct8[label=\"<f0> dict_node_t| { k | v | <next>next }\"]\n  struct9 [label=\"NULL\"]\n\n  struct1:f1 -> struct2:f0;\n  struct2:f1 -> struct3;\n  struct2:f2 -> struct4;\n  struct2:f5 -> struct5;\n  struct2:f6 -> struct6;\n\n  struct2:f3 -> struct7;\n  struct7:next -> struct8;\n  struct8:next -> struct9;\n}\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 2.6 FATAL_ERROR)\n\nproject(lotos)\n\nif (NOT CMAKE_BUILD_TYPE)\n    message(STATUS \"No build type defined; defaulting to 'Debug'\")\n    set(CMAKE_BUILD_TYPE \"Release\" CACHE STRING\n        \"The type of build. Possible values are: Debug, Release, RelWithDebInfo and MinSizeRel.\")\nendif()\n\nmessage(STATUS \"Host is: ${CMAKE_HOST_SYSTEM}.  Build target is: ${CMAKE_SYSTEM}\")\n\nset(SRC\n  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\n)\n\n\nmessage(STATUS \"OS type: ${CMAKE_SYSTEM_NAME}\")\nif (NOT ${CMAKE_SYSTEM_NAME} MATCHES \"Linux\")\n  message( FATAL_ERROR \"Only Linux is supported, CMake will exit.\" )\nendif()\n\nadd_executable(lotos ${SRC})\nadd_definitions(-DNDEBUG)\nset_property(TARGET lotos PROPERTY C_STANDARD 99)\n"
  },
  {
    "path": "src/Makefile",
    "content": "CFLAGS=-std=c99 -Wall -O3 -DNDEBUG -DUSE_MEM_POOL=1\nOPTFLAGS=\n\nOBJS=misc.o ssstr.o dict.o lotos_epoll.o buffer.o request.o response.o \\\n connection.o http_parser.o server.o mem_pool.o main.o\n\nlotos : $(OBJS)\n\t$(CC) $(CFLAGS) $^ -o $@ $(OPTFLAGS)\n\ntest :\n\tmake -C ./test\n\tmake test -C ./test\n\nformat :\n\tfind . -iname '*.[ch]' -exec clang-format -i -style=\"{ColumnLimit: 80}\" {} +\n\nclean :\n\trm -f *.o lotos\n\n.PHONY : test clean format\n"
  },
  {
    "path": "src/buffer.c",
    "content": "#include \"buffer.h\"\n#include \"misc.h\"\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\nbuffer_t *buffer_init() { return buffer_new(BUFSIZ); }\n\nbuffer_t *buffer_new(size_t initlen) {\n  buffer_t *pb = malloc(sizeof(buffer_t) + initlen + 1); // reserve space for \\0\n  assert(pb != NULL);\n  if (pb == NULL) {\n    return NULL;\n  }\n  pb->len = 0;\n  pb->free = initlen;\n  pb->buf[pb->len] = '\\0';\n  return pb;\n}\n\nvoid buffer_free(buffer_t *pb) {\n  if (pb != NULL) {\n    free(pb);\n  }\n}\n\ninline void buffer_clear(buffer_t *pb) {\n  pb->free += pb->len;\n  pb->len = 0;\n}\n\n/**\n *  newbuf = buffer_cat(oldbuf, \"abc\", 3);\n *  After the call, oldbuf is no longer valid, and must be substituted with\n * newbuf\n *\n * @param pb    [description]\n * @param buf   [description]\n * @param nbyte [description]\n */\nbuffer_t *buffer_cat(buffer_t *pb, const char *buf, size_t nbyte) {\n  buffer_t *npb = NULL;\n\n  if (nbyte <= buffer_avail(pb)) { // no need to realloc\n    memcpy(pb->buf + pb->len, buf, nbyte);\n    pb->len += nbyte;\n    pb->free -= nbyte;\n    pb->buf[pb->len] = '\\0';\n    return pb;\n  }\n  /* realloc */\n  size_t cur_len = buffer_len(pb);\n  size_t new_len = cur_len + nbyte;\n  /* realloc strategy */\n  if (new_len < BUFFER_LIMIT)\n    new_len *= 2;\n  else\n    new_len += BUFFER_LIMIT;\n\n  npb = realloc(pb, sizeof(buffer_t) + new_len + 1);\n  if (npb == NULL)\n    return NULL;\n  memcpy(npb->buf + npb->len, buf, nbyte);\n  npb->len += nbyte;\n  npb->free = new_len - npb->len;\n  npb->buf[npb->len] = '\\0';\n  return npb;\n}\n\nbuffer_t *buffer_cat_cstr(buffer_t *pb, const char *cstr) {\n  return buffer_cat(pb, cstr, strlen(cstr));\n}\n\nvoid buffer_print(buffer_t *pb) {\n  char *p = pb->buf;\n  for (; p != pb->buf + pb->len; p++) {\n    printf(\"%c\", *p);\n    fflush(stdout);\n  }\n  printf(\"\\n\");\n}\n"
  },
  {
    "path": "src/buffer.h",
    "content": "#ifndef _BUFFER_H__\n#define _BUFFER_H__\n#include \"misc.h\"\n#include <stdio.h>\n\n/* usually 2M, a request buffer may increase refering to this */\n#define BUFFER_LIMIT (BUFSIZ * 250)\n\ntypedef struct {\n  int len;    /* used space length in buf */\n  int free;   /* free space length in buf */\n  char buf[]; /* store data */\n} buffer_t;\n\nextern buffer_t *buffer_init();\nextern buffer_t *buffer_new(size_t initlen);\nextern void buffer_free(buffer_t *pb);\nextern void buffer_clear(buffer_t *pb);\nextern buffer_t *buffer_cat(buffer_t *pb, const char *buf, size_t nbyte);\nextern buffer_t *buffer_cat_cstr(buffer_t *pb, const char *cstr);\nextern void buffer_print(buffer_t *pb);\n\nstatic inline size_t buffer_len(const buffer_t *pb) { return pb->len; }\n\nstatic inline size_t buffer_avail(const buffer_t *pb) { return pb->free; }\n\nstatic inline buffer_t *buffer_buffer(const char *buf) {\n  return (buffer_t *)(buf - (sizeof(buffer_t)));\n}\n\nstatic inline char *buffer_end(const buffer_t *pb) {\n  return ((char *)(pb->buf) + pb->len);\n}\n\n#endif\n"
  },
  {
    "path": "src/connection.c",
    "content": "#include \"connection.h\"\n#include \"lotos_epoll.h\"\n#include \"misc.h\"\n#include \"server.h\"\n#include <assert.h>\n#include <fcntl.h>\n#include <netinet/in.h>\n#include <netinet/tcp.h>\n#include <sys/socket.h>\n#include <time.h>\n#include <unistd.h>\n\n/**************************  heap operation start  ****************************/\n\n/* lotos_connections is seen as a binary min heap */\nconnection_t *lotos_connections[MAX_CONNECTION] = {0};\nstatic int heap_size = 0;\n\n#define LCHILD(x) (((x) << 1) + 1)\n#define RCHILD(x) (LCHILD(x) + 1)\n#define PARENT(x) ((x - 1) >> 1)\n#define INHEAP(n, x) (((-1) < (x)) && ((x) < (n)))\n\ninline static void c_swap(int x, int y) {\n  assert(x >= 0 && x < heap_size && y >= 0 && y < heap_size);\n  connection_t *tmp = lotos_connections[x];\n  lotos_connections[x] = lotos_connections[y];\n  lotos_connections[y] = tmp;\n  // update heap_idx\n  lotos_connections[x]->heap_idx = x;\n  lotos_connections[y]->heap_idx = y;\n}\n\n/* used for inserting */\nstatic void heap_bubble_up(int idx) {\n  while (PARENT(idx) >= 0) {\n    int fidx = PARENT(idx); // fidx is father of idx;\n    connection_t *c = lotos_connections[idx];\n    connection_t *fc = lotos_connections[fidx];\n    if (c->active_time >= fc->active_time)\n      break;\n    c_swap(idx, fidx);\n    idx = fidx;\n  }\n}\n\n/* used for extracting or active_time update larger */\nstatic void heap_bubble_down(int idx) {\n  while (TRUE) {\n    int proper_child;\n    int lchild = INHEAP(heap_size, LCHILD(idx)) ? LCHILD(idx) : (heap_size + 1);\n    int rchild = INHEAP(heap_size, RCHILD(idx)) ? RCHILD(idx) : (heap_size + 1);\n    if (lchild > heap_size && rchild > heap_size) { // no children\n      break;\n    } else if (INHEAP(heap_size, lchild) && INHEAP(heap_size, rchild)) {\n      proper_child = lotos_connections[lchild]->active_time <\n                             lotos_connections[rchild]->active_time\n                         ? lchild\n                         : rchild;\n    } else if (lchild > heap_size) {\n      proper_child = rchild;\n    } else {\n      proper_child = lchild;\n    }\n    // idx is the smaller than children\n    if (lotos_connections[idx]->active_time <=\n        lotos_connections[proper_child]->active_time)\n      break;\n    assert(INHEAP(heap_size, proper_child));\n    c_swap(idx, proper_child);\n    idx = proper_child;\n  }\n}\n\nstatic int heap_insert(connection_t *c) {\n  if (heap_size >= MAX_CONNECTION) {\n    return ERROR;\n  }\n  lotos_connections[heap_size++] = c;\n  c->heap_idx = heap_size - 1;\n  heap_bubble_up(heap_size - 1);\n  return 0;\n}\n\nstatic void heap_print() {\n  connection_t *c;\n  int i;\n  printf(\"----------------heap---------------\\n\");\n  for (i = 0; i < heap_size; i++) {\n    c = lotos_connections[i];\n    printf(\"[%2d] %p fd: %2d heap_idx: %2d active_time: %lu\\n\", i, c, c->fd,\n           c->heap_idx, c->active_time);\n  }\n  printf(\"----------------heap---------------\\n\");\n}\n\n/**************************  heap operation end  ******************************/\n\n/* ref `man 7 tcp`. disable Nagle Algorithm, make send(2) flush */\nstatic inline void connection_set_nodelay(connection_t *c) {\n  static int enable = 1;\n  setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable));\n}\n\nconnection_t *connection_init() {\n// Too many malloc would be slow, but mem pool seems not popular right now.\n#if USE_MEM_POOL\n  connection_t *c = pool_alloc(&connection_pool);\n#else\n  connection_t *c = malloc(sizeof(connection_t));\n#endif\n  // init request\n  if (c) {\n    if (request_init(&c->req, c) == ERROR) {\n#if USE_MEM_POOL\n      pool_free(&connection_pool, c);\n#else\n      free(c);\n#endif\n      c = NULL;\n    }\n  }\n  return c;\n}\n\nconnection_t *connection_accept(int fd, struct sockaddr_in *paddr) {\n  connection_t *c = connection_init();\n  assert(c != NULL);\n  if (c == NULL) { // malloc fail\n    connection_close(c);\n    return NULL;\n  }\n\n  /* fill in connection_t */\n  c->fd = fd;\n  if (paddr)\n    c->saddr = *paddr;\n  c->active_time = time(NULL);\n\n  set_fd_nonblocking(c->fd);\n  connection_set_nodelay(c);\n\n  if (connection_register(c) == ERROR) {\n    connection_close(c);\n    return NULL;\n  }\n\n  if (lotos_epoll_add(epoll_fd, c, EPOLLIN | EPOLLET, &c->event) == ERROR) {\n    connection_close(c);\n    return NULL;\n  }\n\n#ifndef NDEBUG\n  char ip_addr[32];\n  uint16_t port;\n  get_internet_address(ip_addr, 32, &port, &c->saddr);\n  lotos_log(LOG_INFO, \"fd: %2d %s:%u\\n\", fd, ip_addr, port);\n  lotos_log(LOG_INFO, \"malloc %p %d\\n\", c, heap_size);\n  (void)heap_print; /* Unused. Silent compiler warning. */\n#endif\n\n  return c;\n}\n\nint connection_register(connection_t *c) {\n  if (heap_size >= MAX_CONNECTION) {\n    return ERROR;\n  }\n  return heap_insert(c);\n}\n\nvoid connection_unregister(connection_t *c) {\n  assert(heap_size >= 1);\n  lotos_connections[c->heap_idx] = lotos_connections[heap_size - 1];\n  lotos_connections[c->heap_idx]->heap_idx = c->heap_idx;\n  heap_size--;\n  heap_bubble_down(c->heap_idx);\n}\n\nstatic inline void connection_free(connection_t *c) {\n  if (c) {\n    buffer_free(c->req.ib);\n    c->req.ib = NULL;\n    buffer_free(c->req.ob);\n    c->req.ob = NULL;\n#if USE_MEM_POOL\n    pool_free(&connection_pool, c);\n#else\n    free(c);\n#endif\n  }\n}\n\n/* close connection, free memory */\nint connection_close(connection_t *c) {\n  if (c == NULL)\n    return OK;\n  /*\n   * explicitly delete fd from epoll_set, see `man 7 epoll` Q6\n   * Q6  Will closing a file descriptor cause it to be removed from all epoll\n   * sets automatically?\n   */\n  lotos_epoll_del(epoll_fd, c, 0, NULL);\n  close(c->fd);\n  connection_unregister(c);\n  connection_free(c);\n  return OK;\n}\n\nvoid connection_prune() {\n  while (heap_size > 0) {\n    connection_t *c = lotos_connections[0];\n    if (time(NULL) - c->active_time >= server_config.timeout) {\n#ifndef NDEBUG\n      // heap_print();\n      lotos_log(LOG_INFO, \"prune %p %d\\n\", c, heap_size);\n#endif\n      connection_close(c);\n    } else\n      break;\n  }\n}\n\ninline bool connecion_is_expired(connection_t *c) {\n  return (time(NULL) - c->active_time > server_config.timeout);\n}\n\nvoid connecion_set_reactivated(connection_t *c) {\n  c->active_time = time(NULL);\n  heap_bubble_down(c->heap_idx);\n}\n\nvoid connecion_set_expired(connection_t *c) {\n  c->active_time = 0; // very old time\n  heap_bubble_up(c->heap_idx);\n}\n\nint set_fd_nonblocking(int fd) {\n  int flag = fcntl(fd, F_GETFL, 0);\n  ABORT_ON(flag == ERROR, \"fcntl: F_GETFL\");\n  flag |= O_NONBLOCK;\n  ABORT_ON(fcntl(fd, F_SETFL, flag) == ERROR, \"fcntl: FSETFL\");\n  return 0;\n}\n"
  },
  {
    "path": "src/connection.h",
    "content": "#ifndef _CONNECTION_H__\n#define _CONNECTION_H__\n#include \"buffer.h\"\n#include \"misc.h\"\n#include \"request.h\"\n#include <netinet/in.h>\n#include <sys/epoll.h>\n#include <sys/socket.h>\n#include <time.h>\n\n#define MAX_CONNECTION (10240)\n\nstruct connection {\n  int fd;                   /* connection fildes */\n  struct epoll_event event; /* epoll event */\n  struct sockaddr_in saddr; /* IP socket address */\n  time_t active_time;       /* connection accpet time */\n  int heap_idx;             /* idx at lotos_connections */\n  request_t req;            /* request */\n};\ntypedef struct connection connection_t;\n\nextern connection_t *connection_accept(int fd, struct sockaddr_in *paddr);\nextern connection_t *connection_init();\nextern int connection_register(connection_t *c);\nextern void connection_unregister(connection_t *c);\nextern int connection_close(connection_t *c);\nextern void connection_prune();\nextern bool connecion_is_expired(connection_t *c);\nextern void connecion_set_reactivated(connection_t *c);\nextern void connecion_set_expired(connection_t *c);\n\nextern int set_fd_nonblocking(int fd);\n\n#endif\n"
  },
  {
    "path": "src/dict.c",
    "content": "#include \"dict.h\"\n#include \"misc.h\"\n#include \"ssstr.h\"\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n// https://www.byvoid.com/zhs/blog/string-hash-compare\nstatic unsigned int ssstr_hash_sdbm(const ssstr_t *key) {\n  const char *str = key->str;\n  unsigned int hash = 0;\n  while (str != (key->str + key->len)) {\n    // equivalent to: hash = 65599*hash + (*str++);\n    hash = (*str++) + (hash << 6) + (hash << 16) - hash;\n  }\n  return (hash & 0x7FFFFFFF);\n}\n\n#define LOTOS_HASH(key) (ssstr_hash_sdbm(key) & DICT_MASK_SIZE)\n\nvoid dict_put(dict_t *dict, const ssstr_t *key, void *val) {\n  unsigned int hash = LOTOS_HASH(key);\n  dict_node_t *p = dict->table[hash];\n  dict_node_t *q = malloc(sizeof(dict_node_t));\n\n  // p == NULL or p != NULL, the same\n  dict_node_init(q, (ssstr_t *)key, val, p);\n  dict->table[hash] = q;\n\n  dict->used++;\n}\n\n/* cause we can put NULL as a valid value, so found_flag is necessary */\nvoid *dict_get(dict_t *dict, const ssstr_t *key, bool *found_flag) {\n  if (found_flag != NULL)\n    *found_flag = FALSE;\n  unsigned int hash = LOTOS_HASH(key);\n  dict_node_t *p = dict->table[hash];\n\n  while (p != NULL) {\n    if (ssstr_cmp(&p->k, key) == 0) {\n      if (found_flag != NULL)\n        *found_flag = TRUE;\n      return p->v;\n    }\n    p = p->next;\n  }\n\n  return NULL;\n}\n\nvoid dict_free(dict_t *d) {\n  if (d == NULL)\n    return;\n  int i;\n  for (i = 0; i < sizeof(d->table) / sizeof(dict_node_t *); i++) {\n    dict_node_t *p = d->table[i];\n    while (p != NULL) {\n      dict_node_t *q = p->next;\n      free(p);\n      d->used--;\n      p = q;\n    }\n  }\n  assert(d->used == 0);\n}\n"
  },
  {
    "path": "src/dict.h",
    "content": "#ifndef _DICT_H__\n#define _DICT_H__\n\n/**\n * dict used with a small amount of data, no need to rehash\n */\n\n#include \"misc.h\"\n#include \"ssstr.h\"\n#include <string.h>\n\n#define DICT_MASK_SIZE (0xFF)\n\ntypedef struct dict_node {\n  ssstr_t k;\n  void *v;\n  struct dict_node *next;\n} dict_node_t;\n\ntypedef struct {\n  dict_node_t *table[DICT_MASK_SIZE + 1];\n  unsigned int size_mask;\n  unsigned int used;\n} dict_t;\n\nstatic inline void dict_init(dict_t *d) {\n  memset(d, 0, sizeof(dict_t));\n  d->size_mask = DICT_MASK_SIZE;\n}\n\nstatic inline void dict_node_init(dict_node_t *node, ssstr_t *s, void *v,\n                                  dict_node_t *next) {\n  node->k = *s;\n  node->v = v;\n  node->next = next;\n}\n\nextern void dict_put(dict_t *dict, const ssstr_t *key, void *val);\nextern void *dict_get(dict_t *dict, const ssstr_t *key, bool *found_flag);\nextern void dict_free(dict_t *d);\n\n#endif\n"
  },
  {
    "path": "src/http_parser.c",
    "content": "#include \"http_parser.h\"\n#include \"buffer.h\"\n#include \"misc.h\"\n#include <assert.h>\n#include <string.h>\n\n#define STR2_EQ(p, q) ((p)[0] == (q)[0] && (p)[1] == (q)[1])\n#define STR3_EQ(p, q) (STR2_EQ(p, q) && (p)[2] == (q)[2])\n#define STR4_EQ(p, q) (STR2_EQ(p, q) && STR2_EQ(p + 2, q + 2))\n#define STR5_EQ(p, q) (STR2_EQ(p, q) && STR3_EQ(p + 2, q + 2))\n#define STR6_EQ(p, q) (STR3_EQ(p, q) && STR3_EQ(p + 3, q + 3))\n#define STR7_EQ(p, q) (STR3_EQ(p, q) && STR4_EQ(p + 3, q + 3))\n\n#define HEADER_SET(header, str_beg, str_end)                                   \\\n  do {                                                                         \\\n    assert(str_beg <= str_end);                                                \\\n    (header)->str = str_beg;                                                   \\\n    (header)->len = (str_end) - (str_beg);                                     \\\n  } while (0)\n\nstatic int parse_method(char *begin, char *end);\nstatic int parse_url(char *begin, char *end, parse_archive *ar);\n\n/* parse request line */\n/**\n * @return\n * OK: request line OK\n * AGAIN: parse to the end of buffer, but no complete request line\n * INVALID_REQUEST request not valid\n */\nint parse_request_line(buffer_t *b, parse_archive *ar) {\n  char ch;\n  char *p;\n  for (p = ar->next_parse_pos; p < buffer_end(b); p++) {\n    ch = *p;\n    switch (ar->state) {\n    case S_RL_BEGIN:\n      switch (ch) {\n      case 'a' ... 'z':\n      case 'A' ... 'Z':\n        /* save current pos, which is METHOD beginning */\n        ar->method_begin = p;\n        ar->state = S_RL_METHOD;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_BEGIN\n\n    case S_RL_METHOD:\n      switch (ch) {\n      case 'a' ... 'z':\n      case 'A' ... 'Z':\n        break;\n      case ' ': {\n        ar->method = parse_method(ar->method_begin, p);\n        if (ar->method == HTTP_INVALID)\n          return INVALID_REQUEST;\n        ar->state = S_RL_SP_BEFORE_URL;\n        break;\n      default:\n        return INVALID_REQUEST;\n      }\n      } // end S_RL_METHOD\n      break;\n    case S_RL_SP_BEFORE_URL:\n      switch (ch) {\n      case ' ':\n      case '\\t': /* ease parser, '\\t' is also considered valid */\n        break;\n      case '\\r':\n      case '\\n':\n        return INVALID_REQUEST;\n      default:\n        ar->state = S_RL_URL;\n        ar->url_begin = p;\n      }\n      break;\n\n    case S_RL_URL:\n      switch (ch) {\n      case ' ':\n      case '\\t':\n        // assume url part has been received completely\n        ar->state = S_RL_SP_BEFORE_VERSION;\n        int url_status = parse_url(ar->url_begin, p, ar);\n        if (url_status)\n          return url_status;\n        break;\n      case '\\r':\n      case '\\n':\n        return INVALID_REQUEST;\n      default:\n        break;\n      } // end S_RL_URL\n      break;\n    case S_RL_SP_BEFORE_VERSION:\n      switch (ch) {\n      case ' ':\n      case '\\t':\n        break;\n      case 'H':\n      case 'h':\n        ar->state = S_RL_VERSION_H;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_SP_BEFORE_RL_VERSION\n      break;\n    case S_RL_VERSION_H:\n      switch (ch) {\n      case 'T':\n      case 't':\n        ar->state = S_RL_VERSION_HT;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_VERSION_H\n      break;\n    case S_RL_VERSION_HT:\n      switch (ch) {\n      case 'T':\n      case 't':\n        ar->state = S_RL_VERSION_HTT;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_VERSION_HT\n      break;\n    case S_RL_VERSION_HTT:\n      switch (ch) {\n      case 'P':\n      case 'p':\n        ar->state = S_RL_VERSION_HTTP;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_VERSION_HTT\n      break;\n    case S_RL_VERSION_HTTP:\n      switch (ch) {\n      case '/':\n        ar->state = S_RL_VERSION_HTTP_SLASH;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_VERSION_HTTP\n      break;\n    case S_RL_VERSION_HTTP_SLASH:\n      switch (ch) {\n      case '0' ... '9':\n        ar->version.http_major = ar->version.http_major * 10 + ch - '0';\n        ar->state = S_RL_VERSION_MAJOR;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_VERSION_HTTP_SLASH\n      break;\n    case S_RL_VERSION_MAJOR:\n      switch (ch) {\n      case '0' ... '9':\n        ar->version.http_major = ar->version.http_major * 10 + ch - '0';\n        if (ar->version.http_major > 1)\n          return INVALID_REQUEST;\n        break;\n      case '.':\n        ar->state = S_RL_VERSION_DOT;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_VERSION_MAJOR\n      break;\n    case S_RL_VERSION_DOT:\n      switch (ch) {\n      case '0' ... '9':\n        ar->version.http_minor = ar->version.http_minor * 10 + ch - '0';\n        ar->state = S_RL_VERSION_MINOR;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_VERSION_DOT\n      break;\n    case S_RL_VERSION_MINOR:\n      switch (ch) {\n      case '0' ... '9':\n        ar->version.http_minor = ar->version.http_minor * 10 + ch - '0';\n        if (ar->version.http_minor > 1)\n          return INVALID_REQUEST;\n        break;\n      case '\\r':\n        ar->state = S_RL_CR_AFTER_VERSION;\n        break;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_VERSION_MINOR\n      break;\n    case S_RL_CR_AFTER_VERSION:\n      switch (ch) {\n      case '\\n':\n        ar->state = S_RL_LF_AFTER_VERSION;\n        /* parse request line done*/\n        goto done;\n      default:\n        return INVALID_REQUEST;\n      } // end S_RL_CR_AFTER_VERSION\n      break;\n    } // end switch(state)\n  }   // end for\n  ar->next_parse_pos = buffer_end(b);\n  return AGAIN;\ndone:;\n  ar->next_parse_pos = p + 1;\n  ar->state = S_HD_BEGIN;\n  return OK;\n}\n\n/* parse header line */\n/**\n * @return\n *  OK: one header line has been parsed\n *  AGAIN: parse to the end of buffer, but no complete header\n *  INVALID_REQUEST request not valid\n *  CRLF_LINE: `\\r\\n`, which means all headers have been parsed\n *\n */\nint parse_header_line(buffer_t *b, parse_archive *ar) {\n  char ch, *p;\n  // NOTE: isCRLF_LINE must be an attribute of ar, cannot be a local variable.\n  // see the change in fix commit.\n  // bool isCRLF_LINE = TRUE;\n  for (p = ar->next_parse_pos; p < buffer_end(b); p++) {\n    ch = *p;\n    switch (ar->state) {\n    case S_HD_BEGIN:\n      switch (ch) {\n      case 'A' ... 'Z':\n      case 'a' ... 'z':\n      case '0' ... '9':\n      case '-':\n        ar->state = S_HD_NAME;\n        ar->header_line_begin = p;\n        ar->isCRLF_LINE = FALSE;\n        break;\n      case '\\r':\n        ar->state = S_HD_CR_AFTER_VAL;\n        ar->isCRLF_LINE = TRUE;\n        break;\n      case ' ':\n      case '\\t':\n        break;\n      default:\n        return INVALID_REQUEST;\n      }\n      break;\n\n    case S_HD_NAME:\n      switch (ch) {\n      case 'A' ... 'Z':\n      case 'a' ... 'z':\n      case '0' ... '9':\n      case '-':\n        break;\n      case ':':\n        ar->state = S_HD_COLON;\n        ar->header_colon_pos = p;\n        break;\n      default:\n        return INVALID_REQUEST;\n      }\n      break;\n\n    case S_HD_COLON:\n      switch (ch) {\n      case ' ':\n      case '\\t':\n        ar->state = S_HD_SP_BEFORE_VAL;\n        break;\n      case '\\r':\n      case '\\n':\n        return INVALID_REQUEST;\n      default:\n        ar->state = S_HD_VAL;\n        ar->header_val_begin = p;\n        break;\n      }\n      break;\n\n    case S_HD_SP_BEFORE_VAL:\n      switch (ch) {\n      case ' ':\n      case '\\t':\n        break;\n      case '\\r':\n      case '\\n':\n        return INVALID_REQUEST;\n      default:\n        ar->state = S_HD_VAL;\n        ar->header_val_begin = p;\n        break;\n      }\n      break;\n\n    case S_HD_VAL:\n      switch (ch) {\n      case '\\r':\n        ar->header_val_end = p;\n        ar->state = S_HD_CR_AFTER_VAL;\n        break;\n      case '\\n':\n        ar->state = S_HD_LF_AFTER_VAL;\n        break;\n      default:\n        break;\n      }\n      break;\n\n    case S_HD_CR_AFTER_VAL:\n      switch (ch) {\n      case '\\n':\n        ar->state = S_HD_LF_AFTER_VAL;\n        goto done;\n      default:\n        return INVALID_REQUEST;\n      }\n      break;\n    } // end switch state\n  }   // end for\n  ar->next_parse_pos = buffer_end(b);\n  return AGAIN;\ndone:;\n  ar->next_parse_pos = p + 1;\n  ar->state = S_HD_BEGIN;\n  ar->num_headers++;\n\n  /* put header name and val into header[2] */\n  HEADER_SET(&ar->header[0], ar->header_line_begin, ar->header_colon_pos);\n  HEADER_SET(&ar->header[1], ar->header_val_begin, ar->header_val_end);\n  return ar->isCRLF_LINE ? CRLF_LINE : OK;\n}\n\nstatic int parse_method(char *begin, char *end) {\n  int len = end - begin;\n  switch (len) {\n  case 3:\n    if (STR3_EQ(begin, \"GET\")) {\n      return HTTP_GET;\n    } else if (STR3_EQ(begin, \"PUT\")) {\n      return HTTP_PUT;\n    } else {\n      return HTTP_INVALID;\n    }\n    break;\n  case 4:\n    if (STR4_EQ(begin, \"POST\")) {\n      return HTTP_POST;\n    } else if (STR4_EQ(begin, \"HEAD\")) {\n      return HTTP_HEAD;\n    } else {\n      return HTTP_INVALID;\n    }\n    break;\n  case 6:\n    if (STR6_EQ(begin, \"DELETE\")) {\n      return HTTP_DELETE;\n    } else {\n      return HTTP_INVALID;\n    }\n    break;\n  default:\n    return HTTP_INVALID;\n  }\n  return HTTP_INVALID;\n}\n\n/**\n * Some Samples:\n *\n * /abc/def/\n * /unp.pdf\n * /unp.pdf/  `dir`\n * /abc.def/set?name=chen&val=newbie\n * /video/life.of.pi.BlueRay.rmvb\n * /video/life.of.pi.BlueRay  `dir`\n */\n/* simple parse url */\nstatic int parse_url(char *begin, char *end, parse_archive *ar) {\n  ar->request_url_string.str = begin;\n  ar->request_url_string.len = end - begin;\n  assert(ar->request_url_string.len >= 0);\n\n  int curr_state = S_URL_BEGIN;\n\n  char ch;\n  char *p = begin;\n  for (; p != end + 1; p++) {\n    ch = *p;\n    switch (curr_state) {\n    case S_URL_BEGIN:\n      switch (ch) {\n      case '/':\n        curr_state = S_URL_ABS_PATH;\n        break;\n      default:\n        return ERROR;\n      }\n      break;\n\n    case S_URL_ABS_PATH:\n      switch (ch) {\n      case ' ':\n        ar->url.abs_path.str = begin;\n        ar->url.abs_path.len = p - begin;\n\n        ar->url.query_string.str = p;\n        ar->url.query_string.len = 0;\n        curr_state = S_URL_END;\n        break;\n      case '?':\n        ar->url.abs_path.str = begin;\n        ar->url.abs_path.len = p - begin;\n        begin = p + 1;\n        curr_state = S_URL_QUERY;\n        break;\n      default:\n        break;\n      }\n      break;\n\n    case S_URL_QUERY:\n      switch (ch) {\n      case ' ':\n        ar->url.query_string.str = begin;\n        ar->url.query_string.len = p - begin;\n        curr_state = S_URL_END;\n      default:\n        break;\n      }\n      break;\n\n    case S_URL_END:\n      goto parse_extension;\n    } // end switch(curr_state)\n  }   // end for\n\nparse_extension:;\n  // directory extension will be corrected in `request_handle_request_line`\n  char *abs_path_end = ar->url.abs_path.str + ar->url.abs_path.len;\n\n  for (p = abs_path_end; p != ar->url.abs_path.str; p--) {\n    if (*p == '.') {\n      ar->url.mime_extension.str = p + 1;\n      ar->url.mime_extension.len = abs_path_end - p - 1;\n      break;\n    } else if (*p == '/')\n      break;\n  }\n\n  return OK;\n}\n\nint parse_header_body_identity(buffer_t *b, parse_archive *ar) {\n  if (ar->content_length <= 0)\n    return OK;\n  // not that complicated, using `next_parse_pos` to indicate where to parse\n  size_t received = buffer_end(b) - ar->next_parse_pos;\n  ar->body_received += received;\n#ifndef NDEBUG\n  printf(\"%s %d\\n\", __FUNCTION__, __LINE__);\n  printf(\"%s %lu\\n\", ar->next_parse_pos, received);\n#endif\n  ar->next_parse_pos = buffer_end(b);\n\n  if (ar->body_received >= ar->content_length) { // full data recv\n    return OK;\n  }\n  return AGAIN; // will conitinue to recv until full data recv or conn timeout\n}\n"
  },
  {
    "path": "src/http_parser.h",
    "content": "#ifndef _PARSER_H__\n#define _PARSER_H__\n\n#include \"buffer.h\"\n#include \"misc.h\"\n#include \"ssstr.h\"\n#include <string.h>\n\n/* RFC2616 */\n/* Ref: https://www.w3.org/Protocols/rfc2616/rfc2616.html */\n/* This is a simple implementation */\n\n/**\n *\n * Request       = Request-Line                   ; Section 5.1\n                          (( general-header       ; Section 4.5\n                         | request-header         ; Section 5.3\n                         | entity-header ) CRLF)  ; Section 7.1\n                        CRLF\n                        [ message-body ]          ; Section 4.3\n */\n\n/**\n *  Request-Line   = Method SP Request-URI SP HTTP-Version CRLF\n */\n\n/**\n *  Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2\n *  http_URL = \"http:\" \"//\" host [ \":\" port ] [ abs_path [ \"?\" query ]]\n */\n\n/**\n *        request-header = Accept                 ; Section 14.1\n                      | Accept-Charset           ; Section 14.2\n                      | Accept-Encoding          ; Section 14.3\n                      | Accept-Language          ; Section 14.4\n                      | Authorization            ; Section 14.8\n                      | Expect                   ; Section 14.20\n                      | From                     ; Section 14.22\n                      | Host                     ; Section 14.23\n                      | If-Match                 ; Section 14.24\n                      | If-Modified-Since        ; Section 14.25\n                      | If-None-Match            ; Section 14.26\n                      | If-Range                 ; Section 14.27\n                      | If-Unmodified-Since      ; Section 14.28\n                      | Max-Forwards             ; Section 14.31\n                      | Proxy-Authorization      ; Section 14.34\n                      | Range                    ; Section 14.35\n                      | Referer                  ; Section 14.36\n                      | TE                       ; Section 14.39\n                      | User-Agent               ; Section 14.43\n */\n\n#define INVALID_REQUEST (-1)\n#define CRLF_LINE (2)\n\n#define MAX_ELEMENT_SIZE (2048)\n\n/* basic http method */\ntypedef enum {\n  HTTP_DELETE,\n  HTTP_GET,\n  HTTP_HEAD,\n  HTTP_POST,\n  HTTP_PUT,\n  HTTP_INVALID,\n} http_method;\n\n/* HTTP protocol version */\ntypedef struct {\n  unsigned short http_major;\n  unsigned short http_minor;\n} http_version;\n\n/* Request URL */\ntypedef struct {\n  ssstr_t abs_path;\n  ssstr_t query_string;\n  ssstr_t mime_extension;\n} req_url;\n\n/* parser state used in fsm */\ntypedef enum {\n  /* request line states */\n  S_RL_BEGIN = 0,\n  S_RL_METHOD,\n  S_RL_SP_BEFORE_URL,\n  S_RL_URL,\n  S_RL_SP_BEFORE_VERSION,\n  S_RL_VERSION_H,\n  S_RL_VERSION_HT,\n  S_RL_VERSION_HTT,\n  S_RL_VERSION_HTTP,\n  S_RL_VERSION_HTTP_SLASH,\n  S_RL_VERSION_MAJOR,\n  S_RL_VERSION_DOT,\n  S_RL_VERSION_MINOR,\n  S_RL_CR_AFTER_VERSION,\n  S_RL_LF_AFTER_VERSION,\n\n  /* header states */\n  S_HD_BEGIN,\n  S_HD_NAME,\n  S_HD_COLON,\n  S_HD_SP_BEFORE_VAL,\n  S_HD_VAL,\n  S_HD_CR_AFTER_VAL,\n  S_HD_LF_AFTER_VAL,\n\n  /* url states */\n  S_URL_BEGIN,\n  S_URL_ABS_PATH,\n  S_URL_QUERY,\n  S_URL_END,\n} parser_state;\n\n// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding\ntypedef enum {\n  TE_IDENTITY = 0,\n  TE_CHUNKED,\n  TE_COMPRESS,\n  TE_DEFLATE,\n  TE_GZIP,\n} transfer_encoding_t;\n\n/* some of the request headers we may parse */\ntypedef struct {\n  ssstr_t cache_control;\n  ssstr_t connection;\n  ssstr_t date;\n  ssstr_t transfer_encoding;\n  ssstr_t accept;\n  ssstr_t accept_charset;\n  ssstr_t accept_encoding;\n  ssstr_t accept_language;\n  ssstr_t cookie;\n  ssstr_t host;\n  ssstr_t if_modified_since;\n  ssstr_t if_unmodified_since;\n  ssstr_t max_forwards;\n  ssstr_t range;\n  ssstr_t referer;\n  ssstr_t user_agent;\n  ssstr_t content_length;\n} request_headers_t;\n\ntypedef struct {\n  /* parsed request line result */\n  http_method method;\n  http_version version;\n  ssstr_t request_url_string;\n  req_url url;\n  \n  /* parsed header lines result */\n  bool keep_alive;       /* connection keep alive */\n  int content_length;    /* request body content_length */\n  int transfer_encoding; /* affect body recv strategy */\n  request_headers_t req_headers;\n\n  int num_headers;\n  ssstr_t header[2]; /* store header every time `parse_header_line` */\n\n  /* preserve buffer_t state, so when recv new data, we can keep parsing */\n  char *next_parse_pos; /* parser position in buffer_t */\n  int state;            /* parser state */\n\n  /* private members, do not modify !!! */\n  char *method_begin;\n  char *url_begin;\n  char *header_line_begin;\n  char *header_colon_pos;\n  char *header_val_begin;\n  char *header_val_end;\n  size_t body_received;\n  int buffer_sent;\n  bool isCRLF_LINE;\n  bool response_done;\n  bool err_req;\n} parse_archive;\n\nstatic inline void parse_archive_init(parse_archive *ar, buffer_t *b) {\n  memset(ar, 0, sizeof(parse_archive));\n  ar->next_parse_pos = b->buf;\n  ar->isCRLF_LINE = TRUE;\n  ar->content_length = -1; // no Content-Length header\n}\n\nextern int parse_request_line(buffer_t *b, parse_archive *ar);\nextern int parse_header_line(buffer_t *b, parse_archive *ar);\nextern int parse_header_body_identity(buffer_t *b, parse_archive *ar);\n\n#endif\n"
  },
  {
    "path": "src/lotos_epoll.c",
    "content": "#include \"lotos_epoll.h\"\n#include \"connection.h\"\n#include \"misc.h\"\n#include <sys/epoll.h>\n\nstruct epoll_event lotos_events[MAX_EVENTS];\n\nint lotos_epoll_create(int flags) { return epoll_create1(flags); }\n\nint lotos_epoll_add(int epoll_fd, connection_t *c, uint32_t events,\n                    struct epoll_event *pev) {\n  FILL_EPOLL_EVENT(pev, c, events);\n  return epoll_ctl(epoll_fd, EPOLL_CTL_ADD, c->fd, pev);\n}\n\nint lotos_epoll_mod(int epoll_fd, connection_t *c, uint32_t events,\n                    struct epoll_event *pev) {\n  FILL_EPOLL_EVENT(pev, c, events);\n  return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, pev);\n}\n\nint lotos_epoll_del(int epoll_fd, connection_t *c, uint32_t events,\n                    struct epoll_event *pev) {\n  (void)pev; /* Unused. Silent compiler warning. */\n  return epoll_ctl(epoll_fd, EPOLL_CTL_DEL, c->fd, NULL);\n}\n\ninline int lotos_epoll_wait(int epoll_fd, struct epoll_event *events,\n                            int max_events, int timeout) {\n  return epoll_wait(epoll_fd, events, max_events, timeout);\n}\n"
  },
  {
    "path": "src/lotos_epoll.h",
    "content": "#ifndef _LOTOS_EPOLL_H__\n#define _LOTOS_EPOLL_H__\n#include \"connection.h\"\n#include \"misc.h\"\n#include <sys/epoll.h>\n\n#define MAX_EVENTS (10240)\n#define FILL_EPOLL_EVENT(pev, pconn, e_events)                                 \\\n  do {                                                                         \\\n    struct epoll_event *ev = pev;                                              \\\n    ev->data.ptr = pconn;                                                      \\\n    ev->events = e_events;                                                     \\\n  } while (0)\n#define CONN_IS_IN(c) ((c)->event.events & EPOLLIN)\n#define CONN_IS_OUT(c) ((c)->event.events & EPOLLOUT)\n\nextern struct epoll_event lotos_events[MAX_EVENTS]; // global\n\nextern int lotos_epoll_create(int flags);\nextern int lotos_epoll_add(int epoll_fd, connection_t *restrict c,\n                           uint32_t events, struct epoll_event *pev);\nextern int lotos_epoll_mod(int epoll_fd, connection_t *restrict c,\n                           uint32_t events, struct epoll_event *pev);\nextern int lotos_epoll_del(int epoll_fd, connection_t *restrict c,\n                           uint32_t events, struct epoll_event *pev);\nextern int lotos_epoll_wait(int epoll_fd, struct epoll_event *events,\n                            int max_events, int timeout);\n\nstatic inline int connection_enable_in(int epoll_fd, connection_t *c) {\n  if (CONN_IS_IN(c))\n    return OK;\n  c->event.events |= EPOLLIN;\n  return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event);\n}\n\nstatic inline int connection_disable_in(int epoll_fd, connection_t *c) {\n  if (!CONN_IS_IN(c))\n    return OK;\n  c->event.events &= ~EPOLLIN;\n  return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event);\n}\n\nstatic inline int connection_enable_out(int epoll_fd, connection_t *c) {\n  if (CONN_IS_OUT(c))\n    return OK;\n  c->event.events |= EPOLLOUT;\n  return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event);\n}\n\nstatic inline int connection_disable_out(int epoll_fd, connection_t *c) {\n  if (!CONN_IS_OUT(c))\n    return OK;\n  c->event.events &= ~EPOLLOUT;\n  return epoll_ctl(epoll_fd, EPOLL_CTL_MOD, c->fd, &c->event);\n}\n\n#endif\n"
  },
  {
    "path": "src/main.c",
    "content": "#include \"lotos_epoll.h\"\n#include \"misc.h\"\n#include \"server.h\"\n#include <assert.h>\n#include <errno.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n\nstatic void usage(const char *executable) {\n  printf(\"Usage: %s -r html_root_dir [-p port] \"\n         \"[-t timeout] [-w worker_num] [-d (debug mode)]\\n\",\n         executable);\n}\n\nint main(int argc, char *argv[]) {\n  if (argc < 2 || config_parse(argc, argv) != OK) {\n    usage(argv[0]);\n    exit(ERROR);\n  }\n\n  if (server_config.debug) {\n    goto work;\n  }\n\n  int nworker = 0;\n  while (TRUE) {\n    if (nworker >= server_config.worker) {\n      int status;\n      waitpid(-1, &status, 0); // wait all children\n      if (WIFEXITED(status))\n        raise(SIGINT);\n      lotos_log(LOG_ERR, \"a worker exit, please restart...\");\n      raise(SIGINT);\n    }\n    pid_t pid = fork();\n    ABORT_ON(pid == -1, \"fork\");\n    if (pid == 0) { // child\n      break;        // child ends up in loop and directly goto `work`\n    }\n    nworker++;\n  }\n\nwork:;\n  int nfds;\n  int i;\n\n  server_setup(server_config.port);\n\n  while (TRUE) {\n    /**\n     * nfds is number of file descriptors ready for the requested I/O or zero\n     * if timeout\n     */\n    nfds = lotos_epoll_wait(epoll_fd, lotos_events, MAX_EVENTS, 20);\n    if (nfds == ERROR) {\n      // if not caused by signal, cannot recover\n      ERR_ON(errno != EINTR, \"lotos_epoll_wait\");\n    }\n\n    for (i = 0; i < nfds; i++) {\n      struct epoll_event *curr_event = lotos_events + i;\n      int fd = *((int *)(curr_event->data.ptr));\n      if (fd == listen_fd) {\n        // accept connection\n        server_accept(listen_fd);\n      } else {\n        // handle connection\n        connection_t *c = curr_event->data.ptr;\n        int status;\n        assert(c != NULL);\n\n        if (connecion_is_expired(c))\n          continue;\n\n        if (curr_event->events & EPOLLIN) {\n          // recv\n          status = request_handle(c);\n          if (status == ERROR)\n            connecion_set_expired(c);\n          else\n            connecion_set_reactivated(c);\n        }\n        if (curr_event->events & EPOLLOUT) {\n          // send\n          status = response_handle(c);\n          if (status == ERROR)\n            connecion_set_expired(c);\n          else\n            connecion_set_reactivated(c);\n        }\n      } // else\n    }   // for loop\n    /* prune expired connections */\n    connection_prune();\n  } // while\n\n  close(epoll_fd);\n  server_shutdown();\n  return OK;\n}\n"
  },
  {
    "path": "src/mem_pool.c",
    "content": "#include \"mem_pool.h\"\n#include \"misc.h\"\n#include <stdlib.h>\n#include <string.h>\n\nstatic inline void *last_block(mem_pool_t *pool) {\n  return ((char *)pool->blocks) + (pool->block_num - 1) * pool->block_size;\n}\n\nint pool_create(mem_pool_t *pool, size_t nmemb, size_t size) {\n  memset(pool, 0, sizeof(mem_pool_t));\n\n  pool->block_num = nmemb;\n  pool->block_size = size > sizeof(void *) ? size : sizeof(void *);\n\n  pool->blocks = calloc(pool->block_num, pool->block_size);\n  if (pool->blocks == NULL) {\n    lotos_log(LOG_ERR, \"memory pool creating failed...exit\");\n    exit(1);\n  }\n\n  pool->next = pool->blocks;\n\n  /**\n   * similar to what we do in ACM, pseudo linkedlist\n   *\n   *\n   * suppose block_size = 30 and block_num = 4, the `blocks` memory:\n   * 0          30         60         90        120\n   * ———————————————————————————————————————————\n   * |30        |60        |90        |NULL    |\n   * ———————————————————————————————————————————\n   *\n   */\n  char *char_ptr = pool->blocks;\n  void **ptr_ptr;\n  for (; char_ptr < (char *)last_block(pool);) {\n    ptr_ptr = (void **)char_ptr;\n    *ptr_ptr = (char_ptr += pool->block_size);\n  }\n  ptr_ptr = (void **)char_ptr;\n  *ptr_ptr = NULL;\n  return OK;\n}\n\nvoid pool_destroy(mem_pool_t *pool) {\n  if (pool->blocks) {\n    free(pool->blocks);\n    pool->blocks = NULL;\n  }\n}\n\nvoid *pool_alloc(mem_pool_t *pool) {\n  if (pool->block_allocated >= pool->block_num)\n    return NULL;\n\n  void *cur = pool->next;\n  void **ptr_ptr = cur;\n  if (ptr_ptr) {\n    pool->next = *ptr_ptr;\n    pool->block_allocated++;\n  }\n  return cur;\n}\n\nvoid pool_free(mem_pool_t *pool, void *ptr) {\n  if (ptr == NULL)\n    return;\n\n  void **ptr_ptr = ptr;\n  *ptr_ptr = pool->next;\n  pool->next = ptr;\n  pool->block_allocated--;\n}\n"
  },
  {
    "path": "src/mem_pool.h",
    "content": "#ifndef _MEM_POOL_H__\n#define _MEM_POOL_H__\n#include <stdlib.h>\n\ntypedef struct {\n  size_t block_num;       /* num of blocks expected to alloc */\n  size_t block_size;      /* size of each block */\n  size_t block_allocated; /* num of allocated blocks */\n  void *next;             /* next block to be allocated */\n  void *blocks;           /* store data */\n} mem_pool_t;\n\nint pool_create(mem_pool_t *pool, size_t nmemb, size_t size);\nvoid pool_destroy(mem_pool_t *pool);\nvoid *pool_alloc(mem_pool_t *pool);\nvoid pool_free(mem_pool_t *pool, void *ptr);\n#endif\n"
  },
  {
    "path": "src/misc.c",
    "content": "#include \"misc.h\"\n#include <stdarg.h>\n#include <stdio.h>\n#include <time.h>\n\nvoid lotos_log(int priority, const char *format, ...) {\n  FILE *fp;\n  const char *color_prefix = NULL;\n\n  switch (priority) {\n  case LOG_ERR:\n    fp = stderr;\n    color_prefix = \"\\033[1;31m\";\n    break;\n  case LOG_INFO:\n    fp = stdout;\n    color_prefix = \"\\033[1;32m\";\n    break;\n  default:\n    fp = stdout;\n  }\n  time_t t = time(NULL);\n  struct tm tm = *localtime(&t);\n  fprintf(fp, \"[%4d-%02d-%02d %02d:%02d:%02d] \", tm.tm_year + 1900,\n          tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);\n\n  color_prefix == NULL ? 0 : fprintf(fp, \"%s\", color_prefix);\n  va_list args;\n  va_start(args, format);\n  vfprintf(fp, format, args);\n  fprintf(fp, \"\\033[0m\\n\");\n  fflush(fp);\n  va_end(args);\n}\n"
  },
  {
    "path": "src/misc.h",
    "content": "#ifndef _MISC_H__\n#define _MISC_H__\n#include <stdio.h>\n#include <stdlib.h>\n\ntypedef enum { TRUE = 1, FALSE = 0 } bool;\n\n#define ERROR (-1)\n#define OK (0)\n#define AGAIN (1)\n\n#define CRLF \"\\r\\n\"\n\n#define ABORT_ON(cond, msg)                                                    \\\n  do {                                                                         \\\n    if (cond) {                                                                \\\n      fprintf(stderr, \"%s: %d: \", __FILE__, __LINE__);                         \\\n      perror(msg);                                                             \\\n      abort();                                                                 \\\n    }                                                                          \\\n  } while (0)\n\n#define ERR_ON(cond, msg)                                                      \\\n  do {                                                                         \\\n    if (cond) {                                                                \\\n      fprintf(stderr, \"%s: %d: \", __FILE__, __LINE__);                         \\\n      perror(msg);                                                             \\\n    }                                                                          \\\n  } while (0)\n\nenum { LOG_ERR, LOG_INFO };\n\nextern void lotos_log(int priority, const char *format, ...);\n\n#endif\n"
  },
  {
    "path": "src/request.c",
    "content": "#define _GNU_SOURCE\n#include \"buffer.h\"\n#include \"connection.h\"\n#include \"dict.h\"\n#include \"http_parser.h\"\n#include \"lotos_epoll.h\"\n#include \"misc.h\"\n#include \"response.h\"\n#include \"server.h\"\n#include \"ssstr.h\"\n#include <assert.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/sendfile.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nstatic int request_handle_request_line(request_t *r);\nstatic int request_handle_headers(request_t *r);\nstatic int request_handle_body(request_t *r);\n\nstatic int response_handle_send_buffer(struct request *r);\nstatic int response_handle_send_file(struct request *r);\nstatic int response_assemble_buffer(struct request *r);\nstatic int response_assemble_err_buffer(struct request *r, int status_code);\n\ntypedef int (*header_handle_method)(request_t *, size_t);\n/* handlers for specific http headers */\nstatic int request_handle_hd_base(request_t *r, size_t offset);\nstatic int request_handle_hd_connection(request_t *r, size_t offset);\nstatic int request_handle_hd_content_length(request_t *r, size_t offset);\nstatic int request_handle_hd_transfer_encoding(request_t *r, size_t offset);\n\ntypedef struct {\n  ssstr_t hd;\n  header_handle_method func;\n  size_t offset;\n} header_func;\n\n#define XX(hd, hd_mn, func)                                                    \\\n  { SSSTR(hd), func, offsetof(request_headers_t, hd_mn) }\n\nstatic header_func hf_list[] = {\n    XX(\"accept\", accept, request_handle_hd_base),\n    XX(\"accept-charset\", accept_charset, request_handle_hd_base),\n    XX(\"accept-encoding\", accept_encoding, request_handle_hd_base),\n    XX(\"accept-language\", accept_language, request_handle_hd_base),\n    XX(\"cache-control\", cache_control, request_handle_hd_base),\n    XX(\"content-length\", content_length, request_handle_hd_content_length),\n    XX(\"connection\", connection, request_handle_hd_connection),\n    XX(\"cookie\", cookie, request_handle_hd_base),\n    XX(\"date\", date, request_handle_hd_base),\n    XX(\"host\", host, request_handle_hd_base),\n    XX(\"if-modified-since\", if_modified_since, request_handle_hd_base),\n    XX(\"if-unmodified-since\", if_unmodified_since, request_handle_hd_base),\n    XX(\"max-forwards\", max_forwards, request_handle_hd_base),\n    XX(\"range\", range, request_handle_hd_base),\n    XX(\"referer\", referer, request_handle_hd_base),\n    XX(\"transfer-encoding\", transfer_encoding,\n       request_handle_hd_transfer_encoding),\n    XX(\"user-agent\", user_agent, request_handle_hd_base),\n};\n#undef XX\n\ndict_t header_handler_dict;\n\nvoid header_handler_dict_init() {\n  dict_init(&header_handler_dict);\n  size_t nsize = sizeof(hf_list) / sizeof(hf_list[0]);\n  int i;\n  for (i = 0; i < nsize; i++) {\n    dict_put(&header_handler_dict, &hf_list[i].hd, (void *)&hf_list[i]);\n  }\n}\n\nvoid header_handler_dict_free() { dict_free(&header_handler_dict); }\n\nint request_init(request_t *r, connection_t *c) {\n  assert(r != NULL);\n  memset(r, 0, sizeof(request_t));\n  r->c = c;\n  r->ib = buffer_init();\n  r->ob = buffer_init();\n  if (r->ib == NULL || r->ob == NULL)\n    return ERROR;\n  parse_archive_init(&r->par, r->ib);\n  r->resource_fd = -1;\n  r->status_code = 200;\n\n  r->req_handler = request_handle_request_line;\n  r->res_handler = response_handle_send_buffer;\n  return OK;\n}\n\n/* when connection keep-alive, reuse request_t in connection_t */\nint request_reset(request_t *r) {\n  buffer_t *ib = r->ib;\n  buffer_t *ob = r->ob;\n  connection_t *c = r->c;\n\n  memset(r, 0, sizeof(request_t));\n  r->ib = ib;\n  r->ob = ob;\n  r->c = c;\n  buffer_clear(ib);\n  buffer_clear(ob);\n  parse_archive_init(&r->par, r->ib);\n  r->resource_fd = -1;\n  r->status_code = 200;\n\n  r->req_handler = request_handle_request_line;\n  r->res_handler = response_handle_send_buffer;\n  return OK;\n}\n\nstatic int request_recv(request_t *r) {\n  char buf[BUFSIZ];\n  int len;\n  while (TRUE) {\n    len = recv(r->c->fd, buf, sizeof(buf), 0);\n    // https://stackoverflow.com/questions/2416944/can-read-function-on-a-connected-socket-return-zero-bytes\n    if (len == 0) { /* if client close, will read EOF */\n      return OK;\n    }\n    if (len == ERROR) {\n      if (errno != EAGAIN) {\n        lotos_log(LOG_ERR, \"recv: %s\", strerror(errno));\n        return ERROR;\n      } else\n        return AGAIN; /* does not have data now */\n    }\n    buffer_cat(r->ib, buf, len); /* append new data to buffer */\n  }\n  return AGAIN;\n}\n\nint request_handle(connection_t *c) {\n  request_t *r = &c->req;\n  int status = request_recv(r);\n  if (status == ERROR || status == OK) /* error or client close */\n    return ERROR;\n  /**\n   * parse request main process:\n   *\n   * do {\n   *  status = parse_request_line()\n   *  status = parse_header()\n   *  *status = parse_body()\n   * }  while (status != error  && !parse_done)\n   *\n   * if status == AGAIN, then exit `request_handle` and try to recv in the next\n   * loop.\n   * if status == ERROR, then exit `request_handle` and expire this connection\n   *\n   */\n  do {\n    status = r->req_handler(r);\n  } while (r->req_handler != NULL && status == OK);\n\n  return status;\n}\n\nstatic int request_handle_request_line(request_t *r) {\n  int status;\n  status = parse_request_line(r->ib, &r->par);\n  if (status == AGAIN) // not a complete request line\n    return AGAIN;\n  if (status != OK) { // INVALID_REQUEST\n    // send error response to client\n    return response_assemble_err_buffer(r, 400);\n  }\n  // status = OK now\n  parse_archive *ar = &(r->par);\n  /* check http version */\n  if (ar->version.http_major > 1 || ar->version.http_minor > 1) {\n    // send 505 error response to client\n    return response_assemble_err_buffer(r, 505);\n  }\n  ar->keep_alive = (ar->version.http_major == 1 && ar->version.http_minor == 1);\n\n  // make `relative_path` a c-style string, really ugly....\n  char *p = ar->url.abs_path.str;\n  while (*p != ' ' && *p != '?')\n    p++;\n  *p = '\\0';\n\n  /* check abs_path */\n  const char *relative_path = NULL;\n  relative_path = ar->url.abs_path.len == 1 && ar->url.abs_path.str[0] == '/'\n                      ? \"./\"\n                      : ar->url.abs_path.str + 1;\n\n  int fd = openat(server_config.rootdir_fd, relative_path, O_RDONLY);\n  if (fd == ERROR) {\n    // send 404 error response to client\n    return response_assemble_err_buffer(r, 404);\n  }\n  struct stat st;\n  fstat(fd, &st);\n\n  if (S_ISDIR(st.st_mode)) { // substitute dir to index.html\n    // fd is a dir fildes\n    int html_fd = openat(fd, \"index.html\", O_RDONLY);\n    close(fd);\n    if (fd == ERROR) {\n      // send 404 error response to client\n      return response_assemble_err_buffer(r, 404);\n    }\n    fd = html_fd;\n    fstat(fd, &st);\n    ssstr_set(&ar->url.mime_extension, \"html\");\n  }\n  r->resource_fd = fd;\n  r->resource_size = st.st_size;\n  r->req_handler = request_handle_headers;\n  return OK;\n}\n\nstatic int request_handle_headers(request_t *r) {\n  int status;\n  buffer_t *b = r->ib;\n  parse_archive *ar = &r->par;\n  while (TRUE) {\n    status = parse_header_line(b, ar);\n    switch (status) {\n    /* not a complete header */\n    case AGAIN:\n      return AGAIN;\n    /* header invalid */\n    case INVALID_REQUEST:\n      // send error response to client\n      return response_assemble_err_buffer(r, 400);\n    /* all headers completed */\n    case CRLF_LINE:\n      goto header_done;\n    /* a header completed */\n    case OK:\n      ssstr_tolower(&r->par.header[0]);\n\n      // handle header individually\n      header_func *hf = dict_get(&header_handler_dict, &r->par.header[0], NULL);\n      if (hf == NULL)\n        break;\n      header_handle_method func = hf->func;\n      size_t offset = hf->offset;\n      if (func != NULL) {\n        status = func(r, offset);\n        if (status != OK)\n          return OK;\n      }\n      break;\n    }\n  }\nheader_done:;\n  r->req_handler = request_handle_body;\n  return OK;\n}\n\nstatic int request_handle_body(request_t *r) {\n  int status;\n  buffer_t *b = r->ib;\n  parse_archive *ar = &r->par;\n  switch (r->par.transfer_encoding) {\n  case TE_IDENTITY:\n    status = parse_header_body_identity(b, ar);\n    break;\n  default:\n    status = ERROR;\n    break;\n  }\n\n  switch (status) {\n  case AGAIN:\n    return AGAIN;\n  case OK:\n    connection_disable_in(epoll_fd, r->c);\n    connection_enable_out(epoll_fd, r->c);\n    r->req_handler = NULL; // body parse done !!! no more handlers\n    response_assemble_buffer(r);\n    return OK;\n  default:\n    // send error response to client\n    return response_assemble_err_buffer(r, 501);\n  }\n  return OK;\n}\n\n/* save header value into the proper position of parse_archive.req_headers */\nint request_handle_hd_base(request_t *r, size_t offset) {\n  parse_archive *ar = &r->par;\n  ssstr_t *header = (ssstr_t *)(((char *)(&ar->req_headers)) + offset);\n  *header = ar->header[1];\n  return OK;\n}\n\nint request_handle_hd_connection(request_t *r, size_t offset) {\n  request_handle_hd_base(r, offset);\n  ssstr_t *connection = &(r->par.req_headers.connection);\n  if (ssstr_caseequal(connection, \"keep-alive\")) {\n    r->par.keep_alive = TRUE;\n  } else if (ssstr_caseequal(connection, \"close\")) {\n    r->par.keep_alive = FALSE;\n  } else {\n    // send error response to client\n    return response_assemble_err_buffer(r, 400);\n  }\n  return OK;\n}\n\nint request_handle_hd_content_length(request_t *r, size_t offset) {\n  request_handle_hd_base(r, offset);\n  ssstr_t *content_length = &(r->par.req_headers.content_length);\n  int len = atoi(content_length->str);\n  if (len <= 0) {\n    // send error response to client\n    return response_assemble_err_buffer(r, 400);\n  }\n  r->par.content_length = len;\n  return OK;\n}\n\n// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding\n// https://imququ.com/post/content-encoding-header-in-http.html\nint request_handle_hd_transfer_encoding(request_t *r, size_t offset) {\n  request_handle_hd_base(r, offset);\n  ssstr_t *transfer_encoding = &(r->par.req_headers.transfer_encoding);\n  if (ssstr_caseequal(transfer_encoding, \"chunked\")) {\n    // may implement, send error response to client\n    r->par.transfer_encoding = TE_CHUNKED;\n    return response_assemble_err_buffer(r, 501);\n  } else if (ssstr_caseequal(transfer_encoding, \"compress\")) {\n    // send error response to client\n    r->par.transfer_encoding = TE_COMPRESS;\n    return response_assemble_err_buffer(r, 501);\n  } else if (ssstr_caseequal(transfer_encoding, \"deflate\")) {\n    // send error response to client\n    r->par.transfer_encoding = TE_DEFLATE;\n    return response_assemble_err_buffer(r, 501);\n  } else if (ssstr_caseequal(transfer_encoding, \"gzip\")) {\n    // send error response to client\n    r->par.transfer_encoding = TE_GZIP;\n    return response_assemble_err_buffer(r, 501);\n  } else if (ssstr_caseequal(transfer_encoding, \"identity\")) {\n    r->par.transfer_encoding = TE_IDENTITY;\n    return response_assemble_err_buffer(r, 501);\n  } else {\n    // send error response to client\n    return response_assemble_err_buffer(r, 400);\n  }\n  return OK;\n}\n\n/**\n * Return:\n * OK: all data sent\n * AGAIN: haven't sent all data\n * ERROR: error\n */\nstatic int response_send(request_t *r) {\n  int len = 0;\n  buffer_t *b = r->ob;\n  char *buf_beg;\n  while (TRUE) {\n    buf_beg = b->buf + r->par.buffer_sent;\n    len = send(r->c->fd, buf_beg, buffer_end(b) - buf_beg, 0);\n    if (len == 0) {\n      buffer_clear(b);\n      r->par.buffer_sent = 0;\n      return OK;\n    } else if (len < 0) {\n      if (errno == EAGAIN)\n        return AGAIN;\n      lotos_log(LOG_ERR, \"send: %s\", strerror(errno));\n      return ERROR;\n    }\n    r->par.buffer_sent += len;\n  }\n  return OK;\n}\n\nint response_handle(struct connection *c) {\n  request_t *r = &c->req;\n  int status;\n  do {\n    status = r->res_handler(r);\n  } while (status == OK && r->par.response_done != TRUE);\n  if (r->par.response_done) { // response done\n    if (r->par.keep_alive) {\n      request_reset(r);\n      connection_disable_out(epoll_fd, c);\n      connection_enable_in(epoll_fd, c);\n    } else\n      return ERROR; // make connection expired\n  }\n  return status;\n}\n\nint response_handle_send_buffer(struct request *r) {\n  int status;\n  status = response_send(r);\n  if (status != OK) {\n    return status;\n  } else {\n    if (r->resource_fd != -1) {\n      r->res_handler = response_handle_send_file;\n      return OK;\n    }\n    r->par.response_done = TRUE;\n    connection_disable_out(epoll_fd, r->c);\n    return OK;\n  }\n  return OK;\n}\n\nint response_handle_send_file(struct request *r) {\n  int len;\n  connection_t *c = r->c;\n  while (TRUE) {\n    // zero copy, make it faster\n    len = sendfile(c->fd, r->resource_fd, NULL, r->resource_size);\n    if (len == 0) {\n      r->par.response_done = TRUE;\n      close(r->resource_fd);\n      return OK;\n    } else if (len < 0) {\n      if (errno == EAGAIN) {\n        return AGAIN;\n      }\n      lotos_log(LOG_ERR, \"sendfile: %s\", strerror(errno));\n      return ERROR;\n    }\n  }\n  return OK;\n}\n\nint response_assemble_buffer(struct request *r) {\n  response_append_status_line(r);\n  response_append_date(r);\n  response_append_server(r);\n  response_append_content_type(r);\n  response_append_content_length(r);\n  response_append_connection(r);\n  response_append_crlf(r);\n  return OK;\n}\n\nint response_assemble_err_buffer(struct request *r, int status_code) {\n  r->req_handler = NULL;\n  r->par.err_req = TRUE;\n  r->status_code = status_code;\n\n  response_append_status_line(r);\n  response_append_date(r);\n  response_append_server(r);\n  response_append_content_type(r);\n  response_append_content_length(r);\n  r->par.keep_alive = FALSE;\n  response_append_connection(r);\n  response_append_crlf(r);\n\n  // add err page html\n  r->ob = buffer_cat_cstr(r->ob, err_page_render_buf());\n\n  connection_disable_in(epoll_fd, r->c);\n  connection_enable_out(epoll_fd, r->c);\n  r->par.response_done = TRUE;\n  return OK;\n}\n"
  },
  {
    "path": "src/request.h",
    "content": "#ifndef _REQUEST_H__\n#define _REQUEST_H__\n\n#include \"buffer.h\"\n#include \"connection.h\"\n#include \"http_parser.h\"\n#include \"misc.h\"\n\nstruct request {\n  struct connection *c;                 /* belonged connection */\n  buffer_t *ib;                         /* request buffer */\n  buffer_t *ob;                         /* response buffer */\n  parse_archive par;                    /* parse_archive */\n  int resource_fd;                      /* resource fildes */\n  int resource_size;                    /* resource size */\n  int status_code;                      /* response status code */\n  int (*req_handler)(struct request *); /* request handler for rl, hd, bd */\n  int (*res_handler)(struct request *); /* response handler for hd bd */\n};\ntypedef struct request request_t;\n\nextern int request_init(request_t *r, struct connection *c);\nextern int request_reset(request_t *r);\nextern int request_handle(struct connection *c);\n\nextern int response_handle(struct connection *c);\n\nvoid header_handler_dict_init();\nvoid header_handler_dict_free();\n\n#endif\n"
  },
  {
    "path": "src/response.c",
    "content": "#define _GNU_SOURCE\n#include \"response.h\"\n#include \"buffer.h\"\n#include \"connection.h\"\n#include \"dict.h\"\n#include \"misc.h\"\n#include \"request.h\"\n#include \"server.h\"\n#include \"ssstr.h\"\n#include <fcntl.h>\n#include <string.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <time.h>\n#include <unistd.h>\n\n#define ERR_HEADER_MAX_LEN (BUFSIZ)\n\nstatic const char *status_table[512];\n\nstatic ssstr_t mime_list[][2] = {\n    {SSSTR(\"word\"), SSSTR(\"application/msword\")},\n    {SSSTR(\"pdf\"), SSSTR(\"application/pdf\")},\n    {SSSTR(\"zip\"), SSSTR(\"application/zip\")},\n    {SSSTR(\"js\"), SSSTR(\"application/javascript\")},\n    {SSSTR(\"gif\"), SSSTR(\"image/gif\")},\n    {SSSTR(\"jpeg\"), SSSTR(\"image/jpeg\")},\n    {SSSTR(\"jpg\"), SSSTR(\"image/jpeg\")},\n    {SSSTR(\"png\"), SSSTR(\"image/png\")},\n    {SSSTR(\"css\"), SSSTR(\"text/css\")},\n    {SSSTR(\"html\"), SSSTR(\"text/html\")},\n    {SSSTR(\"htm\"), SSSTR(\"text/html\")},\n    {SSSTR(\"txt\"), SSSTR(\"text/plain\")},\n    {SSSTR(\"xml\"), SSSTR(\"text/xml\")},\n    {SSSTR(\"svg\"), SSSTR(\"image/svg+xml\")},\n    {SSSTR(\"mp4\"), SSSTR(\"video/mp4\")},\n};\n\nstatic dict_t mime_dict;\n\nvoid mime_dict_init() {\n  size_t nsize = sizeof(mime_list) / sizeof(mime_list[0]);\n  int i;\n  dict_init(&mime_dict);\n  for (i = 0; i < nsize; i++) {\n    dict_put(&mime_dict, &mime_list[i][0], &mime_list[i][1]);\n  }\n}\n\nvoid mime_dict_free() { dict_free(&mime_dict); }\n\nvoid status_table_init() {\n  memset(status_table, 0, sizeof(status_table));\n#define XX(num, name, string) status_table[num] = #num \" \" #string;\n  HTTP_STATUS_MAP(XX);\n#undef XX\n}\n\nstatic err_page_t err_page;\n\nint err_page_init() {\n  err_page_t *ep = &err_page;\n  // open error.html\n  ep->err_page_fd = openat(server_config.rootdir_fd, \"error.html\", O_RDONLY);\n  ABORT_ON(ep->err_page_fd == ERROR, \"openat\");\n  struct stat st;\n  fstat(ep->err_page_fd, &st);\n  ep->raw_page_size = st.st_size;\n\n  ep->rendered_err_page = buffer_init(ep->raw_page_size + ERR_HEADER_MAX_LEN);\n  ABORT_ON(ep->rendered_err_page == NULL, \"buffer_init\");\n\n  // mmap file to memory\n  ep->raw_err_page =\n      mmap(NULL, ep->raw_page_size, PROT_READ, MAP_SHARED, ep->err_page_fd, 0);\n  ABORT_ON(ep->raw_err_page == NULL, \"mmap\");\n  return OK;\n}\n\nvoid err_page_free() {\n  err_page_t *ep = &err_page;\n  buffer_free(ep->rendered_err_page);\n  // munmap\n  munmap((void *)ep->raw_err_page, ep->raw_page_size);\n  close(ep->err_page_fd);\n}\n\ninline char *err_page_render_buf() { return err_page.rendered_err_page->buf; }\n\nvoid response_append_status_line(struct request *r) {\n  buffer_t *b = r->ob;\n  if (r->par.version.http_minor == 1) {\n    r->ob = buffer_cat_cstr(b, \"HTTP/1.1 \");\n  } else {\n    r->ob = buffer_cat_cstr(b, \"HTTP/1.0 \");\n  }\n  // status\n  const char *status_str = status_table[r->status_code];\n  if (status_str != NULL)\n    r->ob = buffer_cat_cstr(b, status_str);\n  r->ob = buffer_cat_cstr(b, CRLF);\n}\n\nvoid response_append_date(struct request *r) {\n  buffer_t *b = r->ob;\n  time_t now = time(NULL);\n  struct tm *tm = gmtime(&now);\n  size_t len = strftime(b->buf + buffer_len(b), b->free,\n                        \"Date: %a, %d %b %Y %H:%M:%S GMT\" CRLF, tm);\n  b->len += len;\n  b->free -= len;\n}\n\nvoid response_append_server(struct request *r) {\n  buffer_t *b = r->ob;\n  r->ob = buffer_cat_cstr(b, \"Server: \");\n  r->ob = buffer_cat_cstr(b, SERVER_NAME CRLF);\n}\n\nvoid response_append_content_type(struct request *r) {\n  buffer_t *b = r->ob;\n  parse_archive *ar = &r->par;\n\n  ssstr_t content_type;\n  if (ar->err_req) {\n    content_type = SSSTR(\"text/html\");\n    goto done;\n  }\n  ssstr_t *v = dict_get(&mime_dict, &ar->url.mime_extension, NULL);\n  if (v != NULL) {\n    content_type = *v;\n  } else {\n    content_type = SSSTR(\"text/html\");\n  }\ndone:;\n  r->ob = buffer_cat_cstr(b, \"Content-Type: \");\n  r->ob = buffer_cat_cstr(b, content_type.str);\n  r->ob = buffer_cat_cstr(b, CRLF);\n}\n\nvoid response_append_content_length(struct request *r) {\n  buffer_t *b = r->ob;\n  char cl[128];\n  int len;\n  buffer_t *rendered_err_page = err_page.rendered_err_page;\n  // modify content_length when sending err page\n  if (r->par.err_req) {\n    len = snprintf(rendered_err_page->buf,\n                   rendered_err_page->free + rendered_err_page->len,\n                   err_page.raw_err_page, status_table[r->status_code]);\n    err_page.rendered_page_size = len;\n  } else {\n    len = r->resource_size;\n  }\n  sprintf(cl, \"Content-Length: %d\" CRLF, len);\n  r->ob = buffer_cat_cstr(b, cl);\n}\n\nvoid response_append_connection(struct request *r) {\n  buffer_t *b = r->ob;\n  ssstr_t connection;\n  if (r->par.keep_alive) {\n    connection = SSSTR(\"Connection: keep-alive\");\n  } else {\n    connection = SSSTR(\"Connection: close\");\n  }\n  r->ob = buffer_cat_cstr(b, connection.str);\n  r->ob = buffer_cat_cstr(b, CRLF);\n}\n\nvoid response_append_crlf(struct request *r) {\n  buffer_t *b = r->ob;\n  r->ob = buffer_cat_cstr(b, CRLF);\n}\n"
  },
  {
    "path": "src/response.h",
    "content": "#ifndef _RESPONSE_H__\n#define _RESPONSE_H__\n\n#include \"buffer.h\"\n#include \"connection.h\"\n#include \"request.h\"\n\n#define SERVER_NAME \"lotos/0.1\"\n\n// https://github.com/nodejs/http-parser/blob/b11de0f5c65bcc1b906f85f4df58883b0c133e7b/http_parser.h#L233\n/* status code */\n#define HTTP_STATUS_MAP(XX)                                                    \\\n  XX(100, CONTINUE, Continue)                                                  \\\n  XX(101, SWITCHING_PROTOCOLS, Switching Protocols)                            \\\n  XX(102, PROCESSING, Processing)                                              \\\n  XX(200, OK, OK)                                                              \\\n  XX(201, CREATED, Created)                                                    \\\n  XX(202, ACCEPTED, Accepted)                                                  \\\n  XX(203, NON_AUTHORITATIVE_INFORMATION, Non - Authoritative Information)      \\\n  XX(204, NO_CONTENT, No Content)                                              \\\n  XX(205, RESET_CONTENT, Reset Content)                                        \\\n  XX(206, PARTIAL_CONTENT, Partial Content)                                    \\\n  XX(207, MULTI_STATUS, Multi - Status)                                        \\\n  XX(208, ALREADY_REPORTED, Already Reported)                                  \\\n  XX(226, IM_USED, IM Used)                                                    \\\n  XX(300, MULTIPLE_CHOICES, Multiple Choices)                                  \\\n  XX(301, MOVED_PERMANENTLY, Moved Permanently)                                \\\n  XX(302, FOUND, Found)                                                        \\\n  XX(303, SEE_OTHER, See Other)                                                \\\n  XX(304, NOT_MODIFIED, Not Modified)                                          \\\n  XX(305, USE_PROXY, Use Proxy)                                                \\\n  XX(307, TEMPORARY_REDIRECT, Temporary Redirect)                              \\\n  XX(308, PERMANENT_REDIRECT, Permanent Redirect)                              \\\n  XX(400, BAD_REQUEST, Bad Request)                                            \\\n  XX(401, UNAUTHORIZED, Unauthorized)                                          \\\n  XX(402, PAYMENT_REQUIRED, Payment Required)                                  \\\n  XX(403, FORBIDDEN, Forbidden)                                                \\\n  XX(404, NOT_FOUND, Not Found)                                                \\\n  XX(405, METHOD_NOT_ALLOWED, Method Not Allowed)                              \\\n  XX(406, NOT_ACCEPTABLE, Not Acceptable)                                      \\\n  XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required)        \\\n  XX(408, REQUEST_TIMEOUT, Request Timeout)                                    \\\n  XX(409, CONFLICT, Conflict)                                                  \\\n  XX(410, GONE, Gone)                                                          \\\n  XX(411, LENGTH_REQUIRED, Length Required)                                    \\\n  XX(412, PRECONDITION_FAILED, Precondition Failed)                            \\\n  XX(413, PAYLOAD_TOO_LARGE, Payload Too Large)                                \\\n  XX(414, URI_TOO_LONG, URI Too Long)                                          \\\n  XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type)                      \\\n  XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable)                        \\\n  XX(417, EXPECTATION_FAILED, Expectation Failed)                              \\\n  XX(421, MISDIRECTED_REQUEST, Misdirected Request)                            \\\n  XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity)                          \\\n  XX(423, LOCKED, Locked)                                                      \\\n  XX(424, FAILED_DEPENDENCY, Failed Dependency)                                \\\n  XX(426, UPGRADE_REQUIRED, Upgrade Required)                                  \\\n  XX(428, PRECONDITION_REQUIRED, Precondition Required)                        \\\n  XX(429, TOO_MANY_REQUESTS, Too Many Requests)                                \\\n  XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large)    \\\n  XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons)        \\\n  XX(500, INTERNAL_SERVER_ERROR, Internal Server Error)                        \\\n  XX(501, NOT_IMPLEMENTED, Not Implemented)                                    \\\n  XX(502, BAD_GATEWAY, Bad Gateway)                                            \\\n  XX(503, SERVICE_UNAVAILABLE, Service Unavailable)                            \\\n  XX(504, GATEWAY_TIMEOUT, Gateway Timeout)                                    \\\n  XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported)              \\\n  XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates)                    \\\n  XX(507, INSUFFICIENT_STORAGE, Insufficient Storage)                          \\\n  XX(508, LOOP_DETECTED, Loop Detected)                                        \\\n  XX(510, NOT_EXTENDED, Not Extended)                                          \\\n  XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required)\n\ntypedef enum {\n#define XX(num, name, string) HTTP_STATUS_##name = num,\n  HTTP_STATUS_MAP(XX)\n#undef XX\n} http_status;\n\nextern void mime_dict_init();\nextern void mime_dict_free();\n\nextern void status_table_init();\n\ntypedef struct {\n  int err_page_fd;             /* fildes of err page */\n  const char *raw_err_page;    /* raw data of err page file */\n  size_t raw_page_size;        /* size of err page file */\n  buffer_t *rendered_err_page; /* buffer contains err msg */\n  size_t rendered_page_size;   /* size of err page file */\n} err_page_t;\n\nextern int err_page_init();\nextern void err_page_free();\nextern char *err_page_render_buf();\n\nextern void response_append_status_line(struct request *r);\nextern void response_append_date(struct request *r);\nextern void response_append_server(struct request *r);\nextern void response_append_content_type(struct request *r);\nextern void response_append_content_length(struct request *r);\nextern void response_append_connection(struct request *r);\nextern void response_append_crlf(struct request *r);\n\n#endif\n"
  },
  {
    "path": "src/server.c",
    "content": "#define _GNU_SOURCE\n#include \"connection.h\"\n#include \"lotos_epoll.h\"\n#include \"misc.h\"\n#include \"response.h\"\n#include \"server.h\"\n#include <arpa/inet.h>\n#include <dirent.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <netdb.h>\n#include <netinet/in.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nconfig_t server_config = {\n    .port = 8888,\n    .debug = FALSE,\n    .timeout = 60,\n    .worker = 4,\n    .rootdir = NULL,\n    .rootdir_fd = -1,\n};\n\nint epoll_fd = -1;\nint listen_fd = -1;\n\n#if USE_MEM_POOL\nmem_pool_t connection_pool;\n#endif\n\nstatic void sigint_handler(int signum);\nstatic int make_server_socket(uint16_t port, int backlog);\nstatic int add_listen_fd();\n\nint config_parse(int argc, char *argv[]) {\n  int c;\n  while ((c = getopt(argc, argv, \"p:dt:w:r:\")) != -1) {\n    switch (c) {\n    case 'p':\n      server_config.port = atoi(optarg);\n      break;\n    case 'd':\n      server_config.debug = TRUE;\n      break;\n    case 't':\n      server_config.timeout = atoi(optarg);\n      break;\n    case 'w':\n      server_config.worker = atoi(optarg);\n      if (server_config.worker > sysconf(_SC_NPROCESSORS_ONLN)) {\n        fprintf(stderr,\n                \"Config ERROR: worker num greater than cpu available cores.\\n\");\n        return ERROR;\n      }\n      break;\n    case 'r':\n      server_config.rootdir = optarg;\n      break;\n    default:\n      return ERROR;\n    }\n  }\n  DIR *dirp = NULL;\n  if (server_config.rootdir != NULL &&\n      (dirp = opendir(server_config.rootdir)) != NULL) {\n    closedir(dirp);\n    server_config.rootdir_fd = open(server_config.rootdir, O_RDONLY);\n    ERR_ON(server_config.rootdir_fd == ERROR, server_config.rootdir);\n    return OK;\n  } else {\n    perror(server_config.rootdir);\n    return ERROR;\n  }\n}\n\nstatic void sigint_handler(int signum) {\n  if (signum == SIGINT) {\n    mime_dict_free();\n    header_handler_dict_free();\n    err_page_free();\n#if USE_MEM_POOL\n    pool_destroy(&connection_pool);\n#endif\n    lotos_log(LOG_INFO, \"lotos(PID: %u) exit...\", getpid());\n    kill(-getpid(), SIGINT);\n    exit(0);\n  }\n}\n\nint server_setup(uint16_t port) {\n  signal(SIGINT, sigint_handler);\n  signal(SIGPIPE, SIG_IGN); // client close, server write will recv sigpipe\n\n  mime_dict_init();\n  header_handler_dict_init();\n  status_table_init();\n  err_page_init();\n#if USE_MEM_POOL\n  pool_create(&connection_pool, 1024, sizeof(connection_t));\n#endif\n\n  listen_fd = make_server_socket(port, 1024);\n  ABORT_ON(listen_fd == ERROR, \"make_server_socket\");\n\n  epoll_fd = lotos_epoll_create(0);\n  ABORT_ON(epoll_fd == ERROR, \"lotos_epoll_create\");\n\n  ABORT_ON(add_listen_fd() == ERROR, \"add_listen_fd\");\n  return OK;\n}\n\nint server_shutdown() { return close(listen_fd); }\n\nint server_accept(int listen_fd) {\n  int conn_fd;\n  static struct sockaddr_in saddr;\n  socklen_t saddrlen = sizeof(struct sockaddr_in);\n  while ((conn_fd = accept(listen_fd, &saddr, &saddrlen)) != ERROR) {\n    connection_accept(conn_fd, &saddr);\n  }\n  return 0;\n}\n\nstatic int make_server_socket(uint16_t port, int backlog) {\n  int listen_fd;\n  struct sockaddr_in saddr;\n\n  listen_fd = socket(AF_INET, SOCK_STREAM, 0);\n  if (listen_fd == ERROR)\n    return ERROR;\n\n  int enable = 1;\n  setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));\n  if (server_config.worker > 1) {\n    // since linux 3.9\n    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable));\n  }\n\n  memset((void *)&saddr, 0, sizeof(saddr));\n\n  saddr.sin_family = AF_INET;\n  saddr.sin_port = htons(port);\n  saddr.sin_addr.s_addr = htonl(INADDR_ANY);\n\n  if (bind(listen_fd, (struct sockaddr *)&saddr, sizeof(saddr)) != OK)\n    return ERROR;\n  if (listen(listen_fd, backlog) != OK)\n    return ERROR;\n  return listen_fd;\n}\n\nstatic int add_listen_fd() {\n  set_fd_nonblocking(listen_fd);\n  struct epoll_event ev;\n  ev.data.ptr = &listen_fd;\n  ev.events = EPOLLIN;\n  return epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);\n}\n\nint get_internet_address(char *host, int len, uint16_t *pport,\n                         struct sockaddr_in *paddr) {\n  strncpy(host, inet_ntoa(paddr->sin_addr), len);\n  *pport = ntohs(paddr->sin_port);\n  return 0;\n}\n"
  },
  {
    "path": "src/server.h",
    "content": "#ifndef _SERVER_H__\n#define _SERVER_H__\n#include \"mem_pool.h\"\n#include \"misc.h\"\n#include <netinet/in.h>\n#include <stdint.h>\n#include <sys/socket.h>\n\ntypedef struct {\n  uint16_t port;   /* listen port */\n  bool debug;      /* debug mode */\n  int timeout;     /* connection expired time */\n  uint32_t worker; /* worker num */\n  char *rootdir;   /* html root directory */\n  int rootdir_fd;  /* fildes of rootdir */\n} config_t;\n\nextern config_t server_config;\nextern int epoll_fd;  /* epoll fd */\nextern int listen_fd; /* server listen fd */\n\nextern int config_parse(int argc,\n                        char *argv[]);   /* parse command line options */\nextern int server_setup(uint16_t port);  /* bind and listen */\nextern int server_shutdown();            /* server shutdown */\nextern int server_accept(int listen_fd); /* accpet all connections */\n\nextern int\nget_internet_address(char *host, int len, uint16_t *pport,\n                     struct sockaddr_in *paddr); /* get ip/port info */\n\n#if USE_MEM_POOL\nextern mem_pool_t connection_pool;\n#endif\n\n#endif\n"
  },
  {
    "path": "src/ssstr.c",
    "content": "#include \"ssstr.h\"\n#include \"misc.h\"\n#include <ctype.h>\n#include <stdio.h>\n#include <string.h>\n\nvoid ssstr_print(const ssstr_t *s) {\n  if (s == NULL || s->str == NULL)\n    return;\n  int i;\n  for (i = 0; i < s->len; i++) {\n    printf(\"%c\", s->str[i]);\n    fflush(stdout);\n  }\n  printf(\"\\n\");\n}\n\nvoid ssstr_tolower(ssstr_t *s) {\n  int i;\n  for (i = 0; i < s->len; i++) {\n    s->str[i] = tolower(s->str[i]);\n  }\n}\n\nint ssstr_cmp(const ssstr_t *l, const ssstr_t *r) {\n  if (l == r || (l->str == r->str && l->len == r->len))\n    return 0;\n  if (l->str == NULL)\n    return -1;\n  if (r->str == NULL)\n    return 1;\n\n  int llen = l->len;\n  int rlen = r->len;\n  int minlen = llen > rlen ? rlen : llen;\n\n  int i;\n  for (i = 0; i < minlen; i++) {\n    if (l->str[i] < r->str[i])\n      return -1;\n    else if (l->str[i] > r->str[i])\n      return 1;\n  }\n\n  return (rlen == llen) ? 0 : ((llen < rlen) ? -1 : 1);\n}\n\nbool ssstr_equal(const ssstr_t *s, const char *cstr) {\n  ssstr_t s2;\n  ssstr_set(&s2, cstr);\n  return ssstr_cmp(s, &s2) == 0 ? TRUE : FALSE;\n}\n"
  },
  {
    "path": "src/ssstr.h",
    "content": "/**\n *  simple static c-style string, used with buffer_t or constant c string,\n *  no need to copy memory, just save time\n */\n#ifndef _SSSTR_H__\n#define _SSSTR_H__\n\n#include \"misc.h\"\n#include <string.h>\n#include <strings.h>\n\ntypedef struct {\n  char *str;\n  int len;\n} ssstr_t;\n\n/**\n * const char *p = \"hello\";\n * SSSTR(\"hello\") ✅\n * SSSTR(p)❌\n */\n#define SSSTR(cstr)                                                            \\\n  (ssstr_t) { cstr, sizeof(cstr) - 1 }\n\nstatic inline void ssstr_init(ssstr_t *s) {\n  s->str = NULL;\n  s->len = 0;\n}\n\nstatic inline void ssstr_set(ssstr_t *s, const char *cstr) {\n  s->str = (char *)cstr;\n  s->len = strlen(cstr);\n}\n\nextern void ssstr_print(const ssstr_t *s);\nextern void ssstr_tolower(ssstr_t *s);\nextern int ssstr_cmp(const ssstr_t *l, const ssstr_t *r);\nextern bool ssstr_equal(const ssstr_t *s, const char *cstr);\n\nstatic inline bool ssstr_caseequal(ssstr_t *s, const char *cstr) {\n  return strncasecmp(s->str, cstr, strlen(cstr)) == 0 ? TRUE : FALSE;\n}\n\n#endif\n"
  },
  {
    "path": "src/test/Makefile",
    "content": "CFLAGS=-g -std=c99 -Wall\nOPTFLAGS=\n\nOBJS=slow_client heap_test buffer_test ssstr_test dict_test parse_test\\\nmem_pool_test\n\nall : $(OBJS)\n\nslow_client : slow_client.c ../misc.c\n\t$(CC) $(CFLAGS) $^ -o $@\n\nheap_test : heap_test.c\n\t$(CC) $(CFLAGS) $^ -o $@\n\nbuffer_test : buffer_test.c ../buffer.c\n\t$(CC) $(CFLAGS) $^ -o $@\n\nssstr_test : ssstr_test.c ../ssstr.c\n\t$(CC) $(CFLAGS) $^ -o $@\n\ndict_test : dict_test.c ../dict.c ../ssstr.c\n\t$(CC) $(CFLAGS) $^ -o $@\n\nparse_test : parse_test.c ../http_parser.c ../buffer.c ../ssstr.c\n\t$(CC) $(CFLAGS) $^ -o $@\n\nmem_pool_test : mem_pool_test.c ../mem_pool.c ../misc.c\n\t$(CC) $(CFLAGS) $^ -o $@\n\ntest :\n\t./heap_test\n\t./buffer_test\n\t./ssstr_test\n\t./dict_test\n\t./parse_test\n\t./mem_pool_test\n\nclean :\n\trm -f $(OBJS)\n"
  },
  {
    "path": "src/test/buffer_test.c",
    "content": "#include \"../buffer.h\"\n#include \"minctest.h\"\n#include <stdio.h>\n#include <string.h>\n\nvoid test1() {\n  buffer_t *buffer = buffer_new(10);\n  lok(buffer->len == 0 && buffer->free == 10);\n\n  buffer = buffer_cat(buffer, \"abc\", 3);\n  lok(buffer->len == 3 && buffer->free == 7);\n\n  lsequal(buffer->buf, \"abc\");\n  buffer_free(buffer);\n}\n\nvoid test2() {\n  buffer_t *buffer = buffer_new(10);\n  lok(buffer->len == 0 && buffer->free == 10);\n\n  buffer = buffer_cat(buffer, \"aaaaabbbbb\", 10);\n  lok(buffer->len == 10 && buffer->free == 0);\n\n  lsequal(buffer->buf, \"aaaaabbbbb\");\n  buffer_free(buffer);\n}\n\nvoid test3() {\n  buffer_t *buffer = buffer_new(10);\n  lok(buffer->len == 0 && buffer->free == 10);\n\n  buffer = buffer_cat(buffer, \"\", 0);\n  lok(buffer->len == 0 && buffer->free == 10);\n\n  lsequal(buffer->buf, \"\");\n  buffer_free(buffer);\n}\n\nvoid test4() {\n  buffer_t *buffer = buffer_new(10);\n  lok(buffer->len == 0 && buffer->free == 10);\n\n  buffer = buffer_cat(buffer, \"abc\", 3);\n  lok(buffer->len == 3 && buffer->free == 7);\n  buffer = buffer_cat(buffer, \"defgh\", 5);\n  lok(buffer->len == 8 && buffer->free == 2);\n\n  lsequal(buffer->buf, \"abcdefgh\");\n  buffer_free(buffer);\n}\n\nvoid test5() {\n  buffer_t *buffer = buffer_new(10);\n  lok(buffer->len == 0 && buffer->free == 10);\n\n  buffer = buffer_cat(buffer, \"abc\", 3);\n  lok(buffer->len == 3 && buffer->free == 7);\n  buffer = buffer_cat(buffer, \"defghijk\", 8);\n  lok(buffer->len == 11 && buffer->free == 11);\n\n  lsequal(buffer->buf, \"abcdefghijk\");\n\n  buffer = buffer_cat_cstr(buffer, \"cstr\");\n  lok(buffer->len == 15 && buffer->free == 7);\n  lsequal(buffer->buf, \"abcdefghijkcstr\");\n\n  buffer_free(buffer);\n}\n\nvoid test6() {\n  buffer_t *buffer = buffer_init();\n  lok(buffer->len == 0 && buffer->free == BUFSIZ);\n\n  static char buf[BUFFER_LIMIT];\n  memset(buf, 'x', sizeof(buf));\n\n  buffer = buffer_cat(buffer, \"abc\", 3);\n  lok(buffer->len == 3 && buffer->free == BUFSIZ - 3);\n  buffer = buffer_cat(buffer, buf, sizeof(buf));\n  lequal(buffer->len, BUFFER_LIMIT + 3);\n  lequal(buffer->free, BUFFER_LIMIT);\n\n  buffer_free(buffer);\n}\n\nvoid test7() {\n  buffer_t *buffer = buffer_new(10);\n  lok(buffer->len == 0 && buffer->free == 10);\n  lok(buffer->buf == buffer_end(buffer));\n\n  buffer = buffer_cat(buffer, \"abc\", 3);\n  lok(buffer->len == 3 && buffer->free == 7);\n\n  lsequal(buffer->buf, \"abc\");\n  lequal('c', *(buffer_end(buffer) - 1));\n  lequal('b', *(buffer_end(buffer) - 2));\n  buffer_free(buffer);\n}\n\nint main(int argc, char const *argv[]) {\n  lrun(\"test1\", test1);\n  lrun(\"test2\", test2);\n  lrun(\"test3\", test3);\n  lrun(\"test4\", test4);\n  lrun(\"test5\", test5);\n  lrun(\"test6\", test6);\n  lrun(\"test7\", test7);\n  lresults();\n  printf(\"\\n\\n\");\n  return lfails != 0;\n}\n"
  },
  {
    "path": "src/test/dict_test.c",
    "content": "#include \"../dict.h\"\n#include \"../ssstr.h\"\n#include \"minctest.h\"\n#include <stdio.h>\n#include <string.h>\n\n// https://github.com/nodejs/http-parser/blob/b11de0f5c65bcc1b906f85f4df58883b0c133e7b/http_parser.h#L233\n#define HTTP_STATUS_MAP(XX)                                                    \\\n  XX(100, CONTINUE, Continue)                                                  \\\n  XX(101, SWITCHING_PROTOCOLS, Switching Protocols)                            \\\n  XX(102, PROCESSING, Processing)                                              \\\n  XX(200, OK, OK)                                                              \\\n  XX(201, CREATED, Created)                                                    \\\n  XX(202, ACCEPTED, Accepted)                                                  \\\n  XX(203, NON_AUTHORITATIVE_INFORMATION, Non - Authoritative Information)      \\\n  XX(204, NO_CONTENT, No Content)                                              \\\n  XX(205, RESET_CONTENT, Reset Content)                                        \\\n  XX(206, PARTIAL_CONTENT, Partial Content)                                    \\\n  XX(207, MULTI_STATUS, Multi - Status)                                        \\\n  XX(208, ALREADY_REPORTED, Already Reported)                                  \\\n  XX(226, IM_USED, IM Used)                                                    \\\n  XX(300, MULTIPLE_CHOICES, Multiple Choices)                                  \\\n  XX(301, MOVED_PERMANENTLY, Moved Permanently)                                \\\n  XX(302, FOUND, Found)                                                        \\\n  XX(303, SEE_OTHER, See Other)                                                \\\n  XX(304, NOT_MODIFIED, Not Modified)                                          \\\n  XX(305, USE_PROXY, Use Proxy)                                                \\\n  XX(307, TEMPORARY_REDIRECT, Temporary Redirect)                              \\\n  XX(308, PERMANENT_REDIRECT, Permanent Redirect)                              \\\n  XX(400, BAD_REQUEST, Bad Request)                                            \\\n  XX(401, UNAUTHORIZED, Unauthorized)                                          \\\n  XX(402, PAYMENT_REQUIRED, Payment Required)                                  \\\n  XX(403, FORBIDDEN, Forbidden)                                                \\\n  XX(404, NOT_FOUND, Not Found)                                                \\\n  XX(405, METHOD_NOT_ALLOWED, Method Not Allowed)                              \\\n  XX(406, NOT_ACCEPTABLE, Not Acceptable)                                      \\\n  XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required)        \\\n  XX(408, REQUEST_TIMEOUT, Request Timeout)                                    \\\n  XX(409, CONFLICT, Conflict)                                                  \\\n  XX(410, GONE, Gone)                                                          \\\n  XX(411, LENGTH_REQUIRED, Length Required)                                    \\\n  XX(412, PRECONDITION_FAILED, Precondition Failed)                            \\\n  XX(413, PAYLOAD_TOO_LARGE, Payload Too Large)                                \\\n  XX(414, URI_TOO_LONG, URI Too Long)                                          \\\n  XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type)                      \\\n  XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable)                        \\\n  XX(417, EXPECTATION_FAILED, Expectation Failed)                              \\\n  XX(421, MISDIRECTED_REQUEST, Misdirected Request)                            \\\n  XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity)                          \\\n  XX(423, LOCKED, Locked)                                                      \\\n  XX(424, FAILED_DEPENDENCY, Failed Dependency)                                \\\n  XX(426, UPGRADE_REQUIRED, Upgrade Required)                                  \\\n  XX(428, PRECONDITION_REQUIRED, Precondition Required)                        \\\n  XX(429, TOO_MANY_REQUESTS, Too Many Requests)                                \\\n  XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large)    \\\n  XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons)        \\\n  XX(500, INTERNAL_SERVER_ERROR, Internal Server Error)                        \\\n  XX(501, NOT_IMPLEMENTED, Not Implemented)                                    \\\n  XX(502, BAD_GATEWAY, Bad Gateway)                                            \\\n  XX(503, SERVICE_UNAVAILABLE, Service Unavailable)                            \\\n  XX(504, GATEWAY_TIMEOUT, Gateway Timeout)                                    \\\n  XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported)              \\\n  XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates)                    \\\n  XX(507, INSUFFICIENT_STORAGE, Insufficient Storage)                          \\\n  XX(508, LOOP_DETECTED, Loop Detected)                                        \\\n  XX(510, NOT_EXTENDED, Not Extended)                                          \\\n  XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required)\n\n#define XX(num, name, string) num,\nint HTTP_STATUS_CODE[] = {HTTP_STATUS_MAP(XX)};\n#undef XX\n\n#define XX(num, name, string) #string,\nconst char *HTTP_STATUS_STRING[] = {HTTP_STATUS_MAP(XX)};\n#undef XX\n\nsize_t nsize = sizeof(HTTP_STATUS_CODE) / sizeof(int);\n\ndict_t dict;\n\nvoid make_dict() {\n  dict_init(&dict);\n  int i;\n  for (i = 0; i < nsize; i++) {\n    ssstr_t key;\n    ssstr_set(&key, HTTP_STATUS_STRING[i]);\n\n    dict_put(&dict, &key, (void *)(HTTP_STATUS_CODE + i));\n  }\n}\n\nvoid test1() {\n  lequal(0, dict.size_mask & (dict.size_mask + 1));\n  lok(dict.used == nsize);\n}\n\nvoid test2() {\n  ssstr_t key = SSSTR(\"Network Authentication Required\");\n  bool found;\n  int *val = dict_get(&dict, &key, &found);\n\n  lequal(found, TRUE);\n  lequal(511, *val);\n}\n\nvoid test3() {\n  ssstr_t key = SSSTR(\"Permanent Redirect\");\n  bool found;\n  int *val = dict_get(&dict, &key, &found);\n\n  lequal(found, TRUE);\n  lequal(308, *val);\n}\n\n/**\n * dict->table[244] -> \"Unauthorized\" -> \"Bad Request\" -> 0x0\n */\nvoid test4() {\n  ssstr_t key = SSSTR(\"Bad Request\");\n  bool found;\n  int *val = dict_get(&dict, &key, &found);\n\n  lequal(found, TRUE);\n  lequal(400, *val);\n}\n\nvoid test5() {\n  ssstr_t key = SSSTR(\"Internal Server Error\");\n  bool found;\n  int *val = dict_get(&dict, &key, &found);\n\n  lequal(found, TRUE);\n  lequal(500, *val);\n}\n\nvoid test6() {\n  ssstr_t key = SSSTR(\"Intrnal Server Error\");\n  bool found;\n  int *val = dict_get(&dict, &key, &found);\n\n  lequal(found, FALSE);\n  lok(NULL == val);\n}\n\nvoid test7() {\n  ssstr_t key = SSSTR(\"\");\n  bool found;\n  int *val = dict_get(&dict, &key, &found);\n\n  lequal(found, FALSE);\n  lok(NULL == val);\n}\n\nvoid test8() {\n  ssstr_t key = SSSTR(\"take it easy, chendotjs\");\n  bool found;\n  int *val = dict_get(&dict, &key, &found);\n\n  lequal(found, FALSE);\n  lok(NULL == val);\n}\n\nint main(int argc, char const *argv[]) {\n  make_dict();\n  lrun(\"test1\", test1);\n  lrun(\"test2\", test2);\n  lrun(\"test3\", test3);\n  lrun(\"test4\", test4);\n  lrun(\"test5\", test5);\n  lrun(\"test6\", test6);\n  lrun(\"test7\", test7);\n  lrun(\"test8\", test8);\n  lresults();\n  dict_free(&dict);\n  printf(\"\\n\\n\");\n  return lfails != 0;\n}\n"
  },
  {
    "path": "src/test/heap_test.c",
    "content": "#include \"../connection.h\"\n#include \"../misc.h\"\n#include \"minctest.h\"\n#include <stdio.h>\n#include <string.h>\n\n/* lotos_connections is seen as a binary min heap */\nconnection_t *lotos_connections[MAX_CONNECTION] = {0};\nstatic int heap_size = 0;\n\n#define LCHILD(x) (((x) << 1) + 1)\n#define RCHILD(x) (LCHILD(x) + 1)\n#define PARENT(x) ((x - 1) >> 1)\n#define INHEAP(n, x) (((-1) < (x)) && ((x) < (n)))\n\ninline static void c_swap(int x, int y) {\n  lok(x >= 0 && x < heap_size && y >= 0 && y < heap_size);\n  connection_t *tmp = lotos_connections[x];\n  lotos_connections[x] = lotos_connections[y];\n  lotos_connections[y] = tmp;\n  // update heap_idx\n  lotos_connections[x]->heap_idx = x;\n  lotos_connections[y]->heap_idx = y;\n}\n\n/* used for inserting */\nstatic void heap_bubble_up(int idx) {\n  while (PARENT(idx) >= 0) {\n    int fidx = PARENT(idx); // fidx is father of idx;\n    connection_t *c = lotos_connections[idx];\n    connection_t *fc = lotos_connections[fidx];\n    if (c->active_time >= fc->active_time)\n      break;\n    c_swap(idx, fidx);\n    idx = fidx;\n  }\n}\n\n/* used for extracting or active_time update larger */\nstatic void heap_bubble_down(int idx) {\n  while (TRUE) {\n    int proper_child;\n    int lchild = INHEAP(heap_size, LCHILD(idx)) ? LCHILD(idx) : (heap_size + 1);\n    int rchild = INHEAP(heap_size, RCHILD(idx)) ? RCHILD(idx) : (heap_size + 1);\n    if (lchild > heap_size && rchild > heap_size) { // no children\n      break;\n    } else if (INHEAP(heap_size, lchild) && INHEAP(heap_size, rchild)) {\n      proper_child = lotos_connections[lchild]->active_time <\n                             lotos_connections[rchild]->active_time\n                         ? lchild\n                         : rchild;\n    } else if (lchild > heap_size) {\n      proper_child = rchild;\n    } else {\n      proper_child = lchild;\n    }\n    // idx is the smaller than children\n    if (lotos_connections[idx]->active_time <=\n        lotos_connections[proper_child]->active_time)\n      break;\n    lok(INHEAP(heap_size, proper_child));\n    c_swap(idx, proper_child);\n    idx = proper_child;\n  }\n}\n\nstatic int heap_insert(connection_t *c) {\n  if (heap_size >= MAX_CONNECTION) {\n    return ERROR;\n  }\n  lotos_connections[heap_size++] = c;\n  c->heap_idx = heap_size - 1;\n  heap_bubble_up(heap_size - 1);\n  return 0;\n}\n\n/******************************  TEST CASES  **********************************/\nstatic int test_bubble_up = 0;\nvoid TEST_BUBBLE_UP(int input[], int ans[], size_t n) {\n  // init\n  memset(lotos_connections, 0, sizeof(lotos_connections));\n  heap_size = 0;\n  int *arr = input;\n\n  connection_t *conn_arr = malloc(sizeof(connection_t) * n);\n  ERR_ON(conn_arr == NULL, \"malloc\");\n  for (int i = 0; i < n; i++) {\n    connection_t *c = conn_arr + i;\n    c->active_time = arr[i];\n    heap_insert(c);\n  }\n\n  // TEST\n  printf(\"%s %d:\\n\", \"TEST_BUBBLE_UP CASE\", test_bubble_up++);\n  lok(heap_size == n);\n  for (int i = 0; i < n; i++) {\n    printf(\"{%d %lu} \", i, lotos_connections[i]->active_time);\n    lok(ans[i] == lotos_connections[i]->active_time);\n  }\n  printf(\"\\n\");\n  free(conn_arr);\n}\n\nstatic int test_bubble_down = 0;\nvoid TEST_BUBBLE_DOWN(int input[], int ans[], size_t n, int pos, int nval) {\n  // init\n  memset(lotos_connections, 0, sizeof(lotos_connections));\n  heap_size = 0;\n  int *arr = input;\n\n  connection_t *conn_arr = malloc(sizeof(connection_t) * n);\n  ERR_ON(conn_arr == NULL, \"malloc\");\n  for (int i = 0; i < n; i++) {\n    connection_t *c = conn_arr + i;\n    c->active_time = arr[i];\n    heap_insert(c);\n  }\n\n  // update value\n  lotos_connections[pos]->active_time = nval;\n  heap_bubble_down(pos);\n\n  // TEST\n  printf(\"%s %d:\\n\", \"TEST_BUBBLE_DOWN CASE\", test_bubble_down++);\n  lok(heap_size == n);\n  for (int i = 0; i < n; i++) {\n    printf(\"{%d %lu} \", i, lotos_connections[i]->active_time);\n    lok(ans[i] == lotos_connections[i]->active_time);\n  }\n  printf(\"\\n\");\n  free(conn_arr);\n}\n\nint main(int argc, char const *argv[]) {\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {0, 4, 1, 9, 6, 5};\n    TEST_BUBBLE_UP(arr, ans, sizeof(arr) / sizeof(int));\n  }\n\n  {\n    int arr[] = {5, 8, 12, 19, 28, 20, 15, 22, 3};\n    int ans[] = {3, 5, 12, 8, 28, 20, 15, 22, 19};\n    TEST_BUBBLE_UP(arr, ans, sizeof(arr) / sizeof(int));\n  }\n\n  {\n    int arr[] = {9, 12, 17, 30, 50, 20, 60, 65, 4, 19};\n    int ans[] = {4, 9, 17, 12, 19, 20, 60, 65, 30, 50};\n    TEST_BUBBLE_UP(arr, ans, sizeof(arr) / sizeof(int));\n  }\n\n  printf(\"\\n\\n\");\n\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {1, 4, 5, 9, 6, 100};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 0, 100);\n  }\n\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {0, 6, 1, 9, 100, 5};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 1, 100);\n  }\n\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {0, 6, 1, 9, 8, 5};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 1, 8);\n  }\n\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {0, 5, 1, 9, 6, 5};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 1, 5);\n  }\n\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {0, 4, 1, 9, 7, 5};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 4, 7);\n  }\n\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {0, 4, 1, 9, 6, 10};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 5, 10);\n  }\n\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {0, 4, 3, 9, 6, 5};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 2, 3);\n  }\n\n  {\n    int arr[] = {4, 1, 5, 9, 6, 0};\n    int ans[] = {0, 4, 5, 9, 6, 10};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 2, 10);\n  }\n\n  {\n    int arr[] = {1};\n    int ans[] = {10};\n    TEST_BUBBLE_DOWN(arr, ans, sizeof(arr) / sizeof(int), 0, 10);\n  }\n  lresults();\n  printf(\"\\n\\n\");\n  return lfails != 0;\n}\n"
  },
  {
    "path": "src/test/mem_pool_test.c",
    "content": "#include \"../mem_pool.h\"\n#include \"minctest.h\"\n#include <stdio.h>\n#include <string.h>\n\nvoid test1() {\n  mem_pool_t pool;\n  pool_create(&pool, 4, 20);\n\n  void **q = pool.blocks;\n  lok(*q == pool.blocks + 20);\n\n  q = pool.blocks + 20;\n  lok(*q == pool.blocks + 40);\n\n  q = pool.blocks + 60;\n  lok(*q == 0);\n\n  pool_destroy(&pool);\n}\n\nvoid test2() {\n  mem_pool_t pool;\n  pool_create(&pool, 4, 20);\n  lok(pool.block_num == 4);\n  lok(pool.block_size == 20);\n  lok(pool.block_allocated == 0);\n\n  void *p;\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks);\n  lok(pool.block_allocated == 1);\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks + 20);\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks + 40);\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks + 60);\n  lok(pool.block_allocated == 4);\n\n  p = pool_alloc(&pool);\n  lok(p == NULL);\n  lok(pool.block_allocated == 4);\n\n  p = pool_alloc(&pool);\n  lok(p == NULL);\n  lok(pool.block_allocated == 4);\n\n  pool_destroy(&pool);\n}\n\nvoid test3() {\n  mem_pool_t pool;\n  pool_create(&pool, 4, 20);\n  lok(pool.block_num == 4);\n  lok(pool.block_size == 20);\n  lok(pool.block_allocated == 0);\n\n  void *p;\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks);\n  lok(pool.block_allocated == 1);\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks + 20);\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks + 40);\n\n  pool_free(&pool, p);\n  lok(pool.block_allocated == 2);\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks + 40);\n  lok(pool.block_allocated == 3);\n\n  p = pool_alloc(&pool);\n  lok(p == pool.blocks + 60);\n  lok(pool.block_allocated == 4);\n\n  p = pool_alloc(&pool);\n  lok(p == NULL);\n  lok(pool.block_allocated == 4);\n\n  p = pool_alloc(&pool);\n  lok(p == NULL);\n  lok(pool.block_allocated == 4);\n\n  pool_destroy(&pool);\n}\n\nint main(int argc, char const *argv[]) {\n  lrun(\"test1\", test1);\n  lrun(\"test2\", test2);\n  lrun(\"test3\", test3);\n  lresults();\n  printf(\"\\n\\n\");\n  return lfails != 0;\n}\n"
  },
  {
    "path": "src/test/minctest.h",
    "content": "/*\n *\n * MINCTEST - Minimal C Test Library - 0.2.0\n *\n * Copyright (c) 2014-2017 Lewis Van Winkle\n *\n * http://CodePlea.com\n *\n * This software is provided 'as-is', without any express or implied\n * warranty. In no event will the authors be held liable for any damages\n * arising from the use of this software.\n *\n * Permission is granted to anyone to use this software for any purpose,\n * including commercial applications, and to alter it and redistribute it\n * freely, subject to the following restrictions:\n *\n * 1. The origin of this software must not be misrepresented; you must not\n *    claim that you wrote the original software. If you use this software\n *    in a product, an acknowledgement in the product documentation would be\n *    appreciated but is not required.\n * 2. Altered source versions must be plainly marked as such, and must not be\n *    misrepresented as being the original software.\n * 3. This notice may not be removed or altered from any source distribution.\n *\n */\n\n\n\n/*\n * MINCTEST - Minimal testing library for C\n *\n *\n * Example:\n *\n *      void test1() {\n *           lok('a' == 'a');\n *      }\n *\n *      void test2() {\n *           lequal(5, 6);\n *           lfequal(5.5, 5.6);\n *      }\n *\n *      int main() {\n *           lrun(\"test1\", test1);\n *           lrun(\"test2\", test2);\n *           lresults();\n *           return lfails != 0;\n *      }\n *\n *\n *\n * Hints:\n *      All functions/variables start with the letter 'l'.\n *\n */\n\n\n#ifndef __MINCTEST_H__\n#define __MINCTEST_H__\n\n#include <stdio.h>\n#include <math.h>\n#include <time.h>\n#include <string.h>\n\n\n/* How far apart can floats be before we consider them unequal. */\n#ifndef LTEST_FLOAT_TOLERANCE\n#define LTEST_FLOAT_TOLERANCE 0.001\n#endif\n\n\n/* Track the number of passes, fails. */\n/* NB this is made for all tests to be in one file. */\nstatic int ltests = 0;\nstatic int lfails = 0;\n\n\n/* Display the test results. */\n#define lresults() do {\\\n    if (lfails == 0) {\\\n        printf(\"ALL TESTS PASSED (%d/%d)\\n\", ltests, ltests);\\\n    } else {\\\n        printf(\"SOME TESTS FAILED (%d/%d)\\n\", ltests-lfails, ltests);\\\n    }\\\n} while (0)\n\n\n/* Run a test. Name can be any string to print out, test is the function name to call. */\n#define lrun(name, test) do {\\\n    const int ts = ltests;\\\n    const int fs = lfails;\\\n    const clock_t start = clock();\\\n    printf(\"\\t%-14s\", name);\\\n    test();\\\n    printf(\"pass:%2d   fail:%2d   %4dms\\n\",\\\n            (ltests-ts)-(lfails-fs), lfails-fs,\\\n            (int)((clock() - start) * 1000 / CLOCKS_PER_SEC));\\\n} while (0)\n\n\n/* Assert a true statement. */\n#define lok(test) do {\\\n    ++ltests;\\\n    if (!(test)) {\\\n        ++lfails;\\\n        printf(\"%s:%d error \\n\", __FILE__, __LINE__);\\\n    }} while (0)\n\n\n/* Prototype to assert equal. */\n#define lequal_base(equality, a, b, format) do {\\\n    ++ltests;\\\n    if (!(equality)) {\\\n        ++lfails;\\\n        printf(\"%s:%d (\"format \" != \" format\")\\n\", __FILE__, __LINE__, (a), (b));\\\n    }} while (0)\n\n\n/* Assert two integers are equal. */\n#define lequal(a, b)\\\n    lequal_base((a) == (b), a, b, \"%d\")\n\n\n/* Assert two floats are equal (Within LTEST_FLOAT_TOLERANCE). */\n#define lfequal(a, b)\\\n    lequal_base(fabs((double)(a)-(double)(b)) <= LTEST_FLOAT_TOLERANCE\\\n     && fabs((double)(a)-(double)(b)) == fabs((double)(a)-(double)(b)), (double)(a), (double)(b), \"%f\")\n\n\n/* Assert two strings are equal. */\n#define lsequal(a, b)\\\n    lequal_base(strcmp(a, b) == 0, a, b, \"%s\")\n\n\n#endif /*__MINCTEST_H__*/\n"
  },
  {
    "path": "src/test/parse_test.c",
    "content": "#include \"../buffer.h\"\n#include \"../http_parser.h\"\n#include \"../misc.h\"\n#include \"../ssstr.h\"\n#include \"minctest.h\"\n#include <stdio.h>\n#include <string.h>\n\nvoid test_method1() {\n  buffer_t *buffer = buffer_init();\n  parse_archive ar;\n  parse_archive_init(&ar, buffer);\n\n  buffer_cat(buffer, \"POST / HTTP/1.0\", 10);\n  parse_request_line(buffer, &ar);\n  lok(ar.method_begin == buffer->buf);\n  lok(ar.next_parse_pos == buffer_end(buffer));\n  lequal(HTTP_POST, ar.method);\n  lok(ssstr_equal(&ar.request_url_string, \"/\"));\n  lok(ssstr_equal(&ar.url.abs_path, \"/\"));\n  lok(ssstr_equal(&ar.url.query_string, \"\"));\n}\n\n/* so parse_request_line can be called many times when recv new data */\nvoid test_method2() {\n  buffer_t *buffer = buffer_init();\n  parse_archive ar;\n  parse_archive_init(&ar, buffer);\n\n  int status = -1;\n  buffer_cat(buffer, \"GE\", 2);\n  status = parse_request_line(buffer, &ar);\n  lequal(AGAIN, status);\n  lok(ar.method_begin == buffer->buf);\n  lok(ar.next_parse_pos == buffer_end(buffer));\n\n  char next_buf[] = \"T /s?wd=hello%20world\";\n  buffer_cat(buffer, next_buf, strlen(next_buf));\n  status = parse_request_line(buffer, &ar);\n  lequal(HTTP_GET, ar.method);\n  lequal(AGAIN, status);\n\n  buffer_cat(buffer, \" \", 1);\n  status = parse_request_line(buffer, &ar);\n  lok(ssstr_equal(&ar.url.abs_path, \"/s\"));\n  lok(ssstr_equal(&ar.url.query_string, \"wd=hello%20world\"));\n}\n\n/* valid request line */\nvoid test_method3() {\n  buffer_t *buffer = buffer_init();\n  parse_archive ar;\n  parse_archive_init(&ar, buffer);\n\n  int status = -1;\n  char req_line[] = \"GET /api/set/?wd=123abc HTTP/1.1\\r\\nHost:localhost:8888\";\n  buffer_cat(buffer, req_line, strlen(req_line));\n  status = parse_request_line(buffer, &ar);\n\n  lequal(HTTP_GET, ar.method);\n  lequal(ar.version.http_major, 1);\n  lequal(ar.version.http_minor, 1);\n  lok(ssstr_equal(&ar.request_url_string, \"/api/set/?wd=123abc\"));\n  lok(ssstr_equal(&ar.url.abs_path, \"/api/set/\"));\n  lok(ssstr_equal(&ar.url.query_string, \"wd=123abc\"));\n  lequal(OK, status);\n  lok(ar.next_parse_pos[0] = 'H');\n  lok(ar.next_parse_pos[1] = 'o');\n}\n\n/* invalid request line */\nvoid test_method4() {\n  buffer_t *buffer = buffer_init();\n  parse_archive ar;\n  parse_archive_init(&ar, buffer);\n\n  int status = -1;\n  char req_line[] =\n      \"POST /api/set/?wd=123abc HTTP/01.10\\r\\nHost:localhost:8888\";\n  buffer_cat(buffer, req_line, strlen(req_line));\n  status = parse_request_line(buffer, &ar);\n\n  lequal(HTTP_POST, ar.method);\n  lequal(ar.version.http_major, 1);\n  lequal(ar.version.http_minor, 10);\n  lok(ssstr_equal(&ar.request_url_string, \"/api/set/?wd=123abc\"));\n  lequal(ERROR, status);\n}\n\n/* curl GET */\nvoid test_method5() {\n  buffer_t *buffer = buffer_init();\n  parse_archive ar;\n  parse_archive_init(&ar, buffer);\n  ar.state = S_HD_BEGIN;\n\n  int status = -1;\n  char req_line[] = \"User-Agent: curl/7.18.0 (i486-pc-linux-gnu) \"\n                    \"libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\\r\\n\"\n                    \"Host: 0.0.0.0=5000\\r\\n\"\n                    \"Accept: */*\\r\\n\"\n                    \"\\r\\n\";\n  buffer_cat(buffer, req_line, strlen(req_line));\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"User-Agent\"));\n  lok(ssstr_equal(&ar.header[1],\n                  \"curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 \"\n                  \"OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Host\"));\n  lok(ssstr_equal(&ar.header[1], \"0.0.0.0=5000\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Accept\"));\n  lok(ssstr_equal(&ar.header[1], \"*/*\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lequal(CRLF_LINE, status);\n}\n\n/* firefox GET */\nvoid test_method6() {\n  buffer_t *buffer = buffer_init();\n  parse_archive ar;\n  parse_archive_init(&ar, buffer);\n\n  int status = -1;\n  char req_line[] =\n      \"GET /favicon.ico HTTP/1.1\\r\\n\"\n      \"Host: 0.0.0.0=5000\\r\\n\"\n      \"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) \"\n      \"Gecko/2008061015 Firefox/3.0\\r\\n\"\n      \"Accept: \"\n      \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\\r\\n\"\n      \"Accept-Language: en-us,en;q=0.5\\r\\n\"\n      \"Accept-Encoding: gzip,deflate\\r\\n\"\n      \"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\\r\\n\"\n      \"Keep-Alive: 300\\r\\n\"\n      \"Connection: keep-alive\\r\\n\"\n      \"\\r\\n\";\n  buffer_cat(buffer, req_line, strlen(req_line));\n\n  status = parse_request_line(buffer, &ar);\n\n  lequal(HTTP_GET, ar.method);\n  lequal(ar.version.http_major, 1);\n  lequal(ar.version.http_minor, 1);\n  lok(ssstr_equal(&ar.request_url_string, \"/favicon.ico\"));\n  lok(ssstr_equal(&ar.url.abs_path, \"/favicon.ico\"));\n  lok(ssstr_equal(&ar.url.query_string, \"\"));\n  lok(ssstr_equal(&ar.url.mime_extension, \"ico\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Host\"));\n  lok(ssstr_equal(&ar.header[1], \"0.0.0.0=5000\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"User-Agent\"));\n  lok(ssstr_equal(&ar.header[1],\n                  \"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) \"\n                  \"Gecko/2008061015 Firefox/3.0\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Accept\"));\n  lok(ssstr_equal(\n      &ar.header[1],\n      \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Accept-Language\"));\n  lok(ssstr_equal(&ar.header[1], \"en-us,en;q=0.5\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Accept-Encoding\"));\n  lok(ssstr_equal(&ar.header[1], \"gzip,deflate\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Accept-Charset\"));\n  lok(ssstr_equal(&ar.header[1], \"ISO-8859-1,utf-8;q=0.7,*;q=0.7\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Keep-Alive\"));\n  lok(ssstr_equal(&ar.header[1], \"300\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lok(ssstr_equal(&ar.header[0], \"Connection\"));\n  lok(ssstr_equal(&ar.header[1], \"keep-alive\"));\n  lequal(OK, status);\n\n  status = parse_header_line(buffer, &ar);\n  lequal(CRLF_LINE, status);\n}\n\nint main(int argc, char const *argv[]) {\n  lrun(\"test_method1\", test_method1);\n  lrun(\"test_method2\", test_method2);\n  lrun(\"test_method3\", test_method3);\n  lrun(\"test_method4\", test_method4);\n  lrun(\"test_method5\", test_method5);\n  lrun(\"test_method6\", test_method6);\n\n  lresults();\n  printf(\"\\n\\n\");\n  return lfails != 0;\n}\n"
  },
  {
    "path": "src/test/slow_client.c",
    "content": "/**\n * ./slow_client 8888 [0-9]\n */\n\n#define _GNU_SOURCE\n#include \"../misc.h\"\n#include <arpa/inet.h>\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <netinet/in.h>\n#include <netinet/tcp.h>\n#include <pthread.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <strings.h>\n#include <sys/epoll.h>\n#include <sys/mman.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <time.h>\n#include <unistd.h>\n\nconst char *requests[] = {\n#define SLOW_CLIENT_GET 0\n    \"GET / HTTP/1.1\\r\\n\"\n    \"Host:127.0.0.1:8888\\r\\n\"\n    \"User-Agent:SLOW_CLIENT\\r\\n\"\n    \"\\r\\n\",\n\n#define CURL_GET 1\n    \"GET / HTTP/1.1\\r\\n\"\n    \"User-Agent: curl/7.18.0 (i486-pc-linux-gnu) \"\n    \"libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 \"\n    \"libidn/1.1\\r\\n\"\n    \"Host: 0.0.0.0=5000\\r\\n\"\n    \"Accept: */*\\r\\n\"\n    \"\\r\\n\",\n\n#define FIREFOX_GET 2\n    \"GET /favicon.ico HTTP/1.1\\r\\n\"\n    \"Host: 0.0.0.0=5000\\r\\n\"\n    \"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) \"\n    \"Gecko/2008061015 Firefox/3.0\\r\\n\"\n    \"Accept: \"\n    \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\\r\\n\"\n    \"Accept-Language: en-us,en;q=0.5\\r\\n\"\n    \"Accept-Encoding: gzip,deflate\\r\\n\"\n    \"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\\r\\n\"\n    \"Keep-Alive: 300\\r\\n\"\n    \"Connection: keep-alive\\r\\n\"\n    \"\\r\\n\",\n\n#define POST_IDENTITY_BODY_WORLD 3\n    \"POST /post_identity_body_world?q=search#hey HTTP/1.1\\r\\n\"\n    \"Accept: */*\\r\\n\"\n    \"Transfer-Encoding: identity\\r\\n\"\n    \"Content-Length: 5\\r\\n\"\n    \"\\r\\n\"\n    \"World\",\n\n#define GET_FUNKY_CONTENT_LENGTH 4\n    \"GET / HTTP/1.0\\r\\n\"\n    \"conTENT-Length: 5\\r\\n\"\n    \"\\r\\n\"\n    \"HELLO\",\n\n#define POST_CHUNKED_ALL_YOUR_BASE 5\n    \"POST / HTTP/1.1\\r\\n\"\n    \"Transfer-Encoding: chunked\\r\\n\"\n    \"\\r\\n\"\n    \"1e\\r\\nall your base are belong to us\\r\\n\"\n    \"0\\r\\n\"\n    \"\\r\\n\",\n\n};\n\nint connect_to_server(uint16_t port) {\n  static const char *host = \"127.0.0.1\";\n  int fd = socket(AF_INET, SOCK_STREAM, 0);\n  ABORT_ON(fd == -1, \"socket\");\n\n  struct sockaddr_in addr;\n  addr.sin_family = AF_INET;\n  addr.sin_port = htons(port);\n  int status = inet_pton(AF_INET, host, &addr.sin_addr);\n  ABORT_ON(status <= 0, \"inet_pton\");\n\n  status = connect(fd, (struct sockaddr *)&addr, sizeof(addr));\n  ABORT_ON(status != 0, \"connect\");\n\n  return fd;\n}\n\nint main(int argc, char *argv[]) {\n  int fd, opt;\n  if (argc < 3) {\n    printf(\"Usage: %s port [0-5]\\n\", argv[0]);\n    return 1;\n  }\n  signal(SIGPIPE, SIG_IGN);\n  fd = connect_to_server(atoi(argv[1]));\n  opt = atoi(argv[2]);\n\n  if (opt >= sizeof(requests) / sizeof(requests[0]))\n    return 1;\n\n  /* send begin */\n  for (size_t i = 0; i < strlen(requests[opt]); i++) {\n    int ch = requests[opt][i];\n    if (-1 == send(fd, &ch, 1, 0)) {\n      perror(\"send\"); // probably broken pipe\n      break;\n    }\n    printf(\"%c\", ch);\n    fflush(stdout);\n    usleep(30 * 1000);\n  }\n  /* send end */\n\n  /* recv begin */\n  printf(\"\\n\\n***************Recv**************\\n\\n\");\n  while (1) {\n    int ch;\n    int len = recv(fd, &ch, 1, 0);\n    if (len != 1) {\n      break;\n    }\n    printf(\"%c\", ch);\n    fflush(stdout);\n  }\n  /* recv end */\n\n  close(fd);\n  return 0;\n}\n"
  },
  {
    "path": "src/test/ssstr_test.c",
    "content": "#include \"../ssstr.h\"\n#include \"minctest.h\"\n#include <stdio.h>\n#include <string.h>\n\nvoid test1() {\n  ssstr_t s = SSSTR(\"hello\");\n  lequal(5, s.len);\n  lsequal(\"hello\", s.str);\n}\n\nvoid test2() {\n  ssstr_t s1 = SSSTR(\"hello\");\n  ssstr_t s2 = SSSTR(\"hello\");\n  lequal(0, ssstr_cmp(&s1, &s2));\n\n  s1 = SSSTR(\"\");\n  s2 = SSSTR(\"\");\n  lequal(0, ssstr_cmp(&s1, &s2));\n}\n\nvoid test3() {\n  char str1[] = \"hello\";\n  char str2[] = \"hello\";\n\n  ssstr_t s1 = SSSTR(str1);\n  ssstr_t s2 = SSSTR(str2);\n  lequal(0, ssstr_cmp(&s1, &s2));\n}\n\nvoid test4() {\n  char str1[] = \"hello\";\n  char str2[] = \"hullo\";\n\n  ssstr_t s1 = SSSTR(str1);\n  ssstr_t s2 = SSSTR(str2);\n  lequal(-1, ssstr_cmp(&s1, &s2));\n}\n\nvoid test5() {\n\n  char str1[] = \"hello_world\";\n  char str2[] = \"hello\";\n\n  ssstr_t s1 = SSSTR(str1);\n  ssstr_t s2 = SSSTR(str2);\n\n  lequal(1, ssstr_cmp(&s1, &s2));\n  lequal(11, s1.len);\n  lequal(5, s2.len);\n}\n\nvoid test6() {\n  char str1[] = \"hello_world\";\n  char str2[] = \"hullo\";\n\n  ssstr_t s1 = SSSTR(str1);\n  ssstr_t s2 = SSSTR(str2);\n  lequal(-1, ssstr_cmp(&s1, &s2));\n}\n\nvoid test7() {\n  char str1[] = \"\";\n  char str2[] = \"\";\n\n  ssstr_t s1 = SSSTR(str1);\n  ssstr_t s2 = SSSTR(str2);\n\n  lok(s1.str != s2.str);\n  lequal(0, ssstr_cmp(&s1, &s2));\n}\n\nvoid test8() {\n  char str1[] = \"hello\";\n  char str2[] = \"hello\";\n\n  ssstr_t s1 = SSSTR(str1);\n\n  lok(ssstr_equal(&s1, str2));\n  lok(ssstr_equal(&s1, \"hello\"));\n  lok(!ssstr_equal(&s1, \"hello_world\"));\n  lok(!ssstr_equal(&s1, \"hullo\"));\n}\n\nvoid test9() {\n  char str1[] = \"HellO\";\n  char str2[] = \"heLLo-World\";\n\n  ssstr_t s1 = SSSTR(str1);\n  ssstr_t s2 = SSSTR(str2);\n\n  ssstr_tolower(&s1);\n  ssstr_tolower(&s2);\n\n  lok(ssstr_equal(&s1, \"hello\"));\n  lok(ssstr_equal(&s2, \"hello-world\"));\n}\n\nvoid test10() {\n  char str1[] = \"HellO\";\n  char str2[] = \"heLLo-World\";\n\n  ssstr_t s1 = SSSTR(str1);\n  ssstr_t s2 = SSSTR(str2);\n\n  lok(ssstr_caseequal(&s1, \"hello\"));\n  lok(ssstr_caseequal(&s2, \"hello-world\"));\n}\n\nint main(int argc, char const *argv[]) {\n  lrun(\"test1\", test1);\n  lrun(\"test2\", test2);\n  lrun(\"test3\", test3);\n  lrun(\"test4\", test4);\n  lrun(\"test5\", test5);\n  lrun(\"test6\", test6);\n  lrun(\"test7\", test7);\n  lrun(\"test8\", test8);\n  lrun(\"test9\", test9);\n  lrun(\"test10\", test10);\n  lresults();\n  printf(\"\\n\\n\");\n  return lfails != 0;\n}\n"
  },
  {
    "path": "www/error.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <title>Error</title>\n  <style>\n    body {\n      width: 35em;\n      margin: 0 auto;\n      font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n  </style>\n</head>\n\n<body>\n  <h1> %s </h1>\n  <p>Sorry, the page you are looking for is currently unavailable.<br/> Please try again later.</p>\n  <p>If you are the system administrator of this resource then you should check the error log for details.</p>\n  <p><em>Faithfully yours, lotos.</em></p>\n</body>\n\n</html>\n"
  },
  {
    "path": "www/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <title>Welcome to lotos!</title>\n  <style>\n    body {\n      width: 35em;\n      margin: 0 auto;\n      font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n  </style>\n</head>\n\n<body>\n  <h1>Welcome to lotos!</h1>\n  <p>If you see this page, the lotos web server is successfully installed and working. Further configuration is required.</p>\n\n  <p>For online documentation and support please refer to\n    <a href=\"https://github.com/chendotjs/lotos/\">https://github.com/chendotjs/lotos</a>.</p>\n\n  <p><em>Thank you for using lotos.</em></p>\n</body>\n\n</html>\n"
  }
]