"stream.spl" import "net.spl" import "http" net:register construct net:http namespace { Request Response help ; register { | with name this ; name "net:http" register-field } urlencode { str | with str this ; [ { | with x ; x x "0" :_char lt not x "9" :_char gt not and x "a" :_char lt not x "z" :_char gt not and or x "A" :_char lt not x "Z" :_char gt not and or not if { pop "%" :_char x _mega:_str_radix<16> _array =x x:len 1 eq if { "0" :_char } x:to-stack } } str _array :foreach ] _str } urldecode { str | with str this ; str _array =str [ def i 0 =i while { i str:len lt } { i str:get dup "%" :_char eq if { pop i ++ =i i i 2 + str:sub _str :_mega_radix<16> _int i ++ =i } i ++ =i } ] _str } } construct net:http:Request { host port method path headers body ; construct { this | with host port method path this ; host this:=host port this:=port method this:=method path this:=path List:new this:=headers "" this:=body this } add-header { this | with header this ; header this:headers:push this } set-body { this | with body this ; body this:=body this } send { net:http:Response | with this ; def stream this:host this:port StreamTypes:tcp:create =stream def response net:http:Response:new =response this:method:to-bytes stream:write-exact; " " :to-bytes stream:write-exact; this:path:to-bytes stream:write-exact; " HTTP/1.0\r\n" :to-bytes stream:write-exact; "Host: " :to-bytes stream:write-exact; this:host:to-bytes stream:write-exact; "\r\nConnection: Close\r\nUser-Agent: http.spl v0.1 2023-03 (spl@mail.tudbut.de)\r\n" :to-bytes stream:write-exact; { | with header ; header:to-bytes stream:write-exact; "\r\n" stream:write-exact; } this:headers:foreach "Content-Length: " :to-bytes stream:write-exact; def body this:body:to-bytes =body body:len _str:to-bytes stream:write-exact; "\r\n\r\n" :to-bytes stream:write-exact; body stream:write-exact; stream:flush; def response 1024 stream:read-to-end =response response net:http:Response:new:read-from-bytes stream:close; } } construct net:http:help namespace { ; assert-str { | with expected iter _ ; [ { | pop iter:next } (expected _array):len:foreach ] _str expected _str eq not if { "Expected " expected concat throw } } until-str { str | with expected iter _ ; def match 0 =match def bytes expected:to-bytes =bytes [ while { match bytes:len eq not } { iter:next dup (match bytes:get) eq dup if { match ++ =match } not if { 0 =match } } { | pop pop } match:foreach ] _str } } construct net:http:Response { version state-num state-msg headers body ; construct { this | with this ; MicroMap:new this:=headers "" this:=body this } read-from-bytes { this | with bytes this ; use net:http:help bytes:iter =bytes "HTTP/" bytes help:assert-str " " bytes help:until-str this:=version " " bytes help:until-str _mega this:=state-num "\r\n" bytes help:until-str this:=state-msg while { "\r\n" bytes help:until-str dup "" eq not } { def iter ": " swap:split:iter =iter ( iter:next ": " iter:join ) this:headers:set; } pop 0 ("Content-Length" this:headers:get _mega) bytes:collect:sub this:=body this } content-type { str | with this ; "Content-Type" this:headers:get } }