1 # Fixed prepared HTTP applications and bounded affine dependency bundles. 2 import Base 3 import bend-kit-http@0.30.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.5.1.0/json.bend as Json 7 import ./response.bend as Response 8 import ./input.bend as Input 9 import bend-kit-http@0.30.0.0/completion.bend as TransportCompletion 10 import ./notices.bend as Notices 11 import bend-kit-time@0.1.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 # The resources return through the private owner protocol BEFORE a disposable 97 # caller reply. A closed caller reply cannot erase a handle or strand capacity. 98 def work.return(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 99 messages: Chan(Message), reply: Chan(Outcome), 100 result: R & Result<&2, &2, E, O> 101 ) -> IO(Unit): 102 (returned, outcome) = result 103 do IO: 104 accepted : Bool <- Chan.send(Message, messages, Returned{returned}) 105 sent : Bool <- Chan.send(Outcome, reply, outcome.value(E, O, outcome)) 106 return Unit{} 107 108 def work(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 109 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), 110 resource: R, principal: P, input: I, 111 messages: Chan(Message), reply: Chan(Outcome) 112 ) -> IO(Unit): 113 do IO: 114 result : R & Result<&2, &2, E, O> <- handler(resource, principal, input) 115 work.return(R, P, I, E, O, messages, reply, result) 116 117 def dispose(~R: Type, ~destroy: R -> IO(Unit), resources: List) -> IO(Unit): 118 match resources: 119 case Nil{}: 120 IO.pure(Unit, Unit{}) 121 case Con{resource, rest}: 122 do IO: 123 destroy(resource) 124 dispose(~R, ~destroy, rest) 125 126 # Closing Base channels retains buffered messages. Drain them and answer stopped 127 # callers, rather than stranding accepted commands behind the close command. 128 def drain.message(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 129 ~destroy: R -> IO(Unit), message: Message 130 ) -> IO(Unit): 131 match message: 132 case Submit{principal, input, reply}: 133 do IO: 134 sent : Bool <- Chan.send(Outcome, reply, Stopped{}) 135 return Unit{} 136 case Inspect{reply}: 137 Chan.close(Counts, reply) 138 case Shutdown{reply}: 139 do IO: 140 sent : Bool <- Chan.send(CloseResult, reply, Closed{}) 141 return Unit{} 142 case Returned{resource}: 143 destroy(resource) 144 145 def drain.step(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 146 ~destroy: R -> IO(Unit), pending: Maybe<&1, Message>, 147 next: Unit -> IO(Unit) 148 ) -> IO(Unit): 149 match pending: 150 case None{}: 151 IO.pure(Unit, Unit{}) 152 case Some{message}: 153 do IO: 154 drain.message(~R, ~P, ~I, ~E, ~O, ~destroy, message) 155 next(Unit{}) 156 157 # The closed inbox contains at most room buffered messages. 158 def drain(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 159 ~destroy: R -> IO(Unit), fuel: Nat, +messages: Chan(Message) 160 ) -> IO(Unit): 161 match fuel: 162 case 0n: 163 IO.pure(Unit, Unit{}) 164 case 1n+p: 165 do IO: 166 pending : Maybe<&1, Message> <- Chan.recv(Message, messages) 167 drain.step(~R, ~P, ~I, ~E, ~O, ~destroy, pending, u => drain(~R, ~P, ~I, ~E, ~O, ~destroy, p, messages)) 168 169 type OwnerStep<-R: Type, -P: Data, -I: Data, -E: Data, -O: Data> is Type: 170 Waiting{resources: List, counts: Counts} 171 Received{message: Message, counts: Counts, resources: List} 172 173 # IO.join closes a one-shot channel; the owner inbox must remain reusable. 174 def receive.value(-A: Type, value: Maybe<&1, A>) -> IO(A): 175 match value: 176 case None{}: 177 IO.die(A, 1, "camber internal channel closed unexpectedly") 178 case Some{message}: 179 IO.pure(A, message) 180 181 def receive(-A: Type, channel: Chan(A)) -> IO(A): 182 do IO: 183 value : Maybe<&1, A> <- Chan.recv(A, channel) 184 receive.value(A, value) 185 186 # Only idle close ends this loop. Busy close never waits on a stalled handler. 187 # @unsafe is solely the lifecycle receive loop, not a termination/proof claim. 188 @unsafe def owner(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 189 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit), 190 +messages: Chan(Message), +room: U32, step: OwnerStep 191 ) -> IO(Unit): 192 match step: 193 case Waiting{resources, counts}: 194 do IO: 195 message : Message <- receive(Message, messages) 196 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Received{message, counts, resources}) 197 case Received{message, counts, resources}: 198 match message: 199 case Returned{resource}: 200 Counts{capacity, in_use, completed, rejected} = counts 201 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resource <> resources, Counts{capacity, Nat.sub(in_use, 1n), 1n+completed, rejected}}) 202 case Inspect{reply}: 203 Counts{+capacity, +in_use, +completed, +rejected} = counts 204 do IO: 205 sent : Bool <- Chan.send(Counts, reply, Counts{capacity, in_use, completed, rejected}) 206 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, in_use, completed, rejected}}) 207 case Shutdown{reply}: 208 Counts{+capacity, in_use, +completed, +rejected} = counts 209 match in_use: 210 case 0n: 211 do IO: 212 dispose(~R, ~destroy, resources) 213 Chan.close(Message, messages) 214 sent : Bool <- Chan.send(CloseResult, reply, Closed{}) 215 drain(~R, ~P, ~I, ~E, ~O, ~destroy, U32.to_nat(room), messages) 216 case 1n+ +p: 217 do IO: 218 sent : Bool <- Chan.send(CloseResult, reply, Busy{Counts{capacity, 1n+p, completed, rejected}}) 219 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, 1n+p, completed, rejected}}) 220 case Submit{principal, input, reply}: 221 Counts{capacity, in_use, completed, rejected} = counts 222 match resources: 223 case Nil{}: 224 do IO: 225 sent : Bool <- Chan.send(Outcome, reply, Exhausted{}) 226 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{Nil{}, Counts{capacity, in_use, completed, 1n+rejected}}) 227 case Con{resource, rest}: 228 do IO: 229 IO.spawn(Unit, work(~R, ~P, ~I, ~E, ~O, ~handler, resource, principal, input, messages, reply)) 230 owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{rest, Counts{capacity, 1n+in_use, completed, rejected}}) 231 232 def build.step(~R: Type, ~E: Data, ~destroy: R -> IO(Unit), 233 result: Result<&2, &1, E, R>, resources: List, 234 next: List -> IO(Result<&2, &1, E, List>) 235 ) -> IO(Result<&2, &1, E, List>): 236 match result: 237 case Fail{error}: 238 do IO>>: 239 dispose(~R, ~destroy, resources) 240 return Fail{error} 241 case Done{resource}: 242 next(resource <> resources) 243 244 def build(~K: Data, ~R: Type, ~E: Data, ~create: K -> Nat -> IO(Result<&2, &1, E, R>), ~destroy: R -> IO(Unit), 245 remaining: Nat, +config: K, resources: List 246 ) -> IO(Result<&2, &1, E, List>): 247 match remaining: 248 case 0n: 249 IO.pure(Result<&2, &1, E, List>, Done{resources}) 250 case 1n+ +p: 251 do IO>>: 252 result : Result<&2, &1, E, R> <- create(config, p) 253 build.step(~R, ~E, ~destroy, result, resources, next => build(~K, ~R, ~E, ~create, ~destroy, p, config, next)) 254 255 def start.ready(~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 256 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit), 257 capacity: Nat, +room: U32, result: Result<&2, &1, E, List> 258 ) -> IO(Result<&2, &1, E, Owner>): 259 match result: 260 case Fail{error}: 261 IO.pure(Result<&2, &1, E, Owner>, Fail{error}) 262 case Done{resources}: 263 do IO>>: 264 channel : Chan(Message) <- Chan.new(Message, room) 265 +messages : Chan(Message) = channel 266 finished : Chan(Unit) <- IO.fork(Unit, owner(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, messages, room, Waiting{resources, Counts{capacity, 0n, 0n, 0n}})) 267 return Done{Owner{Context{messages}, finished}} 268 269 # The initializer closes partial handles within a failed bundle; start closes 270 # all prior whole bundles. No workers exist until the complete set opens. 271 def start(~K: Data, ~R: Type, ~P: Data, ~I: Data, ~E: Data, ~O: Data, 272 ~create: K -> Nat -> IO(Result<&2, &1, E, R>), 273 ~handler: R -> P -> I -> IO(R & Result<&2, &2, E, O>), ~destroy: R -> IO(Unit), 274 config: K, +capacity: Nat, room: U32 275 ) -> IO(Result<&2, &1, E, Owner>): 276 do IO>>: 277 result : Result<&2, &1, E, List> <- build(~K, ~R, ~E, ~create, ~destroy, capacity, config, Nil{}) 278 start.ready(~R, ~P, ~I, ~E, ~O, ~handler, ~destroy, capacity, room, result) 279 280 def submit(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 281 context: Context, principal: P, input: I, reply: Chan(Outcome) 282 ) -> IO(Bool): 283 Context{messages} = context 284 Chan.send(Message, messages, Submit{principal, input, reply}) 285 286 def call.accepted(-E: Data, -O: Data, accepted: Bool, +reply: Chan(Outcome)) -> IO(Outcome): 287 match accepted: 288 case False{}: 289 do IO>: 290 Chan.close(Outcome, reply) 291 return Stopped{} 292 case True{}: 293 do IO>: 294 result : Outcome <- IO.join(Outcome, reply) 295 Chan.close(Outcome, reply) 296 return result 297 298 def call(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 299 context: Context, principal: P, input: I 300 ) -> IO(Outcome): 301 do IO>: 302 channel : Chan(Outcome) <- Chan.new(Outcome, 1) 303 +reply : Chan(Outcome) = channel 304 accepted : Bool <- submit(R, P, I, E, O, context, principal, input, reply) 305 call.accepted(E, O, accepted, reply) 306 307 def inspect.value(value: Maybe<&1, Counts>) -> Maybe<&2, Counts>: 308 match value: 309 case None{}: 310 None{} 311 case Some{counts}: 312 Some{counts} 313 314 def inspect.sent(sent: Bool, +reply: Chan(Counts)) -> IO(Maybe<&2, Counts>): 315 match sent: 316 case False{}: 317 do IO>: 318 Chan.close(Counts, reply) 319 return None{} 320 case True{}: 321 do IO>: 322 value : Maybe<&1, Counts> <- Chan.recv(Counts, reply) 323 Chan.close(Counts, reply) 324 return inspect.value(value) 325 326 def inspect(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, context: Context) -> IO(Maybe<&2, Counts>): 327 Context{messages} = context 328 do IO>: 329 channel : Chan(Counts) <- Chan.new(Counts, 1) 330 +reply : Chan(Counts) = channel 331 sent : Bool <- Chan.send(Message, messages, Inspect{reply}) 332 inspect.sent(sent, reply) 333 334 def close.sent(sent: Bool, +reply: Chan(CloseResult)) -> IO(CloseResult): 335 match sent: 336 case False{}: 337 do IO: 338 Chan.close(CloseResult, reply) 339 return Closed{} 340 case True{}: 341 do IO: 342 value : CloseResult <- IO.join(CloseResult, reply) 343 Chan.close(CloseResult, reply) 344 return value 345 346 def close.join(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 347 owner: Owner 348 ) -> IO(OwnerClose): 349 Owner{context, finished} = owner 350 do IO>: 351 IO.join(Unit, finished) 352 return OwnerClosed{} 353 354 def close.completed(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, 355 result: CloseResult, owner: Owner 356 ) -> IO(OwnerClose): 357 match result: 358 case Closed{}: 359 close.join(R, P, I, E, O, owner) 360 case Busy{counts}: 361 IO.pure(OwnerClose, OwnerBusy{owner, counts}) 362 363 # Stop external admissions before close. Busy preserves live context/capacity; 364 # retry after admitted operations finish. Stalled operations remain observable. 365 def close(-R: Type, -P: Data, -I: Data, -E: Data, -O: Data, owner: Owner) -> IO(OwnerClose): 366 Owner{Context{+messages}, finished} = owner 367 do IO>: 368 channel : Chan(CloseResult) <- Chan.new(CloseResult, 1) 369 +reply : Chan(CloseResult) = channel 370 sent : Bool <- Chan.send(Message, messages, Shutdown{reply}) 371 result : CloseResult <- close.sent(sent, reply) 372 close.completed(R, P, I, E, O, result, Owner{Context{messages}, finished}) 373 374 # Policies are prepared Data, not callbacks. Later lifecycle stages can consume 375 # these root-to-route declarations without splitting patterns or finding groups. 376 type Group<-P: Data> is Data: 377 Group{id: U32, ancestors: List<&2, U32>, policy: P} 378 379 type GroupPlan<-P: Data> is Data: 380 GroupPlan{ids: List<&2, U32>, policies: List<&2, P>} 381 382 type Route<-A: Data, -P: Data> is Data: 383 Route{method: String, pattern: String, action: A, groups: List<&2, U32>, policy: P} 384 385 type Endpoint<-A: Data, -P: Data> is Data: 386 Endpoint{action: A, policies: List<&2, P>} 387 388 type Application<-A: Data, -C: Data, -P: Data> is Data: 389 Application{table: Router.Table>, context: C, root: P, groups: List<&2, GroupPlan

