"stream.spl" import "net.spl" import "http" net:register construct net:http namespace { Request Response help } 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 } }