~/bend-docscommunity

http.bend checks

raw source on the hub · import 0x5e4e2a9db839a0214ace6923b04b685b/http.bend as Http

ezhttp/http: HTTP/1.1 message text for client and server — request and response formatting/parsing — with no sockets. Semantics follow RFC 9110; message syntax and framing follow RFC 9112. TLS is outside this module.

2 imports
import Base
import ./body.bend as Body

Types

type Cut source · line 8 · raw

Data

a string divided at the first occurrence of a pattern, or not divided at all

type Reply source · line 17 · raw

Data

what a response turned out to be: status, headers, framed body — or why not

type Next source · line 218 · raw

Data

the next scan step, or why this chunk header is not usable

type Request source · line 655 · raw

Data

a request that came apart, or the reason it would not (RFC 9112 §3)

Definitions

def first source · line 22 · raw

@ps:List<&2, String> -> String

the first of a list of pieces, "" when there are none

def second source · line 30 · raw

@ps:List<&2, String> -> String

the second of a list of pieces, "" when there is no second

def divide.push source · line 38 · raw

@h:Char -> @r:Cut -> Cut

one char of the divide, put back on the front of what the rest found

def divide.step source · line 46 · raw

@here:Bool -> @h:Char -> @t:String -> @n:Nat -> @rest:(@_:Unit -> Cut) -> Cut

one step of the walk; rest is a thunk so Bool.pick does not force recursion

def divide.go source · line 55 · raw

@s:String -> @+pat:String -> @+n:Nat -> Cut

the walk: stop at the first hit

def divide source · line 64 · raw

@s:String -> @+pat:String -> Cut

a string in two halves around the first occurrence of a pattern

def hex.of source · line 68 · raw

@+u:U32 -> U32

the value of one hex digit's code point, or 16 when it is not one

def hex.val source · line 75 · raw

@c:Char -> U32

the value of one hex digit, or 16 when the char is not one

def hex.step source · line 79 · raw

@done:Bool -> @acc:Nat -> @rest:(@_:Unit -> Nat) -> Nat

one step of the hex read

def hex.go source · line 87 · raw

@s:String -> @+acc:Nat -> Nat

the hex digits at the front of a string, read until one is not a digit

def hexlen source · line 97 · raw

@s:String -> Nat

chunk-size from a chunk header (RFC 9112 §7.1); extensions after ; ignored

def hex.digit source · line 101 · raw

@c:Char -> Bool

whether a character is a hex digit

def hex.rest source · line 105 · raw

@token:String -> @good:Bool -> Bool

the rest of a hex token once the first digit is known

def hex.well source · line 115 · raw

@token:String -> Bool

a chunk-size token is one or more hex digits (RFC 9112 §7.1)

def chunks.token source · line 123 · raw

@before:String -> String

the chunk-size token, extensions after ; removed

def utf8.width source · line 128 · raw

@+u:U32 -> Nat

bytes one code point takes in UTF-8. Content-Length counts octets (RFC 9110 §8.6); a Bend String counts code points.

def utf8.len source · line 134 · raw

@s:String -> Nat

how many bytes of UTF-8 a string is

def utf8.take.step source · line 142 · raw

@short:Bool -> @h:Char -> @rest:(@_:Unit -> String) -> String

one step of the take

def utf8.take source · line 150 · raw

@s:String -> @+n:Nat -> String

the front of a string that is this many bytes of UTF-8

def utf8.drop.step source · line 159 · raw

@short:Bool -> @h:Char -> @t:String -> @rest:(@_:Unit -> String) -> String

one step of the drop

def utf8.drop source · line 168 · raw

@s:String -> @+n:Nat -> String

the rest of a string after this many bytes of UTF-8

def digits.val.of source · line 177 · raw

@+u:U32 -> U32

a decimal digit's value, or 10 when the character is not a digit

def digits.val source · line 181 · raw

@c:Char -> U32

a decimal digit's value, or 10 when the character is not a digit

def digits.step source · line 185 · raw

@ok:Bool -> @next:(@_:Unit -> Maybe<&2, Nat>) -> Maybe<&2, Nat>

accept one digit and keep reading, or stop when it is not a digit

def digits.go source · line 193 · raw

@s:String -> @+acc:Nat -> @left:Nat -> Maybe<&2, Nat>

read a decimal token without building Base's Nat.read ceiling

def digits.read source · line 205 · raw

@s:String -> Maybe<&2, Nat>

a short decimal (Content-Length, max-age). Empty is none. At most 9 digits.

def chunks.ready source · line 213 · raw

