tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

870df776ac7142fac5aad7f0169dfd08b2934f2bfa61ad3ae1c9b949f8f75088.tri (6748B)


      1 !import base as B
      2 !import native_math as N
      3 !import io_v2 as IO
      4 !import io_v2_http as Transport
      5 !import io_v2_artifacts as Stream
      6 !import http as HTTP
      7 !import handlers as H
      8 !import handler_artifacts as Artifacts
      9 !import handler_log as Log
     10 !import arboricx_handlers as Registry
     11 !export pump
     12 !export connection
     13 !export serve
     14 !export serveCount
     15 
     16 pump _ 0 _ = H.pure (B.ok t t)
     17 pump client remaining initial = let
     18   limit = N.minimum remaining 4096
     19   consume (unused, (reversed, rest)) = case reversed of
     20     [] = H.bind (H.liftIO (IO.readSome client limit)) (reply : case reply of
     21       ((t t), (("data", chunk), t)) = pump client remaining chunk
     22       _ = H.pure (B.err "Unexpected end of request body" t))
     23     _ = H.bind (Stream.write (B.reverse reversed)) (reply : case reply of
     24       ((t t), (t, t)) = pump client (N.subtract remaining (N.subtract limit unused)) rest
     25       _ = H.pure reply)
     26   consume _ = H.fail "invalid-response" "artifact-stream" "buffer" "Invalid body state"
     27   in consume (HTTP.takeBodyBytes initial limit [])
     28 
     29 logged sink status response = H.bind
     30   (Log.withSink sink (Log.withContext [["method" "POST"] ["target" "/_arboricx/bundle"]] (Log.info status)))
     31   (reply : case reply of
     32     ((t t), (t, t)) = H.pure response
     33     _ = H.pure (HTTP.errorResponse 500 "Internal Server Error\n"))
     34 upload root sink client count initial = H.bind
     35   (Artifacts.publishStreamV2 root count (H.label "streaming-upload" (pump client count initial)))
     36   (reply : case reply of
     37     ((t t), (digest, t)) = logged sink 201 (HTTP.createdResponse digest)
     38     (t, (_, t)) = logged sink 400 (HTTP.badRequestResponse "Upload failed\n")
     39     _ = H.fail "invalid-response" "artifact-stream" "publish" "Invalid upload Result")
     40 -- Supervised request interpretation retains the socket in its connection owner;
     41 -- borrowing does not extend lifetime. Join before responding, so cleanup failure
     42 -- cannot falsely acknowledge success. Cancellation is never rendered as HTTP 500.
     43 response root sink client count initial = IO.scope (_ : IO.require
     44   (IO.spawn 1 1 [] (_ : H.runIOV2 (upload root sink client count initial))) (spawned : case spawned of
     45     [_ ticket] = IO.require (IO.join ticket) (outcome : case outcome of
     46       ["completed" value] = IO.pure value
     47       ["failed" _ _] = IO.pure (HTTP.errorResponse 500 "Internal Server Error\n")
     48       ["cancelled" reason _] = IO.cancelled reason
     49       _ = IO.fail "Invalid upload task outcome")
     50     _ = IO.fail "Invalid upload spawn reply"))
     51 boundedResponse root limit sink client count initial = case N.lte count limit of
     52   (t t) = IO.bind (response root sink client count initial) (Transport.respond client "POST")
     53   _ = Transport.respond client "POST" (HTTP.errorResponse 413 "Request body too large\n")
     54 framed root limit sink client fields initial = case HTTP.contentLength fields of
     55   ((t t), (t, _)) = boundedResponse root limit sink client 0 initial
     56   ((t t), ((t count), _)) = boundedResponse root limit sink client count initial
     57   (t, (status, message)) = Transport.respond client "POST" (HTTP.errorResponse status message)
     58   _ = Transport.respond client "POST" (HTTP.badRequestResponse "Bad Request\n")
     59 headers root limit sink client version raw = case HTTP.parseHeaders raw of
     60   ((t t), (fields, initial)) = case HTTP.validateRequest "POST" "/_arboricx/bundle" version fields of
     61     ((t t), _) = framed root limit sink client fields initial
     62     (t, (status, message)) = Transport.respond client "POST" (HTTP.errorResponse status message)
     63     _ = Transport.respond client "POST" (HTTP.badRequestResponse "Bad Request\n")
     64   _ = Transport.respond client "" (HTTP.badRequestResponse "Bad Request\n")
     65 line root limit sink client ((t t), (("POST", ("/_arboricx/bundle", version)), raw)) =
     66   headers root limit sink client version raw
     67 line root limit sink client parsed = Transport.handle (Registry.routerV2 root limit sink) client parsed
     68 connection root limit sink client = IO.bind (Transport.recvHeaders client) (reply : case reply of
     69   ((t t), (raw, _)) = line root limit sink client (HTTP.parseRequestLine raw)
     70   (t, (431, _)) = Transport.respond client "" (HTTP.errorResponse 431 "Request Header Fields Too Large\n")
     71   _ = Transport.respond client "" (HTTP.badRequestResponse "Bad Request\n"))
     72 
     73 worker root limit sink [client] = connection root limit sink client
     74 worker _ _ _ _ = IO.fail "Invalid connection handoff"
     75 settle ticket = IO.require (IO.join ticket) (outcome : case outcome of
     76   ["completed" _] = IO.pure t
     77   ["failed" _ _] = IO.bind (Registry.writeLogV2 "registry connection failed\n") (_ : IO.pure t)
     78   ["cancelled" reason _] = IO.cancelled reason
     79   _ = IO.fail "Invalid connection task outcome")
     80 drain [] = IO.pure t
     81 drain [ticket | rest] = IO.bind (settle ticket) (_ : drain rest)
     82 drain _ = IO.fail "Invalid connection tickets"
     83 next (t count) = B.just (N.predecessor count)
     84 next _ = t
     85 -- FIFO bounded admission: consume the oldest ticket before accepting beyond the
     86 -- configured window. No discarded tickets, detached tasks or unbounded backlog in
     87 -- the language. A slow oldest connection can delay admission; no deadline promise.
     88 admit _ _ _ _ _ _ (t 0) tickets = drain tickets
     89 admit root limit sink owner listener window remaining tickets = case N.gte (B.length tickets) window of
     90   (t t) = case tickets of
     91     [ticket | rest] = IO.bind (settle ticket) (_ : admit root limit sink owner listener window remaining rest)
     92     _ = IO.fail "Invalid admission window"
     93   _ = IO.require (IO.acceptTCP listener owner) (client : IO.require
     94     (IO.spawn 1 1 [client] (worker root limit sink)) (spawned : case spawned of
     95       [_ ticket] = admit root limit sink owner listener window (next remaining) (B.append tickets [ticket])
     96       _ = IO.fail "Invalid connection spawn reply"))
     97 start root limit sink address window remaining ready = case B.checkContract B.nat? limit of
     98   ((t t), _) = startWindow root limit sink address window remaining ready
     99   _ = IO.fail "Invalid artifact byte limit"
    100 startWindow root limit sink address window remaining ready = case B.checkContract B.nat? window of
    101   ((t t), _) = case B.and? (N.gte window 1) (N.lte window 256) of
    102     (t t) = IO.scope (owner : do IO.require
    103       H.runIOV2 (Artifacts.prepareFilesystemV2 root)
    104       listener <- IO.listenTCP owner address 128
    105       IO.bind (ready listener) (_ : admit root limit sink owner listener window remaining []))
    106     _ = IO.fail "Connection window must be between 1 and 256"
    107   _ = IO.fail "Invalid connection window"
    108 serve root limit sink address window ready = start root limit sink address window t ready
    109 serveCount root limit sink address window count ready = case B.checkContract B.nat? count of
    110   ((t t), _) = start root limit sink address window (B.just count) ready
    111   _ = IO.fail "Invalid connection count"