1 # Fixed prepared HTTP applications and bounded affine dependency bundles. 2 import Base 3 import bend-kit-http@0.33.0.0/http.bend as Http 4 import bend-kit-router@0.2.0.0/router.bend as Router 5 import bend-kit-router@0.2.0.0/target.bend as Target 6 import bend-kit-json@0.6.0.0/json.bend as Json 7 import ./response.bend as Response 8 import ./input.bend as Input 9 import bend-kit-http@0.33.0.0/completion.bend as TransportCompletion 10 import ./notices.bend as Notices 11 import bend-kit-time@0.2.0.0/time.bend as Time 12 13 type ResponseError is Data: 14 InvalidResponse{} 15 InvalidRedirect{} 16 17 def validate.checked(valid: Bool, response: Http.Res) -> Result<&2, &1, ResponseError, Http.Res>: 18 match valid: 19 case True{}: 20 Done{response} 21 case False{}: 22 Fail{InvalidResponse{}} 23 24 def validate(response: Http.Res) -> Result<&2, &1, ResponseError, Http.Res>: 25 Http.Res{+status, +headers, body} = response 26 validate.checked(Response.valid(status, headers), Http.Res{status, headers, body}) 27 28 # The encoder is an explicit public projection, never record reflection. 29 def json(~T: Type, ~encode: T -> Json.Val, status: U32, value: T) -> Http.Res: 30 Http.Res{status, Http.set(Http.empty(), "content-type", "application/json"), Json.encode.bytes(encode(value))} 31 32 def text(status: U32, value: String) -> Http.Res: 33 Http.Res{status, Http.set(Http.empty(), "content-type", "text/plain; charset=utf-8"), Json.utf8(value)} 34 35 def raw(status: U32, body: Http.Body) -> Http.Res: 36 Http.Res{status, Http.empty(), body} 37 38 def empty(status: U32) -> Http.Res: 39 raw(status, Http.from_string("")) 40 41 def redirect.checked(valid: Bool, status: U32, location: String) -> Result<&2, &1, ResponseError, Http.Res>: 42 match valid: 43 case False{}: 44 Fail{InvalidRedirect{}} 45 case True{}: 46 Done{Http.Res{status, Http.set(Http.empty(), "location", location), Http.from_string("")}} 47 48 def redirect(+status: U32, +location: String) -> Result<&2, &1, ResponseError, Http.Res>: 49 redirect.checked((U32.is_eq(status, 301) || U32.is_eq(status, 302) || U32.is_eq(status, 303) || U32.is_eq(status, 307) || U32.is_eq(status, 308)) && Bool.not(String.eq(location, "")) && Response.value(location), status, location) 50 51 type Counts is Data: 52 Counts{capacity: Nat, in_use: Nat, completed: Nat, rejected: Nat} 53 54 type Outcome<-E: Data, -O: Data> is Data: 55 Success{value: O} 56 Expected{error: E} 57 Exhausted{} 58 Stopped{} 59 60 type CloseResult is Data: 61 Closed{} 62 Busy{counts: Counts} 63 64 type Message<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type: 65 Submit{principal: P, input: I, reply: Chan(Outcome)} 66 Returned{resource: R} 67 Inspect{reply: Chan(Counts)} 68 Shutdown{reply: Chan(CloseResult)} 69 70 # Copy only this context, never the resources. The inbox bounds queued commands; 71 # callers must count blocked submissions inside their transport admission limit. 72 type Context<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Data: 73 Context{messages: Chan(Message)} 74 75 # The completion future has one affine owner; request contexts copy only inbox. 76 type Owner<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type: 77 Owner{context: Context, finished: Chan(Unit)} 78 79 type OwnerClose<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type: 80 OwnerClosed{} 81 OwnerBusy{owner: Owner, counts: Counts} 82 83 def owner.context(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 84 owner: Owner 85 ) -> Owner & Context: 86 Owner{+context, finished} = owner 87 (Owner{context, finished}, context) 88 89 def outcome.value(-E: Data, -O: Data, result: Result<&2, &2, E, O>) -> Outcome: 90 match result: 91 case Fail{error}: 92 Expected{error} 93 case Done{value}: 94 Success{value} 95 96 def dispose(~R: Type, ~destroy: R -> IO(Unit), resources: List) -> IO(Unit): 97 match resources: 98 case Nil{}: 99 IO.pure(Unit, Unit{}) 100 case Con{resource, rest}: 101 do IO: 102 destroy(resource) 103 dispose(~R, ~destroy, rest) 104 105 # Closing Base channels retains buffered messages. Drain them and answer stopped 106 # callers, rather than stranding accepted commands behind the close command. 107 def drain.message(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 108 ~destroy: R -> IO(Unit), message: Message 109 ) -> IO(Unit): 110 match message: 111 case Submit{principal, input, reply}: 112 do IO: 113 sent : Result<&1, &1, Outcome, Unit> <- Chan.send(Outcome, reply, Stopped{}) 114 return Unit{} 115 case Inspect{reply}: 116 Chan.close(Counts, reply) 117 case Shutdown{reply}: 118 do IO: 119 sent : Result<&1, &1, CloseResult, Unit> <- Chan.send(CloseResult, reply, Closed{}) 120 return Unit{} 121 case Returned{resource}: 122 destroy(resource) 123 124 # The resources return through the private owner protocol BEFORE a disposable 125 # caller reply. A closed caller reply cannot erase a handle or strand capacity. 126 def work.returned(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 127 ~destroy: R -> IO(Unit), accepted: Result<&1, &1, Message, Unit> 128 ) -> IO(Unit): 129 match accepted: 130 case Fail{message}: 131 drain.message(~R, ~P, ~I, ~E, ~O, ~destroy, message) 132 case Done{u}: 133 IO.pure(Unit, Unit{}) 134 135 def work.return(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 136 ~destroy: R -> IO(Unit), 137 messages: Chan(Message), reply: Chan(Outcome), 138 result: R & Result<&2, &2, E, O> 139 ) -> IO(Unit): 140 (returned, outcome) = result 141 do IO: 142 accepted : Result<&1, &1, Message, Unit> <- Chan.send(Message, messages, Returned{returned}) 143 work.returned(~R, ~P, ~I, ~E, ~O, ~destroy, accepted) 144 sent : Result<&1, &1, Outcome, Unit> <- Chan.send(Outcome, reply, outcome.value(E, O, outcome)) 145 return Unit{} 146 147 def work(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 148 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit), 149 resource: R, principal: P, input: I, 150 messages: Chan(Message), reply: Chan(Outcome) 151 ) -> IO(Unit): 152 do IO: 153 result : R & Result<&2, &2, E, O> <- handler(resource, principal, input) 154 work.return(~R, ~P, ~I, ~E, ~O, ~destroy, messages, reply, result) 155 156 def drain.step(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 157 ~destroy: R -> IO(Unit), pending: Maybe<&1, Message>, 158 next: Unit -> IO(Unit) 159 ) -> IO(Unit): 160 match pending: 161 case None{}: 162 IO.pure(Unit, Unit{}) 163 case Some{message}: 164 do IO: 165 drain.message(~R, ~P, ~I, ~E, ~O, ~destroy, message) 166 next(Unit{}) 167 168 # The closed inbox contains at most room buffered messages. 169 def drain(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 170 ~destroy: R -> IO(Unit), fuel: Nat, +messages: Chan(Message) 171 ) -> IO(Unit): 172 match fuel: 173 case 0n: 174 IO.pure(Unit, Unit{}) 175 case 1n+p: 176 do IO: 177 pending : Maybe<&1, Message> <- Chan.recv(Message, messages) 178 drain.step(~R, ~P, ~I, ~E, ~O, ~destroy, pending, u => drain(~R, ~P, ~I, ~E, ~O, ~destroy, p, messages)) 179 180 type OwnerStep<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type: 181 Waiting{resources: List, counts: Counts} 182 Received{message: Message, counts: Counts, resources: List} 183 184 # IO.join closes a one-shot channel; the owner inbox must remain reusable. 185 def receive.value(-A: Type, value: Maybe<&1, A>) -> IO(A): 186 match value: 187 case None{}: 188 IO.die(A, 1, "camber internal channel closed unexpectedly") 189 case Some{message}: 190 IO.pure(A, message) 191 192 def receive(-A: Type, channel: Chan(A)) -> IO(A): 193 do IO: 194 value : Maybe<&1, A> <- Chan.recv(A, channel) 195 receive.value(A, value) 196 197 # Only idle close ends this loop. Busy close never waits on a stalled handler. 198 # @unsafe is solely the lifecycle receive loop, not a termination/proof claim. 199 @unsafe def owner(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 200 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit), 201 +messages: Chan(Message), +room: U32, step: OwnerStep 202 ) -> IO(Unit): 203 match step: 204 case Waiting{resources, counts}: 205 do IO: 206 message : Message <- receive(Message, messages) 207 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Received{message, counts, resources}) 208 case Received{message, counts, resources}: 209 match message: 210 case Returned{resource}: 211 Counts{capacity, in_use, completed, rejected} = counts 212 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resource <> resources, Counts{capacity, Nat.sub(in_use, 1n), 1n+completed, rejected}}) 213 case Inspect{reply}: 214 Counts{+capacity, +in_use, +completed, +rejected} = counts 215 do IO: 216 sent : Result<&1, &1, Counts, Unit> <- Chan.send(Counts, reply, Counts{capacity, in_use, completed, rejected}) 217 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, in_use, completed, rejected}}) 218 case Shutdown{reply}: 219 Counts{+capacity, in_use, +completed, +rejected} = counts 220 match in_use: 221 case 0n: 222 do IO: 223 dispose(~R, ~destroy, resources) 224 Chan.close(Message, messages) 225 sent : Result<&1, &1, CloseResult, Unit> <- Chan.send(CloseResult, reply, Closed{}) 226 drain(~R, ~P, ~I, ~E, ~O, ~destroy, U32.to_nat(room), messages) 227 case 1n+ +p: 228 do IO: 229 sent : Result<&1, &1, CloseResult, Unit> <- Chan.send(CloseResult, reply, Busy{Counts{capacity, 1n+p, completed, rejected}}) 230 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, 1n+p, completed, rejected}}) 231 case Submit{principal, input, reply}: 232 Counts{capacity, in_use, completed, rejected} = counts 233 match resources: 234 case Nil{}: 235 do IO: 236 sent : Result<&1, &1, Outcome, Unit> <- Chan.send(Outcome, reply, Exhausted{}) 237 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{Nil{}, Counts{capacity, in_use, completed, 1n+rejected}}) 238 case Con{resource, rest}: 239 do IO: 240 IO.spawn(Unit, work(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, resource, principal, input, messages, reply)) 241 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{rest, Counts{capacity, 1n+in_use, completed, rejected}}) 242 243 def build.step(~R: Type, ~E: Data, ~destroy: R -> IO(Unit), 244 result: Result<&2, &1, E, R>, resources: List, 245 next: List -> IO(Result<&2, &1, E, List>) 246 ) -> IO(Result<&2, &1, E, List>): 247 match result: 248 case Fail{error}: 249 do IO>>: 250 dispose(~R, ~destroy, resources) 251 return Fail{error} 252 case Done{resource}: 253 next(resource <> resources) 254 255 def build(~K: Data, ~R: Type, ~E: Data, ~create: K -> Nat -> IO(Result<&2, &1, E, R>), ~destroy: R -> IO(Unit), 256 remaining: Nat, +config: K, resources: List 257 ) -> IO(Result<&2, &1, E, List>): 258 match remaining: 259 case 0n: 260 IO.pure(Result<&2, &1, E, List>, Done{resources}) 261 case 1n+ +p: 262 do IO>>: 263 result : Result<&2, &1, E, R> <- create(config, p) 264 build.step(~R, ~E, ~destroy, result, resources, next => build(~K, ~R, ~E, ~create, ~destroy, p, config, next)) 265 266 def start.ready(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 267 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit), 268 capacity: Nat, +room: U32, result: Result<&2, &1, E, List> 269 ) -> IO(Result<&2, &1, E, Owner>): 270 match result: 271 case Fail{error}: 272 IO.pure(Result<&2, &1, E, Owner>, Fail{error}) 273 case Done{resources}: 274 do IO>>: 275 channel : Chan(Message) <- Chan.new(Message, room) 276 +messages : Chan(Message) = channel 277 finished : Chan(Unit) <- IO.fork(Unit, owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, 0n, 0n, 0n}})) 278 return Done{Owner{Context{messages}, finished}} 279 280 # The initializer closes partial handles within a failed bundle; start closes 281 # all prior whole bundles. No workers exist until the complete set opens. 282 def start(~K: Data, ~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 283 ~create: K -> Nat -> IO(Result<&2, &1, E, R>), 284 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit), 285 config: K, +capacity: Nat, room: U32 286 ) -> IO(Result<&2, &1, E, Owner>): 287 do IO>>: 288 result : Result<&2, &1, E, List> <- build(~K, ~R, ~E, ~create, ~destroy, capacity, config, Nil{}) 289 start.ready(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, capacity, room, result) 290 291 def submit(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 292 context: Context, principal: P, input: I, reply: Chan(Outcome) 293 ) -> IO(Result<&1, &1, Message, Unit>): 294 Context{messages} = context 295 do IO, Unit>>: 296 sent : Result<&1, &1, Message, Unit> <- Chan.send(Message, messages, Submit{principal, input, reply}) 297 return sent 298 299 def call.accepted(-T: Type, -E: Data, -O: Data, accepted: Result<&1, &1, T, Unit>, +reply: Chan(Outcome)) -> IO(Outcome): 300 match accepted: 301 case Fail{v}: 302 do IO>: 303 Chan.close(Outcome, reply) 304 return Stopped{} 305 case Done{u}: 306 do IO>: 307 result : Outcome <- IO.join(Outcome, reply) 308 Chan.close(Outcome, reply) 309 return result 310 311 def call(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 312 context: Context, principal: P, input: I 313 ) -> IO(Outcome): 314 do IO>: 315 channel : Chan(Outcome) <- Chan.new(Outcome, 1) 316 +reply : Chan(Outcome) = channel 317 accepted : Result<&1, &1, Message, Unit> <- submit(R, P, I, E, O, context, principal, input, reply) 318 call.accepted(Message, E, O, accepted, reply) 319 320 def inspect.value(value: Maybe<&1, Counts>) -> Maybe<&2, Counts>: 321 match value: 322 case None{}: 323 None{} 324 case Some{counts}: 325 Some{counts} 326 327 def inspect.sent(-T: Type, sent: Result<&1, &1, T, Unit>, +reply: Chan(Counts)) -> IO(Maybe<&2, Counts>): 328 match sent: 329 case Fail{v}: 330 do IO>: 331 Chan.close(Counts, reply) 332 return None{} 333 case Done{u}: 334 do IO>: 335 value : Maybe<&1, Counts> <- Chan.recv(Counts, reply) 336 Chan.close(Counts, reply) 337 return inspect.value(value) 338 339 def inspect(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, context: Context) -> IO(Maybe<&2, Counts>): 340 Context{messages} = context 341 do IO>: 342 channel : Chan(Counts) <- Chan.new(Counts, 1) 343 +reply : Chan(Counts) = channel 344 sent : Result<&1, &1, Message, Unit> <- Chan.send(Message, messages, Inspect{reply}) 345 inspect.sent(Message, sent, reply) 346 347 def close.sent(-T: Type, sent: Result<&1, &1, T, Unit>, +reply: Chan(CloseResult)) -> IO(CloseResult): 348 match sent: 349 case Fail{v}: 350 do IO: 351 Chan.close(CloseResult, reply) 352 return Closed{} 353 case Done{u}: 354 do IO: 355 value : CloseResult <- IO.join(CloseResult, reply) 356 Chan.close(CloseResult, reply) 357 return value 358 359 def close.join(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 360 owner: Owner 361 ) -> IO(OwnerClose): 362 Owner{context, finished} = owner 363 do IO>: 364 IO.join(Unit, finished) 365 return OwnerClosed{} 366 367 def close.completed(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 368 result: CloseResult, owner: Owner 369 ) -> IO(OwnerClose): 370 match result: 371 case Closed{}: 372 close.join(R, P, I, E, O, owner) 373 case Busy{counts}: 374 IO.pure(OwnerClose, OwnerBusy{owner, counts}) 375 376 # Stop external admissions before close. Busy preserves live context/capacity; 377 # retry after admitted operations finish. Stalled operations remain observable. 378 def close(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, owner: Owner) -> IO(OwnerClose): 379 Owner{Context{+messages}, finished} = owner 380 do IO>: 381 channel : Chan(CloseResult) <- Chan.new(CloseResult, 1) 382 +reply : Chan(CloseResult) = channel 383 sent : Result<&1, &1, Message, Unit> <- Chan.send(Message, messages, Shutdown{reply}) 384 result : CloseResult <- close.sent(Message, sent, reply) 385 close.completed(R, P, I, E, O, result, Owner{Context{messages}, finished}) 386 387 # Policies are prepared Data, not callbacks. Later lifecycle stages can consume 388 # these root-to-route declarations without splitting patterns or finding groups. 389 type Group<-P: Data> is Data: 390 Group{id: U32, ancestors: List<&2, U32>, policy: P} 391 392 type GroupPlan<-P: Data> is Data: 393 GroupPlan{ids: List<&2, U32>, policies: List<&2, P>} 394 395 type Route<-A: Data, -P: Data> is Data: 396 Route{method: String, pattern: String, action: A, groups: List<&2, U32>, policy: P} 397 398 type Endpoint<-A: Data, -P: Data> is Data: 399 Endpoint{action: A, policies: List<&2, P>} 400 401 type Application<-A: Data, -C: Data, -P: Data> is Data: 402 Application{table: Router.Table>, context: C, root: P, groups: List<&2, GroupPlan