@+after:String -> @+n:Nat -> Bool

a non-zero chunk has its data and the following CRLF

def chunks.next.of source · line 224 · raw

@zero:Bool -> @ready:Bool -> @+after:String -> @+n:Nat -> Next

one chunk header: reject non-hex sizes, stop on the 0 chunk, else continue

def chunks.next.hex source · line 234 · raw

@+before:String -> @+after:String -> @ok:Bool -> Next

hex check, then the length

def chunks.next source · line 243 · raw

@+before:String -> @+after:String -> Next

classify one chunk header (RFC 9112 §7.1)

def chunks.scan.step source · line 247 · raw

@step:Next -> @rest:(@_:Cut -> Maybe<&2, String>) -> Maybe<&2, String>

apply one scan step

def chunks.scan source · line 258 · raw

@fuel:Nat -> @c:Cut -> Maybe<&2, String>

None when every chunk size is hex and the 0 chunk is reached

def chunks.more source · line 268 · raw

@+n:Nat -> @+after:String -> @rest:(@_:Unit -> String) -> @zero:Bool -> String

a zero chunk ends the body; anything after it is a trailer (RFC 9112 §7.1)

def chunks source · line 278 · raw

@fuel:Nat -> @c:Cut -> String

a chunked body put back together (RFC 9112 §7.1)

def dechunk source · line 291 · raw

@+s:String -> String

a Transfer-Encoding: chunked body as the octets it stands for

def head.hit source · line 296 · raw

@h:String -> @+name:String -> Bool

whether a head line names this header (field names are case-insensitive: RFC 9110 §5.1)

def head.pick source · line 300 · raw

@hit:Bool -> @h:String -> @name:String -> @rest:String -> String

this line's value when it is the header asked for

def head.go source · line 308 · raw

@ls:List<&2, String> -> @+name:String -> String

the value a head gave a header, "" when it gave none

def head.value source · line 316 · raw

@h:String -> @name:String -> String

this line's trimmed field value

def head.values.cons source · line 320 · raw

@hit:Bool -> @value:String -> @rest:(@_:Unit -> List<&2, String>) -> List<&2, String>

cons a value when the line is the header asked for

def head.values source · line 329 · raw

@ls:List<&2, String> -> @+name:String -> List<&2, String>

every value a head gave a header, in order (RFC 9112 §6.3 lists Content-Length)

def field.folded source · line 338 · raw

@+value:String -> Bool

a field value still holding a line break is obs-fold, which we refuse

def field.keep source · line 342 · raw

@name:String -> @+value:String -> @folded:Bool -> Maybe<&2, Header>

a trimmed field value, or none when it still contains obs-fold

def head.from_kept source · line 350 · raw

@before:String -> @+after:String -> Maybe<&2, Header>

name and trimmed value from a cut at the first colon

def head.from_cut source · line 355 · raw

@c:Cut -> Maybe<&2, Header>

one header line as a Header, or none when it has no colon

def head.line source · line 363 · raw

@h:String -> Maybe<&2, Header>

one header line as a Header, or none when it has no colon

def head.cons source · line 367 · raw

@m:Maybe<&2, Header> -> @rest:List<&2, Header> -> List<&2, Header>

prepend a parsed header when present

def head.list source · line 375 · raw

@ls:List<&2, String> -> List<&2, Header>

header lines after the status line as a list of fields

def header.line source · line 383 · raw

@h:Header -> String

one request header line

def headers.find.pick source · line 389 · raw

@value:String -> @hit:Bool -> @rest:(@_:Unit -> String) -> String

the value when this field is the one asked for

def headers.find.go source · line 397 · raw

@hs:List<&2, Header> -> @+name:String -> String

case-insensitive field lookup (RFC 9110 §5.1)

def headers.find source · line 406 · raw

@hs:List<&2, Header> -> @+name:String -> String

the value of a field, "" when it is absent

def headers.lines source · line 410 · raw

@hs:List<&2, Header> -> String

extra headers rendered in order

def clen.line source · line 418 · raw

@+n:Nat -> @empty:Bool -> String

Content-Length line when the body is not empty (RFC 9110 §8.6)

def request source · line 428 · raw

@method:String -> @host:String -> @path:String -> @headers:List<&2, Header> -> @+body:String -> String

a request-line and headers (RFC 9112 §3, §5). Absolute-path request-target (RFC 9112 §3.2.1). Connection: close so the connection end frames the body when no Content-Length / chunked framing applies (RFC 9112 §9.6 / §6.3).

def get_req source · line 442 · raw

