aboutsummaryrefslogtreecommitdiff
path: root/src/protocols/http.nim
blob: 2de11df73b4e11f60aab9c9d065573afa9b6ae6d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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)