>} 403 404 type RegistrationError is Data: 405 RouteError{error: Router.Error} 406 InvalidGroups{groups: List<&2, U32>} 407 408 type Match<-P: Data> is Data: 409 Match{target: Target.Target, params: Map<&2, String>, groups: List<&2, U32>, method: String, pattern: String, policies: List<&2, P>} 410 411 def groups.equal(xs: List<&2, U32>, ys: List<&2, U32>) -> Bool: 412 match xs ys: 413 case Nil{} Nil{}: 414 True{} 415 case x <> xt y <> yt: 416 U32.is_eq(x, y) && groups.equal(xt, yt) 417 case _ _: 418 False{} 419 420 def group.pick(~P: Data, same: Bool, group: Group

, later: Unit -> Maybe<&2, Group

>) -> Maybe<&2, Group

>: 421 match same: 422 case True{}: 423 Some{group} 424 case False{}: 425 later(Unit{}) 426 427 def group.find(~P: Data, groups: List<&2, Group

>, +id: U32) -> Maybe<&2, Group

>: 428 match groups: 429 case Nil{}: 430 None{} 431 case Group{+here, ancestors, policy} <> rest: 432 group.pick(~P, U32.is_eq(here, id), Group{here, ancestors, policy}, u => group.find(~P, rest, id)) 433 434 def group.checked(~P: Data, same: Bool, policy: P) -> Maybe<&2, P>: 435 match same: 436 case False{}: 437 None{} 438 case True{}: 439 Some{policy} 440 441 def group.policy(~P: Data, found: Maybe<&2, Group

>, ancestors: List<&2, U32>) -> Maybe<&2, P>: 442 match found: 443 case None{}: 444 None{} 445 case Some{Group{id, expected, policy}}: 446 group.checked(~P, groups.equal(expected, ancestors), policy) 447 448 def groups.policies(~P: Data, ids: List<&2, U32>, +declared: List<&2, Group

>, +ancestors: List<&2, U32>) -> Maybe<&2, List<&2, P>>: 449 match ids: 450 case Nil{}: 451 Some{Nil{}} 452 case +id <> rest: 453 do Maybe<&2, List<&2, P>>: 454 policy : P <- group.policy(~P, group.find(~P, declared, id), ancestors) 455 policies : List<&2, P> <- groups.policies(~P, rest, declared, List.append(&2, U32, ancestors, [id])) 456 return policy <> policies 457 458 def groups.unique(~P: Data, found: Maybe<&2, Group

>) -> Bool: 459 match found: 460 case None{}: 461 True{} 462 case Some{group}: 463 False{} 464 465 def groups.valid(~P: Data, groups: List<&2, Group

>, +all: List<&2, Group

>) -> Bool: 466 match groups: 467 case Nil{}: 468 True{} 469 case Group{+id, +ancestors, policy} <> +rest: 470 chain = groups.policies(~P, List.append(&2, U32, ancestors, [id]), all, Nil{}) 471 groups.unique(~P, group.find(~P, rest, id)) && Maybe.is_some(&2, List<&2, P>, chain) && groups.valid(~P, rest, all) 472 473 def routes.policies(~P: Data, result: Maybe<&2, List<&2, P>>, ids: List<&2, U32>) -> Result<&2, &2, RegistrationError, List<&2, P>>: 474 match result: 475 case None{}: 476 Fail{InvalidGroups{ids}} 477 case Some{policies}: 478 Done{policies} 479 480 def routes.prepare(~A: Data, ~P: Data, routes: List<&2, Route>, +groups: List<&2, Group

>, +root: P) -> Result<&2, &2, RegistrationError, List<&2, Router.Entry>>>: 481 match routes: 482 case Nil{}: 483 Done{Nil{}} 484 case Route{method, pattern, action, +ids, policy} <> rest: 485 do Result<&2, &2, RegistrationError, List<&2, Router.Entry>>>: 486 policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, groups, Nil{}), ids) 487 entries : List<&2, Router.Entry>> <- routes.prepare(~A, ~P, rest, groups, root) 488 return Router.Entry{method, pattern, Endpoint{action, root <> List.append(&2, P, policies, [policy])}, ids} <> entries 489 490 def groups.prepare(~P: Data, groups: List<&2, Group

>, +all: List<&2, Group

>) -> Result<&2, &2, RegistrationError, List<&2, GroupPlan