@host:String -> @path:String -> @headers:List<&2, Header> -> String

GET with optional headers

def head_req source · line 446 · raw

@host:String -> @path:String -> @headers:List<&2, Header> -> String

HEAD with optional headers (RFC 9110 §9.3.2): same as GET, no body

def post_req source · line 450 · raw

@host:String -> @path:String -> @headers:List<&2, Header> -> @body:0x5e4e2a9db839a0214ace6923b04b685b/body.Body -> String

POST with body and optional headers

def put_req source · line 455 · raw

@host:String -> @path:String -> @headers:List<&2, Header> -> @body:0x5e4e2a9db839a0214ace6923b04b685b/body.Body -> String

PUT with body and optional headers (RFC 9110 §9.3.4)

def delete_req source · line 460 · raw

@host:String -> @path:String -> @headers:List<&2, Header> -> @body:0x5e4e2a9db839a0214ace6923b04b685b/body.Body -> String

DELETE with body and optional headers (RFC 9110 §9.3.5)

def options_req source · line 465 · raw

@host:String -> @path:String -> @headers:List<&2, Header> -> String

OPTIONS with optional headers (RFC 9110 §9.3.7). No request body.

def method.safe source · line 469 · raw

@+method:String -> Bool

GET, HEAD, and OPTIONS are safe (RFC 9110 §9.2.1). Tokens are case-sensitive.

def method.idempotent source · line 474 · raw

@+method:String -> Bool

safe methods, plus PUT and DELETE, are idempotent (RFC 9110 §9.2.2)

def token.ok.step source · line 479 · raw

@space:Bool -> @rest:(@_:Unit -> Bool) -> Bool

the rest of a token once this character is known

def token.ok source · line 487 · raw

@s:String -> Bool

a bearer token has no whitespace (RFC 6750 §2.1)

def target.origin source · line 495 · raw

@path:String -> Bool

origin-form request-target: absolute-path, optional query (RFC 9112 §3.2.1)

def target.asterisk source · line 499 · raw

@method:String -> @star:Bool -> Bool

asterisk-form is only * and only for OPTIONS (RFC 9112 §3.2.4)

def target.form source · line 507 · raw

@method:String -> @path:String -> @absolute:Bool -> Bool

origin-form, or OPTIONS asterisk-form

def parse.short source · line 515 · raw

@status:U32 -> @headers:List<&2, Header> -> @n:Nat -> @body:String -> @short:Bool -> Reply

a body against the length its head promised

def parse.clen source · line 524 · raw

@status:U32 -> @headers:List<&2, Header> -> @+n:Nat -> @+body:String -> Reply

a body framed by Content-Length (RFC 9110 §8.6 / RFC 9112 §6.3)

def parse.len source · line 529 · raw

@status:U32 -> @headers:List<&2, Header> -> @m:Maybe<&2, Nat> -> @body:String -> Reply

Content-Length when present; else the rest of the connection is the body

def clen.agree.step source · line 538 · raw

@same:Bool -> @rest:(@_:Unit -> Bool) -> Bool

every Content-Length text equals the first

def clen.agree.go source · line 546 · raw

@rest:List<&2, String> -> @+h:String -> Bool

the tail of a Content-Length list against the first value

def clen.agree source · line 554 · raw

@vs:List<&2, String> -> Bool

duplicate Content-Length fields must be the same digits (RFC 9112 §6.3)

def clen.any source · line 562 · raw

@vs:List<&2, String> -> Bool

whether any Content-Length field was present

def parse.chunked source · line 570 · raw

@status:U32 -> @headers:List<&2, Header> -> @body:String -> @bad:Maybe<&2, String> -> Reply

a chunked body, or why the chunk framing was rejected

def parse.chunks source · line 579 · raw

@status:U32 -> @headers:List<&2, Header> -> @+body:String -> Reply

scan chunk sizes, then decode when they are hex and closed by 0

def parse.te.clash source · line 584 · raw

@status:U32 -> @headers:List<&2, Header> -> @body:String -> @present:Bool -> Reply

Transfer-Encoding together with Content-Length is a bad message

def parse.te source · line 593 · raw

@status:U32 -> @headers:List<&2, Header> -> @+lens:List<&2, String> -> @+body:String -> @chunked:Bool -> Reply

chunked framing, or Content-Length, or the rest of the connection

def parse.agree source · line 602 · raw

@status:U32 -> @headers:List<&2, Header> -> @te:String -> @+lens:List<&2, String> -> @body:String -> @ok:Bool -> Reply

disagreeing Content-Length values are torn before framing

