import std/[strutils, net], ../formats/uri # https://datatracker.ietf.org/doc/html/rfc1945 type Http* = object of RootObj version*: string # always present headers*: seq[tuple[header: string, value: string]] type HttpRequest* = object of Http `method`*: string # only present in requests uri*: string # only present in requests type HttpResponse* = object of Http status*: int # status code, only in responses reason*: string # status elaboration, only in responses body*: string # html document, usually # This parses a HTTP response that has been split into headers and a body. func parseResponse*(http: seq[string], body: string): HttpResponse = # let http: seq[string] = http.split("\r\n") let split = http[0].split(' ', maxsplit=2) if split.len == 3: result.version = split[0] result.status = split[1].parseInt result.reason = split[2] else: raise newException(RangeDefect, "First line of response is invalid: " & http[0]) # Note: the spec specifies that \r\n\r\n marks the end of a request for header in http[1 ..< http.len]: let split = header.split(':', maxsplit=1) if split.len == 2: result.headers.add((split[0].toLower, split[1].strip())) result.body = body proc httpRequest*(url: Url): HttpResponse = let exampleRequest = "GET " & url.path & " HTTP/1.0\r\nHost: example.com\r\n\r\n" let socket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP) var port = 80 if url.scheme == "https": port = 443 let ctx: SSLContext = newContext() ctx.wrapSocket(socket) if url.port != 0: port = url.port socket.connect(url.host, Port(port)) socket.send(exampleRequest) var response: seq[string] buffer: string while true: socket.readLine(buffer) if buffer == "\r\n": break response.add(buffer) # assert "transfer-encoding" notin parsed.headers # assert "content-encoding" notin parsed.headers var body: string while socket.recv(buffer, 1024) > 0: # why? body &= buffer result = parseResponse(response, body)