Improvements to httpclient. Refs #4423.
* Adds ability to query HttpCode and compare it with strings.
* Moves HttpMethod to HttpCore module.
* Implements synchronous HttpClient using {.multisync.}.
This commit is contained in:
parent
1740619c0c
commit
3ad368f8ca
4 changed files with 198 additions and 87 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Nim's Runtime Library
|
# Nim's Runtime Library
|
||||||
# (c) Copyright 2010 Dominik Picheta, Andreas Rumpf
|
# (c) Copyright 2016 Dominik Picheta, Andreas Rumpf
|
||||||
#
|
#
|
||||||
# See the file "copying.txt", included in this
|
# See the file "copying.txt", included in this
|
||||||
# distribution, for details about the copyright.
|
# distribution, for details about the copyright.
|
||||||
|
|
@ -87,12 +87,20 @@ import nativesockets
|
||||||
export httpcore except parseHeader # TODO: The ``except`` doesn't work
|
export httpcore except parseHeader # TODO: The ``except`` doesn't work
|
||||||
|
|
||||||
type
|
type
|
||||||
Response* = tuple[
|
Response* = object
|
||||||
version: string,
|
version*: string
|
||||||
status: string,
|
status*: string
|
||||||
headers: HttpHeaders,
|
headers*: HttpHeaders
|
||||||
body: string]
|
body*: string
|
||||||
|
|
||||||
|
proc code*(response: Response): HttpCode {.raises: [ValueError].} =
|
||||||
|
## Retrieves the specified response's ``HttpCode``.
|
||||||
|
##
|
||||||
|
## Raises a ``ValueError`` if the response's ``status`` does not have a
|
||||||
|
## corresponding ``HttpCode``.
|
||||||
|
return parseEnum[HttpCode](response.status)
|
||||||
|
|
||||||
|
type
|
||||||
Proxy* = ref object
|
Proxy* = ref object
|
||||||
url*: Uri
|
url*: Uri
|
||||||
auth*: string
|
auth*: string
|
||||||
|
|
@ -253,25 +261,6 @@ proc parseResponse(s: Socket, getBody: bool, timeout: int): Response =
|
||||||
else:
|
else:
|
||||||
result.body = ""
|
result.body = ""
|
||||||
|
|
||||||
type
|
|
||||||
HttpMethod* = enum ## the requested HttpMethod
|
|
||||||
httpHEAD, ## Asks for the response identical to the one that would
|
|
||||||
## correspond to a GET request, but without the response
|
|
||||||
## body.
|
|
||||||
httpGET, ## Retrieves the specified resource.
|
|
||||||
httpPOST, ## Submits data to be processed to the identified
|
|
||||||
## resource. The data is included in the body of the
|
|
||||||
## request.
|
|
||||||
httpPUT, ## Uploads a representation of the specified resource.
|
|
||||||
httpDELETE, ## Deletes the specified resource.
|
|
||||||
httpTRACE, ## Echoes back the received request, so that a client
|
|
||||||
## can see what intermediate servers are adding or
|
|
||||||
## changing in the request.
|
|
||||||
httpOPTIONS, ## Returns the HTTP methods that the server supports
|
|
||||||
## for specified address.
|
|
||||||
httpCONNECT ## Converts the request connection to a transparent
|
|
||||||
## TCP/IP tunnel, usually used for proxies.
|
|
||||||
|
|
||||||
{.deprecated: [THttpMethod: HttpMethod].}
|
{.deprecated: [THttpMethod: HttpMethod].}
|
||||||
|
|
||||||
when not defined(ssl):
|
when not defined(ssl):
|
||||||
|
|
@ -397,7 +386,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
|
||||||
## server takes longer than specified an ETimeout exception will be raised.
|
## server takes longer than specified an ETimeout exception will be raised.
|
||||||
var r = if proxy == nil: parseUri(url) else: proxy.url
|
var r = if proxy == nil: parseUri(url) else: proxy.url
|
||||||
var hostUrl = if proxy == nil: r else: parseUri(url)
|
var hostUrl = if proxy == nil: r else: parseUri(url)
|
||||||
var headers = substr(httpMethod, len("http"))
|
var headers = substr(httpMethod, len("http")).toUpper()
|
||||||
# TODO: Use generateHeaders further down once it supports proxies.
|
# TODO: Use generateHeaders further down once it supports proxies.
|
||||||
|
|
||||||
var s = newSocket()
|
var s = newSocket()
|
||||||
|
|
@ -620,7 +609,7 @@ proc downloadFile*(url: string, outputFilename: string,
|
||||||
proc generateHeaders(r: Uri, httpMethod: string,
|
proc generateHeaders(r: Uri, httpMethod: string,
|
||||||
headers: StringTableRef, body: string): string =
|
headers: StringTableRef, body: string): string =
|
||||||
# TODO: Use this in the blocking HttpClient once it supports proxies.
|
# TODO: Use this in the blocking HttpClient once it supports proxies.
|
||||||
result = substr(httpMethod, len("http"))
|
result = substr(httpMethod, len("http")).toUpper()
|
||||||
# TODO: Proxies
|
# TODO: Proxies
|
||||||
result.add ' '
|
result.add ' '
|
||||||
if r.path[0] != '/': result.add '/'
|
if r.path[0] != '/': result.add '/'
|
||||||
|
|
@ -643,8 +632,8 @@ proc generateHeaders(r: Uri, httpMethod: string,
|
||||||
add(result, "\c\L")
|
add(result, "\c\L")
|
||||||
|
|
||||||
type
|
type
|
||||||
AsyncHttpClient* = ref object
|
HttpClientBase*[SocketType] = ref object
|
||||||
socket: AsyncSocket
|
socket: SocketType
|
||||||
connected: bool
|
connected: bool
|
||||||
currentURL: Uri ## Where we are currently connected.
|
currentURL: Uri ## Where we are currently connected.
|
||||||
headers*: StringTableRef
|
headers*: StringTableRef
|
||||||
|
|
@ -653,6 +642,30 @@ type
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
sslContext: net.SslContext
|
sslContext: net.SslContext
|
||||||
|
|
||||||
|
type
|
||||||
|
HttpClient* = HttpClientBase[Socket]
|
||||||
|
|
||||||
|
proc newHttpClient*(userAgent = defUserAgent,
|
||||||
|
maxRedirects = 5, sslContext = defaultSslContext): HttpClient =
|
||||||
|
## Creates a new HttpClient instance.
|
||||||
|
##
|
||||||
|
## ``userAgent`` specifies the user agent that will be used when making
|
||||||
|
## requests.
|
||||||
|
##
|
||||||
|
## ``maxRedirects`` specifies the maximum amount of redirects to follow,
|
||||||
|
## default is 5.
|
||||||
|
##
|
||||||
|
## ``sslContext`` specifies the SSL context to use for HTTPS requests.
|
||||||
|
new result
|
||||||
|
result.headers = newStringTable(modeCaseInsensitive)
|
||||||
|
result.userAgent = userAgent
|
||||||
|
result.maxRedirects = maxRedirects
|
||||||
|
when defined(ssl):
|
||||||
|
result.sslContext = sslContext
|
||||||
|
|
||||||
|
type
|
||||||
|
AsyncHttpClient* = HttpClientBase[AsyncSocket]
|
||||||
|
|
||||||
{.deprecated: [PAsyncHttpClient: AsyncHttpClient].}
|
{.deprecated: [PAsyncHttpClient: AsyncHttpClient].}
|
||||||
|
|
||||||
proc newAsyncHttpClient*(userAgent = defUserAgent,
|
proc newAsyncHttpClient*(userAgent = defUserAgent,
|
||||||
|
|
@ -673,13 +686,14 @@ proc newAsyncHttpClient*(userAgent = defUserAgent,
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
result.sslContext = sslContext
|
result.sslContext = sslContext
|
||||||
|
|
||||||
proc close*(client: AsyncHttpClient) =
|
proc close*(client: HttpClient | AsyncHttpClient) =
|
||||||
## Closes any connections held by the HTTP client.
|
## Closes any connections held by the HTTP client.
|
||||||
if client.connected:
|
if client.connected:
|
||||||
client.socket.close()
|
client.socket.close()
|
||||||
client.connected = false
|
client.connected = false
|
||||||
|
|
||||||
proc recvFull(socket: AsyncSocket, size: int): Future[string] {.async.} =
|
proc recvFull(socket: Socket | AsyncSocket,
|
||||||
|
size: int): Future[string] {.multisync.} =
|
||||||
## Ensures that all the data requested is read and returned.
|
## Ensures that all the data requested is read and returned.
|
||||||
result = ""
|
result = ""
|
||||||
while true:
|
while true:
|
||||||
|
|
@ -688,7 +702,8 @@ proc recvFull(socket: AsyncSocket, size: int): Future[string] {.async.} =
|
||||||
if data == "": break # We've been disconnected.
|
if data == "": break # We've been disconnected.
|
||||||
result.add data
|
result.add data
|
||||||
|
|
||||||
proc parseChunks(client: AsyncHttpClient): Future[string] {.async.} =
|
proc parseChunks(client: HttpClient | AsyncHttpClient): Future[string]
|
||||||
|
{.multisync.} =
|
||||||
result = ""
|
result = ""
|
||||||
while true:
|
while true:
|
||||||
var chunkSize = 0
|
var chunkSize = 0
|
||||||
|
|
@ -721,9 +736,9 @@ proc parseChunks(client: AsyncHttpClient): Future[string] {.async.} =
|
||||||
# Trailer headers will only be sent if the request specifies that we want
|
# Trailer headers will only be sent if the request specifies that we want
|
||||||
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
|
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
|
||||||
|
|
||||||
proc parseBody(client: AsyncHttpClient,
|
proc parseBody(client: HttpClient | AsyncHttpClient,
|
||||||
headers: HttpHeaders,
|
headers: HttpHeaders,
|
||||||
httpVersion: string): Future[string] {.async.} =
|
httpVersion: string): Future[string] {.multisync.} =
|
||||||
result = ""
|
result = ""
|
||||||
if headers.getOrDefault"Transfer-Encoding" == "chunked":
|
if headers.getOrDefault"Transfer-Encoding" == "chunked":
|
||||||
result = await parseChunks(client)
|
result = await parseChunks(client)
|
||||||
|
|
@ -752,8 +767,8 @@ proc parseBody(client: AsyncHttpClient,
|
||||||
if buf == "": break
|
if buf == "": break
|
||||||
result.add(buf)
|
result.add(buf)
|
||||||
|
|
||||||
proc parseResponse(client: AsyncHttpClient,
|
proc parseResponse(client: HttpClient | AsyncHttpClient,
|
||||||
getBody: bool): Future[Response] {.async.} =
|
getBody: bool): Future[Response] {.multisync.} =
|
||||||
var parsedStatus = false
|
var parsedStatus = false
|
||||||
var linei = 0
|
var linei = 0
|
||||||
var fullyRead = false
|
var fullyRead = false
|
||||||
|
|
@ -803,11 +818,17 @@ proc parseResponse(client: AsyncHttpClient,
|
||||||
else:
|
else:
|
||||||
result.body = ""
|
result.body = ""
|
||||||
|
|
||||||
proc newConnection(client: AsyncHttpClient, url: Uri) {.async.} =
|
proc newConnection(client: HttpClient | AsyncHttpClient,
|
||||||
|
url: Uri) {.multisync.} =
|
||||||
if client.currentURL.hostname != url.hostname or
|
if client.currentURL.hostname != url.hostname or
|
||||||
client.currentURL.scheme != url.scheme:
|
client.currentURL.scheme != url.scheme:
|
||||||
if client.connected: client.close()
|
if client.connected: client.close()
|
||||||
|
|
||||||
|
when client is HttpClient:
|
||||||
|
client.socket = newSocket()
|
||||||
|
elif client is AsyncHttpClient:
|
||||||
client.socket = newAsyncSocket()
|
client.socket = newAsyncSocket()
|
||||||
|
else: {.fatal: "Unsupported client type".}
|
||||||
|
|
||||||
# TODO: I should be able to write 'net.Port' here...
|
# TODO: I should be able to write 'net.Port' here...
|
||||||
let port =
|
let port =
|
||||||
|
|
@ -829,8 +850,8 @@ proc newConnection(client: AsyncHttpClient, url: Uri) {.async.} =
|
||||||
client.currentURL = url
|
client.currentURL = url
|
||||||
client.connected = true
|
client.connected = true
|
||||||
|
|
||||||
proc request*(client: AsyncHttpClient, url: string, httpMethod: string,
|
proc request*(client: HttpClient | AsyncHttpClient, url: string,
|
||||||
body = ""): Future[Response] {.async.} =
|
httpMethod: string, body = ""): Future[Response] {.multisync.} =
|
||||||
## Connects to the hostname specified by the URL and performs a request
|
## Connects to the hostname specified by the URL and performs a request
|
||||||
## using the custom method string specified by ``httpMethod``.
|
## using the custom method string specified by ``httpMethod``.
|
||||||
##
|
##
|
||||||
|
|
@ -853,8 +874,8 @@ proc request*(client: AsyncHttpClient, url: string, httpMethod: string,
|
||||||
|
|
||||||
result = await parseResponse(client, httpMethod != "httpHEAD")
|
result = await parseResponse(client, httpMethod != "httpHEAD")
|
||||||
|
|
||||||
proc request*(client: AsyncHttpClient, url: string, httpMethod = httpGET,
|
proc request*(client: HttpClient | AsyncHttpClient, url: string,
|
||||||
body = ""): Future[Response] =
|
httpMethod = HttpGET, body = ""): Future[Response] {.multisync.} =
|
||||||
## Connects to the hostname specified by the URL and performs a request
|
## Connects to the hostname specified by the URL and performs a request
|
||||||
## using the method specified.
|
## using the method specified.
|
||||||
##
|
##
|
||||||
|
|
@ -863,9 +884,10 @@ proc request*(client: AsyncHttpClient, url: string, httpMethod = httpGET,
|
||||||
## connection can be closed by using the ``close`` procedure.
|
## connection can be closed by using the ``close`` procedure.
|
||||||
##
|
##
|
||||||
## The returned future will complete once the request is completed.
|
## The returned future will complete once the request is completed.
|
||||||
result = request(client, url, $httpMethod, body)
|
result = await request(client, url, $httpMethod, body)
|
||||||
|
|
||||||
proc get*(client: AsyncHttpClient, url: string): Future[Response] {.async.} =
|
proc get*(client: HttpClient | AsyncHttpClient,
|
||||||
|
url: string): Future[Response] {.multisync.} =
|
||||||
## Connects to the hostname specified by the URL and performs a GET request.
|
## Connects to the hostname specified by the URL and performs a GET request.
|
||||||
##
|
##
|
||||||
## This procedure will follow redirects up to a maximum number of redirects
|
## This procedure will follow redirects up to a maximum number of redirects
|
||||||
|
|
@ -878,7 +900,8 @@ proc get*(client: AsyncHttpClient, url: string): Future[Response] {.async.} =
|
||||||
result = await client.request(redirectTo, httpGET)
|
result = await client.request(redirectTo, httpGET)
|
||||||
lastURL = redirectTo
|
lastURL = redirectTo
|
||||||
|
|
||||||
proc post*(client: AsyncHttpClient, url: string, body = "", multipart: MultipartData = nil): Future[Response] {.async.} =
|
proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "",
|
||||||
|
multipart: MultipartData = nil): Future[Response] {.multisync.} =
|
||||||
## Connects to the hostname specified by the URL and performs a POST request.
|
## Connects to the hostname specified by the URL and performs a POST request.
|
||||||
##
|
##
|
||||||
## This procedure will follow redirects up to a maximum number of redirects
|
## This procedure will follow redirects up to a maximum number of redirects
|
||||||
|
|
@ -895,45 +918,4 @@ proc post*(client: AsyncHttpClient, url: string, body = "", multipart: Multipart
|
||||||
client.headers["Content-Type"] = mpHeader.split(": ")[1]
|
client.headers["Content-Type"] = mpHeader.split(": ")[1]
|
||||||
client.headers["Content-Length"] = $len(xb)
|
client.headers["Content-Length"] = $len(xb)
|
||||||
|
|
||||||
result = await client.request(url, httpPOST, xb)
|
result = await client.request(url, HttpPOST, xb)
|
||||||
|
|
||||||
when not defined(testing) and isMainModule:
|
|
||||||
when true:
|
|
||||||
# Async
|
|
||||||
proc main() {.async.} =
|
|
||||||
var client = newAsyncHttpClient()
|
|
||||||
var resp = await client.request("http://picheta.me")
|
|
||||||
|
|
||||||
echo("Got response: ", resp.status)
|
|
||||||
echo("Body:\n")
|
|
||||||
echo(resp.body)
|
|
||||||
|
|
||||||
resp = await client.request("http://picheta.me/asfas.html")
|
|
||||||
echo("Got response: ", resp.status)
|
|
||||||
|
|
||||||
resp = await client.request("http://picheta.me/aboutme.html")
|
|
||||||
echo("Got response: ", resp.status)
|
|
||||||
|
|
||||||
resp = await client.request("http://nim-lang.org/")
|
|
||||||
echo("Got response: ", resp.status)
|
|
||||||
|
|
||||||
resp = await client.request("http://nim-lang.org/download.html")
|
|
||||||
echo("Got response: ", resp.status)
|
|
||||||
|
|
||||||
waitFor main()
|
|
||||||
|
|
||||||
else:
|
|
||||||
#downloadFile("http://force7.de/nim/index.html", "nimindex.html")
|
|
||||||
#downloadFile("http://www.httpwatch.com/", "ChunkTest.html")
|
|
||||||
#downloadFile("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com",
|
|
||||||
# "validator.html")
|
|
||||||
|
|
||||||
#var r = get("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com&
|
|
||||||
# charset=%28detect+automatically%29&doctype=Inline&group=0")
|
|
||||||
|
|
||||||
var data = newMultipartData()
|
|
||||||
data["output"] = "soap12"
|
|
||||||
data["uploaded_file"] = ("test.html", "text/html",
|
|
||||||
"<html><head></head><body><p>test</p></body></html>")
|
|
||||||
|
|
||||||
echo postContent("http://validator.w3.org/check", multipart=data)
|
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,28 @@ type
|
||||||
HttpVer11,
|
HttpVer11,
|
||||||
HttpVer10
|
HttpVer10
|
||||||
|
|
||||||
|
HttpMethod* = enum ## the requested HttpMethod
|
||||||
|
HttpHead, ## Asks for the response identical to the one that would
|
||||||
|
## correspond to a GET request, but without the response
|
||||||
|
## body.
|
||||||
|
HttpGet, ## Retrieves the specified resource.
|
||||||
|
HttpPost, ## Submits data to be processed to the identified
|
||||||
|
## resource. The data is included in the body of the
|
||||||
|
## request.
|
||||||
|
HttpPut, ## Uploads a representation of the specified resource.
|
||||||
|
HttpDelete, ## Deletes the specified resource.
|
||||||
|
HttpTrace, ## Echoes back the received request, so that a client
|
||||||
|
## can see what intermediate servers are adding or
|
||||||
|
## changing in the request.
|
||||||
|
HttpOptions, ## Returns the HTTP methods that the server supports
|
||||||
|
## for specified address.
|
||||||
|
HttpConnect ## Converts the request connection to a transparent
|
||||||
|
## TCP/IP tunnel, usually used for proxies.
|
||||||
|
|
||||||
|
{.deprecated: [httpGet: HttpGet, httpHead: HttpHead, httpPost: HttpPost,
|
||||||
|
httpPut: HttpPut, httpDelete: HttpDelete, httpTrace: HttpTrace,
|
||||||
|
httpOptions: HttpOptions, httpConnect: HttpConnect].}
|
||||||
|
|
||||||
const headerLimit* = 10_000
|
const headerLimit* = 10_000
|
||||||
|
|
||||||
proc newHttpHeaders*(): HttpHeaders =
|
proc newHttpHeaders*(): HttpHeaders =
|
||||||
|
|
@ -188,6 +210,25 @@ proc `==`*(protocol: tuple[orig: string, major, minor: int],
|
||||||
of HttpVer10: 0
|
of HttpVer10: 0
|
||||||
result = protocol.major == major and protocol.minor == minor
|
result = protocol.major == major and protocol.minor == minor
|
||||||
|
|
||||||
|
proc `==`*(rawCode: string, code: HttpCode): bool =
|
||||||
|
return rawCode.toLower() == ($code).toLower()
|
||||||
|
|
||||||
|
proc is2xx*(code: HttpCode): bool =
|
||||||
|
## Determines whether ``code`` is a 2xx HTTP status code.
|
||||||
|
return ($code).startsWith("2")
|
||||||
|
|
||||||
|
proc is3xx*(code: HttpCode): bool =
|
||||||
|
## Determines whether ``code`` is a 3xx HTTP status code.
|
||||||
|
return ($code).startsWith("3")
|
||||||
|
|
||||||
|
proc is4xx*(code: HttpCode): bool =
|
||||||
|
## Determines whether ``code`` is a 4xx HTTP status code.
|
||||||
|
return ($code).startsWith("4")
|
||||||
|
|
||||||
|
proc is5xx*(code: HttpCode): bool =
|
||||||
|
## Determines whether ``code`` is a 5xx HTTP status code.
|
||||||
|
return ($code).startsWith("5")
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
var test = newHttpHeaders()
|
var test = newHttpHeaders()
|
||||||
test["Connection"] = @["Upgrade", "Close"]
|
test["Connection"] = @["Upgrade", "Close"]
|
||||||
|
|
|
||||||
|
|
@ -966,6 +966,22 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1,
|
||||||
socket.socketError(result, lastError = lastError)
|
socket.socketError(result, lastError = lastError)
|
||||||
data.setLen(result)
|
data.setLen(result)
|
||||||
|
|
||||||
|
proc recv*(socket: Socket, size: int, timeout = -1,
|
||||||
|
flags = {SocketFlag.SafeDisconn}): string {.inline.} =
|
||||||
|
## Higher-level version of ``recv`` which returns a string.
|
||||||
|
##
|
||||||
|
## When ``""`` is returned the socket's connection has been closed.
|
||||||
|
##
|
||||||
|
## This function will throw an EOS exception when an error occurs.
|
||||||
|
##
|
||||||
|
## A timeout may be specified in milliseconds, if enough data is not received
|
||||||
|
## within the time specified an ETimeout exception will be raised.
|
||||||
|
##
|
||||||
|
##
|
||||||
|
## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
|
||||||
|
result = newString(size)
|
||||||
|
discard recv(socket, result, size, timeout, flags)
|
||||||
|
|
||||||
proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
|
proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
|
||||||
if socket.isBuffered:
|
if socket.isBuffered:
|
||||||
result = 1
|
result = 1
|
||||||
|
|
@ -1035,6 +1051,25 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
|
||||||
return
|
return
|
||||||
add(line.string, c)
|
add(line.string, c)
|
||||||
|
|
||||||
|
proc recvLine*(socket: Socket, timeout = -1,
|
||||||
|
flags = {SocketFlag.SafeDisconn}): TaintedString =
|
||||||
|
## Reads a line of data from ``socket``.
|
||||||
|
##
|
||||||
|
## If a full line is read ``\r\L`` is not
|
||||||
|
## added to the result, however if solely ``\r\L`` is read then the result
|
||||||
|
## will be set to it.
|
||||||
|
##
|
||||||
|
## If the socket is disconnected, the result will be set to ``""``.
|
||||||
|
##
|
||||||
|
## An EOS exception will be raised in the case of a socket error.
|
||||||
|
##
|
||||||
|
## A timeout can be specified in milliseconds, if data is not received within
|
||||||
|
## the specified time an ETimeout exception will be raised.
|
||||||
|
##
|
||||||
|
## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
|
||||||
|
result = ""
|
||||||
|
readLine(socket, result, timeout, flags)
|
||||||
|
|
||||||
proc recvFrom*(socket: Socket, data: var string, length: int,
|
proc recvFrom*(socket: Socket, data: var string, length: int,
|
||||||
address: var string, port: var Port, flags = 0'i32): int {.
|
address: var string, port: var Port, flags = 0'i32): int {.
|
||||||
tags: [ReadIOEffect].} =
|
tags: [ReadIOEffect].} =
|
||||||
|
|
|
||||||
53
tests/stdlib/thttpclient.nim
Normal file
53
tests/stdlib/thttpclient.nim
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import strutils
|
||||||
|
|
||||||
|
import httpclient, asyncdispatch
|
||||||
|
|
||||||
|
proc asyncTest() {.async.} =
|
||||||
|
var client = newAsyncHttpClient()
|
||||||
|
var resp = await client.request("http://example.com/")
|
||||||
|
doAssert(resp.code.is2xx)
|
||||||
|
doAssert("<title>Example Domain</title>" in resp.body)
|
||||||
|
|
||||||
|
resp = await client.request("http://example.com/404")
|
||||||
|
doAssert(resp.code.is4xx)
|
||||||
|
doAssert(resp.code == Http404)
|
||||||
|
doAssert(resp.status == Http404)
|
||||||
|
|
||||||
|
resp = await client.request("https://google.com/")
|
||||||
|
doAssert(resp.code.is2xx or resp.code.is3xx)
|
||||||
|
|
||||||
|
proc syncTest() =
|
||||||
|
var client = newHttpClient()
|
||||||
|
var resp = client.request("http://example.com/")
|
||||||
|
doAssert(resp.code.is2xx)
|
||||||
|
doAssert("<title>Example Domain</title>" in resp.body)
|
||||||
|
|
||||||
|
resp = client.request("http://example.com/404")
|
||||||
|
doAssert(resp.code.is4xx)
|
||||||
|
doAssert(resp.code == Http404)
|
||||||
|
doAssert(resp.status == Http404)
|
||||||
|
|
||||||
|
resp = client.request("https://google.com/")
|
||||||
|
doAssert(resp.code.is2xx or resp.code.is3xx)
|
||||||
|
|
||||||
|
syncTest()
|
||||||
|
|
||||||
|
waitFor(asyncTest())
|
||||||
|
|
||||||
|
#[
|
||||||
|
|
||||||
|
else:
|
||||||
|
#downloadFile("http://force7.de/nim/index.html", "nimindex.html")
|
||||||
|
#downloadFile("http://www.httpwatch.com/", "ChunkTest.html")
|
||||||
|
#downloadFile("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com",
|
||||||
|
# "validator.html")
|
||||||
|
|
||||||
|
#var r = get("http://validator.w3.org/check?uri=http%3A%2F%2Fgoogle.com&
|
||||||
|
# charset=%28detect+automatically%29&doctype=Inline&group=0")
|
||||||
|
|
||||||
|
var data = newMultipartData()
|
||||||
|
data["output"] = "soap12"
|
||||||
|
data["uploaded_file"] = ("test.html", "text/html",
|
||||||
|
"<html><head></head><body><p>test</p></body></html>")
|
||||||
|
|
||||||
|
echo postContent("http://validator.w3.org/check", multipart=data)]#
|
||||||
Loading…
Add table
Add a link
Reference in a new issue