-
Notifications
You must be signed in to change notification settings - Fork 261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
supporting HTTP/2. #399
Merged
Merged
supporting HTTP/2. #399
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
562371a
supporting HTTP/2.
kazu-yamamoto 3b18edc
lower boundary for http2 (#399).
kazu-yamamoto c80193f
adding comments according to #339.
kazu-yamamoto 7362b99
adding comments according to #339.
kazu-yamamoto 02f4449
using NamedFieldPuns according to #339.
kazu-yamamoto 8ff3212
using NamedFieldPuns according to #339.
kazu-yamamoto a4490e2
better usage of NamedFieldPuns.
kazu-yamamoto 53f7a57
avoiding the concurrency limit problem.
kazu-yamamoto 24909e0
flattening worker loop logic.
kazu-yamamoto cdf40bb
better comments.
kazu-yamamoto 5e96733
improving the worker loop.
kazu-yamamoto 074b973
calling tickle on streaming.
kazu-yamamoto f0b14dc
fixing buffer overrun.
kazu-yamamoto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -239,13 +239,27 @@ runTLSSocket' tlsset@TLSSettings{..} set credential sock app = | |
, TLS.serverSupported = def { | ||
TLS.supportedVersions = tlsAllowedVersions | ||
, TLS.supportedCiphers = tlsCiphers | ||
, TLS.supportedHashSignatures = [ | ||
(TLS.HashSHA256, TLS.SignatureRSA) | ||
, (TLS.HashSHA1, TLS.SignatureRSA) | ||
] | ||
} | ||
, TLS.serverShared = def { | ||
TLS.sharedCredentials = TLS.Credentials [credential] | ||
} | ||
, TLS.serverHooks = tlsServerHooks | ||
, TLS.serverHooks = tlsServerHooks { | ||
TLS.onALPNClientSuggest = Just alpn | ||
} | ||
} | ||
|
||
alpn :: [S.ByteString] -> IO S.ByteString | ||
alpn xs | ||
| "h2" `elem` xs = return "h2" | ||
| "h2-16" `elem` xs = return "h2-16" | ||
| "h2-15" `elem` xs = return "h2-15" | ||
| "h2-14" `elem` xs = return "h2-14" | ||
| otherwise = return "http/1.1" | ||
|
||
---------------------------------------------------------------- | ||
|
||
getter :: TLS.TLSParams params => TLSSettings -> Socket -> params -> IO (IO (Connection, Transport), SockAddr) | ||
|
@@ -275,21 +289,24 @@ httpOverTls TLSSettings{..} s bs0 params = do | |
TLS.contextHookSetLogging ctx tlsLogging | ||
TLS.handshake ctx | ||
writeBuf <- allocateBuffer bufferSize | ||
-- Creating a cache for leftover input data. | ||
ref <- I.newIORef "" | ||
tls <- getTLSinfo ctx | ||
return (conn ctx writeBuf, tls) | ||
return (conn ctx writeBuf ref, tls) | ||
where | ||
backend recvN = TLS.Backend { | ||
TLS.backendFlush = return () | ||
, TLS.backendClose = sClose s | ||
, TLS.backendSend = sendAll s | ||
, TLS.backendRecv = recvN | ||
} | ||
conn ctx writeBuf = Connection { | ||
conn ctx writeBuf ref = Connection { | ||
connSendMany = TLS.sendData ctx . L.fromChunks | ||
, connSendAll = sendall | ||
, connSendFile = sendfile | ||
, connClose = close | ||
, connRecv = recv | ||
, connRecv = recv ref | ||
, connRecvBuf = recvBuf ref | ||
, connWriteBuffer = writeBuf | ||
, connBufferSize = bufferSize | ||
} | ||
|
@@ -302,7 +319,18 @@ httpOverTls TLSSettings{..} s bs0 params = do | |
void (tryIO $ TLS.bye ctx) `finally` | ||
TLS.contextClose ctx | ||
|
||
recv = handle onEOF go | ||
-- TLS version of recv with a cache for leftover input data. | ||
-- The cache is shared with recvBuf. | ||
recv cref = do | ||
cached <- I.readIORef cref | ||
if cached /= "" then do | ||
I.writeIORef cref "" | ||
return cached | ||
else | ||
recv' | ||
|
||
-- TLS version of recv (decrypting) without a cache. | ||
recv' = handle onEOF go | ||
where | ||
onEOF e | ||
| Just TLS.Error_EOF <- fromException e = return S.empty | ||
|
@@ -314,6 +342,37 @@ httpOverTls TLSSettings{..} s bs0 params = do | |
else | ||
return x | ||
|
||
-- TLS version of recvBuf with a cache for leftover input data. | ||
recvBuf cref buf siz = do | ||
cached <- I.readIORef cref | ||
(ret, leftover) <- fill cached buf siz recv' | ||
I.writeIORef cref leftover | ||
return ret | ||
|
||
fill :: S.ByteString -> Buffer -> BufSize -> Recv -> IO (Bool,S.ByteString) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. document this function? |
||
fill bs0 buf0 siz0 recv | ||
| siz0 <= len0 = do | ||
let (bs, leftover) = S.splitAt siz0 bs0 | ||
void $ copy buf0 bs | ||
return (True, leftover) | ||
| otherwise = do | ||
buf <- copy buf0 bs0 | ||
loop buf (siz0 - len0) | ||
where | ||
len0 = S.length bs0 | ||
loop _ 0 = return (True, "") | ||
loop buf siz = do | ||
bs <- recv | ||
let len = S.length bs | ||
if len == 0 then return (False, "") | ||
else if (len <= siz) then do | ||
buf' <- copy buf bs | ||
loop buf' (siz - len) | ||
else do | ||
let (bs1,bs2) = S.splitAt siz bs | ||
void $ copy buf bs1 | ||
return (True, bs2) | ||
|
||
getTLSinfo :: TLS.Context -> IO Transport | ||
getTLSinfo ctx = do | ||
proto <- TLS.getNegotiatedProtocol ctx | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE RecordWildCards #-} | ||
|
||
module Network.Wai.Handler.Warp.HTTP2 (isHTTP2, http2) where | ||
|
||
import Control.Concurrent (forkIO, killThread) | ||
import qualified Control.Exception as E | ||
import Control.Monad (when, unless, replicateM) | ||
import Data.ByteString (ByteString) | ||
import Network.HTTP2 | ||
import Network.Socket (SockAddr) | ||
import Network.Wai | ||
import Network.Wai.Handler.Warp.HTTP2.EncodeFrame | ||
import Network.Wai.Handler.Warp.HTTP2.Receiver | ||
import Network.Wai.Handler.Warp.HTTP2.Request | ||
import Network.Wai.Handler.Warp.HTTP2.Sender | ||
import Network.Wai.Handler.Warp.HTTP2.Types | ||
import Network.Wai.Handler.Warp.HTTP2.Worker | ||
import qualified Network.Wai.Handler.Warp.Settings as S (Settings) | ||
import Network.Wai.Handler.Warp.Types | ||
|
||
---------------------------------------------------------------- | ||
|
||
http2 :: Connection -> InternalInfo -> SockAddr -> Transport -> S.Settings -> (BufSize -> IO ByteString) -> Application -> IO () | ||
http2 conn ii addr transport settings readN app = do | ||
checkTLS | ||
ok <- checkPreface | ||
when ok $ do | ||
ctx <- newContext | ||
let responder = response ctx | ||
mkreq = mkRequest settings addr | ||
tid <- forkIO $ frameReceiver ctx mkreq readN | ||
-- fixme: 10 is hard-coded | ||
-- To prevent thread-leak, we executed the fixed number of threads | ||
-- statically at this moment. But ResponseStream occupies one | ||
-- worker thread. So, this should be dynamic. | ||
tids <- replicateM 10 $ forkIO $ worker ctx settings tm app responder | ||
-- frameSender is the main thread because it ensures to send | ||
-- a goway frame. | ||
frameSender ctx conn ii settings `E.finally` do | ||
clearContext ctx | ||
mapM_ killThread (tid:tids) | ||
where | ||
tm = timeoutManager ii | ||
checkTLS = case transport of | ||
TCP -> return () -- direct | ||
tls -> unless (tls12orLater tls) $ goaway conn InadequateSecurity "Weak TLS" | ||
tls12orLater tls = tlsMajorVersion tls == 3 && tlsMinorVersion tls >= 3 | ||
checkPreface = do | ||
preface <- readN connectionPrefaceLength | ||
if connectionPreface /= preface then do | ||
goaway conn ProtocolError "Preface mismatch" | ||
return False | ||
else | ||
return True | ||
|
||
-- connClose must not be called here since Run:fork calls it | ||
goaway :: Connection -> ErrorCodeId -> ByteString -> IO () | ||
goaway Connection{..} etype debugmsg = connSendAll bytestream | ||
where | ||
bytestream = goawayFrame 0 etype debugmsg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
module Network.Wai.Handler.Warp.HTTP2.EncodeFrame where | ||
|
||
import Data.ByteString (ByteString) | ||
import Network.HTTP2 | ||
|
||
---------------------------------------------------------------- | ||
|
||
goawayFrame :: StreamId -> ErrorCodeId -> ByteString -> ByteString | ||
goawayFrame sid etype debugmsg = encodeFrame einfo frame | ||
where | ||
einfo = encodeInfo id 0 | ||
frame = GoAwayFrame sid etype debugmsg | ||
|
||
resetFrame :: ErrorCodeId -> StreamId -> ByteString | ||
resetFrame etype sid = encodeFrame einfo frame | ||
where | ||
einfo = encodeInfo id sid | ||
frame = RSTStreamFrame etype | ||
|
||
settingsFrame :: (FrameFlags -> FrameFlags) -> SettingsList -> ByteString | ||
settingsFrame func alist = encodeFrame einfo $ SettingsFrame alist | ||
where | ||
einfo = encodeInfo func 0 | ||
|
||
pingFrame :: ByteString -> ByteString | ||
pingFrame bs = encodeFrame einfo $ PingFrame bs | ||
where | ||
einfo = encodeInfo setAck 0 | ||
|
||
windowUpdateFrame :: StreamId -> WindowSize -> ByteString | ||
windowUpdateFrame sid winsiz = encodeFrame einfo $ WindowUpdateFrame winsiz | ||
where | ||
einfo = encodeInfo id sid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE RecordWildCards, NamedFieldPuns #-} | ||
|
||
module Network.Wai.Handler.Warp.HTTP2.HPACK where | ||
|
||
import Control.Arrow (first) | ||
import qualified Control.Exception as E | ||
import Data.ByteString.Builder (Builder) | ||
import qualified Data.ByteString.Char8 as B8 | ||
import Data.CaseInsensitive (foldedCase) | ||
import Data.IORef (readIORef, writeIORef) | ||
import Network.HPACK | ||
import qualified Network.HTTP.Types as H | ||
import Network.HTTP2 | ||
import Network.Wai | ||
import Network.Wai.Handler.Warp.HTTP2.Types | ||
import Network.Wai.Handler.Warp.Header | ||
import Network.Wai.Handler.Warp.Response | ||
import qualified Network.Wai.Handler.Warp.Settings as S | ||
import Network.Wai.Handler.Warp.Types | ||
|
||
hpackEncodeHeader :: Context -> InternalInfo -> S.Settings -> Response | ||
-> IO Builder | ||
hpackEncodeHeader Context{encodeDynamicTable} ii settings rsp = do | ||
hdr1 <- addServerAndDate hdr0 | ||
let hdr2 = (":status", status) : map (first foldedCase) hdr1 | ||
ehdrtbl <- readIORef encodeDynamicTable | ||
(ehdrtbl', builder) <- encodeHeaderBuilder defaultEncodeStrategy ehdrtbl hdr2 | ||
writeIORef encodeDynamicTable ehdrtbl' | ||
return builder | ||
where | ||
hdr0 = responseHeaders rsp | ||
status = B8.pack $ show $ H.statusCode $ responseStatus rsp | ||
dc = dateCacher ii | ||
rspidxhdr = indexResponseHeader hdr0 | ||
defServer = S.settingsServerName settings | ||
addServerAndDate = addDate dc rspidxhdr . addServer defServer rspidxhdr | ||
|
||
|
||
---------------------------------------------------------------- | ||
|
||
hpackDecodeHeader :: HeaderBlockFragment -> Context -> IO HeaderList | ||
hpackDecodeHeader hdrblk Context{decodeDynamicTable} = do | ||
hdrtbl <- readIORef decodeDynamicTable | ||
(hdrtbl', hdr) <- decodeHeader hdrtbl hdrblk `E.onException` cleanup | ||
writeIORef decodeDynamicTable hdrtbl' | ||
return hdr | ||
where | ||
cleanup = E.throwIO $ ConnectionError CompressionError "cannot decompress the header" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document the ref, recvBuf or recv functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Network.Wai.Handler.Warp.Types
says:ref
implements a cache for leftover input data.