Merge branch 'devel' of https://github.com/Araq/Nimrod into devel

This commit is contained in:
Araq 2014-12-18 20:42:29 +01:00
commit bce10ac1d3
3 changed files with 55 additions and 30 deletions

View file

@ -88,10 +88,9 @@
## constructor should be used for this purpose. However, ## constructor should be used for this purpose. However,
## currently only basic authentication is supported. ## currently only basic authentication is supported.
import sockets, strutils, parseurl, parseutils, strtabs, base64, os import net, strutils, uri, parseutils, strtabs, base64, os
import asyncnet, asyncdispatch import asyncnet, asyncdispatch
import rawsockets import rawsockets
from net import nil
type type
Response* = tuple[ Response* = tuple[
@ -101,7 +100,7 @@ type
body: string] body: string]
Proxy* = ref object Proxy* = ref object
url*: Url url*: Uri
auth*: string auth*: string
ProtocolError* = object of IOError ## exception that is raised when server ProtocolError* = object of IOError ## exception that is raised when server
@ -279,7 +278,7 @@ else:
proc newProxy*(url: string, auth = ""): Proxy = proc newProxy*(url: string, auth = ""): Proxy =
## Constructs a new ``TProxy`` object. ## Constructs a new ``TProxy`` object.
result = Proxy(url: parseUrl(url), auth: auth) result = Proxy(url: parseUri(url), auth: auth)
proc request*(url: string, httpMethod = httpGET, extraHeaders = "", proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
body = "", body = "",
@ -290,7 +289,7 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
## | Extra headers can be specified and must be seperated by ``\c\L`` ## | Extra headers can be specified and must be seperated by ``\c\L``
## | An optional timeout can be specified in miliseconds, if reading from the ## | An optional timeout can be specified in miliseconds, if reading from the
## 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: parseUrl(url) else: proxy.url var r = if proxy == nil: parseUri(url) else: proxy.url
var headers = substr($httpMethod, len("http")) var headers = substr($httpMethod, len("http"))
if proxy == nil: if proxy == nil:
headers.add(" /" & r.path & r.query) headers.add(" /" & r.path & r.query)
@ -308,18 +307,18 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
add(headers, extraHeaders) add(headers, extraHeaders)
add(headers, "\c\L") add(headers, "\c\L")
var s = socket() var s = newSocket()
if s == invalidSocket: raiseOSError(osLastError()) if s == nil: raiseOSError(osLastError())
var port = sockets.Port(80) var port = net.Port(80)
if r.scheme == "https": if r.scheme == "https":
when defined(ssl): when defined(ssl):
sslContext.wrapSocket(s) sslContext.wrapSocket(s)
port = sockets.Port(443) port = net.Port(443)
else: else:
raise newException(HttpRequestError, raise newException(HttpRequestError,
"SSL support is not available. Cannot connect over SSL.") "SSL support is not available. Cannot connect over SSL.")
if r.port != "": if r.port != "":
port = sockets.Port(r.port.parseInt) port = net.Port(r.port.parseInt)
if timeout == -1: if timeout == -1:
s.connect(r.hostname, port) s.connect(r.hostname, port)
@ -342,9 +341,9 @@ proc getNewLocation(lastUrl: string, headers: StringTableRef): string =
result = headers["Location"] result = headers["Location"]
if result == "": httpError("location header expected") if result == "": httpError("location header expected")
# Relative URLs. (Not part of the spec, but soon will be.) # Relative URLs. (Not part of the spec, but soon will be.)
let r = parseURL(result) let r = parseUri(result)
if r.hostname == "" and r.path != "": if r.hostname == "" and r.path != "":
let origParsed = parseURL(lastUrl) let origParsed = parseUri(lastUrl)
result = origParsed.hostname & "/" & r.path result = origParsed.hostname & "/" & r.path
proc get*(url: string, extraHeaders = "", maxRedirects = 5, proc get*(url: string, extraHeaders = "", maxRedirects = 5,
@ -437,7 +436,7 @@ proc downloadFile*(url: string, outputFilename: string,
else: else:
fileError("Unable to open file") fileError("Unable to open file")
proc generateHeaders(r: Url, httpMethod: HttpMethod, proc generateHeaders(r: Uri, httpMethod: HttpMethod,
headers: StringTableRef): string = headers: StringTableRef): string =
result = substr($httpMethod, len("http")) result = substr($httpMethod, len("http"))
# TODO: Proxies # TODO: Proxies
@ -455,7 +454,7 @@ type
AsyncHttpClient* = ref object AsyncHttpClient* = ref object
socket: AsyncSocket socket: AsyncSocket
connected: bool connected: bool
currentURL: Url ## Where we are currently connected. currentURL: Uri ## Where we are currently connected.
headers*: StringTableRef headers*: StringTableRef
maxRedirects: int maxRedirects: int
userAgent: string userAgent: string
@ -499,7 +498,6 @@ proc recvFull(socket: AsyncSocket, size: int): Future[string] {.async.} =
proc parseChunks(client: AsyncHttpClient): Future[string] {.async.} = proc parseChunks(client: AsyncHttpClient): Future[string] {.async.} =
result = "" result = ""
var ri = 0
while true: while true:
var chunkSize = 0 var chunkSize = 0
var chunkSizeStr = await client.socket.recvLine() var chunkSizeStr = await client.socket.recvLine()
@ -607,7 +605,7 @@ proc parseResponse(client: AsyncHttpClient,
else: else:
result.body = "" result.body = ""
proc newConnection(client: AsyncHttpClient, url: Url) {.async.} = proc newConnection(client: AsyncHttpClient, url: Uri) {.async.} =
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()
@ -643,7 +641,7 @@ 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.
let r = parseUrl(url) let r = parseUri(url)
await newConnection(client, r) await newConnection(client, r)
if not client.headers.hasKey("user-agent") and client.userAgent != "": if not client.headers.hasKey("user-agent") and client.userAgent != "":

View file

@ -256,7 +256,21 @@ proc socketError*(socket: Socket, err: int = -1, async = false,
else: raiseSSLError("Not enough data on socket.") else: raiseSSLError("Not enough data on socket.")
of SSL_ERROR_WANT_X509_LOOKUP: of SSL_ERROR_WANT_X509_LOOKUP:
raiseSSLError("Function for x509 lookup has been called.") raiseSSLError("Function for x509 lookup has been called.")
of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: of SSL_ERROR_SYSCALL:
var errStr = "IO error has occured "
let sslErr = ErrPeekLastError()
if sslErr == 0 and err == 0:
errStr.add "because an EOF was observed that violates the protocol"
elif sslErr == 0 and err == -1:
errStr.add "in the BIO layer"
else:
let errStr = $ErrErrorString(sslErr, nil)
raiseSSLError(errStr & ": " & errStr)
let osMsg = osErrorMsg osLastError()
if osMsg != "":
errStr.add ". The OS reports: " & osMsg
raise newException(OSError, errStr)
of SSL_ERROR_SSL:
raiseSSLError() raiseSSLError()
else: raiseSSLError("Unknown Error") else: raiseSSLError("Unknown Error")
@ -418,15 +432,21 @@ proc accept*(server: Socket, client: var Socket,
proc close*(socket: Socket) = proc close*(socket: Socket) =
## Closes a socket. ## Closes a socket.
socket.fd.close() try:
when defined(ssl): when defined(ssl):
if socket.isSSL: if socket.isSSL:
ErrClearError()
# As we are closing the underlying socket immediately afterwards,
# it is valid, under the TLS standard, to perform a unidirectional
# shutdown i.e not wait for the peers "close notify" alert with a second
# call to SSLShutdown
let res = SSLShutdown(socket.sslHandle) let res = SSLShutdown(socket.sslHandle)
if res == 0: if res == 0:
if SSLShutdown(socket.sslHandle) != 1: discard
socketError(socket)
elif res != 1: elif res != 1:
socketError(socket) socketError(socket, res)
finally:
socket.fd.close()
proc toCInt*(opt: SOBool): cint = proc toCInt*(opt: SOBool): cint =
## Converts a ``SOBool`` into its Socket Option cint representation. ## Converts a ``SOBool`` into its Socket Option cint representation.

View file

@ -89,6 +89,8 @@ type
{.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].} {.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].}
const const
SSL_SENT_SHUTDOWN* = 1
SSL_RECEIVED_SHUTDOWN* = 2
EVP_MAX_MD_SIZE* = 16 + 20 EVP_MAX_MD_SIZE* = 16 + 20
SSL_ERROR_NONE* = 0 SSL_ERROR_NONE* = 0
SSL_ERROR_SSL* = 1 SSL_ERROR_SSL* = 1
@ -233,6 +235,8 @@ proc SSL_CTX_check_private_key*(ctx: SslCtx): cInt{.cdecl, dynlib: DLLSSLName,
proc SSL_set_fd*(ssl: SslPtr, fd: SocketHandle): cint{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_set_fd*(ssl: SslPtr, fd: SocketHandle): cint{.cdecl, dynlib: DLLSSLName, importc.}
proc SSL_shutdown*(ssl: SslPtr): cInt{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_shutdown*(ssl: SslPtr): cInt{.cdecl, dynlib: DLLSSLName, importc.}
proc SSL_set_shutdown*(ssl: SslPtr, mode: cint) {.cdecl, dynlib: DLLSSLName, importc: "SSL_set_shutdown".}
proc SSL_get_shutdown*(ssl: SslPtr): cint {.cdecl, dynlib: DLLSSLName, importc: "SSL_get_shutdown".}
proc SSL_connect*(ssl: SslPtr): cint{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_connect*(ssl: SslPtr): cint{.cdecl, dynlib: DLLSSLName, importc.}
proc SSL_read*(ssl: SslPtr, buf: pointer, num: int): cint{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_read*(ssl: SslPtr, buf: pointer, num: int): cint{.cdecl, dynlib: DLLSSLName, importc.}
proc SSL_write*(ssl: SslPtr, buf: cstring, num: int): cint{.cdecl, dynlib: DLLSSLName, importc.} proc SSL_write*(ssl: SslPtr, buf: cstring, num: int): cint{.cdecl, dynlib: DLLSSLName, importc.}
@ -314,6 +318,11 @@ proc sslDoHandshake*(ssl: SslPtr): cint {.cdecl,
dynlib: DLLSSLName, importc: "SSL_do_handshake".} dynlib: DLLSSLName, importc: "SSL_do_handshake".}
proc ErrClearError*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_clear_error".}
proc ErrFreeStrings*(){.cdecl, dynlib: DLLUtilName, importc: "ERR_free_strings".}
proc ErrRemoveState*(pid: cInt){.cdecl, dynlib: DLLUtilName, importc: "ERR_remove_state".}
when true: when true:
discard discard
else: else:
@ -414,9 +423,7 @@ else:
# function ErrErrorString(e: cInt; buf: PChar): PChar; # function ErrErrorString(e: cInt; buf: PChar): PChar;
proc SSLeayversion*(t: cInt): cstring{.cdecl, dynlib: DLLUtilName, importc.} proc SSLeayversion*(t: cInt): cstring{.cdecl, dynlib: DLLUtilName, importc.}
proc ErrClearError*(){.cdecl, dynlib: DLLUtilName, importc.}
proc ErrFreeStrings*(){.cdecl, dynlib: DLLUtilName, importc.}
proc ErrRemoveState*(pid: cInt){.cdecl, dynlib: DLLUtilName, importc.}
proc OPENSSLaddallalgorithms*(){.cdecl, dynlib: DLLUtilName, importc.} proc OPENSSLaddallalgorithms*(){.cdecl, dynlib: DLLUtilName, importc.}
proc CRYPTOcleanupAllExData*(){.cdecl, dynlib: DLLUtilName, importc.} proc CRYPTOcleanupAllExData*(){.cdecl, dynlib: DLLUtilName, importc.}
proc RandScreen*(){.cdecl, dynlib: DLLUtilName, importc.} proc RandScreen*(){.cdecl, dynlib: DLLUtilName, importc.}