1 # ezhttp/src/step: what the server decides for one connection, kept pure so 2 # a law can state it. server.bend runs the effects and asks this module what 3 # each outcome means; it decides nothing itself. 4 import Base 5 import ./http.bend as Http 6 import ./qs.bend as Qs 7 8 # what a request asks of the server: answer it now, or call the handler 9 type Step is Data: 10 Answer{reply: Http.Reply} 11 Call{req: Http.Request} 12 13 # an effect's outcome on one connection: the value, or the line to log for 14 # a connection that is lost 15 type Got<-A: Type> is Type: 16 Kept{value: A} 17 Lost{line: String} 18 19 # a request received as octets, as text: its method, target, header names 20 # and values and body each decoded as UTF-8 (SPEC.md HTTP-SRV-2); a bad one 21 # keeps its reason 22 def step.text(req: Http.Request) -> Http.Request: 23 match req: 24 case Http.Bad{why}: 25 Http.Bad{why} 26 case Http.Request{method, target, headers, body}: 27 Http.Request{Qs.octets.text(method), Qs.octets.text(target), 28 Qs.octets.headers(headers), Qs.octets.text(body)} 29 30 # a bad request is answered 400 with its reason and never reaches the 31 # handler; a good one goes to the handler as it was parsed 32 def step.of(req: Http.Request) -> Step: 33 match req: 34 case Http.Bad{why}: 35 Answer{Http.Reply{400, [], why}} 36 case Http.Request{method, target, headers, body}: 37 Call{Http.Request{method, target, headers, body}} 38 39 # the line logged for a failed effect: `ezhttp: : ` 40 def step.line(phase: String, why: String) -> String: 41 "ezhttp: " ++ phase ++ ": " ++ why 42 43 # a failed accept or recv loses the connection instead of ending the server 44 def step.got( 45 -A: Type, 46 phase: String, 47 res: Result<&1, &1, U32 & String, A> 48 ) -> Got: 49 match res: 50 case Fail{(code, why)}: 51 Lost{step.line(phase, why)} 52 case Done{value}: 53 Kept{value} 54 55 # a failed send loses the connection too; the bytes it did not send are dropped 56 def step.sent(res: Result<&1, &1, (U32 & String) & List<&2, U32>, Unit>) 57 -> Got: 58 match res: 59 case Fail{((code, why), left)}: 60 Lost{step.line("write", why)} 61 case Done{value}: 62 Kept{value}