HTTP/2 server library in Haskell

I'm trying to develop QUIC in Haskell. In short, QUIC is a fast and reliable transport protocol based on UDP. You can think of it as TCP2. HTTP/2 over QUIC is now called HTTP/3.

Two level dispatchings are necessary for QUIC:

  1. Dispatching QUIC packets to connections
  2. Dispatching QUIC streams in a connection to something (perhaps to lightweight thread workers)

OS kernels are taking care of the first dispatching for TCP. But we have to implement it in a user land for QUIC. I believe that its implementation in Haskell is not so difficult.

But the second dispatching is tough. As I described in Experience Report: Developing High Performance HTTP/2 Server in Haskell and Supporting HTTP/2, I mapped an HTTP/2 stream to a worker of lightweight thread. In this architecture, some other threads are involved to control workers.

Should I reinvent the similar architecture for QUIC? My brain got befuddled.

I finally reached a conclusion: my question was raised because my HTTP/2 server was hard-coded in Warp. If I can extract it as a generic library for HTTP/2 servers, I will be able to reuse it for HTTTP/3.

It took much time but the result is promising. The APIs is so beautiful and functional. This APIs even enable to calculate a checksum in a trailer for streaming response body.

I have already released the http2 library version 2.0.0 with Network.HTTP2.Server module. Warp will switch from the original implementation to this server library soon.

The APIs are inspired by WAI but are independent from it. I hope that other HTTP engines can adopt the HTTP/2 server library easily.

実践的な Haskell debugging




% prog +RTS -xc




% prog +RTS -p
% cat prog.prof


以下で、prog.hp というファイルが作られ、ヒーププロファイルが格納される。このファイルは、プログラムが実装されている間中育っていく。プログラムを終了する必要はない。サーバの実働中もヒーププロファイルが見れるので、監視に便利。

% prog +RTS -h -L50

注意:以前の定番である -hT は、最近使えないようだ。


% hp2ps -c prog.hp; open prog.ps

PINNED が多ければ、 ByteString がリークしている。


% mkdir profile
% cd profile
% cab init
% cab install -e -p prog
% .cabal-sandbox/bin/prog +RTS -h


% cd package
% cab init
% cab install -d -p -t
% cab conf -e -p -t
% cab build
% ./dist/build/test/test +RTS -xc


手元のライブラリを利用する場合は、cab init した後に cab add package するとよい。パッケージ名とディレクトリ名が異なっていても、ちゃんと認識される。

Bringing TLS 1.3 to Haskell

Bringing TLS 1.3 to Haskell

Haskell TLS library version 1.4.1 or earlier support SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1 and TLS 1.2. Here is brief summary of their security:

  • SSL 2.0 is insecure and obsoleted by RFC 6176
  • SSL 3.0 is insecure and obsoleted by RFC 7568
  • TLS 1.0 is insecure due to lack of AEAD
  • TLS 1.1 is insecure due to lack of AEAD
  • TLS 1.2 is secure if it is used with proper parameters (using (EC)DHE and AEAD, disabling compression and renegotiation).

You may be surprised that both TLS 1.0 and TLS 1.1 are vulnerable. Actually, HTTP/2 requires TLS 1.2 with TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 or stronger. Also, major browsers are planning to disable TLS 1.0 and 1.1 in 2020.

To make your web sites secure, you should specify good parameters to TLS 1.2. Unless you are a security expert, it would be a tough job. Any other smart solutions? Yes, here is TLS 1.3.

Standardization of TLS 1.3 was finished in August 2018 and resulted in RFC 8446. It is secure by design. It allows only (EC)DHE for key exchange and only AEAD for traffic encryption. And it also removed compression and renegotiation.

Chrome and Firefox have supported TLS 1.3 in version 70 and version 63, respectively. To know support status of other browsers, please refer to Can I use.

So, it's high time to bring TLS 1.3 to the Haskell community. We proudly announce that we have released TLS library version 1.5.0 with TLS 1.3! To use TLS 1.3 in TLS library, you should specify TLS13 to supportedVersions. For more information, please see #167 and #282.

If you are using Warp TLS, you should obtain the newest Warp TLS and build it with TLS version 1.5.0. You can check if TLS 1.3 is used with Firefox or Chrome:

  • Firefox: click the lock beside the URL bar, ">" and "More Information".
  • Chrome: use the developer tool and click "Security" tab.

Since TLS 1.3 is a completely different protocol comparing to the older versions, I needed to write a lot of code. Olivier Chéron reviewed my code carefully and thoroughly. He also brought high-quality code to support missing features. My deep thank goes to him. I thank Vincent Hanquez and Viktor Dukhovni for enhancing crytonite and improving certification handling, respectively.

