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

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

>) -> Maybe<&2, Group

>: 369 match same: 370 case True{}: 371 Some{group} 372 case False{}: 373 later(Unit{}) 374 375 def group.find(~P: Data, groups: List<&2, Group

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

>: 376 match groups: 377 case Nil{}: 378 None{} 379 case Group{+here, ancestors, policy} <> rest: 380 group.pick(~P, U32.is_eq(here, id), Group{here, ancestors, policy}, u => group.find(~P, rest, id)) 381 382 def group.checked(~P: Data, same: Bool, policy: P) -> Maybe<&2, P>: 383 match same: 384 case False{}: 385 None{} 386 case True{}: 387 Some{policy} 388 389 def group.policy(~P: Data, found: Maybe<&2, Group

>, ancestors: List<&2, U32>) -> Maybe<&2, P>: 390 match found: 391 case None{}: 392 None{} 393 case Some{Group{id, expected, policy}}: 394 group.checked(~P, groups.equal(expected, ancestors), policy) 395 396 def groups.policies(~P: Data, ids: List<&2, U32>, +declared: List<&2, Group

>, +ancestors: List<&2, U32>) -> Maybe<&2, List<&2, P>>: 397 match ids: 398 case Nil{}: 399 Some{Nil{}} 400 case +id <> rest: 401 do Maybe<&2, List<&2, P>>: 402 policy : P <- group.policy(~P, group.find(~P, declared, id), ancestors) 403 policies : List<&2, P> <- groups.policies(~P, rest, declared, List.append(&2, U32, ancestors, [id])) 404 return policy <> policies 405 406 def groups.unique(~P: Data, found: Maybe<&2, Group

>) -> Bool: 407 match found: 408 case None{}: 409 True{} 410 case Some{group}: 411 False{} 412 413 def groups.valid(~P: Data, groups: List<&2, Group

>, +all: List<&2, Group

>) -> Bool: 414 match groups: 415 case Nil{}: 416 True{} 417 case Group{+id, +ancestors, policy} <> +rest: 418 chain = groups.policies(~P, List.append(&2, U32, ancestors, [id]), all, Nil{}) 419 groups.unique(~P, group.find(~P, rest, id)) && Maybe.is_some(&2, List<&2, P>, chain) && groups.valid(~P, rest, all) 420 421 def routes.policies(~P: Data, result: Maybe<&2, List<&2, P>>, ids: List<&2, U32>) -> Result<&2, &2, RegistrationError, List<&2, P>>: 422 match result: 423 case None{}: 424 Fail{InvalidGroups{ids}} 425 case Some{policies}: 426 Done{policies} 427 428 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>>>: 429 match routes: 430 case Nil{}: 431 Done{Nil{}} 432 case Route{method, pattern, action, +ids, policy} <> rest: 433 do Result<&2, &2, RegistrationError, List<&2, Router.Entry>>>: 434 policies : List<&2, P> <- routes.policies(~P, groups.policies(~P, ids, groups, Nil{}), ids) 435 entries : List<&2, Router.Entry>> <- routes.prepare(~A, ~P, rest, groups, root) 436 return Router.Entry{method, pattern, Endpoint{action, root <> List.append(&2, P, policies, [policy])}, ids} <> entries 437 438 def application.table(~A: Data, ~C: Data, ~P: Data, result: Result<&2, &2, Router.Error, Router.Table>>, context: C, root: P) -> Result<&2, &2, RegistrationError, Application>: 439 match result: 440 case Fail{error}: 441 Fail{RouteError{error}} 442 case Done{table}: 443 Done{Application{table, context, root}} 444 445 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>: 446 match valid: 447 case False{}: 448 Fail{InvalidGroups{Nil{}}} 449 case True{}: 450 do Result<&2, &2, RegistrationError, Application>: 451 entries : List<&2, Router.Entry>> <- routes.prepare(~A, ~P, routes, groups, root) 452 application.table(~A, ~C, ~P, Router.prepare(~Endpoint, entries), context, root) 453 454 # Construction is pure and fallible; no listener or owner is started here. 455 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>: 456 application.valid(~A, ~C, ~P, groups.valid(~P, groups, groups), routes, groups, context, root) 457 458 def describe(~A: Data, ~C: Data, ~P: Data, app: Application) -> List<&2, Router.Description>: 459 Application{table, context, root} = app 460 Router.describe(~Endpoint, table) 461 462 def request.authority(authority: Maybe<&2, String>, req: Http.Req) -> Http.Req: 463 match authority: 464 case None{}: 465 req 466 case Some{host}: 467 Http.Req{method, path, headers, body} = req 468 Http.Req{method, path, Http.set(headers, "host", host), body} 469 470 # A closed template can run repeatedly; an affine callback registry cannot. 471 def plain(~handler: Http.Req -> IO(Http.Res), req: Http.Req) -> IO(Result<&2, &1, ResponseError, Http.Res>): 472 do IO>: 473 response : Http.Res <- handler(req) 474 return validate(response) 475 476 def dispatch.choice(~A: Data, ~C: Data, ~P: Data, 477 ~run: A -> C -> Match

-> Http.Req -> IO(Http.Res), 478 choice: Router.Choice>, context: C, target: Target.Target, req: Http.Req 479 ) -> IO(Http.Res): 480 match choice: 481 case Router.NotFound{}: 482 IO.pure(Http.Res, Http.Res{404, Http.empty(), Http.from_string("")}) 483 case Router.MethodMissing{groups, allow}: 484 IO.pure(Http.Res, Http.Res{405, Http.set(Http.empty(), "allow", allow), Http.from_string("")}) 485 case Router.Options{groups, allow}: 486 IO.pure(Http.Res, Http.Res{204, Http.set(Http.empty(), "allow", allow), Http.from_string("")}) 487 case Router.Found{Endpoint{action, policies}, params, groups, method, pattern}: 488 run(action, context, Match{target, params, groups, method, pattern, policies}, req) 489 490 def dispatch.resolved(~A: Data, ~C: Data, ~P: Data, 491 ~run: A -> C -> Match

-> Http.Req -> IO(Http.Res), 492 result: Result<&2, &2, Router.Error, Router.Resolved>>, context: C, req: Http.Req 493 ) -> IO(Http.Res): 494 match result: 495 case Fail{error}: 496 IO.pure(Http.Res, Http.Res{400, Http.empty(), Http.from_string("")}) 497 case Done{Router.Resolved{+target, choice}}: 498 Target.Target{segments, query, authority, star} = target 499 dispatch.choice(~A, ~C, ~P, ~run, choice, context, target, request.authority(authority, req)) 500 501 # Dispatch moves the original packed body once and returns application bytes. 502 # In particular it never performs transport's HEAD/204/304 body suppression. 503 def dispatch(~A: Data, ~C: Data, ~P: Data, 504 ~run: A -> C -> Match

-> Http.Req -> IO(Http.Res), 505 app: Application, req: Http.Req 506 ) -> IO(Result<&2, &1, ResponseError, Http.Res>): 507 Application{table, context, root} = app 508 Http.Req{+method, +path, headers, body} = req 509 do IO>: 510 response : Http.Res <- dispatch.resolved(~A, ~C, ~P, ~run, Router.resolve(~Endpoint, table, method, path), context, Http.Req{method, path, headers, body}) 511 return validate(response)