Installing and Configuring the H2O Web-Server

· klm's blog

Compiling the H2O web-server on Arch Linux. Report on crashes in mruby. Discussion of weaknesses of H2O.

Original post is here: eklausmeier.goip.de

1. Task at hand. Install H2O web-server on Arch Linux. H2O is a web-server written by Kazuho Oku et al. It supports:

  1. HTTP/1 and HTTP/1.1,
  2. HTTP/2,
  3. HTTP/3 ("QUIC"),
  4. FastCGI, therefore PHP-FPM,
  5. Reverse proxy,
  6. Builtin mruby, though, that crashes.

In benchmarks it ranks at the top constantly. See Web Framework Benchmarks.

Photo

It works way faster than NGINX or Apache. It shines for static web content.

2. Building. The already existing AUR packages for H2O do not work. I.e., they generate a binary which crashes. Below PKGBUILD produces a H2O binary.

 1pkgname=h2o-master-git
 2pkgver=1.0
 3pkgrel=1
 4arch=('i686' 'x86_64')
 5pkgdesc="H2O: the optimized HTTP/1.x, HTTP/2, HTTP/3 server"
 6provides=(h2o)
 7url="https://h2o.examp1e.net"
 8source=("git+https://github.com/h2o/h2o.git?commit=master?signed/" h2o.service)
 9sha256sums=('SKIP' 734e9d045dd5568665762d48e4077208c3da8c68f87510aaa9559d495dd680fd)
10
11
12build() {
13	cd "$srcdir"/h2o
14	cmake -DCMAKE_INSTALL_PREFIX=/usr .
15	make
16}
17
18package() {
19	cd "$srcdir"/h2o
20	install -Dm 644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE
21	install -Dm 644 README.md "$pkgdir"/usr/share/doc/h2o/README.md
22	install -Dm 644 "$srcdir"/h2o.service "$pkgdir"/usr/lib/systemd/system/h2o.service
23	install -Dm 644 examples/h2o/h2o.conf "$pkgdir/etc/h2o.conf"
24	make DESTDIR="$pkgdir" install
25}

Compiling on AMD Ryzen 7 5700G, max clock 4.673 GHz, 64 GB RAM, finishes in less than two minutes.

 1$ time makepkg -f
 2...
 3==> Tidying install...
 4  -> Removing libtool files...
 5  -> Purging unwanted files...
 6  -> Removing static library files...
 7  -> Copying source files needed for debug symbols...
 8  -> Compressing man and info pages...
 9==> Checking for packaging issues...
10==> Creating package "h2o-master-git"...
11  -> Generating .PKGINFO file...
12  -> Generating .BUILDINFO file...
13  -> Generating .MTREE file...
14  -> Compressing package...
15==> Leaving fakeroot environment.
16==> Finished making: h2o-master-git 1.0-1 (Fri 12 Apr 2024 09:48:36 PM CEST)
17        real 92.42s
18        user 447.76s
19        sys 0
20        swapped 0
21        total space 0

3. Configuration. Below is a working configuration in file /etc/h2o.conf. The configuration accomplishes the following:

  1. it serves http and https,
  2. it compresses via gzip and brotli,
  3. it is started user root, then switches to user http,
  4. Log format is similar to the Hiawatha log-format,
  5. PHP files are handled by php-fpm.

The entire configuration file is a YAML file.

 1listen: 80
 2listen: &ssl_listen
 3  port: 443
 4  ssl:
 5    certificate-file:    /etc/letsencrypt/live/eklausmeier.goip.de/fullchain.pem
 6    key-file:  /etc/letsencrypt/live/eklausmeier.goip.de/privkey.pem
 7    minimum-version: TLSv1.2
 8    cipher-preference: server
 9    cipher-suite: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