>} 390 391 type RegistrationError is Data: 392 RouteError{error: Router.Error} 393 InvalidGroups{groups: List<&2, U32>} 394 395 type Match<-P: Data> is Data: 396 Match{target: Target.Target, params: Map<&2, String>, groups: List<&2, U32>, method: String, pattern: String, policies: List<&2, P>} 397 398 def groups.equal(xs: List<&2, U32>, ys: List<&2, U32>) -> Bool: 399 match xs ys: 400 case Nil{} Nil{}: 401 True{} 402 case x <> xt y <> yt: 403 U32.is_eq(x, y) && groups.equal(xt, yt) 404 case _ _: 405 False{} 406 407 def group.pick(~P: Data, same: Bool, group: Group

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

>) -> Maybe<&2, Group

>: 408 match same: 409 case True{}: 410 Some{group} 411 case False{}: 412 later(Unit{}) 413 414 def group.find(~P: Data, groups: List<&2, Group

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

>: 415 match groups: 416 case Nil{}: 417 None{} 418 case Group{+here, ancestors, policy} <> rest: 419 group.pick(~P, U32.is_eq(here, id), Group{here, ancestors, policy}, u => group.find(~P, rest, id)) 420 421 def group.checked(~P: Data, same: Bool, policy: P) -> Maybe<&2, P>: 422 match same: 423 case False{}: 424 None{} 425 case True{}: 426 Some{policy} 427 428 def group.policy(~P: Data, found: Maybe<&2, Group

>, ancestors: List<&2, U32>) -> Maybe<&2, P>: 429 match found: 430 case None{}: 431 None{} 432 case Some{Group{id, expected, policy}}: 433 group.checked(~P, groups.equal(expected, ancestors), policy) 434 435 def groups.policies(~P: Data, ids: List<&2, U32>, +declared: List<&2, Group

>, +ancestors: List<&2, U32>) -> Maybe<&2, List<&2, P>>: 436 match ids: 437 case Nil{}: 438 Some{Nil{}} 439 case +id <> rest: 440 do Maybe<&2, List<&2, P>>: 441 policy : P <- group.policy(~P, group.find(~P, declared, id), ancestors) 442 policies : List<&2, P> <- groups.policies(~P, rest, declared, List.append(&2, U32, ancestors, [id])) 443 return policy <> policies 444 445 def groups.unique(~P: Data, found: Maybe<&2, Group

>) -> Bool: 446 match found: 447 case None{}: 448 True{} 449 case Some{group}: 450 False{} 451 452 def groups.valid(~P: Data, groups: List<&2, Group

>, +all: List<&2, Group

>) -> Bool: 453 match groups: 454 case Nil{}: 455 True{} 456 case Group{+id, +ancestors, policy} <> +rest: 457 chain = groups.policies(~P, List.append(&2, U32, ancestors, [id]), all, Nil{}) 458 groups.unique(~P, group.find(~P, rest, id)) && Maybe.is_some(&2, List<&2, P>, chain) && groups.valid(~P, rest, all) 459 460 def routes.policies(~P: Data, result: Maybe<&2, List<&2, P>>, ids: List<&2, U32>) -> Result<&2, &2, RegistrationError, List<&2, P>>: 461 match result: 462 case None{}: 463 Fail{InvalidGroups{ids}} 464 case Some{policies}: 465 Done{policies} 466 467 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>>>: 468 match routes: 469 case Nil{}: 470 Done{Nil{}} 471 case Route{method, pattern, action, +ids, policy} <> rest: 472 do Result<&2, &2, RegistrationError, List<&2, Router.Entry>>>: 473 policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, groups, Nil{}), ids) 474 entries : List<&2, Router.Entry>> <- routes.prepare(~A, ~P, rest, groups, root) 475 return Router.Entry{method, pattern, Endpoint{action, root <> List.append(&2, P, policies, [policy])}, ids} <> entries 476 477 def groups.prepare(~P: Data, groups: List<&2, Group

>, +all: List<&2, Group

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

>>: 478 match groups: 479 case Nil{}: 480 Done{Nil{}} 481 case Group{id, ancestors, policy} <> rest: 482 +ids = List.append(&2, U32, ancestors, [id]) 483 do Result<&2, &2, RegistrationError, List<&2, GroupPlan

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

> <- groups.prepare(~P, rest, all) 486 return GroupPlan{ids, policies} <> plans 487 488 def groups.select(~P: Data, same: Bool, policies: List<&2, P>, later: Unit -> List<&2, P>) -> List<&2, P>: 489 match same: 490 case True{}: policies 491 case False{}: later(Unit{}) 492 493 def groups.prepared(~P: Data, plans: List<&2, GroupPlan

>, +ids: List<&2, U32>) -> List<&2, P>: 494 match plans: 495 case Nil{}: 496 Nil{} 497 case GroupPlan{here, policies} <> rest: 498 groups.select(~P, groups.equal(here, ids), policies, u => groups.prepared(~P, rest, ids)) 499 500 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>: 501 match result: 502 case Fail{error}: 503 Fail{RouteError{error}} 504 case Done{table}: 505 Done{Application{table, context, root, groups}} 506 507 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>: 508 match valid: 509 case False{}: 510 Fail{InvalidGroups{Nil{}}} 511 case True{}: 512 do Result<&2, &2, RegistrationError, Application>: 513 entries : List<&2, Router.Entry>> <- routes.prepare(~A, ~P, routes, groups, root) 514 plans : List<&2, GroupPlan

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