httpcore.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2016 Dominik Picheta
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Contains functionality shared between the ``httpclient`` and
  10. ## ``asynchttpserver`` modules.
  11. import tables, strutils, parseutils
  12. type
  13. HttpHeaders* = ref object
  14. table*: TableRef[string, seq[string]]
  15. HttpHeaderValues* = distinct seq[string]
  16. # The range starts at '0' so that we don't have to explicitly initialise
  17. # it. See: http://irclogs.nim-lang.org/19-09-2016.html#19:48:27 for context.
  18. HttpCode* = distinct range[0 .. 599]
  19. HttpVersion* = enum
  20. HttpVer11,
  21. HttpVer10
  22. HttpMethod* = enum ## the requested HttpMethod
  23. HttpHead, ## Asks for the response identical to the one that would
  24. ## correspond to a GET request, but without the response
  25. ## body.
  26. HttpGet, ## Retrieves the specified resource.
  27. HttpPost, ## Submits data to be processed to the identified
  28. ## resource. The data is included in the body of the
  29. ## request.
  30. HttpPut, ## Uploads a representation of the specified resource.
  31. HttpDelete, ## Deletes the specified resource.
  32. HttpTrace, ## Echoes back the received request, so that a client
  33. ## can see what intermediate servers are adding or
  34. ## changing in the request.
  35. HttpOptions, ## Returns the HTTP methods that the server supports
  36. ## for specified address.
  37. HttpConnect, ## Converts the request connection to a transparent
  38. ## TCP/IP tunnel, usually used for proxies.
  39. HttpPatch ## Applies partial modifications to a resource.
  40. {.deprecated: [httpGet: HttpGet, httpHead: HttpHead, httpPost: HttpPost,
  41. httpPut: HttpPut, httpDelete: HttpDelete, httpTrace: HttpTrace,
  42. httpOptions: HttpOptions, httpConnect: HttpConnect].}
  43. const
  44. Http100* = HttpCode(100)
  45. Http101* = HttpCode(101)
  46. Http200* = HttpCode(200)
  47. Http201* = HttpCode(201)
  48. Http202* = HttpCode(202)
  49. Http203* = HttpCode(203)
  50. Http204* = HttpCode(204)
  51. Http205* = HttpCode(205)
  52. Http206* = HttpCode(206)
  53. Http300* = HttpCode(300)
  54. Http301* = HttpCode(301)
  55. Http302* = HttpCode(302)
  56. Http303* = HttpCode(303)
  57. Http304* = HttpCode(304)
  58. Http305* = HttpCode(305)
  59. Http307* = HttpCode(307)
  60. Http400* = HttpCode(400)
  61. Http401* = HttpCode(401)
  62. Http403* = HttpCode(403)
  63. Http404* = HttpCode(404)
  64. Http405* = HttpCode(405)
  65. Http406* = HttpCode(406)
  66. Http407* = HttpCode(407)
  67. Http408* = HttpCode(408)
  68. Http409* = HttpCode(409)
  69. Http410* = HttpCode(410)
  70. Http411* = HttpCode(411)
  71. Http412* = HttpCode(412)
  72. Http413* = HttpCode(413)
  73. Http414* = HttpCode(414)
  74. Http415* = HttpCode(415)
  75. Http416* = HttpCode(416)
  76. Http417* = HttpCode(417)
  77. Http418* = HttpCode(418)
  78. Http421* = HttpCode(421)
  79. Http422* = HttpCode(422)
  80. Http426* = HttpCode(426)
  81. Http428* = HttpCode(428)
  82. Http429* = HttpCode(429)
  83. Http431* = HttpCode(431)
  84. Http451* = HttpCode(451)
  85. Http500* = HttpCode(500)
  86. Http501* = HttpCode(501)
  87. Http502* = HttpCode(502)
  88. Http503* = HttpCode(503)
  89. Http504* = HttpCode(504)
  90. Http505* = HttpCode(505)
  91. const headerLimit* = 10_000
  92. proc newHttpHeaders*(): HttpHeaders =
  93. new result
  94. result.table = newTable[string, seq[string]]()
  95. proc newHttpHeaders*(keyValuePairs:
  96. openarray[tuple[key: string, val: string]]): HttpHeaders =
  97. var pairs: seq[tuple[key: string, val: seq[string]]] = @[]
  98. for pair in keyValuePairs:
  99. pairs.add((pair.key.toLowerAscii(), @[pair.val]))
  100. new result
  101. result.table = newTable[string, seq[string]](pairs)
  102. proc clear*(headers: HttpHeaders) =
  103. headers.table.clear()
  104. proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
  105. ## Returns the values associated with the given ``key``. If the returned
  106. ## values are passed to a procedure expecting a ``string``, the first
  107. ## value is automatically picked. If there are
  108. ## no values associated with the key, an exception is raised.
  109. ##
  110. ## To access multiple values of a key, use the overloaded ``[]`` below or
  111. ## to get all of them access the ``table`` field directly.
  112. return headers.table[key.toLowerAscii].HttpHeaderValues
  113. converter toString*(values: HttpHeaderValues): string =
  114. return seq[string](values)[0]
  115. proc `[]`*(headers: HttpHeaders, key: string, i: int): string =
  116. ## Returns the ``i``'th value associated with the given key. If there are
  117. ## no values associated with the key or the ``i``'th value doesn't exist,
  118. ## an exception is raised.
  119. return headers.table[key.toLowerAscii][i]
  120. proc `[]=`*(headers: HttpHeaders, key, value: string) =
  121. ## Sets the header entries associated with ``key`` to the specified value.
  122. ## Replaces any existing values.
  123. headers.table[key.toLowerAscii] = @[value]
  124. proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) =
  125. ## Sets the header entries associated with ``key`` to the specified list of
  126. ## values.
  127. ## Replaces any existing values.
  128. headers.table[key.toLowerAscii] = value
  129. proc add*(headers: HttpHeaders, key, value: string) =
  130. ## Adds the specified value to the specified key. Appends to any existing
  131. ## values associated with the key.
  132. if not headers.table.hasKey(key.toLowerAscii):
  133. headers.table[key.toLowerAscii] = @[value]
  134. else:
  135. headers.table[key.toLowerAscii].add(value)
  136. proc del*(headers: HttpHeaders, key: string) =
  137. ## Delete the header entries associated with ``key``
  138. headers.table.del(key.toLowerAscii)
  139. iterator pairs*(headers: HttpHeaders): tuple[key, value: string] =
  140. ## Yields each key, value pair.
  141. for k, v in headers.table:
  142. for value in v:
  143. yield (k, value)
  144. proc contains*(values: HttpHeaderValues, value: string): bool =
  145. ## Determines if ``value`` is one of the values inside ``values``. Comparison
  146. ## is performed without case sensitivity.
  147. for val in seq[string](values):
  148. if val.toLowerAscii == value.toLowerAscii: return true
  149. proc hasKey*(headers: HttpHeaders, key: string): bool =
  150. return headers.table.hasKey(key.toLowerAscii())
  151. proc getOrDefault*(headers: HttpHeaders, key: string,
  152. default = @[""].HttpHeaderValues): HttpHeaderValues =
  153. ## Returns the values associated with the given ``key``. If there are no
  154. ## values associated with the key, then ``default`` is returned.
  155. if headers.hasKey(key):
  156. return headers[key]
  157. else:
  158. return default
  159. proc len*(headers: HttpHeaders): int = return headers.table.len
  160. proc parseList(line: string, list: var seq[string], start: int): int =
  161. var i = 0
  162. var current = ""
  163. while line[start + i] notin {'\c', '\l', '\0'}:
  164. i += line.skipWhitespace(start + i)
  165. i += line.parseUntil(current, {'\c', '\l', ','}, start + i)
  166. list.add(current)
  167. if line[start + i] == ',':
  168. i.inc # Skip ,
  169. current.setLen(0)
  170. proc parseHeader*(line: string): tuple[key: string, value: seq[string]] =
  171. ## Parses a single raw header HTTP line into key value pairs.
  172. ##
  173. ## Used by ``asynchttpserver`` and ``httpclient`` internally and should not
  174. ## be used by you.
  175. result.value = @[]
  176. var i = 0
  177. i = line.parseUntil(result.key, ':')
  178. inc(i) # skip :
  179. if i < len(line):
  180. i += parseList(line, result.value, i)
  181. elif result.key.len > 0:
  182. result.value = @[""]
  183. else:
  184. result.value = @[]
  185. proc `==`*(protocol: tuple[orig: string, major, minor: int],
  186. ver: HttpVersion): bool =
  187. let major =
  188. case ver
  189. of HttpVer11, HttpVer10: 1
  190. let minor =
  191. case ver
  192. of HttpVer11: 1
  193. of HttpVer10: 0
  194. result = protocol.major == major and protocol.minor == minor
  195. proc contains*(methods: set[HttpMethod], x: string): bool =
  196. return parseEnum[HttpMethod](x) in methods
  197. proc `$`*(code: HttpCode): string =
  198. ## Converts the specified ``HttpCode`` into a HTTP status.
  199. ##
  200. ## For example:
  201. ##
  202. ## .. code-block:: nim
  203. ## doAssert($Http404 == "404 Not Found")
  204. case code.int
  205. of 100: "100 Continue"
  206. of 101: "101 Switching Protocols"
  207. of 200: "200 OK"
  208. of 201: "201 Created"
  209. of 202: "202 Accepted"
  210. of 203: "203 Non-Authoritative Information"
  211. of 204: "204 No Content"
  212. of 205: "205 Reset Content"
  213. of 206: "206 Partial Content"
  214. of 300: "300 Multiple Choices"
  215. of 301: "301 Moved Permanently"
  216. of 302: "302 Found"
  217. of 303: "303 See Other"
  218. of 304: "304 Not Modified"
  219. of 305: "305 Use Proxy"
  220. of 307: "307 Temporary Redirect"
  221. of 400: "400 Bad Request"
  222. of 401: "401 Unauthorized"
  223. of 403: "403 Forbidden"
  224. of 404: "404 Not Found"
  225. of 405: "405 Method Not Allowed"
  226. of 406: "406 Not Acceptable"
  227. of 407: "407 Proxy Authentication Required"
  228. of 408: "408 Request Timeout"
  229. of 409: "409 Conflict"
  230. of 410: "410 Gone"
  231. of 411: "411 Length Required"
  232. of 412: "412 Precondition Failed"
  233. of 413: "413 Request Entity Too Large"
  234. of 414: "414 Request-URI Too Long"
  235. of 415: "415 Unsupported Media Type"
  236. of 416: "416 Requested Range Not Satisfiable"
  237. of 417: "417 Expectation Failed"
  238. of 418: "418 I'm a teapot"
  239. of 421: "421 Misdirected Request"
  240. of 422: "422 Unprocessable Entity"
  241. of 426: "426 Upgrade Required"
  242. of 428: "428 Precondition Required"
  243. of 429: "429 Too Many Requests"
  244. of 431: "431 Request Header Fields Too Large"
  245. of 451: "451 Unavailable For Legal Reasons"
  246. of 500: "500 Internal Server Error"
  247. of 501: "501 Not Implemented"
  248. of 502: "502 Bad Gateway"
  249. of 503: "503 Service Unavailable"
  250. of 504: "504 Gateway Timeout"
  251. of 505: "505 HTTP Version Not Supported"
  252. else: $(int(code))
  253. proc `==`*(a, b: HttpCode): bool {.borrow.}
  254. proc `==`*(rawCode: string, code: HttpCode): bool =
  255. return cmpIgnoreCase(rawCode, $code) == 0
  256. proc is2xx*(code: HttpCode): bool =
  257. ## Determines whether ``code`` is a 2xx HTTP status code.
  258. return code.int in {200 .. 299}
  259. proc is3xx*(code: HttpCode): bool =
  260. ## Determines whether ``code`` is a 3xx HTTP status code.
  261. return code.int in {300 .. 399}
  262. proc is4xx*(code: HttpCode): bool =
  263. ## Determines whether ``code`` is a 4xx HTTP status code.
  264. return code.int in {400 .. 499}
  265. proc is5xx*(code: HttpCode): bool =
  266. ## Determines whether ``code`` is a 5xx HTTP status code.
  267. return code.int in {500 .. 599}
  268. proc `$`*(httpMethod: HttpMethod): string =
  269. return (system.`$`(httpMethod))[4 .. ^1].toUpperAscii()
  270. when isMainModule:
  271. var test = newHttpHeaders()
  272. test["Connection"] = @["Upgrade", "Close"]
  273. doAssert test["Connection", 0] == "Upgrade"
  274. doAssert test["Connection", 1] == "Close"
  275. test.add("Connection", "Test")
  276. doAssert test["Connection", 2] == "Test"
  277. doAssert "upgrade" in test["Connection"]
  278. # Bug #5344.
  279. doAssert parseHeader("foobar: ") == ("foobar", @[""])
  280. let (key, value) = parseHeader("foobar: ")
  281. test = newHttpHeaders()
  282. test[key] = value
  283. doAssert test["foobar"] == ""
  284. doAssert parseHeader("foobar:") == ("foobar", @[""])