10    # Oldest compatible clients: Firefox 27, Chrome 30, IE 11 on Windows 7, Edge, Opera 17, Safari 9, Android 5.0, and Java 8
11    # see: https://wiki.mozilla.org/Security/Server_Side_TLS
12
13# The following three lines enable HTTP/3
14listen:
15  <<: *ssl_listen
16  type: quic
17header.set: "Alt-Svc: h3-25=\":443\""
18
19user: http
20#pid-file: /var/run/h2o/h2o.pid
21#crash-handler: /usr/local/bin/h2obacktrace
22access-log:
23  path: /var/log/h2o/access.log
24  format: "%h|%{%Y/%m/%d:%T %z}t|%s|%b|%r|%{referer}i|%{user-agent}i|%V:%p|"
25error-log: /var/log/h2o/error.log
26compress: [ br, gzip ]
27#file.dirlisting: ON
28
29file.custom-handler:
30  extension: .php
31  fastcgi.connect:
32    port: /run/php-fpm/php-fpm.sock
33    type: unix
34
35hosts:
36  0:
37    paths:
38      /jpilot/favicon.ico:
39        file.file: /home/klm/php/saaze-jpilot/public/favicon.ico
40      /jpilot/img:
41        file.dir: /home/klm/php/saaze-jpilot/public/img
42      /jpilot/jpilot.css:
43        file.file: /home/klm/php/saaze-jpilot/public/jpilot.css
44      /koehntopp/assets:
45        file.dir: /home/klm/php/saaze-koehntopp/public/assets
46      /koehntopp/jscss:
47        file.dir: /home/klm/php/saaze-koehntopp/public/jscss
48      /lemire/jscss:
49        file.dir: /home/klm/php/saaze-lemire/public/jscss
50      /mobility/img:
51        file.dir: /home/klm/php/saaze-mobility/public/img
52      /nukeklaus/img:
53        file.dir: /home/klm/php/saaze-nukeklaus/public/img
54      /nukeklaus/jscss:
55        file.dir: /home/klm/php/saaze-nukeklaus/public/jscss
56      /panorama/img:
57        file.dir: /home/klm/php/saaze-panorama/public/img
58      /paternoster/paternoster.css:
59        file.file: /home/klm/php/saaze-paternoster/public/paternoster.css
60      /saaze-example/blogklm.css:
61        file.file: /home/klm/php/saaze-example/public/blogklm.css
62      /vonhoff/img:
63        file.dir: /home/klm/php/saaze-vonhoff/public/img
64      /wendt/pagefind:
65        file.dir: /home/klm/php/saaze-wendt/public/pagefind
66      /:
67        file.dir: /srv/http
68        redirect:
69          status: 301
70          internal: YES
71          url: /index.php?
72      /p:
73        mruby.handler: |
74          Proc.new do |env|
75            [200, {'content-type' => 'text/plain'}, ["Hello world"]]
76          end          

As already mentioned at the top: mruby doesn't work. Once you access /p the entire web-server crashes.

H2O does not offer URL rewriting out of the box. The above path-configurations operate on a prefix match schema. I.e., if the URL in question starts with the string provided, this is considered a match. The string after the match is appended to the part in file.dir.

4. Discussion. While alternatives to Apache and NGINX are highly welcome, the current state of H2O leaves many questions unanswered.

  1. The builtin brotli compression is "stone old": it is seven years behind the official Google Brotli repository, which contains a number of serious fixes.
  2. The builtin mruby software is two years behind, offering mruby version 3.1 instead of 3.3.
  3. mruby crashes once called.
  4. In the hosts part the hostname seems to have no effect.

I tried to replace the old mruby dependency with the current 3.3 version. The build of H2O then failed.

While embodying software packages directly into the H2O GitHub repo makes building the software easier, it risks that the included software rots. That's exactly what is happening here.

Fun fact: I noticed H2O when reading about the LWAN web-server written by L. Pereira. Both, Kazuho Oku and L. Pereira, work at Fastly.

Also see H2O Tutorial.

