lwan Web服务器
lwan是高性能和可扩展的Web服务器。
该项目网站包含更多详细信息。
建立状态
| 操作系统 | 拱 | 发布 | 调试 | 静态分析 | 测试 |
|---|---|---|---|---|---|
| Linux | x86_64 | 报告历史记录 | |||
| FreeBSD 14 | x86_64 | ||||
| OpenBSD 7.4 | x86_64 |
安装
您可以自己构建lwan ,使用容器映像,也可以从您喜欢的发行版中获取包裹。
建筑
在安装lwan之前,请确保安装所有依赖关系。所有这些都是在任何GNU/Linux分布中发现的常见依赖性;软件包名称会有所不同,但是使用您的发行版使用的任何软件包管理工具搜索不难。
所需的依赖项
- Cmake,至少2.8版
- Zlib
可选依赖性
构建系统将寻找这些库,并在可用的情况下启用/链接。
- LUA 5.1或Luajit 2.0
- Valgrind
- Brotli
- 可以通过
-DENABLE_BROTLI=NO来禁用
- 可以通过
- ZSTD
- 可以通过
-DENABLE_ZSTD=NO来禁用
- 可以通过
- 在Linux构建上,如果
-DENABLE_TLS=ON(默认)传递:- mbedtls
- 可以通过将
-DUSE_ALTERNATIVE_MALLOC传递到以下值来使用替代内存分配器:- “ mimalloc”
- “ jemalloc”
- “ tcmalloc”
- “自动”:从上面的列表中自动检索,如果没有发现,请回到libc malloc
- 运行测试套件:
- 带有请求的Python(2.6+)
- LUA 5.1
- 运行基准:
- heightpp-为方便起见,与lwan一起捆绑并建造
- matplotlib
- 构建TechEmpower基准套件:
- Mariadb的客户库
- Sqlite 3
常见的操作系统软件包名称
最低构建
- Archlinux:
pacman -S cmake zlib - FreeBSD:
pkg install cmake pkgconf - ubuntu 14+:
apt-get update && apt-get install git cmake zlib1g-dev pkg-config - macos:
brew install cmake
使用所有可选功能构建
- Archlinux:
pacman -S cmake zlib sqlite luajit mariadb-libs gperftools valgrind mbedtls - FreeBSD:
pkg install cmake pkgconf sqlite3 lua51 - Ubuntu 14+:
apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmariadb-dev libmbedtls-dev - MACOS:
brew install cmake mariadb-connector-c sqlite lua@5.1 pkg-config
构建命令
克隆存储库
lwan
~$ cd lwan \”>
~$ git clone git://github.com/lpereira/ lwan
~$ cd lwan
创建构建目录
lwan$ mkdir build
~/ lwan $ cd build\”>
~/ lwan $ mkdir build
~/ lwan $ cd build
选择构建类型
选择发行版本(没有调试符号,消息,启用一些优化等):
~/ lwan /build$ cmake .. -DCMAKE_BUILD_TYPE=Release
如果您想启用优化但仍使用调试器,请改用此方法:
~/ lwan /build$ cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
禁用优化并构建更具调试的版本:
~/ lwan /build$ cmake .. -DCMAKE_BUILD_TYPE=Debug
建造lwan
~/ lwan /build$ make
这将产生一些二进制文件:
-
src/bin/ lwan / lwan:主lwan可执行文件。可以使用--help来执行指导。 -
src/bin/testrunner/testrunner:包含执行测试套件(src/scripts/testsuite.py)的代码。 -
src/samples/freegeoip/freegeoip:FreeGeoip样本实现。需要sqlite。 -
src/samples/techempower/techempower:Techempower Web框架基准的代码。需要SQLite和Mariadb库。 -
src/samples/clock/clock:时钟样本。生成一个永无止境的动画GIF文件,该文件总是显示本地时间。 -
src/samples/forthsalon/forthsalon:从第四节目语言的第四沙龙方言编写的程序中生成一个永无止境的动画gif。在建筑中! -
src/samples/forthsalon/forth:用于forthsalon样品中使用的第四方言的测试线束。 -
src/bin/tools/mimegen:构建扩展模仿类型表。在构建过程中使用。 -
src/bin/tools/bin2hex:从二进制文件中生成一个C文件,适用于#include。在构建过程中使用。 -
src/bin/tools/configdump:使用配置读取器API转移配置文件。用于测试。 -
src/bin/tools/weighttp:重写weighttpHTTP基准测试工具。 -
src/bin/tools/statuslookupgen:为HTTP状态代码及其描述生成完美的哈希表。在构建过程中使用。
评论
通过-DCMAKE_BUILD_TYPE=Release将启用一些编译器优化(例如LTO),并为当前体系结构调整代码。
重要的
基准测试时,请使用发行版构建。默认值是调试构建,它不仅将所有请求记录到标准输出中,而且在握住锁定的同时会严重按住服务器。
默认构建(即不通过-DCMAKE_BUILD_TYPE=Release )将构建一个适合调试目的的版本。此版本可以在Valgrind (如果存在标题)下使用,并包括在发行版中删除的调试消息。每个请求都打印调试消息。
在这些构建中,可以启用消毒剂。要选择使用哪个使用lwan的一个,请为CMAKE INVOCATION行指定以下选项之一:
-
-DSANITIZER=ubsan选择未定义的行为消毒剂。 -
-DSANITIZER=address选择地址消毒剂。 -
-DSANITIZER=thread选择线程消毒剂。
也可以选择其他内存分配器。 lwan目前支持TCMalloc,Mimalloc和Jemalloc开箱即用。要使用其中的任何一个,请使用“可选依赖项”部分中提供的名称将-DALTERNATIVE_MALLOC=name传递给Cmake Invocation行。
-DUSE_SYSLOG=ON选件可以传递给CMake,除了标准输出外,还可以登录到系统日志。
如果您要构建用于分发的lwan ,则使用-DMTUNE_NATIVE=OFF选项可能是明智的,否则生成的二进制可能无法在某些计算机上运行。
在有足够新的标头的Linux系统上安装合适的MBEDTLS安装的情况下,将自动启用TLS支持,以支持KTLS,但可以通过传递-DENABLE_TLS=NO to Cmake禁用。
测试
~/ lwan /build$ make testsuite
这将在src/scripts/testsuite.py中编译testrunner程序并执行回归测试套件。
基准
~/ lwan /build$ make benchmark
这将编译testrunner并执行基准脚本src/scripts/benchmark.py 。
覆盖范围
lwan也可以通过指定-DCMAKE_BUILD_TYPE=Coverage来构建覆盖范围构建类型。这使generate-coverage制造目标可以运行testrunner ,可以与LCOV一起准备测试覆盖报告。
该存储库中的每个提交都会触发该报告的产生,结果可公开可用。
跑步
通过编辑提供的lwan .conf来设置服务器;在下面的详细信息中解释了该格式。
笔记
lwan将尝试在当前目录中找到基于可执行名称的配置文件; testrunner.conf将用于testrunner二进制, lwan .conf用于lwan二进制,等等。
配置文件是从当前目录加载的。如果未对此文件进行更改,则运行lwan将服务于./wwwroot目录中的静态文件。 lwan将在所有接口上的端口8080上收听。
lwan将检测到CPU的数量,将增加开放文件描述符的最大数量,并通常会尽力为正在运行的环境自动设置合理的设置。这些设置中的许多可以在配置文件中进行调整,但是不要与它们混乱通常是个好主意。
提示
可选地, lwan二进制文件可用于无需任何配置文件的单发静态文件服务。与--help一起运行 – 为此提供帮助。
配置文件
格式
lwan使用熟悉的key = value配置文件语法。注释由#字符(类似于Shell脚本,Python和Perl)支持。可以使用卷曲支架创建嵌套部分。部分可以是空的;在这种情况下,卷曲括号是可选的。
some_key_name等效于配置文件中的some key name (作为实现详细信息,代码读取配置选项只能以下划线为版本)。
提示
值可以包含环境变量。使用语法${VARIABLE_NAME} 。默认值可以用colon(例如${VARIABLE_NAME:foo}指定,如果设置为${VARIABLE_NAME} ,否则否则foo )。
sound volume = 11 # This one is 1 louder
playlist metal {
files = \'\'\'
/multi/line/strings/are/supported.mp3
/anything/inside/these/are/stored/verbatim.mp3
\'\'\'
}
playlist chiptune {
files = \"\"\"
/if/it/starts/with/single/quotes/it/ends/with/single/quotes.mod
/but/it/can/use/double/quotes.s3m
\"\"\"
}
可以在lwan .conf和techempower.conf中找到一些示例。
常数
可以通过在配置文件中的任何位置中在constants部分中指定常数在整个配置文件中定义和重复使用。仅在该部分定义特定常数之后才能使用常数。常数可以重新定义。如果未定义常数,则其值将从环境变量中获得。如果在一个constants部分或环境中未定义它, lwan将使用适当的错误消息中止。
constants {
user_name = ${USER}
home_directory = ${HOME}
buffer_size = 1000000
}
上面指定的默认值的相同语法在此处有效(例如,如果${USER:nobody}未在环境变量中设置${user_name} user_name },则指定user_name为${USER}设置为nobody 。
价值类型
| 类型 | 描述 |
|---|---|
str |
任何形式的自由形式文本,通常是特定应用程序的 |
int |
整数编号。范围特定于应用程序 |
time |
时间间隔。请参阅下表的单位 |
bool |
布尔值。有关有效值,请参见下表 |
时间间隔
可以使用乘数指定时间字段。可以指定多个,它们只是添加在一起;例如,“ 1m 1W”指定“ 1个月和1周”(37天)。下表列出了所有已知乘数:
| 乘数 | 描述 |
|---|---|
s |
秒 |
m |
分钟 |
h |
小时 |
d |
天 |
w |
7天星期 |
M |
30天 |
y |
365天 |
笔记
该表中没有乘数的数字被忽略;读取配置文件时会发出警告。数字及其乘数之间必须不存在空间。
布尔值
| 真值 | 假值 |
|---|---|
| 与0不同的整数数字 | 0 |
on
|
off
|
true
|
false
|
yes
|
no |
全球设置
通常,让lwan决定为您的环境决定最佳设置是一个好主意。但是,并非每个环境都是相同的,并且并非所有使用都可以自动确定,因此提供了一些配置选项。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
keep_alive_timeout |
time
|
15
|
保持连接活力的超时 |
quiet |
bool
|
false
|
设置为TRUE,以不打印任何调试消息。仅有效释放构建。 |
expires
|
time
|
1M 1w
|
“到期”标头的价值。默认值是1个月零1周 |
threads |
int
|
0
|
I/O线程的数量。默认(0)是在线CPU的数量 |
proxy_protocol |
bool
|
false
|
启用代理协议。支持版本1和2。仅在代理后面使用lwan时才启用此设置,并且代理支持此协议。否则,这允许任何人欺骗原点IP地址 |
max_post_data_size |
int
|
40960
|
设置发布请求的最大数据大小数量,字节 |
max_put_data_size |
int
|
40960
|
在字节中设置最大数据大小的数据大小数量 |
max_file_descriptors |
int
|
524288
|
最大文件描述符数。需要至少10倍threads
|
request_buffer_size
|
int
|
4096
|
请求缓冲尺寸长度。如果大于4096的默认值,则将动态分配。 |
allow_temp_files
|
str
|
\"\"
|
使用临时文件;设置post发布请求, put请求或all (等同于两者都设置为post put )。 |
error_template
|
str
|
默认错误模板 | 错误代码的模板。请参阅下面的变量。 |
error_template的变量
| 多变的 | 类型 | 描述 |
|---|---|---|
short_message |
str
|
简短错误消息(例如Not found ) |
long_message
|
str
|
长错误消息(例如, The requested resource could not be found on this server ) |
海峡夹克
lwan可以将其特权放在系统中的用户中,并用Chroot限制其文件系统视图。尽管没有防弹性,但在lwan情况下,这提供了第一层安全性。
为了使用此功能,请声明straitjacket (或straightjacket )部分,然后设置一些选项。这要求将lwan作为root执行。
尽管可以在文件中的任何位置写入此部分(只要是最高级别的声明),但是,由于打开任何目录,例如由于实例化了serve_files模块, lwan将拒绝启动。 (此检查仅在Linux上作为Malconfiguration的保障。)
提示
在site部分之前声明一件紧身的夹克,以使配置文件和私人数据(例如TLS密钥)在初始化后无法触及的服务器。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
user |
str
|
NULL
|
将特权放在此用户名中 |
chroot |
str
|
NULL
|
通往chroot()的路径 |
drop_capabilities |
bool
|
true
|
将所有功能(2)(在Linux下)或承诺(2)(在OpenBSD下)删除所有功能。 |
标题
如果需要为每个响应指定自定义标头,则可以在全局范围中声明headers部分。本节看起来并不重要的顺序。
例如,此声明:
headers {
Server = Apache/1.0.0 or nginx/1.0.0 (at your option)
Some-Custom-Header = ${WITH_THIS_ENVIRONMENT_VARIABLE}
}
都将覆盖Server标头( Server: lwan ),并设置Some-Custom-Header并用$WITH_THIS_ENVIRONMENT_VARIABLE获得的值设置。
某些标头不能被覆盖,因为在为其服务请求时发送其实际值时会引起问题。这些包括但不限于:
-
Date -
Expires -
WWW-Authenticate -
Connection -
Content-Type -
Transfer-Encoding - 所有
Access-Control-Allow-标题
笔记
标题名称也是对案例不敏感(和案例保存)的。覆盖SeRVeR将覆盖Server标头,但按照配置文件的写入方式发送。
听众
每个lwan过程仅支持两个侦听器:HTTP侦听器( listener部分)和HTTPS侦听器( tls_listener部分)。每种类型的听众只允许。
警告
TLS支持是实验性的。尽管在初始测试期间稳定,但您的里程可能会有所不同。此时仅支持TLSV1.2,但计划了TLSV1.3。
笔记
TLS支持需要tls.ko模块内置或加载的Linux。将来可能会增加对其他操作系统的支持。 FreeBSD似乎是可能的,其他操作系统似乎没有提供类似的功能。对于不支持的操作系统,使用TLS终端代理(例如Hitch)是一个不错的选择。
对于listener和tls_listener部分,唯一的参数是接口地址和要聆听的端口。侦听器语法为${ADDRESS}:${PORT} ,其中${ADDRESS}可以* (绑定到所有接口),IPv6地址(如果被Square Brackets包围),IPv4地址或hostname。例如, listener localhost:9876只能在lo接口,端口9876中聆听。
虽然listener部分不采用键,但tls_listener部分需要两个: cert和key (每个指数分别指向磁盘上的位置,位于TLS证书和私有密钥文件的位置),并采用可选的布尔hsts键,该密钥控制是否在HTTPS响应上发送了Strict-Transport-Security标题。
提示
为了生成这些键用于测试目的,可以像以下内容一样使用OpenSSL命令行工具: openssl req -nodes -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 7
笔记
建议在tls_listener部分之后立即声明具有chroot选项的Straitjacket,以使证书和钥匙的路径从那时起就无法触及。
如果使用SystemD插座激活,则可以将systemd指定为参数。 (如果指定了来自SystemD的多个侦听器,则可以指定systemd:FileDescriptorName , FileDescriptorName遵循systemd.socket文档中设置的约定。)
示例:
listener *:8080 # Listen on all interfaces, port 8080, HTTP
tls_listener *:8081 { # Listen on all interfaces, port 8081, HTTPS
cert = /path/to/cert.pem
key = /path/to/key.pem
}
# Use named systemd socket activation for HTTP listener
listener systemd:my-service-http.socket
# Use named systemd socket activation for HTTPS listener
tls_listener systemd:my-service-https.socket {
...
}
地点
site部分将对给定URL前缀的请求做出响应的模块和处理程序的实例。
使用模块或处理程序的路由URL
为了路由URL, lwan与请求URI最大的普通前缀与“侦听器”部分中指定的一组前缀匹配。如何处理对特定前缀的请求取决于在侦听器部分中声明了哪个处理程序或模块。处理程序和模块在内部相似。处理程序仅仅是函数,并且没有状态,模块持状态(“实例”)。模块的多个实例可以显示在侦听器部分中。
没有特殊的语法将前缀连接到处理程序或模块。所有配置解析器规则在此处应用。使用${NAME} ${PREFIX}将${PREFIX}链接到一个名为${NAME}的处理程序(如果${NAME}开始,则以&为&,如C的“ ofer of” ofer of ofers\’oferator),或者是“运算符”的地址),或一个名为${NAME}的模块。可以在此处使用空的部分。
每个模块将具有其特定的选项集,并在下一节中列出。除了配置选项外,还可以在模块实例的声明中存在一个特殊的authorization部分。处理程序不采用任何配置选项,但可能包括authorization部分。
提示
使用--version命令行参数执行lwan将显示内置模块和处理程序的列表。
以下是使用lwan运送的模块的一些基本文档。
文件服务
serve_files模块将使用静态文件,并自动创建目录索引或提供预压缩文件。根据某些启发式方法,它通常会尽力以最快的方式提供文件。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
path |
str
|
NULL
|
通往包含要提供文件的目录的路径 |
index_path |
str
|
index.html
|
文件名用作目录的索引 |
serve_precompressed_path |
bool
|
true
|
如果存在$ file.gz,则比$ file更小且较新,并且客户端接受gzip编码,转移它 |
auto_index |
bool
|
true
|
如果没有index_path文件,则会自动生成目录列表。否则,产量为404 |
auto_index_readme
|
bool
|
true
|
作为自动生成目录索引的一部分,包括读书文件的内容 |
directory_list_template |
str
|
NULL
|
目录列表的胡须模板的路径;默认情况下,使用内部模板 |
read_ahead |
int
|
131702
|
缓存打开文件时,最大的字节量要提前读取。值为0禁用ReadAhead。 ReadAhead由低优先级线程执行,以在文件系统中读取文件范围时不会阻止I/O线程。 |
cache_for
|
time
|
5s
|
是时候将文件元数据(大小,压缩内容,打开文件描述符等)保存在缓存中 |
笔记
在cache_for设置中指定的持续时间内,小于16KIB的文件将在RAM中被压缩。 lwan将始终尝试使用Deflate压缩,并选择使用Brotli和ZSTD压缩(如果在适当的支持下构建lwan )。
如果压缩不值得(例如,添加Content-Encoding标头)会产生比发送未压缩文件更大的响应,通常是很小的文件), lwan不会花时间压缩文件。
对于大于16KIB的文件, lwan不会尝试压缩它们。在将来的版本中,它可能会执行此操作,并在压缩文件时使用块编码(当然要达到一定限制),但就目前而言,仅考虑上表中的serve_precompressed_path设置)。
在所有情况下,如果在文件系统中找到该版本,并且客户端请求此编码, lwan可能会尝试使用GZZPICPECT版本。
directory_list_template的变量
| 多变的 | 类型 | 描述 |
|---|---|---|
rel_path |
str
|
相对于根目录真实路径的路径 |
readme |
str
|
找到的第一个读数文件的内容( readme , readme.txt , read.me , README.TXT , README ) |
file_list
|
迭代器 | 迭代文件列表 |
file_list.zebra_class |
str
|
odd物品, even物品 |
file_list.icon |
str
|
通往文件类型图标的路径 |
file_list.name |
str
|
文件名(逃脱) |
file_list.type
|
str
|
文件类型(目录或常规文件) |
file_list.size
|
int
|
文件大小 |
file_list.unit |
str
|
file_size的单元 |
卢阿
lua模块将允许用LUA编程语言编写的脚本来维修请求。尽管该模块提供的功能非常斯巴达,但它能够运行水手等框架。
可以从文件中提供脚本,也可以嵌入在配置文件中,并且加载它们的结果,标准LUA模块,并且(如果使用Luajit)优化代码的(如果使用Luajit)将被缓存一段时间。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
default_type |
str
|
text/plain
|
用于响应的默认mime类型 |
script_file |
str
|
NULL
|
LUA脚本的路径 |
cache_period |
time
|
15s
|
是时候将LUA状态加载到内存中了 |
script |
str
|
NULL
|
内联LUA脚本 |
编写请求处理程序
笔记
LUA脚本无法使用全局变量,因为它们不仅可以由不同的线程提供服务,而且该状态仅适用于cache_period配置选项中指定的时间。这是因为lwan中的每个I/O线程都会创建一个LUA VM的实例(即每个I/O线程的一个lua_State struct),并且每个lwan COROUTINE都会通过请求产生LUA线程(带有lua_newthread() )。
每个端点都不需要一个LUA模块的实例。嵌入在配置文件或其他方式中的单个脚本可以服务许多不同的端点。脚本应该具有以下签名来实现函数: handle_${METHOD}_${ENDPOINT}(req) ,其中${METHOD}可以是http方法(即get , post ,post, head等),而${ENDPOINT}是由该功能处理所需的端点。如果特定版本不存在,将调用通用handle(req)函数。
提示
使用root端点进行catchall。例如,如果找不到该请求的其他处理程序,将调用处理程序函数handle_get_root() 。如果未指定CatchAll,则服务器将返回404 Not Found错误。
req参数指向一个包含从请求获取信息或设置响应信息的方法,如下所示:
-
req:query_param(param)用密钥param返回查询参数(从查询字符串),或者如果找不到的nil -
req:post_param(param)返回post参数(仅适用于${POST}处理程序)带有键param,或者如果找不到的话,nil -
req:set_response(str)设置对字符串str响应 -
req:say(str)发送响应块(使用http中的块编码) -
req:send_event(event, str)发送一个事件(使用服务器序列事件) -
req:cookie(param)返回名为param的cookie,或找不到nil -
req:set_headers(tbl)从表tbl设置响应标头;可以使用表值({\'foo\'={\'bar\', \'baz\'}}}中的表(\’foo\’= {\’baz\’}})中,可以多次指定标题。必须在发送任何响应say()或send_event()之前要调用 -
req:header(name)从请求中获取标题,如果找不到的话,nil -
req:sleep(ms)暂停了当前处理程序的指定数量的毫秒 -
req:ws_upgrade()如果可以将连接升级到Websocket,则返回1;0否则 -
req:ws_write_text(str)通过WebSocket-updraded Connection发送str作为文本框架 -
req:ws_write_binary(str)通过WebSocket-updraded Connection将str作为二进制帧发送 -
req:ws_write(str)通过WebSocket-upgraded Connection发送str作为文本或二进制帧,具体取决于仅包含ASCII字符的内容 -
req:ws_read()返回带有最后一个Websocket框架内容的字符串,或一个指示状态的数字(如果已断开连接,则在linux上的enotConn/107;如果没有任何可用的话,则在linux上eagain/11 eagain/eagain/noces in linomsg/nomsg/42 inlinux on Linux on Linux on Linux上否则否则)。以后的回报价值可能会改变一些更像Lua的东西。 -
req:remote_address()返回带有远程IP地址的字符串。 -
req:path()带有请求路径的字符串。 -
req:query_string()返回带有查询字符串的字符串(如果没有查询字符串)。 -
req:body()返回请求主体(发布/放置请求)。 -
req:request_id()返回包含请求ID的字符串。 -
req:request_date()返回日期响应标题中的Date。 -
req:is_https()如果通过https服务此请求,则返回true,否则为false。 -
req:host()返回Host标头(如果存在)的值,否则nil。 -
req:http_version()返回HTTP/1.0或HTTP/1.1具体取决于请求版本。 -
req:http_method()用http方法(例如\"GET\")返回大写中的字符串。 -
req:http_headers()返回一个带有所有标题及其值的表。
处理程序功能可以返回nil (在这种情况下,生成200 OK响应),也可以返回与HTTP状态代码匹配的数字。尝试返回无效的HTTP状态代码或数字或nil以外的任何其他内容将导致500 Internal Server Error响应。
记录
除了req参数中的MetAmethods外,还可以通过调用lwan .log :LWAN .LOG:
-
lwan .log:warning(str) -
lwan .log:info(str) -
lwan .log:error(str) -
lwan .log:critical(str)(也将谨慎使用lwan ! -
lwan .log:debug(str)(仅在调试构建中可用;否则否则)
笔记
如果lwan是在Syslog支持的情况下构建的,则这些消息也将发送到系统日志,否则将其打印为标准错误。
改写
rewrite模块将匹配URL中的模式,并给出重定向到另一个URL的选项,或以lwan处理请求的方式重写请求,就好像最初以这种方式制作了请求一样。
笔记
从LUA 5.3.1分叉,常规的Expresion Engine可能不像大多数通用用途的发动机那样包装功能,而是专门选择的,因为它是确定性的有限自动机,试图使某种否认服务攻击不可能。
可以使用简单的文本替代语法指定新的URL,也可以使用LUA脚本。
提示
LUA脚本将包含LUA模块提供的req METATOT中可用的相同的metAthods,因此它可以非常强大。
重写模块的每个实例都需要在匹配此类模式时执行pattern和操作。按照配置文件中出现的顺序评估模式,并使用配置文件中的嵌套部分指定。例如,考虑以下示例,其中指定了两种模式:
rewrite /some/base/endpoint {
pattern posts/(%d+) {
# Matches /some/base/endpointposts/2600 and /some/base/endpoint/posts/2600
rewrite_as = /cms/view-post?id=%1
}
pattern imgur/(%a+)/(%g+) {
# Matches /some/base/endpointimgur/gif/mpT94Ld and /some/base/endpoint/imgur/gif/mpT94Ld
redirect_to = https://i.i*m*gu*r.com/%2.%1
}
}
该示例定义了两种模式,一种提供了一个更漂亮的URL,该URL对用户隐藏了,另一种提供了一种不同的方法来获得直接链接,直接链接到流行的图像托管服务上托管的图像(即请求/some/base/endpoint/imgur/mp4/4kOZNYX将直接向IMGUR服务中的资源进行红色指向)。
rewrite_as或redirect_to的值也可以是lua脚本;在这种情况下,必须将expand_with_lua设置为true ,而不是将简单的文本替换语法用作上面的示例,而是必须定义一个名为handle_rewrite(req, captures)的函数。 req参数记录在LUA模块部分中; captures参数是包含所有捕获的表(即captures[2]等于简单的文本替代语法中的%2 )。此功能将新的URL返回重定向到。
该模块本身没有任何选择。每个模式中都指定选项。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
rewrite_as |
str
|
NULL
|
按照此模式重写URL |
redirect_to
|
str
|
NULL
|
重定向到新的URL遵循此模式 |
expand_with_lua |
bool
|
false
|
使用LUA脚本重定向或重写请求 |
redirect_to和rewrite_as选项是相互排斥的,其中之一至少必须指定。
也可以指定条件以触发重写。要指定一个,打开condition块,指定条件类型,然后将其参数用于评估该条件。只要每种条件有一个条件,就可以根据重写规则设置多个条件:
| 健康)状况 | 可以使用替代。句法 | 要求的部分 | 参数 | 描述 |
|---|---|---|---|---|
cookie |
是的 | 是的 | 一个key = value
|
检查请求是否具有cookie key具有值value
|
query
|
是的 | 是的 | 一个key = value
|
检查是否请求有查询变量key具有值value
|
post
|
是的 | 是的 | 一个key = value
|
检查请求是否有发布数据key具有值value
|
header
|
是的 | 是的 | 一个key = value
|
检查请求标头key是否具有值value
|
environment
|
是的 | 是的 | 一个key = value
|
检查环境变量key是否具有值value
|
stat
|
是的 | 是的 |
path , is_dir , is_file
|
检查文件系统中是否存在path ,并选择检查is_dir或is_file是否存在 |
encoding |
不 | 是的 |
deflate , gzip , brotli , zstd , none
|
检查客户端是否接受确定的编码中的响应(例如deflate = yes用于Deflate编码) |
proxied
|
不 | 不 | 布尔 | 检查请求是否已通过代理协议代理 |
http_1.0 |
不 | 不 | 布尔 | 检查是否使用HTTP/1.0客户端提出请求 |
is_https |
不 | 不 | 布尔 | 检查是否通过https提出请求 |
has_query_string |
不 | 不 | 布尔 | 检查请求是否具有查询字符串(即使为空) |
method
|
不 | 不 | 方法名称 | 检查HTTP方法是否是指定的方法 |
lua |
不 | 不 | 细绳 | 在字符串中运行LUA函数matches(req) ,并检查它是否返回true或false
|
backref
|
不 | 是的 | 单个backref index = value
|
检查BackRef号码是否匹配提供的值 |
可以使用替代。语法是指使用用于rewrite as或redirect to动作的相同替代语法引用匹配模式的能力。例如, condition cookie { some-cookie-name = foo-%1-bar }将替换%1与该条件相关的模式的第一匹匹配。
笔记
不需要部分的条件必须作为钥匙写;例如, condition has_query_string = yes 。
例如,如果一个人想发送site-dark-mode.css如果有一个带有dark style cookie,并发送site-light-mode.css否则可以写下:
pattern site.css {
rewrite as = /site-dark-mode.css
condition cookie { style = dark }
}
pattern site.css {
rewrite as = /site-light-mode.css
}
另一个示例:如果一个人想在文件系统中确实存在预压缩文件,并且用户请求它们:
pattern (%g+) {
condition encoding { brotli = yes }
condition stat { path = %1.brotli }
rewrite as = %1.brotli
}
pattern (%g+) {
condition encoding { gzip = yes }
condition stat { path = %1.gzip }
rewrite as = %1.gzip
}
pattern (%g+) {
condition encoding { zstd = yes }
condition stat { path = %1.zstd }
rewrite as = %1.zstd
}
pattern (%g+) {
condition encoding { deflate = yes }
condition stat { path = %1.deflate }
rewrite as = %1.deflate
}
笔记
通常,这不是必需的,因为文件服务模块将自动执行此操作并选择可用于所请求的编码的最小文件,但这表明仅通过配置就可以具有类似的功能。
重定向
根据锡在锡中所说, redirect模块将根据其配置中指定的选项301 Moved permanently (默认情况下;可以更改代码,请参见下文)。通常,应使用rewrite模块,因为它包含更多功能。但是,该模块也用作如何编写lwan模块(少于100行代码)的示例。
如果未指定to选项”,则始终会生成500 Internal Server Error响应。指定无效的HTTP代码或lwan不知道的代码(请参阅enum lwan _http_status ),将产生301 Moved Permanently响应。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
to |
str
|
NULL
|
重定向到 |
code |
int
|
301
|
HTTP代码执行重定向 |
回复
response模块将生成任何HTTP代码的人为响应。除了作为如何编写lwan模块的示例外,还可以用来从其他模块中解释空隙(例如,如果/与serve_files模块一起使用/.git中的405 Not Allowed响应)。
如果所提供的code属于lwan已知的响应代码,则将404 Not Found错误发送。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
code |
int
|
999
|
HTTP响应代码 |
fastcgi
fastcgi模块代理在连接到lwan HTTP客户端与lwan可访问的FastCGI服务器之间的请求。例如,这很有用,可以使用脚本语言(例如PHP)的页面。
笔记
这是该模块的初步版本,因此,它没有得到很好的优化,缺少某些功能,并且提供给环境的某些值是硬编码的。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
address |
str
|
连接的地址。可以是文件路径(对于UNIX域插座),IPv4地址( aaa.bbb.ccc.ddd:port )或IPv6地址( [...]:port )。 |
|
script_path
|
str
|
CGI脚本所在的位置。 | |
default_index
|
str
|
index.php
|
如果在请求URI中未指定的如果执行默认脚本。 |
授权部分
可以在任何模块实例或处理程序中声明授权部分,并提供通过标准HTTP授权机制授权实现该请求的方法。为了要求授权访问某个模块实例或处理程序,请声明具有basic参数的authorization部分,并设置其选项之一。
| 选项 | 类型 | 默认 | 描述 |
|---|---|---|---|
realm |
str
|
lwan
|
授权领域。这通常在浏览器中的用户/密码UI中显示 |
password_file |
str
|
NULL
|
包含用户名和密码的文件的路径(用清晰的文本)。文件格式与lwan使用的配置文件格式相同 |
警告
不仅将密码存储在服务器应访问的文件中,还将保存在存储器中几秒钟。如果可能的话,请避免使用此功能。
黑客
如果您打算向lwan贡献,请阅读本节(并遵循)。这里没有任何意外。这主要遵循许多其他FOSS项目的规则和期望,但是每个人都期望事情有些不同。
编码样式
lwan试图在整个项目中遵循一致的编码样式。如果您正在考虑为项目贡献补丁,请尝试匹配周围代码的样式,尊重这种样式。一般来说:
-
global_variables_are_named_like_this,即使它们往往很少见,应该标记为static(少数例外) - 本地变量通常较短,例如
local_var,i,conn - 结构名称通常与描述性一样短。结构的
typedef很少在lwan中使用 - 标头文件应使用
#pragma once而不是通常的guard hackery - .c文件之间使用但不是要暴露于lib lwan函数应将其原型添加到
lwan -private.h - 功能应该短而甜。例外可能适用
- 公共功能应以
lwan _ - 公共类型应在
lwan _ - 私人功能必须是静态的,并且可以在没有
lwan _前缀的情况下命名 - 代码缩进了4个空间; don\’t use tabs
- There\’s a suggested line break at column 80, but it\’s not enforced
-
/* Old C-style comments are preferred */ -
clang-formatcan be used to format the source code in an acceptable way; a.clang-formatfile is provided
测试
If modifying well-tested areas of the code (eg the event loop, HTTP parser, etc.), please add a new integration test and make sure that, before you send a pull request, all tests (including the new ones you\’ve sent) are working. Tests can be added by modifying src/scripts/testsuite.py , and executed by either invoking that script directly from the source root, or executing the testsuite build target.
Some tests will only work on Linux, and won\’t be executed on other platforms.
Fuzz-testing
lwan is automatically fuzz-tested by OSS-Fuzz. To fuzz-test locally, though, one can follow the instructions to test locally.
Currently, there are fuzzing drivers for the request parsing code, the configuration file parser, the template parser, and the Lua string pattern matching library used in the rewrite module.
Adding new fuzzers is trivial:
- Fuzzers are implemented in C++ and the sources are placed in
src/bin/fuzz. - Fuzzers should be named
${FUZZER_NAME}_fuzzer.cc. Look at the OSS-Fuzz documentation and other fuzzers on information about how to write these. - These files are not compiled by the lwan build system, but rather by the build scripts used by OSS-Fuzz. To test your fuzzer, please follow the instructions to test locally, which will build the fuzzer in the environment they\’ll be executed in.
- A fuzzing corpus has to be provided in
src/fuzz/corpus. Files have to be namedcorpus-${FUZZER_NAME}-${UNIQUE_ID}.
Exporting APIs
The shared object version of lib lwan on ELF targets (eg Linux) will use a symbol filter script to hide symbols that are considered private to the library. Please edit src/lib/lib lwan .sym to add new symbols that should be exported to lib lwan .so .
Using Git and Pull Requests
lwan tries to maintain a source history that\’s as flat as possible, devoid of merge commits. This means that pull requests should be rebased on top of the current master before they can be merged; sometimes this can be done automatically by the GitHub interface, sometimes they need some manual work to fix conflicts. It is appreciated if the contributor fixes these conflicts when asked.
It is advisable to push your changes to your fork on a branch-per-pull request, rather than pushing to the master branch; the reason is explained below.
Please ensure that Git is configured properly with your name (it doesn\’t really matter if it is your legal name or a nickname, but it should be enough to credit you) and a valid email address. There\’s no need to add Signed-off-by lines, even though it\’s fine to send commits with them.
If a change is requested in a pull request, you have two choices:
- Reply asking for clarification. Maybe the intentions were not clear enough, and whoever asked for changes didn\’t fully understand what you were trying to achieve
- Fix the issue. When fixing issues found in pull requests, please use interactive rebases to squash or fixup commits; don\’t add your fixes on top of your tree. Do not create another pull request just to accomodate the changes. After rewriting the history locally, force-push to your PR branch; the PR will update automatically with your changes. Rewriting the history of development branches is fine, and force-pushing them is normal and expected
It is not enforced, but it is recommended to create smaller commits. How commits are split in lwan is pretty much arbitrary, so please take a look at the commit history to get an idea on how the division should be made. Git offers a plethora of commands to achieve this result: the already mentioned interactive rebase, the -p option to git add , and git commit --amend are good examples.
Commit messages should have one line of summary (~72 chars), followed by an empty line, followed by paragraphs of 80-char lines explaining the change. The paragraphs explaining the changes are usually not necessary if the summary is good enough. Try to write good commit messages.
许可
lwan is licensed under the GNU General Public License, version 2, or (at your option), any later version.所以:
- Code must be either LGPLv2.1, GPLv2, a permissive \”copyfree\” license that is compatible with GPLv2 (eg MIT, BSD 3-clause), or public domain code (eg CC0)
- Although the program can be distributed and used as if it were licensed as GPLv3, its code must be compatible with GPLv2 as well; no new code can be licensed under versions of GPL newer than 2
- Likewise, code licensed under licenses compatible with GPLv3 but incompatible with GPLv2 (eg Apache 2) are not suitable for inclusion in lwan
- Even if the license does not specify that credit should be given (eg CC0-licensed code), please give credit to the original author for that piece of code
- Contrary to popular belief, it is possible to use a GPL\’d piece of code on a server without having to share the code for your application. It is only when the binary of that server is shared that source must be available to whoever has that binary. Merely accessing a lwan server through HTTP does not qualify as having access to the binary program that\’s running on the server
- When in doubt, don\’t take legal