>>: 491 match groups: 492 case Nil{}: 493 Done{Nil{}} 494 case Group{id, ancestors, policy} <> rest: 495 +ids = List.append(&2, U32, ancestors, [id]) 496 do Result<&2, &2, RegistrationError, List<&2, GroupPlan

>>: 497 policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, all, Nil{}), ids) 498 plans : List<&2, GroupPlan

> <- groups.prepare(~P, rest, all) 499 return GroupPlan{ids, policies} <> plans 500 501 def groups.select(~P: Data, same: Bool, policies: List<&2, P>, later: Unit -> List<&2, P>) -> List<&2, P>: 502 match same: 503 case True{}: policies 504 case False{}: later(Unit{}) 505 506 def groups.prepared(~P: Data, plans: List<&2, GroupPlan

>, +ids: List<&2, U32>) -> List<&2, P>: 507 match plans: 508 case Nil{}: 509 Nil{} 510 case GroupPlan{here, policies} <> rest: 511 groups.select(~P, groups.equal(here, ids), policies, u => groups.prepared(~P, rest, ids)) 512 513 def application.table(~A: Data, ~C: Data, ~P: Data, result: Result<&2, &2, Router.Error, Router.Table>>, context: C, root: P, groups: List<&2, GroupPlan

>) -> Result<&2, &2, RegistrationError, Application>: 514 match result: 515 case Fail{error}: 516 Fail{RouteError{error}} 517 case Done{table}: 518 Done{Application{table, context, root, groups}} 519 520 def application.valid(~A: Data, ~C: Data, ~P: Data, valid: Bool, routes: List<&2, Route>, +groups: List<&2, Group

>, context: C, +root: P) -> Result<&2, &2, RegistrationError, Application>: 521 match valid: 522 case False{}: 523 Fail{InvalidGroups{Nil{}}} 524 case True{}: 525 do Result<&2, &2, RegistrationError, Application>: 526 entries : List<&2, Router.Entry>> <- routes.prepare(~A, ~P, routes, groups, root) 527 plans : List<&2, GroupPlan