In case someone wants to analyze why mruby crashes, here is the result of where in gdb:

 1Core was generated by `h2o -c h2o.conf'.
 2Program terminated with signal SIGSEGV, Segmentation fault.
 3#0  0x000062085dc9ae9b in mrb_str_hash (mrb=<optimized out>, str=...) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/string.c:1673
 41673        hval ^= (uint32_t)*bp++;
 5[Current thread is 1 (Thread 0x7002156006c0 (LWP 18088))]
 6(gdb) where
 7#0  0x000062085dc9ae9b in mrb_str_hash (mrb=<optimized out>, str=...) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/string.c:1673
 8#1  0x000062085dc8cb6c in obj_hash_code (h=0x7001d0028660, key=..., mrb=0x1a0) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/hash.c:325
 9#2  ib_it_init (mrb=mrb@entry=0x7001d00015a0, it=it@entry=0x7002155fe550, h=h@entry=0x7001d0028660, key=...) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/hash.c:645
10#3  0x000062085dc8cd3a in ib_init (ib_byte_size=<optimized out>, ib_bit=<optimized out>, h=0x7001d0028660, mrb=<optimized out>) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/hash.c:151
11#4  ht_init (mrb=mrb@entry=0x7001d00015a0, h=h@entry=0x7001d0028660, size=size@entry=17, ea=0x7001d0047700, ea_capa=ea_capa@entry=25, ht=ht@entry=0x0, ib_bit=<optimized out>) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/hash.c:793
12#5  0x000062085dc8d11a in ar_set (mrb=0x7001d00015a0, h=0x7001d0028660, key=..., val=...) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/hash.c:536
13#6  0x000062085dc8c2e6 in h_set (val=..., key=..., h=0x7001d0028660, mrb=0x7001d00015a0) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/hash.c:169
14#7  mrb_hash_set (mrb=0x7001d00015a0, hash=..., key=..., val=...) at /usr/src/debug/h2o-master-git/h2o/deps/mruby/src/hash.c:1245
15#8  0x000062085dc67938 in iterate_headers_callback (shared_ctx=shared_ctx@entry=0x7001d0001540, pool=pool@entry=0x7001d0076958, header=header@entry=0x7002155fe8d0, cb_data=cb_data@entry=0x7001d0028660) at /usr/src/debug/h2o-master-git/h2o/lib/handler/mruby.c:748
16#9  0x000062085dc67e4c in h2o_mruby_iterate_native_headers (shared_ctx=shared_ctx@entry=0x7001d0001540, pool=<optimized out>, headers=<optimized out>, cb=cb@entry=0x62085dc678a0 <iterate_headers_callback>, cb_data=cb_data@entry=0x7001d0028660)
17    at /usr/src/debug/h2o-master-git/h2o/lib/handler/mruby.c:727
18#10 0x000062085dc6a76e in build_env (generator=0x7001d006cbe0) at /usr/src/debug/h2o-master-git/h2o/lib/handler/mruby.c:836
19#11 on_req (_handler=<optimized out>, req=<optimized out>) at /usr/src/debug/h2o-master-git/h2o/lib/handler/mruby.c:974
20#12 0x000062085dbc603a in call_handlers (req=0x7001d00765d8, handler=0x62085f2d5ef0) at /usr/src/debug/h2o-master-git/h2o/lib/core/request.c:165
21#13 0x000062085dbeeb89 in handle_incoming_request (conn=<optimized out>) at /usr/src/debug/h2o-master-git/h2o/lib/http1.c:714
22#14 0x000062085dba6293 in run_socket (sock=0x7001d009b660) at /usr/src/debug/h2o-master-git/h2o/lib/common/socket/evloop.c.h:834
23#15 run_pending (loop=loop@entry=0x7001d0000b70) at /usr/src/debug/h2o-master-git/h2o/lib/common/socket/evloop.c.h:876
24#16 0x000062085dba6300 in h2o_evloop_run (loop=0x7001d0000b70, max_wait=<optimized out>) at /usr/src/debug/h2o-master-git/h2o/lib/common/socket/evloop.c.h:925
25#17 0x000062085dc5da1b in run_loop (_thread_index=<optimized out>) at /usr/src/debug/h2o-master-git/h2o/src/main.c:4210
26#18 0x000070022b8a955a in ?? () from /usr/lib/libc.so.6
27#19 0x000070022b926a3c in ?? () from /usr/lib/libc.so.6