Enjoy TLS 1.3 in Haskell!






  • 単位元id <$> x = x
  • 合成 : f <$> (g <$> x) = (f . g) <$> x




  • 単位元pure id <*> x = x
  • 準同型: pure g <*> pure x = pure (g x)
  • 交換 : x <*> pure y = pure (\g -> g y) <*> x
  • 結合 : x <*> (y <*> z) = pure (.) <*> x <*> y <*> z


pure g <*> x1 <*> x2 <*> ... <*> xn

でした(pure g <*> の部分は g <$> と書くことが多い)。Applicative法則は、アプリカティブならすべてこの形に直せることを保証する規則なのです。例として、t <*> pure s <*> (u <*> v) を変換してみましょう。

  t <*> pure s <*> (u <*> v)
= pure (.) <*> (t <*> pure s) <*> u <*> v
= pure (.) <*> (pure (\g -> g s) <*> t) <*> u <*> v
= pure (.) <*> pure (.) <*> pure (\g -> g s) <*> t <*> u <*> v
= pure ((.)(.)) <*> pure (\g -> g s) <*> t <*> u <*> v
= pure ((.)(.)(\g -> g s)) <*> t <*> u <*> v



Monad法則は以下の3つです(returnpure の別名):

  • 単位元return x >>= f = f x
  • 単位元mx >>= return = mx
  • 結合  : mx >>= (\x -> (f x) >>= g) = mx >>= f >>= g

二つの単位元ですが、右辺がオススメなのは分かりますよね? 右単位元は特に有用です。do の中をいろいろ書き換えていると、知らない間に、

do ...
   y <- f x
   return y


do ...
   f x


do y <- do x <- mx
           f x
   g y


do x <- mx
   do y <- f x
      g y


do x <- mx
   y <- f x
   g y






4つのApplicative法則が満たされると、 pure f <*> x = f <$> x が自動的に満たされます。

関手の単位元を示すために、f = id を代入:

  pure f <*> x
= pure id <*> x
= x


  pure f <*> (pure g <*> x)
= pure (.) <*> pure f <*> pure g <*> x
= pure ((.) f) <*> pure g <*> x
= pure ((.) f g) <*> x
= pure (f . g) <*> x

よって、2つの関手法則は満たされるので、 pure f <*> x = f <$> x です。




今まで、このようなセリフを何度聞いたか分からない。 そもそも遅延評価が役立つことはあるのだろうか?


  1. リスト(あるいは類似のデータ構造)処理
  2. 純粋性に対する暗黙のテスト
  3. 効率的なCAS

1.はよいだろう。2.は純粋さを守るために必要だが、コンパイラを開発する人にとって重要なのであり、ユーザには関係ない。3.は、並行プログラミングの奥義である。atomicModifyIORef'を用いれば、実際の仕事を待機させた状態で compare-and-swap を成功させ、後からその仕事を片付けられる。


実はHaskellでも、正格評価を利用できる。それには !(バンと読む)を使う。

