warp-s2n-tls 0.1.0.0 → 0.1.1.0
raw patch · 4 files changed
+93/−3 lines, 4 files
Files
- CHANGELOG.md +18/−0
- src/Network/Wai/Handler/WarpS2N.hs +10/−0
- test/ProtocolTests.hs +63/−1
- warp-s2n-tls.cabal +2/−2
CHANGELOG.md view
@@ -7,6 +7,24 @@ ## [Unreleased] +## [0.1.1.0] - 2026-09-08++### Added+- ALPN protocol advertisement. The server now offers `h2` and `http/1.1`, so+ Warp can select HTTP/2 for clients that request it.++### Changed+- **HTTP/2 is now reachable, where previously it was not.** Warp chooses+ between HTTP/1.1 and HTTP/2 from the ALPN protocol negotiated on the+ `Transport`. No protocol preferences were ever set on the s2n config, so no+ application protocol was negotiated, `tlsNegotiatedProtocol` was always+ `Nothing`, and every connection fell through to HTTP/1.1 regardless of the+ caller's Warp settings.++ The advertised list is derived from Warp's own `settingsHTTP2Enabled`, so+ `setHTTP2Disabled` withdraws `h2` from the offer and restores the previous+ behaviour exactly. Callers that require HTTP/1.1 should set it.+ ## [0.1.0.0] - 2026-04-28 ### Added
src/Network/Wai/Handler/WarpS2N.hs view
@@ -413,6 +413,16 @@ -- Initialize s2n config config <- initS2nConfig tls tlsSet + -- Advertise ALPN protocols. Warp selects HTTP/2 from the negotiated+ -- protocol on the Transport, so without this it never sees "h2" and every+ -- connection stays HTTP/1.1. Deriving the list from the caller's Warp+ -- settings keeps 'setHTTP2Disabled' working as the single kill switch+ -- rather than adding a second, separately-configured one here.+ tls.setProtocolPreferences config+ $ if WarpI.settingsHTTP2Enabled settings+ then ["h2", "http/1.1"]+ else ["http/1.1"]+ -- Set up ticket key manager if configured, then run the server rotateAction <- case tlsTicketKeyManager of Nothing -> pure (pure ()) -- dummy rotation action
test/ProtocolTests.hs view
@@ -4,6 +4,7 @@ import Control.Applicative import Control.Exception (SomeException, bracket, try)+import Data.ByteString (ByteString) import Data.Default.Class (def) import Network.HTTP.Types (status200) import Network.Socket qualified as Socket@@ -11,7 +12,12 @@ import Network.TLS qualified as TLS import Network.TLS.Extra.Cipher qualified as TLS import Network.Wai (responseLBS)-import Network.Wai.Handler.Warp (defaultSettings, setBeforeMainLoop)+import Network.Wai.Handler.Warp (+ Settings,+ defaultSettings,+ setBeforeMainLoop,+ setHTTP2Disabled,+ ) import Network.Wai.Handler.WarpS2N ( TLSSettings (..), runTLSSocketLib,@@ -28,7 +34,34 @@ protocolSpec = do describe "TLS version negotiation" versionNegotiationSpec describe "Cipher preferences" cipherPreferencesSpec+ describe "ALPN" alpnSpec +alpnSpec :: SpecWith S2nTls+alpnSpec = do+ it "negotiates h2 when the client offers it" $ \tls -> do+ let serverSettings = tlsSettings testCertPath testKeyPath+ proto <- withTestServerGetALPN tls serverSettings defaultSettings ["h2", "http/1.1"]+ proto `shouldBe` Just "h2"++ it "selects http/1.1 when the client does not offer h2" $ \tls -> do+ let serverSettings = tlsSettings testCertPath testKeyPath+ proto <- withTestServerGetALPN tls serverSettings defaultSettings ["http/1.1"]+ proto `shouldBe` Just "http/1.1"++ -- The advertised list is derived from Warp's own settingsHTTP2Enabled, so+ -- setHTTP2Disabled has to withdraw h2 from the offer entirely. Leaving it+ -- advertised would let a client select a protocol Warp then refuses to+ -- speak, which is worse than never offering it.+ it "withdraws h2 when the Warp settings disable HTTP/2" $ \tls -> do+ let serverSettings = tlsSettings testCertPath testKeyPath+ proto <-+ withTestServerGetALPN+ tls+ serverSettings+ (setHTTP2Disabled defaultSettings)+ ["h2", "http/1.1"]+ proto `shouldBe` Just "http/1.1"+ versionNegotiationSpec :: SpecWith S2nTls versionNegotiationSpec = do it "negotiates TLS 1.3 with default settings" $ \tls -> do@@ -99,6 +132,35 @@ let version = TLS.infoVersion <$> info TLS.bye ctx pure version++{- | Helper: Start server and report the ALPN protocol the client settles on.++Takes the Warp 'Settings' explicitly, because the advertised protocol list is+derived from them.+-}+withTestServerGetALPN :: S2nTls -> TLSSettings -> Settings -> [ByteString] -> IO (Maybe ByteString)+withTestServerGetALPN tls tlsSet baseWarpSet clientProtos =+ bracket bindFreePort (Socket.close . fst) $ \(sock, port) -> do+ serverReady <- newEmptyTMVarIO+ let app _ respond = respond $ responseLBS status200 [] "blarg!"+ warpSet = setBeforeMainLoop (atomically $ putTMVar serverReady ()) baseWarpSet+ withAsync (runTLSSocketLib tls tlsSet warpSet sock app) $ \as -> do+ atomically $ waitSTM as <|> takeTMVar serverReady+ threadDelay 10_000+ backend <- makeClientSocket port+ params <- makeClientParams [TLS.TLS13, TLS.TLS12]+ let alpnParams =+ params+ { TLS.clientHooks =+ (TLS.clientHooks params)+ { TLS.onSuggestALPN = pure (Just clientProtos)+ }+ }+ ctx <- TLS.contextNew backend alpnParams+ TLS.handshake ctx+ proto <- TLS.getNegotiatedProtocol ctx+ TLS.bye ctx+ pure proto -- | Helper: Start server and test if connection succeeds withTestServerConnect :: S2nTls -> TLSSettings -> IO Bool
warp-s2n-tls.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: warp-s2n-tls-version: 0.1.0.0+version: 0.1.1.0 synopsis: TLS support for Warp via s2n-tls description: This package provides TLS support for the Warp web server using@@ -28,7 +28,7 @@ source-repository this type: git location: https://github.com/goertzenator/warp-s2n-tls.git- tag: v0.1.0.0+ tag: v0.1.1.0 library exposed-modules: Network.Wai.Handler.WarpS2N