Lwan은 고성능 및 확장 가능한 웹 서버입니다.
프로젝트 웹 사이트에는 자세한 내용이 포함되어 있습니다.
| OS | 아치 | 풀어 주다 | 디버그 | 정적 분석 | 테스트 |
|---|---|---|---|---|---|
| 리눅스 | x86_64 | 보고서 기록 | |||
| freebsd 14 | x86_64 | ||||
| OpenBsd 7.4 | x86_64 |
lwan 직접 구축하거나 컨테이너 이미지를 사용하거나 좋아하는 분포에서 패키지를 잡을 수 있습니다.
LWAN을 설치하기 전에 모든 종속성이 설치되어 있는지 확인하십시오. 이들은 모두 GNU/Linux 분포에서 발견되는 일반적인 종속성입니다. 패키지 이름은 다르지만 배포에서 사용하는 패키지 관리 도구를 사용하여 검색하기가 어렵지 않아야합니다.
빌드 시스템은 이러한 라이브러리를 찾고 사용 가능한 경우 활성화/링크를 활성화합니다.
-DENABLE_BROTLI=NO 통과하여 비활성화 할 수 있습니다-DENABLE_ZSTD=NO 통과하여 비활성화 할 수 있습니다-DENABLE_TLS=ON (기본값)이 통과됩니다.-DUSE_ALTERNATIVE_MALLOC 전달하여 사용할 수 있습니다.pacman -S cmake zlibpkg install cmake pkgconfapt-get update && apt-get install git cmake zlib1g-dev pkg-configbrew install cmake pacman -S cmake zlib sqlite luajit mariadb-libs gperftools valgrind mbedtlspkg install cmake pkgconf sqlite3 lua51apt-get update && apt-get install git cmake zlib1g-dev pkg-config lua5.1-dev libsqlite3-dev libmariadb-dev libmbedtls-devbrew install cmake mariadb-connector-c sqlite [email protected] pkg-config ~$ git clone git://github.com/lpereira/lwan
~$ cd lwan
~/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/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 웹 프레임 워크 벤치 마크 코드. sqlite 및 mariadb 라이브러리가 필요합니다.src/samples/clock/clock : 클록 샘플. 항상 현지 시간을 보여주는 GIF 파일을 생성합니다.src/bin/tools/mimegen : Extension-Mime 유형 테이블을 작성합니다. 빌드 프로세스 중에 사용됩니다.src/bin/tools/bin2hex : #include와 함께 사용하기에 적합한 이진 파일에서 C 파일을 생성합니다. 빌드 프로세스 중에 사용됩니다.src/bin/tools/configdump : configuration reader API를 사용하여 구성 파일을 덤프합니다. 테스트에 사용됩니다.src/bin/tools/weighttp : weighttp HTTP 벤치마킹 도구를 다시 작성합니다.src/bin/tools/statuslookupgen : HTTP 상태 코드 및 설명을위한 완벽한 해시 테이블을 생성합니다. 빌드 프로세스 중에 사용됩니다. Passing -DCMAKE_BUILD_TYPE=Release 일부 컴파일러 최적화 (예 : LTO)를 활성화하고 현재 아키텍처 코드를 조정할 수 있습니다.
중요한
벤치마킹시 릴리스 빌드를 사용하십시오 . 기본값은 디버그 빌드로 모든 요청을 표준 출력에 기록 할뿐만 아니라 잠금을 누른 상태에서 서버를 심각하게 누른 상태에서 그렇게합니다.
기본 빌드 (즉, -DCMAKE_BUILD_TYPE=Release 전달하지 않음)는 디버깅 목적에 적합한 버전을 구축합니다. 이 버전은 Valgrind (헤더가있는 경우) 에서 사용할 수 있으며 릴리스 버전에 제거 된 디버깅 메시지가 포함됩니다. 디버깅 메시지는 각 요청마다 인쇄됩니다.
이러한 빌드에서는 소독제를 활성화 할 수 있습니다. LWAN을 구축 할 것을 선택하려면 다음 옵션 중 하나를 CMAKE 호출 라인에 지정하십시오.
-DSANITIZER=ubsan 정의되지 않은 동작 소독제를 선택합니다.-DSANITIZER=address 주소 소독제를 선택합니다.-DSANITIZER=thread 스레드 소독제를 선택합니다. 대체 메모리 할당 자도 선택할 수 있습니다. Lwan은 현재 Tcmalloc, Mimalloc 및 Jemalloc을 상자에서 지원합니다. 그 중 하나를 사용하려면 "선택적 종속성"섹션에 제공된 이름을 사용하여 -DALTERNATIVE_MALLOC=name Passed하십시오.
-DUSE_SYSLOG=ON 옵션을 CMAKE로 전달하여 표준 출력 외에 시스템 로그에 로그인 할 수 있습니다.
배포를 위해 lwan을 구축하는 경우 -DMTUNE_NATIVE=OFF 옵션을 사용하는 것이 좋습니다. 그렇지 않으면 생성 된 바이너리가 일부 컴퓨터에서 실행되지 않을 수 있습니다.
TLS 지원은 KTLS를 지원할 수있을 정도로 새로운 헤더가있는 Linux 시스템에 적절한 MBEDTLS 설치가있을 때 자동으로 활성화되지만 -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 Make Target을 수행 할 수 있으며,이를 통해 testrunner 실행하여 LCOV로 테스트 범위 보고서를 준비합니다.
이 저장소의 모든 커밋은이 보고서의 생성을 트리거하며 결과는 공개적으로 제공됩니다.
제공된 lwan.conf 편집하여 서버를 설정하십시오. 형식은 아래 세부 사항에 설명되어 있습니다.
메모
lwan은 현재 디렉토리의 실행 가능 이름을 기반으로 구성 파일을 찾으려고합니다. testrunner.conf lwan binary의 경우 testrunner binary, lwan.conf 등에 사용됩니다.
구성 파일은 현재 디렉토리에서로드됩니다. 이 파일을 변경하지 않으면 LWAN을 실행하면 ./wwwroot 디렉토리에 위치한 정적 파일이 제공됩니다. Lwan은 모든 인터페이스에서 포트 8080에서 듣습니다.
LWAN은 CPU 수를 감지하고 최대 개방 파일 설명자 수를 늘리며 일반적으로 실행중인 환경에 대한 합리적인 설정을 자동으로 설정하기 위해 최선을 다합니다. 이러한 설정 중 다수는 구성 파일에서 조정할 수 있지만 일반적으로 엉망이되지 않는 것이 좋습니다.
팁
선택적으로 lwan 바이너리는 구성 파일없이 원샷 정적 파일 서빙에 사용할 수 있습니다. 도움을 받으려면 --help 로 실행하십시오.
lwan은 친숙한 key = value 구성 파일 구문을 사용합니다. 주석은 # 문자 (예 : 쉘 스크립트, 파이썬 및 Perl과 유사)로 지원됩니다. 중첩 된 섹션은 곱슬 괄호로 만들 수 있습니다. 섹션은 비어있을 수 있습니다. 이 경우 곱슬 괄호는 선택 사항입니다.
some_key_name 구성 파일의 some key name 과 동일합니다 (구현 세부 사항으로 코드 읽기 구성 옵션은 밑줄이있는 버전 만 제공됩니다).
팁
값에는 환경 변수가 포함될 수 있습니다. 구문 ${VARIABLE_NAME} 사용하십시오. 기본값은 결장 (예 : $ { ${VARIABLE_NAME} ${VARIABLE_NAME:foo} 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_name ${USER:nobody} 로 지정하는 것은 ${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 | PUT 요청에 대한 최대 데이터 크기 수를 바이트로 설정합니다. |
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의 보호 장치로만 수행됩니다.)
팁
초기화가 이루어진 후 구성 파일 및 개인 데이터 (예 : TLS 키)가 서버에 도달하지 못하는 방식으로 site 섹션 바로 앞에 구속 재킷을 선언하십시오.
| 옵션 | 유형 | 기본 | 설명 |
|---|---|---|---|
user | str | NULL | 이 사용자 이름으로 권한을 삭제하십시오 |
chroot | str | NULL | chroot() |
drop_capabilities | bool | true | CAPSET (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 전송되지 않음)를 무시하고 환경 변수 $WITH_THIS_ENVIRONMENT_VARIABLE 에서 얻은 값으로 Some-Custom-Header 설정합니다.
요청을 서비스하는 동안 실제 값을 보낼 때 문제가 발생할 수 있으므로 일부 헤더는 재정의 할 수 없습니다. 여기에는 다음이 포함되지만 이에 국한되지는 않습니다.
DateExpiresWWW-AuthenticateConnectionContent-TypeTransfer-EncodingAccess-Control-Allow- 헤더 메모
헤더 이름은 또한 사례에 민감하지 않습니다 (및 사례 보존). 오버 딩 SeRVeR Server 헤더를 무시하지만 구성 파일에 기록 된 방식으로 보냅니다.
HTTP 리스너 ( listener 섹션) 및 HTTPS 리스너 ( tls_listener 섹션)의 LWAN 프로세스에 따라 두 명의 청취자 만 지원됩니다. 각 유형의 하나의 리스너 만 허용됩니다.
경고
TLS 지원은 실험적입니다. 초기 테스트 중에는 안정적이지만 마일리지는 다를 수 있습니다. 이 시점에서 TLSV1.2 만 지원되지만 TLSV1.3은 계획됩니다.
메모
TLS 지원이 필요합니까? tls.ko 모듈이 내장 또는로드 된 Linux. 다른 운영 체제에 대한 지원은 향후 추가 될 수 있습니다. freebsd는 가능해 보이며 다른 운영 체제는 비슷한 기능을 제공하지 않는 것 같습니다. 지원되지 않는 운영 체제의 경우 히치와 같은 TLS 터미네이터 프록시를 사용하는 것이 좋습니다.
listener 와 tls_listener 섹션 모두의 경우 유일한 매개 변수는 인터페이스 주소와 청취 할 포트입니다. 리스너 구문은 ${ADDRESS}:${PORT} 이며, 여기서 ${ADDRESS} 는 * (모든 인터페이스에 바인딩), IPv6 주소 (사각형 브래킷으로 둘러싸인 경우), IPv4 주소 또는 호스트 이름이 될 수 있습니다. 예를 들어, listener localhost:9876 lo 인터페이스, 포트 9876 에서만 듣습니다.
listener 섹션은 키를 사용하지 않지만 tls_listener 섹션에는 두 가지 : cert 및 key (각각 TLS 인증서 및 개인 키 파일이있는 디스크 위치에 대한 각각의 포인팅)가 필요하며, 선택적인 부울 hsts 키를 취하는 경우 Strict-Transport-Security 헤더가 HTTPS 응답으로 전송되는 경우 제어합니다.
팁
테스트 목적으로 이러한 키를 생성하려면 OpenSSL 명령 줄 도구는 다음과 같이 사용할 수 있습니다. openssl req -nodes -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 7
메모
chroot 옵션이있는 슈트 재킷은 tls_listener 섹션 바로 다음에 인증서와 키가 해당 시점에서 도달 할 수없는 방식으로 선언되는 것이 좋습니다.
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을 라우팅하기 위해 LWAN은 요청 URI의 가장 큰 공통 접두사와 일치하여 리스너 섹션에 지정된 접두사 세트와 일치합니다. 특정 접두사에 대한 요청이 처리되는 방법은 리스너 섹션에서 선언 된 핸들러 또는 모듈에 따라 다릅니다. 핸들러와 모듈은 내부적으로 비슷합니다. 핸들러는 단지 함수이며 상태를 유지하지 않으며 모듈은 상태 (인스턴스)를 보유합니다. 모듈의 여러 인스턴스가 리스너 섹션에 나타날 수 있습니다.
접두사를 핸들러 또는 모듈에 부착하는 특수 구문은 없습니다. 모든 구성 파서 규칙이 여기에 적용됩니다. ${NAME} ${PREFIX} 를 사용하여 ${PREFIX} prefix 경로를 ${NAME} 이라는 핸들러에 연결하십시오 (if ${NAME} C의 "연산자 주소"또는 ${NAME} 이라는 & 로 시작합니다. 빈 섹션은 여기에서 사용할 수 있습니다.
각 모듈에는 특정 옵션 세트가 있으며 다음 섹션에 나열되어 있습니다. 구성 옵션 외에도 모듈 인스턴스의 선언에 특수 authorization 섹션이 참석할 수 있습니다. 핸들러는 구성 옵션을 사용하지 않지만 authorization 섹션을 포함 할 수 있습니다.
팁
--help 명령 줄 인수로 LWAN을 실행하면 내장 모듈 및 핸들러 목록이 표시됩니다.
다음은 Lwan과 함께 배송 된 모듈에 대한 몇 가지 기본 문서입니다.
serve_files 모듈은 정적 파일을 제공하고 자동으로 디렉토리 지수를 작성하거나 사전 압축 된 파일을 제공합니다. 일부 휴리스틱에 따르면 일반적으로 가능한 가장 빠른 방법으로 파일을 제공하기 위해 최선을 다할 것입니다.
| 옵션 | 유형 | 기본 | 설명 |
|---|---|---|---|
path | str | NULL | 제공 할 파일이 포함 된 디렉토리로가는 경로 |
index_path | str | index.html | 디렉토리의 색인 역할을 할 파일 이름 |
serve_precompressed_path | bool | true | $ file.gz가 존재하는 경우 $ 파일보다 작고 새롭고 클라이언트는 gzip 인코딩을 수락하고 전송합니다. |
auto_index | bool | true | index_path 파일이없는 경우 디렉토리 목록을 자동으로 생성하십시오. 그렇지 않으면 404를 생성합니다 |
auto_index_readme | bool | true | 자동으로 생성 된 디렉토리 인덱스의 일부로 readme 파일의 내용을 포함 |
directory_list_template | str | NULL | 디렉토리 목록의 콧수염 템플릿으로가는 경로; 기본적으로 내부 템플릿을 사용하십시오 |
read_ahead | int | 131702 | 열린 파일을 캐싱 할 때 미리 읽을 수있는 최대 바이트 양. 0 의 값은 readahead를 비활성화합니다. readahead는 파일 범위가 파일 시스템에서 읽는 반면 I/O 스레드를 차단하지 않도록 최하서 스레드에 의해 수행됩니다. |
cache_for | time | 5s | 캐시에서 파일 메타 데이터 (크기, 압축 내용, 열린 파일 디스크립터 등)를 유지하는 시간 |
메모
16kib보다 작은 파일은 cache_for 설정에 지정된 기간 동안 RAM에서 압축됩니다. Lwan은 항상 Deflate로 압축을 시도하며 선택적으로 Brotli 및 Zstd로 압축됩니다 (Lwan이 적절한 지원으로 구축 된 경우).
압축의 가치가없는 경우 (예 : Content-Encoding 헤더를 추가하면 압축되지 않은 파일을 보내는 것보다 더 큰 응답을 초래할 수 있습니다. 일반적으로 매우 작은 파일의 경우) Lwan은 파일을 압축하는 데 시간을 소비하지 않습니다.
16kib보다 큰 파일의 경우 Lwan은 압축을 시도하지 않습니다. 향후 버전에서는 파일이 압축되는 동안 청크 인코딩을 사용하여이를 수행하고 응답을 보낼 수 있지만 (물론 특정 한도까지), 현재는 미리 압축 serve_precompressed_path 파일 만 고려됩니다.
모든 경우에 Lwan은 파일 시스템에서 발견 된 경우 GZIPPEN 버전을 사용해 보이고 클라이언트 가이 인코딩을 요청했습니다.
directory_list_template 의 변수| 변하기 쉬운 | 유형 | 설명 |
|---|---|---|
rel_path | str | 루트 디렉토리에 대한 경로 실제 경로 |
readme | str | 첫 번째 readme 파일의 내용 ( readme , readme.txt , read.me , README.TXT , README ) |
file_list | 반복자 | 파일 목록에서 반복 |
file_list.zebra_class | str | 홀수 항목, 또는 even 항목의 경우 odd |
file_list.icon | str | 파일 유형의 아이콘 경로 |
file_list.name | str | 파일 이름 (탈출) |
file_list.type | str | 파일 유형 (디렉토리 또는 일반 파일) |
file_list.size | int | 파일 크기 |
file_list.unit | str | file_size 용 장치 |
lua 모듈은 LUA 프로그래밍 언어로 작성된 스크립트로 요청을 서비스 할 수 있도록합니다. 이 모듈에서 제공하는 기능은 매우 스파르타이지만 Sailor와 같은 프레임 워크를 실행할 수 있습니다.
스크립트는 파일에서 제공되거나 구성 파일에 포함되거나이를로드 한 결과, 표준 LUA 모듈 및 (선택적으로 LUAJIT를 사용하는 경우) 코드를 최적화하면 한동안 캐시됩니다.
| 옵션 | 유형 | 기본 | 설명 |
|---|---|---|---|
default_type | str | text/plain | 응답에 대한 기본 마임 유형 |
script_file | str | NULL | LUA 스크립트로가는 길 |
cache_period | time | 15s | Lua State를 메모리에 유지할 시간입니다 |
script | str | NULL | 인라인 LUA 스크립트 |
메모
LUA 스크립트는 다른 스레드에 의해 서비스 될 수있을뿐만 아니라 cache_period 구성 옵션에 지정된 시간 동안 만 사용할 수 있으므로 글로벌 변수를 사용할 수 없습니다. Lwan의 각 I/O 스레드는 LUA VM (즉, 모든 I/O 스레드에 대해 하나의 lua_State struct) 인스턴스를 생성하고 요청 당 LUA 스레드 ( lua_newthread() 포함)를 생성하기 때문입니다.
각 엔드 포인트에 대해 하나의 인스턴스 인스턴스가 필요하지 않습니다. 구성 파일에 포함 된 단일 스크립트는 다른 엔드 포인트를 서비스 할 수 있습니다. 스크립트는 다음 시그니처와 함께 함수를 구현해야합니다 head handle_${METHOD}_${ENDPOINT}(req) , ${METHOD} post http 메소드 get 될 수 있으며 ${ENDPOINT} 해당 기능으로 처리하려는 원하는 엔드 포인트입니다. 특정 버전이 존재하지 않으면 일반 handle(req) 기능이 호출됩니다.
팁
포획에 root 엔드 포인트를 사용하십시오. 예를 들어, 해당 요청에 대해 다른 핸들러를 찾을 수없는 경우 핸들러 기능 handle_get_root() 호출됩니다. Catchall이 지정되지 않으면 서버는 404 Not Found 오류를 반환합니다.
req 매개 변수는 아래에서 볼 수 있듯이 요청으로부터 정보를 얻거나 응답을 설정하는 방법을 포함하는 메타 테이블을 가리 킵니다.
req:query_param(param) query 매개 변수 (쿼리 문자열에서)를 키 매개 param 로 반환하거나 찾을 수없는 경우 nil 반환합니다.req:post_param(param) 키 매개 변수를 사용하여 게시물 매개 변수 ( ${POST} handlers에 대해서만)를 param 하거나 찾을 수없는 경우 nil 반환합니다.req:set_response(str) 문자열 str 에 대한 응답을 설정합니다req:say(str) 응답 청크를 보냅니다 (HTTP에서 청크 인코딩 사용)req:send_event(event, str) 이벤트를 보냅니다 (서버 entent 이벤트 사용)req:cookie(param) param 이라는 쿠키를 반환하거나 nil 찾을 수 없습니다.req:set_headers(tbl) 테이블 tbl 에서 응답 헤더를 설정합니다. 테이블 값 ( {'foo'={'bar', 'baz'}} )에서 문자열이 아닌 테이블을 사용하여 헤더를 여러 번 지정할 수 있습니다. say() 또는 send_event() 로 응답을 보내기 전에 호출해야합니다.req:header(name) 주어진 이름 또는 찾기 nil 아닌 경우 요청에서 헤더를 얻습니다.req:sleep(ms) 지정된 양의 밀리 초에 대한 현재 처리기를 일시 중지합니다.req:ws_upgrade() 반환 1 연결을 웹 사이트로 업그레이드 할 수있는 경우 1; 그렇지 않으면 0req:ws_write_text(str) 텍스트 프레임으로 webSocket 업그레이드 된 연결을 통해 str 보냅니다.req:ws_write_binary(str) 이진 프레임으로 webSocket 업그레이드 된 연결을 통해 str 보냅니다.req:ws_write(str) ascii 문자 만 포함 된 컨텐츠에 따라 텍스트 또는 이진 프레임으로 webSocket 업그레이드 된 연결을 통해 str 보냅니다.req:ws_read() 마지막 WebSocket 프레임의 내용이있는 문자열 또는 상태를 나타내는 숫자를 반환합니다 (Linux에서 연결이 끊어진 경우 EnotConn/107; Linux의 Eagain/11이 없으면 Linux의 Enomsg/42). 여기서의 반환 값은 미래에 더 많은 LUA와 같은 것이 변할 수 있습니다.req:remote_address() 원격 IP 주소로 문자열을 반환합니다.req:path() 요청 경로로 문자열을 반환합니다.req:query_string() 쿼리 문자열이있는 문자열을 반환합니다 (쿼리 문자열이없는 경우 빈 문자열).req:body() 요청 본문을 반환합니다 (Post/Put 요청).req:request_id() 요청 ID가 포함 된 문자열을 반환합니다.req:request_date() Date 응답 헤더에 작성된 날짜를 반환합니다.req:is_https() 이 요청이 https를 통해 서비스되면 true 반환합니다. 그렇지 않으면 false .req:host() 존재하는 경우 Host 헤더의 값을 반환합니다 nilreq: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 매개 변수의 메타 메드 외에도 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에서 포크 된 일반적인 엑스 피션 엔진은 대부분의 일반 목적 엔진만큼 기능으로 포장되지 않을 수 있지만, 어떤 종류의 서비스 거부 공격을 불가능하게 만들기 위해 결정적인 유한 자동 항구이기 때문에 구체적으로 선택되었습니다.
새 URL은 간단한 텍스트 대체 구문을 사용하여 지정하거나 LUA 스크립트를 사용할 수 있습니다.
팁
LUA 스크립트에는 LUA 모듈이 제공하는 req Metatable에서 사용할 수있는 동일한 메타 메드가 포함되어 있으므로 매우 강력 할 수 있습니다.
다시 쓰기 모듈의 각 인스턴스에는 이러한 패턴이 일치 할 때 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.imgur.com/%2.%1
}
}
이 예제는 사용자로부터 숨겨진 더 좋은 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 블록을 열고 조건 유형을 지정한 다음 해당 조건의 매개 변수를 평가하십시오. 유형 당 하나의 조건이있는 한 다시 쓰기 규칙에 따라 여러 조건을 설정할 수 있습니다.
| 상태 | Subst를 사용할 수 있습니다. 통사론 | 필요한 섹션 | 매개 변수 | 설명 |
|---|---|---|---|---|
cookie | 예 | 예 | 단일 key = value | 요청 쿠키 key 있는지 확인하십시오. 값 value 있는지 확인합니다 |
query | 예 | 예 | 단일 key = value | 요청에 query 변수 key 있는지 확인 값 value 있는지 확인합니다. |
post | 예 | 예 | 단일 key = value | 요청에 Post Data 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 인코딩의 경우 deflate = yes ) |
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 번호가 제공된 값과 일치하는지 확인합니다 |
Subst를 사용할 수 있습니다. 구문은 rewrite as 에 사용 된 동일한 대체 구문을 사용하여 일치 패턴을 참조하는 기능을 말하거나 동작 redirect to . 예를 들어, condition cookie { some-cookie-name = foo-%1-bar } 이 조건이 관련된 패턴에서 첫 번째 일치로 %1 대체합니다.
메모
섹션이 필요없는 조건은 키로 작성해야합니다. 예를 들어, condition has_query_string = yes .
예를 들어, 값이 dark style 쿠키가있는 경우 site-dark-mode.css 보내려면 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 모듈을 작성하는 방법의 예제 역할을하는 것 외에도 다른 모듈에서 공극을 개척하는 데 사용될 수 있습니다 (예 : /.git 의 파일에 대한 405 파일에 대한 응답이 serve_files 405 Not Allowed 응답을 생성 / ).
제공된 code Lwan이 알려진 응답 코드를 벗어나면 404 Not Found 오류가 대신 전송됩니다.
| 옵션 | 유형 | 기본 | 설명 |
|---|---|---|---|
code | int | 999 | HTTP 응답 코드 |
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 , conntypedef Lwan에서 거의 사용되지 않습니다#pragma once 사용해야합니다.lwan-private.h 에 추가해야합니다.lwan_ 로 접두사해야합니다.lwan_ 로 접두사해야합니다.lwan_ 접두사없이 이름을 지정할 수 있습니다./* Old C-style comments are preferred */clang-format 소스 코드를 허용 가능한 방식으로 포맷하는 데 사용될 수 있습니다. a .clang-format file 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.
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:
src/bin/fuzz .${FUZZER_NAME}_fuzzer.cc . Look at the OSS-Fuzz documentation and other fuzzers on information about how to write these.src/fuzz/corpus . Files have to be named corpus-${FUZZER_NAME}-${UNIQUE_ID} . The shared object version of liblwan 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/liblwan.sym to add new symbols that should be exported to liblwan.so .
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:
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. 그러므로:
While Lwan was written originally for Linux, it has been ported to BSD systems as well. The build system will detect the supported features and build support library functions as appropriate.
For instance, epoll has been implemented on top of kqueue, and Linux-only syscalls and GNU extensions have been implemented for the supported systems. This blog post explains the details and how #include_next is used.
It can achieve good performance, yielding about 320000 requests/second on a Core i7 laptop for requests without disk access, and without pipelining.
When disk I/O is required, for files up to 16KiB, it yields about 290000 requests/second ; for larger files, this drops to 185000 requests/second , which isn't too shabby either.
These results, of course, with keep-alive connections, and with weighttp running on the same machine (and thus using resources that could be used for the webserver itself).
Without keep-alive, these numbers drop around 6-fold.
There is an IRC channel ( #lwan ) on Libera. A standard IRC client can be used.
Here's a non-definitive list of third-party stuff that uses Lwan and have been seen in the wild. If you see mentions of Lwan in the media or academia, however small it might be, please contact the author! It'll make her day!
Some other distribution channels were made available as well:
Dockerfile is maintained by @jaxgeller, and is available from the Docker registry.Lwan has been also used as a benchmark:
Mentions in academic journals:
Mentions in magazines:
Mentions in books:
Some talks mentioning Lwan:
Not really third-party, but alas:
Lwan container images are available at ghcr.io/lpereira/lwan. Container runtimes like Docker or Podman may be used to build and run Lwan in a container.
Container images are tagged with release version numbers, so a specific version of Lwan can be pulled.
# latest version
docker pull ghcr.io/lpereira/lwan:latest
# pull a specific version
docker pull ghcr.io/lpereira/lwan:v0.3
Clone the repository and use Containerfile (Dockerfile) to build Lwan with all optional dependencies enabled.
podman build -t lwan .
The image expects to find static content at /wwwroot , so a volume containing your content can be mounted.
docker run --rm -p 8080:8080 -v ./www:/wwwroot lwan
To bring your own lwan.conf , simply mount it at /lwan.conf .
podman run --rm -p 8080:8080 -v ./lwan.conf:/lwan.conf lwan
Podman supports socket activation of containers. This example shows how to run lwan with socket activation and Podman on a Linux host.
Requirements: Podman version 4.5.0 or higher.
sudo useradd test
sudo machinectl shell test@
podman build -t lwan ~/lwan
mkdir -p ~/.config/containers/systemd
mkdir -p ~/.config/systemd/user
listener systemd:my.socket
site {
serve_files / {
path = /web
}
}
[Socket]
ListenStream=8080
[Unit]
After=my.socket
Requires=my.socket
[Container]
Network=none
Image=localhost/lwan
Volume=/home/test/lwan.conf:/lwan.conf:Z
Volume=/home/test/web:/web:Z
:Z is needed on SELinux systems. As lwan only needs to communicate over the socket-activated socket, it's possible to use Network=none . See the article How to limit container privilege with socket activation. mkdir ~/web
echo hello > ~/web/file.txt
systemctl --user daemon-reload
systemctl --user start my.socket
$ curl localhost:8080/file.txt
hello
These are some of the quotes found in the wild about Lwan. They're presented in no particular order. Contributions are appreciated:
"Lwan is like a classic, according to the definition given by Italian -- writer Italo Calvino: you can read it again and again" Antonio Piccolboni
"I read lwan's source code. Especially, the part of using coroutine was very impressive and it was more interesting than a good novel. Thank you for that." -- @patagonia
"For the server side, we're using Lwan, which can handle 100k+ reqs/s. It's supposed to be super robust and it's working well for us." -- @fawadkhaliq
"Insane C thing" -- Michael Sproul
"The best performer is LWAN, a newcomer" -- InfoQ
"I've never had a chance to thank you for Lwan. It inspired me a lot to develop Zewo" -- @paulofariarl
"Let me say that lwan is a thing of beauty. I got sucked into reading the source code for pure entertainment, it's so good. high five " -- @kwilczynski
"mad science" -- jwz
"Nice work with Lwan! I haven't looked that carefully yet but so far I like what I saw. You definitely have the right ideas." -- @thinkingfish
"Lwan is a work of art. Every time I read through it, I am almost always awe-struck." -- @neurodrone
"For Round 10, Lwan has taken the crown" -- TechEmpower
"Jeez this is amazing. Just end to end, rock solid engineering. (...) But that sells this work short." kjeetgill
"I am only a spare time C coder myself and was surprised that I can follow the code. Nice!" cntlzw
"Impressive all and all, even more for being written in (grokkable!) C. Nice work." tpaschalis
"LWAN was a complete failure" dermetfan