{-# LANGUAGE BangPatterns #-}

data T = C !Int

f !x = y
    !y = x + 1

この問題点は、コードのいたるところがバンバンして、読みにくくなることである。実際、コードを書いてみると嫌になってくる。この問題を解決するために、GHC 8.0 では、言語拡張 StrictStrictData が提供された。この二つを使えば、デフォルトの評価戦略が正格評価となる。

つまり、以下のコードの評価戦略は遅延評価だが、StrictStrictData を用いると正格評価となる。

data T = C Int

f x = y
    y = x + 1


デフォルトの評価戦略を正格評価にすると、今度は遅延評価を明示的に書けるようにする必要がある。それには ~ を使う。

data T = C ~Int

f ~x = y
    ~y = x + 1

StrictStrictData をもう少しく知りたい人はStrict Haskellを読んでほしい。


GHC 8.0.1がリリースされたのは、2016年5月。あれから2年強。現在は、GHC 8.6の時代だ。GHCのメジャーバージョンは3つサポートする掟に鑑みてもお釣りがくる。

Haskellerよ、時は来た。新しいプロジェクトを始めるときは、迷わずに以下を cabalファイルに入れるのだ!

  default-extensions:  Strict StrictData

Haskell network library version 3.0

Brief history

The first commit of the network library in Haskell was created by Simon Marlow in 2001. It says:

Package 'net' moved over. URI & CGI still missing because they have dependencies on other bits that haven't made it over yet.

So, I guess that the code existed before the day and actually I heard that network was one of the oldest packages in Haskell. When Johan Tibell became the previous maintainer, network was messy already. Before he started refactoring, he added many test cases. Thank you, Johan!

In 2015, he passed the baton to Evan Borden and me without drastic refactoring. After that, we concentrated on bug fixes only for a while. I don't know about Evan but this is because I didn't have any ideas to improve this package.

In December 2017, I decided to resolve issues as much as possible. During this work, I realized what is important for network:

  • The code was messy like other long-life code. We should clean up the code for maintainability.
  • The build system was terrible. We cannot understand which depends on which. We should also clean up it.
  • Believing or not, Socket cannot be GCed. This is a shame. Socket should be GCed.
  • SockAddr was not extensible. If users want to add a new one, they must send a PR. Once merged, the maintainers must maintain it even they don't know it well. Other packages should be able to extend SockAddr without modifying network.

I divided the jumbo Network.Socket module into small sub-modules. Also, I cleaned up the build system. This work was painful because I don't know Windows well. Luckily, we welcomed Tamar Christina as a new maintainer for Windows.

I will explain the last two items in the next section in detail. But briefly, we had to change the signatures of two APIs.

In network v2.6:

fdSocket :: Socket -> CInt
mkSocket :: CInt -> Family -> SocketType -> ProtocolNumber -> SocketStatus -> IO Socket

But in network v3.0:

fdSocket :: Socket -> IO CInt
mkSocket :: CInt -> IO Socket

To provide migration path, we did:


  • Making SockAddrCan deprecated


  • Making Network deprecated
  • Making Network.BSD deprecated
  • Making MkSocket deprecated
  • Making many APIs deprecated


  • Stop exporting the PortNum constructor in PortNumber


  • Removing Network
  • Removing Network.BSD
  • Removing SockAddrCan
  • Changing the internal structure of Socket.
  • Make address extensible.
  • Remove EOF errors

Like Network.URI in the network-uri package, Herbert Valerio Riedel kindly released the network-bsd package for Network.BSD.

Main jobs for v3.0 were done in Dec 2017 and v3.0 was released in Jan 2019. I'm very sorry for breaking backward compatibility but we waited for at least one year.

GC and extensibility

Recall the signature of the old API:

mkSocket :: CInt -> Family -> SocketType -> ProtocolNumber -> SocketStatus -> IO Socket

To make a Socket, we needed to supply Family, SocketType and ProtocolNumber. Since they are sum types, they cannot be extended without modifying the definitions. But CInt, a socket descriptor, is created by the socket() system call with its protocol family, its socket type and its protocol number. Why should we specify them again?

See the old definition of Socket:

data Socket = MkSocket CInt Family SocketType ProtocolNumber (MVar SocketStatus)

I don't know why they were included in. Let's remove them for extensibility. So, what about MVar SocketStatus? A good question! The reason why Socket cannot be GCed is MVar. We tried two approaches: mkWeakMVar and addFinalizer but it appeared that they did not solve the problem.

So, let's remove MVar, too:

data Socket = Socket CInt

But without status control, unexpected things would happen. Consider this scenario:

  • Haskell thread (A) creates Socket with a socket descriptor and close it.
  • The socket descriptor is re-used in another Haskell thread (B).
  • Haskell thread (C) can close the Socket again.
  • At this point, Haskell thread (B) suffers from unexpected behavior.

The key idea to solve this problem was provided by Viktor Dukhovni. He suggested to use IORef:

data Socket = Socket (IORef CInt)

When Socket is closed, we modify the value of IORef to -1 for safety. Unfortunately, to extract the file descriptor in Socket, IO is necessary:

fdSocket :: Socket -> IO CInt

This is the reason why the signature changed. With this definition, we need unsafePerformIO to make Socket an instance of Show. So, the final definition of Socket is:

data Socket = Socket (IORef CInt) CInt -- for Show

Final note

If you want to extend socket addresses, see the new Network.Socket.Address module.

I hope that the reasons for the breaking changes are now more clear.

I thank Lars Petersen for showcasing design for extensibility in his socket package.

QUIC開発日記 その1 参戦




2017年の7月ごろ、QUICの実装を始めました。Haskellの有名なシリアライザ/デシリアライザである binary や cereal では、バッファ操作ができないので、パケットヘッダを複雑に処理する必要がある QUIC には不向きです。そこで、Haskell HTTP/2 ライブラリから、バッファ操作の部分を切り出して、network-byte-orderというライブラリを作るところから始めました。




2018年4月に開発室が立ち上がりました。帰ってきてから、まずTLS 1.3を片付ける必要があり、根気強く上流にマージました。そして、ようやく2019年の年明けからQUICの実装を再開しました。1月末にIETF QUIC分科会の相互接続試験イベントと中間ミーティングが東京で開催されるので、それに間に合わせるためです。




最初の目標は、テストベクタに載っているネットワーク上のバイナリをデコードすることです。鍵を生成するには Haskell TLS ライブラリから非公開の関数を公開する必要がありました。興味がある人は、quicブランチを見てください。また、QUICパケットを扱うには network-byte-order ライブラリを育てていく必要もありました。



  1. 保護されたヘッダをパースする
  2. 暗号化されたペイロードを用いてヘッダの保護を外し、パケット番号を取り出す
  3. ヘッダとパケット番号などを用いてペイロードを復号化する


  1. 可変長の整数デコーダ
  2. パケット番号に用いられる差分変数のデコーダ

これらはQUICに特有なので、自分で実装する必要があります。実装は簡単です。テストベクタは、TLS 1.3の鍵交換をする前のInitialパケットの例であり、自明な鍵でペイロードを復号化できます。


QUIC パケットのエンコード

QUIC パケットのエンコードは、この逆をやります。

  1. ヘッダを組み立てる
  2. ヘッダとパケット番号を用いてペイロードを暗号化する
  3. 暗号化されたペイロードを用いてヘッダを保護する

分かりやすい図があるので、ペイロードの暗号化についてもう少し詳しく見てみましょう。Martin Thomson氏のQUIC Secruityより抜粋:

QUIC ペイロードの暗号化

AEADの入力は、平文(Packet Payload)、付加データ(Packet Header)、鍵(Key)、Nonce(IV xor Packet Number)です。AEADを安全に使うには、付加データか Nonce が一意である必要があります。パケット番号を使うことで、Nonce が一意になるように設計されていることが分かります。

ヘッダの保護の方も図で見てみましょう。再びMartin Thomson氏のQUIC Secruityより抜粋:

QUIC ヘッダの保護



TLS 1.3

次の目標はハンドシェクです。QUICの元々の設計では、TLS 1.3を従来の方法で使うことになっていました。つまり、入出力を伴うソケット層として使うのです。TLS 1.3のハンドシェイクメッセージが入ったTLSレコードは、QUICパケットに格納して運ぶ必要があります。

何を言っているか分からないかもしれませんが、実はこれは簡単に実装できます。というのは、通常のTLSライブラリにはIOバックエンドを指定する方法があるからです。QUICが、TLSライブラリのハンドシェイクAPIを呼ぶときに、QUICをバックエンドに指定しておけばいいのです。交換した鍵は、Exporter Master Secretとして提供されます。

しかし、ちゃぶ台は見事にひっくり返されました。QUICは、TLS 1.3をソケット層ではなく、IOを伴わないエンコーダ/デコーダとして使うように再設計されたのです。QUICとTLSの間では、TLSハンドシェイクメッセージは平文で交換され、ネットワークに送信する前に暗号化/復号化するのはQUIC側になります。詳しくはTLS Handshake Messages on QUIC, and Address Validationを読んでください。

TLSは Transport Layer Security の略語ですが、QUICでは T も L も使わずに、S しか使いません。QUICは、新しい Transport Layer ですからね!

さぁ、TLS 1.3 ライブラリの大幅改造が必要です。APIからIOを分離する必要があります。僕はしばらくTLS沼でもがいていましたが、どれくらい大変だったかは、このプルリクを見ていただくと感じ取れるかもしれません。


QUIC transport草稿の図5より:

Initial[0]: CRYPTO[CH]
0-RTT[0]: STREAM[0, "..."] ->
                                 Initial[0]: CRYPTO[SH] ACK[0]
                        Handshake[0] CRYPTO[EE, CERT, CV, FIN]
                          <- 1-RTT[0]: STREAM[1, "..."] ACK[0]

Initial[1]: ACK[0]
Handshake[0]: CRYPTO[FIN], ACK[0]
1-RTT[2]: STREAM[0, "..."] ACK[0] ->

                         1-RTT[1]: STREAM[55, "..."], ACK[1,2]
                                       <- Handshake[1]: ACK[0]

UDPの部分を付け足して、なんとかQUICパケットをネットワークに送信できるようになりました。辻川さんが隣にいたので、最初の相手としてngtcp2サーバに話しかけます。いわゆる Client Hello を送信し、Server Hello を受信しすると、なんと Encrypted Extensions (EE) 以降が復号化できません。


いや、Haskell TLSライブラリでもできなくはないんです。でも、TLSAPIから「TLSパケットをパースしている途中の状態」を返すのは避けたいのです。一晩悩みましたが、TLSハンドシェイクメッセージは平文であり、TLVの形が見える、そう、型と長さが分かることに気づきました。そこで、TLSAPIを呼ぶ前に、TLSハンドシェイクメッセージを組み立てて完全な形にすことができるようになりました。

ここで、試合終了。Client Finishedは、まだ送れていません。

To be continued


I will be back in March!