> <- groups.prepare(~P, groups, groups) 528 application.table(~A, ~C, ~P, Router.prepare(~Endpoint, entries), context, root, plans) 529 530 # Construction is pure and fallible; no listener or owner is started here. 531 def application(~A: Data, ~C: Data, ~P: Data, routes: List<&2, Route>, +groups: List<&2, Group

>, context: C, root: P) -> Result<&2, &2, RegistrationError, Application>: 532 application.valid(~A, ~C, ~P, groups.valid(~P, groups, groups), routes, groups, context, root) 533 534 def describe(~A: Data, ~C: Data, ~P: Data, app: Application) -> List<&2, Router.Description>: 535 Application{table, context, root, groups} = app 536 Router.describe(~Endpoint, table) 537 538 # A closed template can run repeatedly; an affine callback registry cannot. 539 def plain(~handler: Http.Req -> IO(Http.Res), req: Http.Req) -> IO(Result<&2, &1, ResponseError, Http.Res>): 540 do IO>: 541 response : Http.Res <- handler(req) 542 return validate(response) 543 544 type Policy<-H: Data> is Data: 545 Policy{id: U32, before: List<&2, H>, transforms: List<&2, H>} 546 547 def policy(~H: Data, id: U32, before: List<&2, H>, transforms: List<&2, H>) -> Policy: 548 Policy{id, before, List.reverse(&2, H, transforms)} 549 550 type Metadata is Data: 551 Metadata{method: String, target: String, headers: Map<&2, List<&2, String>>} 552 553 type Error<-E: Data> is Data: 554 ApplicationError{error: E} 555 Framework{status: U32, allow: String} 556 ResponseInvalid{} 557 558 # Adapt reusable input status failures to the existing one-pass dispatch mapper. 559 def input(~I: Type, ~E: Data, outcome: Result<&2, &1, U32, I>) -> Result<&2, &1, Error, I>: 560 match outcome: 561 case Fail{status}: Fail{Framework{status, ""}} 562 case Done{value}: Done{value} 563 564 def execute.decoded(~I: Type, ~S: Type, ~K: Data, ~E: Data, 565 ~handler: S -> K -> I -> IO(S & Result<&2, &1, Error, Http.Res>), 566 outcome: Result<&2, &1, Error, I>, state: S, config: K 567 ) -> IO(S & Result<&2, &1, Error, Http.Res>): 568 match outcome: 569 case Fail{error}: IO.pure(S & Result<&2, &1, Error, Http.Res>, (state, Fail{error})) 570 case Done{value}: handler(state, config, value) 571 572 # An executor, not a second dispatcher: call from dispatch's run template, after 573 # all applicable before hooks. Only validated typed input reaches business IO. 574 def execute(~I: Type, ~S: Type, ~K: Data, ~E: Data, ~H: Data, 575 ~decoder: K -> Match> -> Http.Req -> Result<&2, &1, Error, I>, 576 ~handler: S -> K -> I -> IO(S & Result<&2, &1, Error, Http.Res>), 577 state: S, +config: K, selected: Match>, req: Http.Req 578 ) -> IO(S & Result<&2, &1, Error, Http.Res>): 579 execute.decoded(~I, ~S, ~K, ~E, ~handler, decoder(config, selected, req), state, config) 580 581 type Step<-S: Type, -E: Data> is Type: 582 Continue{state: S} 583 Early{state: S, response: Http.Res} 584 Reject{state: S, error: Error} 585 586 type Completion<-S: Type, -H: Data> is Type: 587 Completed{state: S, response: Http.Res, entered: List<&2, Policy>, mapped: Bool, stopped: Bool} 588 589 # Application completion only: no socket write, duration, or peer-receipt claim. 590 def minimal() -> Http.Res: 591 empty(500) 592 593 def problem.allow(add: Bool, headers: Map<&2, List<&2, String>>, allow: String) -> Map<&2, List<&2, String>>: 594 match add: 595 case True{}: Http.set(headers, "allow", allow) 596 case False{}: headers 597 598 def problem.response(allow: String, response: Http.Res) -> Http.Res: 599 Http.Res{+status, headers, body} = response 600 Http.Res{status, problem.allow(U32.is_eq(status, 405), Http.set(headers, "content-type", "application/problem+json"), allow), body} 601 602 def problem(+status: U32, title: String, allow: String) -> Http.Res: 603 problem.response(allow, json(~Json.Val, ~(x => x), status, Json.Obj{[(Json.utf8("type"), Json.Str{Json.utf8("about:blank")}), (Json.utf8("title"), Json.Str{Json.utf8(title)}), (Json.utf8("status"), Json.Num{Json.utf8(U32.show(status))})]})) 604 605 def default.error(~E: Data, error: Error) -> Http.Res: 606 match error: 607 case Framework{status, allow}: 608 match status: 609 case 400: 610 problem(400, "Bad Request", allow) 611 case 404: 612 problem(404, "Not Found", allow) 613 case 405: 614 problem(405, "Method Not Allowed", allow) 615 case 415: 616 problem(415, "Unsupported Media Type", allow) 617 case _: 618 minimal() 619 case _: 620 minimal() 621 622 def default.mapper(~S: Type, ~K: Data, ~E: Data, state: S, config: K, error: Error) -> IO(S & Result<&2, &1, Error, Http.Res>): 623 IO.pure(S & Result<&2, &1, Error, Http.Res>, (state, Done{default.error(~E, error)})) 624 625 def mapped.checked(~S: Type, ~H: Data, state: S, entered: List<&2, Policy>, stopped: Bool, result: Result<&2, &1, ResponseError, Http.Res>) -> Completion: 626 match result: 627 case Fail{error}: 628 Completed{state, minimal(), entered, True{}, True{}} 629 case Done{response}: 630 Completed{state, response, entered, True{}, stopped} 631 632 def mapped.result(~S: Type, ~E: Data, ~H: Data, entered: List<&2, Policy>, stopped: Bool, result: S & Result<&2, &1, Error, Http.Res>) -> Completion: 633 (state, outcome) = result 634 match outcome: 635 case Fail{error}: 636 Completed{state, minimal(), entered, True{}, True{}} 637 case Done{response}: 638 mapped.checked(~S, ~H, state, entered, stopped, validate(response)) 639 640 def failed(~S: Type, ~K: Data, ~E: Data, ~H: Data, 641 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 642 mapped: Bool, state: S, config: K, error: Error, entered: List<&2, Policy>, stopped: Bool 643 ) -> IO(Completion): 644 match mapped: 645 case True{}: 646 IO.pure(Completion, Completed{state, minimal(), entered, True{}, True{}}) 647 case False{}: 648 do IO>: 649 result : S & Result<&2, &1, Error, Http.Res> <- mapper(state, config, error) 650 return mapped.result(~S, ~E, ~H, entered, stopped, result) 651 652 def result(~S: Type, ~K: Data, ~E: Data, ~H: Data, 653 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 654 config: K, entered: List<&2, Policy>, outcome: S & Result<&2, &1, Error, Http.Res> 655 ) -> IO(Completion): 656 (state, response) = outcome 657 match response: 658 case Fail{error}: 659 failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, error, entered, False{}) 660 case Done{res}: 661 IO.pure(Completion, Completed{state, res, entered, False{}, False{}}) 662 663 def before.next(~S: Type, ~E: Data, step: Step, next: S -> IO(Step)) -> IO(Step): 664 match step: 665 case Continue{state}: 666 next(state) 667 case Early{state, response}: 668 IO.pure(Step, Early{state, response}) 669 case Reject{state, error}: 670 IO.pure(Step, Reject{state, error}) 671 672 def before(~S: Type, ~K: Data, ~E: Data, ~H: Data, 673 ~hook: H -> S -> K -> Metadata -> IO(Step), 674 hooks: List<&2, H>, state: S, +config: K, +meta: Metadata 675 ) -> IO(Step): 676 match hooks: 677 case Nil{}: 678 IO.pure(Step, Continue{state}) 679 case h <> rest: 680 do IO>: 681 step : Step <- hook(h, state, config, meta) 682 before.next(~S, ~E, step, s => before(~S, ~K, ~E, ~H, ~hook, rest, s, config, meta)) 683 684 def transformed(~S: Type, ~K: Data, ~E: Data, ~H: Data, 685 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 686 config: K, entered: List<&2, Policy>, mapped: Bool, outcome: S & Result<&2, &1, Error, Http.Res> 687 ) -> IO(Completion): 688 (state, response) = outcome 689 match response: 690 case Fail{error}: 691 failed(~S, ~K, ~E, ~H, ~mapper, mapped, state, config, error, entered, True{}) 692 case Done{res}: 693 IO.pure(Completion, Completed{state, res, entered, mapped, False{}}) 694 695 def transforms(~S: Type, ~K: Data, ~E: Data, ~H: Data, 696 ~hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 697 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 698 hooks: List<&2, H>, +config: K, completion: Completion 699 ) -> IO(Completion): 700 match hooks: 701 case Nil{}: 702 IO.pure(Completion, completion) 703 case h <> rest: 704 match completion: 705 case Completed{state, response, entered, mapped, True{}}: 706 IO.pure(Completion, Completed{state, response, entered, mapped, True{}}) 707 case Completed{state, response, +entered, +mapped, False{}}: 708 do IO>: 709 changed : S & Result<&2, &1, Error, Http.Res> <- hook(h, state, config, response) 710 next : Completion <- transformed(~S, ~K, ~E, ~H, ~mapper, config, entered, mapped, changed) 711 transforms(~S, ~K, ~E, ~H, ~hook, ~mapper, rest, config, next) 712 713 def entered.step(~S: Type, ~K: Data, ~E: Data, ~H: Data, 714 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 715 config: K, entered: List<&2, Policy>, body: Http.Body, step: Step, next: S -> List<&2, Policy> -> Http.Body -> IO(Completion) 716 ) -> IO(Completion): 717 match step: 718 case Continue{state}: 719 next(state, entered, body) 720 case Early{state, response}: 721 IO.pure(Completion, Completed{state, response, entered, False{}, False{}}) 722 case Reject{state, error}: 723 failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, error, entered, False{}) 724 725 def scopes(~S: Type, ~K: Data, ~E: Data, ~H: Data, 726 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 727 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 728 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 729 policies: List<&2, Policy>, state: S, +config: K, +meta: Metadata, entered: List<&2, Policy>, body: Http.Body, 730 next: S -> List<&2, Policy> -> Http.Body -> IO(Completion) 731 ) -> IO(Completion): 732 match policies: 733 case Nil{}: 734 next(state, entered, body) 735 case Policy{+id, +hooks, +unwind} <> rest: 736 scope = {Policy{id, hooks, unwind} : Policy} 737 do IO>: 738 step : Step <- before(~S, ~K, ~E, ~H, ~before_hook, hooks, state, config, meta) 739 inner : Completion <- entered.step(~S, ~K, ~E, ~H, ~mapper, config, scope <> entered, body, step, s => ids => b => scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, rest, s, config, meta, ids, b, next)) 740 transforms(~S, ~K, ~E, ~H, ~after_hook, ~mapper, unwind, config, inner) 741 742 def finish.checked(~S: Type, ~K: Data, ~E: Data, ~H: Data, 743 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 744 state: S, config: K, entered: List<&2, Policy>, mapped: Bool, stopped: Bool, checked: Result<&2, &1, ResponseError, Http.Res> 745 ) -> IO(Completion): 746 match checked: 747 case Done{response}: 748 IO.pure(Completion, Completed{state, response, entered, mapped, stopped}) 749 case Fail{error}: 750 failed(~S, ~K, ~E, ~H, ~mapper, mapped, state, config, ResponseInvalid{}, entered, True{}) 751 752 def finish(~S: Type, ~K: Data, ~E: Data, ~H: Data, 753 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 754 config: K, completion: Completion 755 ) -> IO(Completion): 756 Completed{state, response, entered, mapped, stopped} = completion 757 finish.checked(~S, ~K, ~E, ~H, ~mapper, state, config, entered, mapped, stopped, validate(response)) 758 759 def policies.tail(~H: Data, policies: List<&2, Policy>) -> List<&2, Policy>: 760 match policies: 761 case Nil{}: 762 Nil{} 763 case root <> rest: 764 rest 765 766 767 def endpoint(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data, 768 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 769 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 770 action: A, state: S, +config: K, selected: Match>, meta: Metadata, entered: List<&2, Policy>, body: Http.Body 771 ) -> IO(Completion): 772 Metadata{method, target, headers} = meta 773 do IO>: 774 outcome : S & Result<&2, &1, Error, Http.Res> <- run(action, state, config, selected, Http.Req{method, target, headers, body}) 775 result(~S, ~K, ~E, ~H, ~mapper, config, entered, outcome) 776 777 def framework(~S: Type, ~K: Data, ~E: Data, ~H: Data, 778 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 779 +status: U32, allow: String, state: S, config: K, entered: List<&2, Policy>, body: Http.Body 780 ) -> IO(Completion): 781 match status: 782 case 204: 783 IO.pure(Completion, Completed{state, Http.Res{204, Http.set(Http.empty(), "allow", allow), Http.from_string("")}, entered, False{}, False{}}) 784 case _: 785 failed(~S, ~K, ~E, ~H, ~mapper, False{}, state, config, Framework{status, allow}, entered, False{}) 786 787 def choice(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data, 788 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 789 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 790 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 791 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 792 selected: Router.Choice>>, target: Target.Target, 793 groups: List<&2, GroupPlan>>, state: S, +config: K, +meta: Metadata, entered: List<&2, Policy>, body: Http.Body 794 ) -> IO(Completion): 795 match selected: 796 case Router.NotFound{}: 797 framework(~S, ~K, ~E, ~H, ~mapper, 404, "", state, config, entered, body) 798 case Router.MethodMissing{ids, allow}: 799 policies = groups.prepared(~Policy, groups, ids) 800 scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies, state, config, meta, entered, body, 801 s => entered => b => framework(~S, ~K, ~E, ~H, ~mapper, 405, allow, s, config, entered, b)) 802 case Router.Options{ids, allow}: 803 policies = groups.prepared(~Policy, groups, ids) 804 scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies, state, config, meta, entered, body, 805 s => entered => b => framework(~S, ~K, ~E, ~H, ~mapper, 204, allow, s, config, entered, b)) 806 case Router.Found{Endpoint{action, +policies}, params, ids, method, pattern}: 807 selected = {Match{target, params, ids, method, pattern, policies} : Match>} 808 scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, policies.tail(~H, policies), state, config, meta, entered, body, 809 s => entered => b => endpoint(~A, ~S, ~K, ~E, ~H, ~run, ~mapper, action, s, config, selected, meta, entered, b)) 810 811 def metadata.authority(authority: Maybe<&2, String>, meta: Metadata) -> Metadata: 812 match authority: 813 case None{}: 814 meta 815 case Some{host}: 816 Metadata{method, target, headers} = meta 817 Metadata{method, target, Http.set(headers, "host", host)} 818 819 def resolved(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data, 820 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 821 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 822 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 823 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 824 outcome: Result<&2, &2, Router.Error, Router.Resolved>>>, 825 groups: List<&2, GroupPlan>>, state: S, config: K, meta: Metadata, entered: List<&2, Policy>, body: Http.Body 826 ) -> IO(Completion): 827 match outcome: 828 case Fail{error}: 829 framework(~S, ~K, ~E, ~H, ~mapper, 400, "", state, config, entered, body) 830 case Done{Router.Resolved{+target, selected}}: 831 Target.Target{segments, query, authority, star} = target 832 choice(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, selected, target, groups, state, config, metadata.authority(authority, meta), entered, body) 833 834 def dispatch.application(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data, 835 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 836 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 837 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 838 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 839 app: Application>, state: S, req: Http.Req 840 ) -> IO(Completion): 841 Application{table, +config, root, groups} = app 842 Http.Req{+method, +path, +headers, body} = req 843 +meta = {Metadata{method, path, headers} : Metadata} 844 do IO>: 845 completion : Completion <- scopes(~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~mapper, [root], state, config, meta, Nil{}, body, 846 s => entered => b => resolved(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, 847 Router.resolve(~Endpoint>, table, method, path), groups, s, config, meta, entered, b)) 848 finish(~S, ~K, ~E, ~H, ~mapper, config, completion) 849 850 def access.disabled(~K: Data, config: K) -> Notices.Access: 851 Notices.Disabled{} 852 853 def notice.ignore(~N: Data, ~H: Data, ~E: Data, observer: N, policy: Policy, notice: Notices.Notice) -> IO(Result<&2, &2, E, Unit>): 854 IO.pure(Result<&2, &2, E, Unit>, Done{Unit{}}) 855 856 def notice.error.ignore(~N: Data, ~E: Data, observer: N, error: E) -> IO(Unit): 857 IO.pure(Unit, Unit{}) 858 859 def notice.result(~N: Data, ~E: Data, ~report: N -> E -> IO(Unit), observer: N, result: Result<&2, &2, E, Unit>) -> IO(Unit): 860 match result: 861 case Done{unit}: IO.pure(Unit, Unit{}) 862 case Fail{error}: report(observer, error) 863 864 def notices(~N: Data, ~H: Data, ~E: Data, 865 ~notify: N -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 866 ~report: N -> E -> IO(Unit), 867 entered: List<&2, Policy>, +observer: N, +notice: Notices.Notice 868 ) -> IO(Unit): 869 match entered: 870 case Nil{}: IO.pure(Unit, Unit{}) 871 case policy <> rest: 872 do IO: 873 result : Result<&2, &2, E, Unit> <- notify(observer, policy, notice) 874 notice.result(~N, ~E, ~report, observer, result) 875 notices(~N, ~H, ~E, ~notify, ~report, rest, observer, notice) 876 877 def dispatch.completed(~S: Type, ~N: Data, ~H: Data, ~E: Data, 878 ~notify: N -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 879 ~report: N -> E -> IO(Unit), 880 observer: N, access: Notices.Access, started: Time.Duration, completion: Completion 881 ) -> IO(Completion): 882 Completed{state, Http.Res{+status, headers, body}, +entered, +mapped, +stopped} = completion 883 do IO>: 884 ended : Time.Duration <- Time.mono() 885 +notice : Notices.Notice = Notices.Notice{status, Time.Duration.sub(ended, started), mapped, stopped, Notices.ApplicationOnly{}} 886 notices(~N, ~H, ~E, ~notify, ~report, entered, observer, notice) 887 Notices.access(access, notice) 888 return Completed{state, Http.Res{status, headers, body}, entered, mapped, stopped} 889 890 # The same application pipeline is used live; only this boundary emits direct notices. 891 def dispatch(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data, ~N: Data, 892 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 893 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 894 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 895 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 896 ~notify: N -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 897 ~report: N -> E -> IO(Unit), 898 observer: N, access: Notices.Access, app: Application>, state: S, req: Http.Req 899 ) -> IO(Completion): 900 do IO>: 901 started : Time.Duration <- Time.mono() 902 completion : Completion <- dispatch.application(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, app, state, req) 903 dispatch.completed(~S, ~N, ~H, ~E, ~notify, ~report, observer, access, started, completion) 904 905 # The wrapper owns both pipes until its admitted operation really returns. 906 # Only disposable HTTP packed buffers cross them, never external handles. 907 type BufferedRequest is Data: 908 BufferedRequest{metadata: Metadata, body: Chan(Http.Body), response: Chan(Http.Res)} 909 910 type ApplicationReport<-H: Data> is Data: 911 Dispatched{entered: List<&2, Policy>, mapped: Bool, stopped: Bool} 912 CapacityRejected{} 913 914 type ServingContext<-A: Data, -S: Type, -K: Data, -H: Data> is Data: 915 ServingContext{application: Application>, 916 owner: Context>, BufferedRequest, Unit, ApplicationReport>} 917 918 type Server<-A: Data, -S: Type, -K: Data, -H: Data> is Type: 919 Server{transport: Http.Server>, 920 owner: Owner>, BufferedRequest, Unit, ApplicationReport>} 921 922 type StartupError is Data: 923 RegistrationFailed{error: RegistrationError} 924 TransportFailed{error: Http.StartupError} 925 EmptyBundles{} 926 927 type ServerExit<-A: Data, -S: Type, -K: Data, -H: Data> is Type: 928 ServerExit{transport: Http.ServerExit, application: OwnerClose>, BufferedRequest, Unit, ApplicationReport>} 929 930 def server.config(port: U32) -> Http.ServerConfig: 931 Http.ServerConfig{"127.0.0.1", port, 1048576, 65536, 128, 128, 128, 5000, 30000, 30000, 30000} 932 933 def server.bundles.cons(~S: Type, resource: S, counted: List & Nat) -> List & Nat: 934 (returned, count) = counted 935 (resource <> returned, 1n+count) 936 937 def server.bundles(~S: Type, resources: List) -> List & Nat: 938 match resources: 939 case Nil{}: (Nil{}, 0n) 940 case resource <> rest: 941 server.bundles.cons(~S, resource, server.bundles(~S, rest)) 942 943 def server.work.completed(~S: Type, ~H: Data, response_channel: Chan(Http.Res), 944 completion: Completion 945 ) -> IO(S & Result<&2, &2, Unit, ApplicationReport>): 946 Completed{state, response, entered, mapped, stopped} = completion 947 do IO>>: 948 sent : Result<&1, &1, Http.Res, Unit> <- Chan.send(Http.Res, response_channel, response) 949 return (state, Done{Dispatched{entered, mapped, stopped}}) 950 951 def server.work(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data, 952 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 953 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 954 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 955 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 956 state: S, app: Application>, input: BufferedRequest 957 ) -> IO(S & Result<&2, &2, Unit, ApplicationReport>): 958 BufferedRequest{Metadata{method, target, headers}, body_channel, response_channel} = input 959 do IO>>: 960 body : Http.Body <- receive(Http.Body, body_channel) 961 completion : Completion <- dispatch.application(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, 962 app, state, Http.Req{method, target, headers, body}) 963 server.work.completed(~S, ~H, response_channel, completion) 964 965 type NoticePayload<-K: Data, -H: Data> is Data: 966 NoticePayload{config: K, started: Time.Duration, application: ApplicationReport} 967 968 def server.notified(~K: Data, ~H: Data, ~E: Data, 969 ~notify: K -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 970 ~report: K -> E -> IO(Unit), ~access: K -> Notices.Access, 971 ~done: TransportCompletion.Completion> -> IO(Unit), 972 +config: K, +notice: Notices.Notice, application: ApplicationReport, 973 status: U32, outcome: TransportCompletion.Outcome 974 ) -> IO(Unit): 975 match application: 976 case Dispatched{+entered, +mapped, +stopped}: 977 do IO: 978 notices(~K, ~H, ~E, ~notify, ~report, entered, config, notice) 979 Notices.access(access(config), notice) 980 done(TransportCompletion.Completion{Dispatched{entered, mapped, stopped}, status, outcome}) 981 case CapacityRejected{}: 982 do IO: 983 Notices.access(access(config), notice) 984 done(TransportCompletion.Completion{CapacityRejected{}, status, outcome}) 985 986 def server.completed.report(~K: Data, ~H: Data, ~E: Data, 987 ~notify: K -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 988 ~report: K -> E -> IO(Unit), ~access: K -> Notices.Access, 989 ~done: TransportCompletion.Completion> -> IO(Unit), 990 config: K, duration: Time.Duration, application: ApplicationReport, +status: U32, +outcome: TransportCompletion.Outcome 991 ) -> IO(Unit): 992 match application: 993 case Dispatched{+entered, +mapped, +stopped}: 994 server.notified(~K, ~H, ~E, ~notify, ~report, ~access, ~done, config, 995 Notices.Notice{status, duration, mapped, stopped, Notices.Transport{outcome}}, Dispatched{entered, mapped, stopped}, status, outcome) 996 case CapacityRejected{}: 997 server.notified(~K, ~H, ~E, ~notify, ~report, ~access, ~done, config, 998 Notices.Notice{status, duration, False{}, False{}, Notices.Transport{outcome}}, CapacityRejected{}, status, outcome) 999 1000 def server.completed(~K: Data, ~H: Data, ~E: Data, 1001 ~notify: K -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 1002 ~report: K -> E -> IO(Unit), ~access: K -> Notices.Access, 1003 ~done: TransportCompletion.Completion> -> IO(Unit), 1004 completion: TransportCompletion.Completion> 1005 ) -> IO(Unit): 1006 TransportCompletion.Completion{NoticePayload{config, started, +application}, +status, +outcome} = completion 1007 do IO: 1008 ended : Time.Duration <- Time.mono() 1009 server.completed.report(~K, ~H, ~E, ~notify, ~report, ~access, ~done, config, Time.Duration.sub(ended, started), application, status, outcome) 1010 1011 def server.response(~K: Data, ~H: Data, 1012 ~done: TransportCompletion.Completion> -> IO(Unit), 1013 config: K, started: Time.Duration, outcome: Outcome>, +body: Chan(Http.Body), +response: Chan(Http.Res) 1014 ) -> IO(Http.Reply): 1015 match outcome: 1016 case Success{report}: 1017 do IO: 1018 result : Http.Res <- receive(Http.Res, response) 1019 Chan.close(Http.Body, body) 1020 Chan.close(Http.Res, response) 1021 return Http.reply.complete(~NoticePayload, ~done, NoticePayload{config, started, report}, result) 1022 case _: 1023 do IO: 1024 # Closing alone retains buffered values in Base. Consume the rejected 1025 # body before closing; the single room1 send has already completed. 1026 discarded : Http.Body <- receive(Http.Body, body) 1027 Chan.close(Http.Body, body) 1028 Chan.close(Http.Res, response) 1029 return Http.reply.complete(~NoticePayload, ~done, NoticePayload{config, started, CapacityRejected{}}, 1030 Http.Res{503, Http.set(Http.empty(), "connection", "close"), Http.from_string("")}) 1031 1032 def server.handle(~A: Data, ~S: Type, ~K: Data, ~H: Data, 1033 ~done: TransportCompletion.Completion> -> IO(Unit), 1034 context: ServingContext, req: Http.Req 1035 ) -> IO(Http.Reply): 1036 ServingContext{+app, owner} = context 1037 Application{table, config, root, groups} = app 1038 Http.Req{method, target, headers, body} = req 1039 do IO: 1040 started : Time.Duration <- Time.mono() 1041 +body_channel : Chan(Http.Body) <- Chan.new(Http.Body, 1) 1042 +response_channel : Chan(Http.Res) <- Chan.new(Http.Res, 1) 1043 sent : Result<&1, &1, Http.Body, Unit> <- Chan.send(Http.Body, body_channel, body) 1044 outcome : Outcome> <- call(S, Application>, BufferedRequest, Unit, ApplicationReport, 1045 owner, app, BufferedRequest{Metadata{method, target, headers}, body_channel, response_channel}) 1046 server.response(~K, ~H, ~done, config, started, outcome, body_channel, response_channel) 1047 1048 1049 def server.started(~A: Data, ~S: Type, ~K: Data, ~H: Data, 1050 ~work: S -> Application> -> BufferedRequest -> IO(S & Result<&2, &2, Unit, ApplicationReport>), 1051 ~destroy: S -> IO(Unit), 1052 +context: Context>, BufferedRequest, Unit, ApplicationReport>, 1053 room: U32, capacity: Nat, 1054 started: List & Result<&2, &1, Http.StartupError, Http.Server>> 1055 ) -> IO(Result<&2, &1, StartupError, Server>): 1056 Context{+messages} = context 1057 (resources, result) = started 1058 match result: 1059 case Fail{error}: 1060 do IO>>: 1061 dispose(~S, ~destroy, resources) 1062 Chan.close(Message>, BufferedRequest, Unit, ApplicationReport>, messages) 1063 return Fail{TransportFailed{error}} 1064 case Done{transport}: 1065 do IO>>: 1066 finished : Chan(Unit) <- IO.fork(Unit, owner(~S, ~Application>, ~BufferedRequest, ~Unit, ~ApplicationReport, 1067 ~work, ~destroy, messages, room, Waiting{resources, Counts{capacity, 0n, 0n, 0n}})) 1068 return Done{Server{transport, Owner{Context{messages}, finished}}} 1069 1070 def server.prepared(~A: Data, ~S: Type, ~K: Data, ~H: Data, 1071 ~work: S -> Application> -> BufferedRequest -> IO(S & Result<&2, &2, Unit, ApplicationReport>), 1072 ~destroy: S -> IO(Unit), app: Application>, +config: Http.ServerConfig, resources: List & Nat 1073 ) -> IO(Result<&2, &1, StartupError, Server>): 1074 Http.ServerConfig{host, port, body, headers, connections, requests, +buffered, hm, bm, im, wm} = config 1075 (bundles, capacity) = resources 1076 match capacity: 1077 case 0n: 1078 do IO>>: 1079 dispose(~S, ~destroy, bundles) 1080 return Fail{EmptyBundles{}} 1081 case 1n+ +rest: 1082 do IO>>: 1083 messages : Chan(Message>, BufferedRequest, Unit, ApplicationReport>) <- 1084 Chan.new(Message>, BufferedRequest, Unit, ApplicationReport>, buffered) 1085 +context : Context>, BufferedRequest, Unit, ApplicationReport> = Context{messages} 1086 started : List & Result<&2, &1, Http.StartupError, Http.Server>> <- 1087 Http.server.start(~ServingContext, ~List, ServingContext{app, context}, bundles, config) 1088 server.started(~A, ~S, ~K, ~H, ~work, ~destroy, context, buffered, 1n+rest, started) 1089 1090 def server.valid(~A: Data, ~S: Type, ~K: Data, ~H: Data, 1091 ~work: S -> Application> -> BufferedRequest -> IO(S & Result<&2, &2, Unit, ApplicationReport>), 1092 ~destroy: S -> IO(Unit), valid: Bool, app: Application>, config: Http.ServerConfig, resources: List 1093 ) -> IO(Result<&2, &1, StartupError, Server>): 1094 match valid: 1095 case False{}: 1096 do IO>>: 1097 dispose(~S, ~destroy, resources) 1098 return Fail{TransportFailed{Http.InvalidConfig{}}} 1099 case True{}: 1100 server.prepared(~A, ~S, ~K, ~H, ~work, ~destroy, app, config, server.bundles(~S, resources)) 1101 1102 def server.registered(~A: Data, ~S: Type, ~K: Data, ~H: Data, 1103 ~work: S -> Application> -> BufferedRequest -> IO(S & Result<&2, &2, Unit, ApplicationReport>), 1104 ~destroy: S -> IO(Unit), registration: Result<&2, &2, RegistrationError, Application>>, 1105 +config: Http.ServerConfig, resources: List 1106 ) -> IO(Result<&2, &1, StartupError, Server>): 1107 match registration: 1108 case Fail{error}: 1109 do IO>>: 1110 dispose(~S, ~destroy, resources) 1111 return Fail{RegistrationFailed{error}} 1112 case Done{app}: 1113 server.valid(~A, ~S, ~K, ~H, ~work, ~destroy, Http.server.config.valid(config), app, config, resources) 1114 1115 def server.start(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data, 1116 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 1117 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 1118 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 1119 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 1120 ~destroy: S -> IO(Unit), 1121 registration: Result<&2, &2, RegistrationError, Application>>, 1122 config: Http.ServerConfig, resources: List 1123 ) -> IO(Result<&2, &1, StartupError, Server>): 1124 server.registered(~A, ~S, ~K, ~H, 1125 ~(state => app => input => server.work(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, state, app, input)), 1126 ~destroy, registration, config, resources) 1127 1128 def server.control.result(~A: Data, ~S: Type, ~K: Data, ~H: Data, 1129 controlled: Http.Server> & Http.ServerControl, 1130 owner: Owner>, BufferedRequest, Unit, ApplicationReport> 1131 ) -> Server & Http.ServerControl: 1132 (returned, control) = controlled 1133 (Server{returned, owner}, control) 1134 1135 def server.control(~A: Data, ~S: Type, ~K: Data, ~H: Data, server: Server) -> Server & Http.ServerControl: 1136 Server{transport, owner} = server 1137 server.control.result(~A, ~S, ~K, ~H, Http.server.control(~ServingContext, transport), owner) 1138 1139 def server.observer.result(~A: Data, ~S: Type, ~K: Data, ~H: Data, 1140 observed: Http.Server> & Http.ServerStats, 1141 owner: Owner>, BufferedRequest, Unit, ApplicationReport> 1142 ) -> Server & Http.ServerStats: 1143 (returned, stats) = observed 1144 (Server{returned, owner}, stats) 1145 1146 def server.observer(~A: Data, ~S: Type, ~K: Data, ~H: Data, server: Server) -> Server & Http.ServerStats: 1147 Server{transport, owner} = server 1148 server.observer.result(~A, ~S, ~K, ~H, Http.server.observer(~ServingContext, transport), owner) 1149 1150 def server.dependencies(~A: Data, ~S: Type, ~K: Data, ~H: Data, server: Server 1151 ) -> Server & Context>, BufferedRequest, Unit, ApplicationReport>: 1152 Server{transport, Owner{+context, finished}} = server 1153 (Server{transport, Owner{context, finished}}, context) 1154 1155 def server.run(~A: Data, ~S: Type, ~K: Data, ~H: Data, ~E: Data, 1156 ~done: TransportCompletion.Completion> -> IO(Unit), 1157 ~observe: U32 -> TransportCompletion.Outcome -> IO(Unit), 1158 ~notify: K -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 1159 ~report: K -> E -> IO(Unit), ~access: K -> Notices.Access, 1160 server: Server 1161 ) -> IO(ServerExit): 1162 Server{transport, owner} = server 1163 do IO>: 1164 exited : Http.ServerExit <- Http.server.run(~ServingContext, 1165 ~(context => req => server.handle(~A, ~S, ~K, ~H, 1166 ~(completion => server.completed(~K, ~H, ~E, ~notify, ~report, ~access, ~done, completion)), context, req)), 1167 ~(context => status => outcome => observe(status, outcome)), transport) 1168 closed : OwnerClose>, BufferedRequest, Unit, ApplicationReport> <- close(S, Application>, BufferedRequest, Unit, ApplicationReport, owner) 1169 return ServerExit{exited, closed} 1170 1171 # Without starting the run loop, the caller still explicitly drains transport 1172 # and joins application cleanup. A stop request alone is never completion. 1173 def server.close(~A: Data, ~S: Type, ~K: Data, ~H: Data, server: Server) -> IO(ServerExit): 1174 Server{transport, owner} = server 1175 do IO>: 1176 counts : Http.ServerCounts <- Http.server.close(~ServingContext, transport) 1177 closed : OwnerClose>, BufferedRequest, Unit, ApplicationReport> <- close(S, Application>, BufferedRequest, Unit, ApplicationReport, owner) 1178 return ServerExit{Http.ServerStopped{counts}, closed} 1179 1180 def serve.started(~A: Data, ~S: Type, ~K: Data, ~H: Data, ~E: Data, 1181 ~done: TransportCompletion.Completion> -> IO(Unit), 1182 ~observe: U32 -> TransportCompletion.Outcome -> IO(Unit), 1183 ~notify: K -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 1184 ~report: K -> E -> IO(Unit), ~access: K -> Notices.Access, 1185 started: Result<&2, &1, StartupError, Server> 1186 ) -> IO(Result<&2, &1, StartupError, ServerExit>): 1187 match started: 1188 case Fail{error}: IO.pure(Result<&2, &1, StartupError, ServerExit>, Fail{error}) 1189 case Done{server}: 1190 do IO>>: 1191 exited : ServerExit <- server.run(~A, ~S, ~K, ~H, ~E, ~done, ~observe, ~notify, ~report, ~access, server) 1192 return Done{exited} 1193 1194 # Owners that need readiness/control use server.start then server.run instead. 1195 def serve(~A: Data, ~S: Type, ~K: Data, ~E: Data, ~H: Data, 1196 ~before_hook: H -> S -> K -> Metadata -> IO(Step), 1197 ~after_hook: H -> S -> K -> Http.Res -> IO(S & Result<&2, &1, Error, Http.Res>), 1198 ~run: A -> S -> K -> Match> -> Http.Req -> IO(S & Result<&2, &1, Error, Http.Res>), 1199 ~mapper: S -> K -> Error -> IO(S & Result<&2, &1, Error, Http.Res>), 1200 ~destroy: S -> IO(Unit), 1201 ~done: TransportCompletion.Completion> -> IO(Unit), 1202 ~observe: U32 -> TransportCompletion.Outcome -> IO(Unit), 1203 ~notify: K -> Policy -> Notices.Notice -> IO(Result<&2, &2, E, Unit>), 1204 ~report: K -> E -> IO(Unit), ~access: K -> Notices.Access, 1205 registration: Result<&2, &2, RegistrationError, Application>>, 1206 config: Http.ServerConfig, resources: List 1207 ) -> IO(Result<&2, &1, StartupError, ServerExit>): 1208 do IO>>: 1209 started : Result<&2, &1, StartupError, Server> <- 1210 server.start(~A, ~S, ~K, ~E, ~H, ~before_hook, ~after_hook, ~run, ~mapper, ~destroy, registration, config, resources) 1211 serve.started(~A, ~S, ~K, ~H, ~E, ~done, ~observe, ~notify, ~report, ~access, started)