1 | module Pact.Server.HTTP
3 | import Pact.Server.Core
4 | import Pact.Server.Util
6 | import public Data.SortedMap
7 | import Data.ByteVect as BV
8 | import public FS.Posix
9 | import public FS.Socket
11 | import public IO.Async.Loop.Posix
12 | import public IO.Async.Loop.Epoll
13 | import public System
15 | import Derive.Prelude
16 | import Control.Monad.Reader
25 | 0 Prog : List Type -> Type -> Type
26 | Prog = AsyncStream Poll
33 | runServer' : Prog [Errno] Void -> IO ()
34 | runServer' prog = epollApp $
mpull (handle [stderrLn . interpolate] prog)
39 | MaxHeaderSize, MaxContentSize : Nat
40 | MaxHeaderSize = 0xffff
41 | MaxContentSize = 0xffff_ffff
46 | SPACE, COLON : Bits8
54 | startLine : ByteString -> Either HTTPErr (Method,String,Version)
56 | case toString <$> split SPACE (trim bs) of
57 | [m,t,v] => [| (\x,y,z => (x,y,z)) (method m) (pure t) (version v) |]
58 | _ => Left InvalidRequest
66 | headers : Headers -> List ByteString -> Either HTTPErr Headers
67 | headers hs [] = Right hs
69 | case break (COLON ==) h of
71 | let name := toLower (toString xs)
72 | val := toString (trim $
tail bv)
73 | in headers (insert name val hs) t
74 | _ => Left InvalidRequest
79 | contentLength : Headers -> Nat
80 | contentLength = maybe 0 cast . lookup "content-length"
84 | contentType : Headers -> Maybe String
85 | contentType = lookup "content-type"
93 | HTTPPull (List ByteString) (HTTPStream ByteString)
94 | -> HTTPPull o (Maybe Request)
95 | assemble p = Prelude.do
96 | Right (h,rem) <- C.uncons p | _ => pure Nothing
97 | (met,uri,vrs) <- injectEither (startLine h)
98 | (hs,body) <- foldPairE headers empty rem
99 | let cl := contentLength hs
100 | ct := contentType hs
101 | queryParams := SortedMap.fromList $
[]
102 | when (cl > MaxContentSize) (throw ContentSizeExceeded)
103 | pure $
Just (MkRequest met uri queryParams vrs hs cl ct $
C.take cl body)
109 | request : RequestBody -> HTTPPull o (Maybe Request)
111 | breakAtSubstring pure "\r\n\r\n" req
112 | |> C.limit HeaderSizeExceeded MaxHeaderSize
122 | encodeResponse' : Response -> ByteString
123 | encodeResponse' res = let bs = fromString . renderResponse $
res in bs
129 | badRequestHTTP : String -> ByteString
130 | badRequestHTTP body = fromString $
renderResponse (badRequestResponse body)
138 | consumeSocket: HTTPApplication -> Socket AF_INET -> Async Poll [] ()
139 | consumeSocket app cli =
140 | flip guarantee (close' cli) $
141 | mpull $
handleErrors (\(Here x) => stderrLn "\{x}") $
146 | response : Maybe Request -> HTTPStream ByteString
147 | response Nothing = pure ()
148 | response (Just r) = let s1 = app r in mapOutput encodeResponse' s1
150 | handleRequest' : HTTPPull ByteString (Maybe Request) -> AsyncStream Poll [Errno] Void
152 | extractErr HTTPErr (writeTo cli (p >>= response)) >>= \case
153 | Left err => emit (badRequestHTTP (show err)) |> writeTo cli
154 | Right () => pure ()
157 | handleRequest'' p = onError (writeTo cli (p >>= response)) $
\errs => case errs of
167 | serverFunc : ServerConfig -> HTTPApplication -> Prog [Errno] Void
168 | serverFunc config app =
169 | let conn = acceptOn AF_INET SOCK_STREAM config.bind
170 | serve' = consumeSocket app
172 | case config.maxConns of
173 | S k => foreachPar (S k) serve' conn
174 | Z => foreachPar 1 serve' conn
178 | run: ServerConfig -> HTTPApplication -> IO ()
179 | run config app = runServer' $
serverFunc config app