def parse.frame source · line 612 · raw

@status:U32 -> @headers:List<&2, Header> -> @te:String -> @+lens:List<&2, String> -> @body:String -> Reply

the headers that decide the framing (RFC 9112 §6.3)

def parse.status source · line 617 · raw

@m:Maybe<&2, U32> -> @+ls:List<&2, String> -> @body:String -> Reply

a head whose status line has been read (RFC 9112 §4)

def parse.head source · line 628 · raw

@+ls:List<&2, String> -> @body:String -> Reply

the head's lines, with the status line read off the first

def parse.cut source · line 633 · raw

@c:Cut -> Reply

a response divided into its head and everything after the blank line (RFC 9112 §2.1: header section ends at empty line)

def parse source · line 641 · raw

@raw:String -> Reply

the status, headers, and body of a raw response

def show source · line 645 · raw

@r:Reply -> String

a parsed response on one line, for a test to read

def reason source · line 661 · raw

@+code:U32 -> String

reason-phrase for a few common status codes (RFC 9112 §4); others get ""

def respond source · line 673 · raw

@+status:U32 -> @headers:List<&2, Header> -> @+body:String -> String

status-line + headers + body (RFC 9112 §4, §5). Connection: close ends the exchange after one response.

def status.nocontent source · line 683 · raw

@+code:U32 -> Bool

204 and 304 are terminated by the header section (RFC 9110 §15.3.5, §15.4.5)

def respond.of.body source · line 687 · raw

@+status:U32 -> @headers:List<&2, Header> -> @body:String -> @drop:Bool -> String

drop a 204/304 body; any other status keeps it

def respond.of source · line 696 · raw

@r:Reply -> String

format a structured Reply as a response message

def reply.for.pick source · line 704 · raw

@reply:Reply -> @head:Bool -> Reply

HEAD carries no message body; any other method keeps the body (RFC 9110 §9.3.2)

def reply.for source · line 714 · raw

@method:String -> @reply:Reply -> Reply

drop a HEAD response body before it is written

def method.of source · line 718 · raw

@req:Request -> String

the method token, or "" when the request did not parse

def ask.short source · line 726 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @n:Nat -> @body:String -> @short:Bool -> Request

request body against Content-Length

def ask.clen source · line 735 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @+n:Nat -> @+body:String -> Request

a request body framed by Content-Length (RFC 9110 §8.6 / RFC 9112 §6.3)

def ask.len source · line 740 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @m:Maybe<&2, Nat> -> @body:String -> Request

Content-Length when present; else the rest of the connection is the body

def ask.chunked source · line 749 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @body:String -> @bad:Maybe<&2, String> -> Request

a chunked request body, or why the chunk framing was rejected

def ask.chunks source · line 758 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @+body:String -> Request

scan chunk sizes, then decode when they are hex and closed by 0

def ask.te.clash source · line 764 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @body:String -> @present:Bool -> Request

Transfer-Encoding together with Content-Length is a bad request

def ask.te source · line 773 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @+lens:List<&2, String> -> @+body:String -> @chunked:Bool -> Request

chunked framing, or Content-Length, or the rest of the connection

def ask.agree source · line 782 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @te:String -> @+lens:List<&2, String> -> @body:String -> @ok:Bool -> Request

disagreeing Content-Length values are bad before framing

def ask.frame source · line 792 · raw

@method:String -> @target:String -> @headers:List<&2, Header> -> @te:String -> @+lens:List<&2, String> -> @body:String -> Request

the headers that decide request framing (RFC 9112 §6.3)

def ask.origin source · line 797 · raw

@method:String -> @target:String -> @+ls:List<&2, String> -> @body:String -> @ok:Bool -> Request

origin-form, or a bad request when the target is not an absolute-path

def ask.line source · line 808 · raw

@+method:String -> @+target:String -> @+ls:List<&2, String> -> @body:String -> Request

request-line: method SP request-target SP HTTP-version (RFC 9112 §3)

def ask.parts source · line 814 · raw

@ps:List<&2, String> -> @+ls:List<&2, String> -> @body:String -> Request

three fields of the request-line, or torn

def ask.head source · line 827 · raw

@+ls:List<&2, String> -> @body:String -> Request

the head's lines, with the request-line read off the first

def ask.cut source · line 835 · raw

@c:Cut -> Request

a request divided into its head and everything after the blank line

def ask source · line 843 · raw

@raw:String -> Request

the method, target, headers, and body of a raw request

def ask.show source · line 847 · raw

@r:Request -> String

a parsed request on one line, for a test to read