summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2013-02-24 03:52:02 +0100
committerAraq <rumpf_a@web.de>2013-02-24 03:52:02 +0100
commit6cbd5bb017337801ac7985e8a95834a6cc6ef73a (patch)
treeee2a26d4c7ed2b1c186723c8a26cf777a2799899
parent9fc2bfa799ef432c96853d13b4487e99d5028f83 (diff)
parentf2041afad5594321ac21b584143f6db4ad5d697f (diff)
downloadNim-6cbd5bb017337801ac7985e8a95834a6cc6ef73a.tar.gz
Merge branch 'master' of github.com:Araq/Nimrod
-rwxr-xr-xlib/pure/httpclient.nim135
-rwxr-xr-xlib/pure/sockets.nim250
-rwxr-xr-xlib/wrappers/openssl.nim2
3 files changed, 236 insertions, 151 deletions
diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim
index 5be4af8a4..cc0129b45 100755
--- a/lib/pure/httpclient.nim
+++ b/lib/pure/httpclient.nim
@@ -50,6 +50,18 @@
 ## any of the functions a url with the ``https`` schema, for example:
 ## ``https://github.com/``, you also have to compile with ``ssl`` defined like so:
 ## ``nimrod c -d:ssl ...``.
+##
+## Timeouts
+## ========
+## Currently all functions support an optional timeout, by default the timeout is set to
+## `-1` which means that the function will never time out. The timeout is
+## measured in miliseconds, once it is set any call on a socket which may
+## block will be susceptible to this timeout, however please remember that the
+## function as a whole can take longer than the specified timeout, only
+## individual internal calls on the socket are affected. In practice this means
+## that as long as the server is sending data an exception will not be raised,
+## if however data does not reach client within the specified timeout an ETimeout
+## exception will then be raised.
 
 import sockets, strutils, parseurl, parseutils, strtabs
 
@@ -68,6 +80,8 @@ type
                                       ## and ``postContent`` proc,
                                       ## when the server returns an error
 
+const defUserAgent* = "Nimrod httpclient/0.1"
+
 proc httpError(msg: string) =
   var e: ref EInvalidProtocol
   new(e)
@@ -80,13 +94,13 @@ proc fileError(msg: string) =
   e.msg = msg
   raise e
 
-proc parseChunks(s: TSocket): string =
+proc parseChunks(s: TSocket, timeout: int): string =
   result = ""
   var ri = 0
   while true:
     var chunkSizeStr = ""
     var chunkSize = 0
-    if s.recvLine(chunkSizeStr):
+    if s.recvLine(chunkSizeStr, timeout):
       var i = 0
       if chunkSizeStr == "":
         httpError("Server terminated connection prematurely")
@@ -111,18 +125,17 @@ proc parseChunks(s: TSocket): string =
     result.setLen(ri+chunkSize)
     var bytesRead = 0
     while bytesRead != chunkSize:
-      let ret = recv(s, addr(result[ri]), chunkSize-bytesRead)
+      let ret = recv(s, addr(result[ri]), chunkSize-bytesRead, timeout)
       ri += ret
       bytesRead += ret
-    s.skip(2) # Skip \c\L
+    s.skip(2, timeout) # Skip \c\L
     # Trailer headers will only be sent if the request specifies that we want
     # them: http://tools.ietf.org/html/rfc2616#section-3.6.1
   
-proc parseBody(s: TSocket,
-               headers: PStringTable): string =
+proc parseBody(s: TSocket, headers: PStringTable, timeout: int): string =
   result = ""
   if headers["Transfer-Encoding"] == "chunked":
-    result = parseChunks(s)
+    result = parseChunks(s, timeout)
   else:
     # -REGION- Content-Length
     # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.3
@@ -133,7 +146,7 @@ proc parseBody(s: TSocket,
       var received = 0
       while true:
         if received >= length: break
-        let r = s.recv(addr(result[received]), length-received)
+        let r = s.recv(addr(result[received]), length-received, timeout)
         if r == 0: break
         received += r
       if received != length:
@@ -148,12 +161,12 @@ proc parseBody(s: TSocket,
         var buf = ""
         while True:
           buf = newString(4000)
-          let r = s.recv(addr(buf[0]), 4000)
+          let r = s.recv(addr(buf[0]), 4000, timeout)
           if r == 0: break
           buf.setLen(r)
           result.add(buf)
 
-proc parseResponse(s: TSocket, getBody: bool): TResponse =
+proc parseResponse(s: TSocket, getBody: bool, timeout: int): TResponse =
   var parsedStatus = false
   var linei = 0
   var fullyRead = false
@@ -162,7 +175,7 @@ proc parseResponse(s: TSocket, getBody: bool): TResponse =
   while True:
     line = ""
     linei = 0
-    if s.recvLine(line):
+    if s.recvLine(line, timeout):
       if line == "": break # We've been disconnected.
       if line == "\c\L":
         fullyRead = true
@@ -194,9 +207,11 @@ proc parseResponse(s: TSocket, getBody: bool): TResponse =
         linei += skipWhitespace(line, linei)
         
         result.headers[name] = line[linei.. -1]
-  if not fullyRead: httpError("Connection was closed before full request has been made")
+    else: SocketError(s)
+  if not fullyRead:
+    httpError("Connection was closed before full request has been made")
   if getBody:
-    result.body = parseBody(s, result.headers)
+    result.body = parseBody(s, result.headers, timeout)
   else:
     result.body = ""
 
@@ -227,9 +242,12 @@ else:
 
 proc request*(url: string, httpMethod = httpGET, extraHeaders = "", 
               body = "",
-              sslContext: PSSLContext = defaultSSLContext): TResponse =
+              sslContext: PSSLContext = defaultSSLContext,
+              timeout = -1, userAgent = defUserAgent): TResponse =
   ## | Requests ``url`` with the specified ``httpMethod``.
   ## | Extra headers can be specified and must be seperated by ``\c\L``
+  ## | An optional timeout can be specified in miliseconds, if reading from the
+  ## server takes longer than specified an ETimeout exception will be raised.
   var r = parseUrl(url)
   var headers = substr($httpMethod, len("http"))
   headers.add(" /" & r.path & r.query)
@@ -237,25 +255,32 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
   headers.add(" HTTP/1.1\c\L")
   
   add(headers, "Host: " & r.hostname & "\c\L")
+  if userAgent != "":
+    add(headers, "User-Agent: " & userAgent & "\c\L")
   add(headers, extraHeaders)
   add(headers, "\c\L")
-
+  
   var s = socket()
   var port = TPort(80)
   if r.scheme == "https":
     when defined(ssl):
       sslContext.wrapSocket(s)
     else:
-      raise newException(EHttpRequestErr, "SSL support was not compiled in. Cannot connect over SSL.")
+      raise newException(EHttpRequestErr,
+                "SSL support is not available. Cannot connect over SSL.")
     port = TPort(443)
   if r.port != "":
     port = TPort(r.port.parseInt)
-  s.connect(r.hostname, port)
+  
+  if timeout == -1:
+    s.connect(r.hostname, port)
+  else:
+    s.connect(r.hostname, port, timeout)
   s.send(headers)
   if body != "":
     s.send(body)
   
-  result = parseResponse(s, httpMethod != httpHEAD)
+  result = parseResponse(s, httpMethod != httpHEAD, timeout)
   s.close()
   
 proc redirection(status: string): bool =
@@ -263,56 +288,94 @@ proc redirection(status: string): bool =
   for i in items(redirectionNRs):
     if status.startsWith(i):
       return True
+
+proc getNewLocation(lastUrl: string, headers: PStringTable): string =
+  result = headers["Location"]
+  if result == "": httpError("location header expected")
+  # Relative URLs. (Not part of the spec, but soon will be.)
+  let r = parseURL(result)
+  if r.hostname == "" and r.path != "":
+    let origParsed = parseURL(lastUrl)
+    result = origParsed.hostname & "/" & r.path
   
-proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse =
+proc get*(url: string, extraHeaders = "", maxRedirects = 5,
+          sslContext: PSSLContext = defaultSSLContext,
+          timeout = -1, userAgent = defUserAgent): TResponse =
   ## | GETs the ``url`` and returns a ``TResponse`` object
   ## | This proc also handles redirection
-  result = request(url)
+  ## | Extra headers can be specified and must be separated by ``\c\L``.
+  ## | An optional timeout can be specified in miliseconds, if reading from the
+  ## server takes longer than specified an ETimeout exception will be raised.
+  result = request(url, httpGET, extraHeaders, "", sslContext, timeout, userAgent)
+  var lastURL = url
   for i in 1..maxRedirects:
     if result.status.redirection():
-      var locationHeader = result.headers["Location"]
-      if locationHeader == "": httpError("location header expected")
-      result = request(locationHeader, sslContext = sslContext)
+      let redirectTo = getNewLocation(lastURL, result.headers)
+      result = request(redirectTo, httpGET, extraHeaders, "", sslContext,
+                       timeout, userAgent)
+      lastUrl = redirectTo
       
-proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): string =
+proc getContent*(url: string, extraHeaders = "", maxRedirects = 5,
+                 sslContext: PSSLContext = defaultSSLContext,
+                 timeout = -1, userAgent = defUserAgent): string =
   ## | GETs the body and returns it as a string.
   ## | Raises exceptions for the status codes ``4xx`` and ``5xx``
-  var r = get(url, sslContext = sslContext)
+  ## | Extra headers can be specified and must be separated by ``\c\L``.
+  ## | An optional timeout can be specified in miliseconds, if reading from the
+  ## server takes longer than specified an ETimeout exception will be raised.
+  var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent)
   if r.status[0] in {'4','5'}:
     raise newException(EHTTPRequestErr, r.status)
   else:
     return r.body
   
-proc post*(url: string, extraHeaders = "", body = "", 
-           maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse =
+proc post*(url: string, extraHeaders = "", body = "",
+           maxRedirects = 5,
+           sslContext: PSSLContext = defaultSSLContext,
+           timeout = -1, userAgent = defUserAgent): TResponse =
   ## | POSTs ``body`` to the ``url`` and returns a ``TResponse`` object.
   ## | This proc adds the necessary Content-Length header.
   ## | This proc also handles redirection.
+  ## | Extra headers can be specified and must be separated by ``\c\L``.
+  ## | An optional timeout can be specified in miliseconds, if reading from the
+  ## server takes longer than specified an ETimeout exception will be raised.
   var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L"
-  result = request(url, httpPOST, xh, body, sslContext)
+  result = request(url, httpPOST, xh, body, sslContext, timeout, userAgent)
+  var lastUrl = ""
   for i in 1..maxRedirects:
     if result.status.redirection():
-      var locationHeader = result.headers["Location"]
-      if locationHeader == "": httpError("location header expected")
+      let redirectTo = getNewLocation(lastURL, result.headers)
       var meth = if result.status != "307": httpGet else: httpPost
-      result = request(locationHeader, meth, xh, body)
+      result = request(redirectTo, meth, xh, body, sslContext, timeout,
+                       userAgent)
+      lastUrl = redirectTo
   
 proc postContent*(url: string, extraHeaders = "", body = "",
-                  sslContext: PSSLContext = defaultSSLContext): string =
+                  maxRedirects = 5,
+                  sslContext: PSSLContext = defaultSSLContext,
+                  timeout = -1, userAgent = defUserAgent): string =
   ## | POSTs ``body`` to ``url`` and returns the response's body as a string
   ## | Raises exceptions for the status codes ``4xx`` and ``5xx``
-  var r = post(url, extraHeaders, body)
+  ## | Extra headers can be specified and must be separated by ``\c\L``.
+  ## | An optional timeout can be specified in miliseconds, if reading from the
+  ## server takes longer than specified an ETimeout exception will be raised.
+  var r = post(url, extraHeaders, body, maxRedirects, sslContext, timeout,
+               userAgent)
   if r.status[0] in {'4','5'}:
     raise newException(EHTTPRequestErr, r.status)
   else:
     return r.body
   
 proc downloadFile*(url: string, outputFilename: string,
-                   sslContext: PSSLContext = defaultSSLContext) =
-  ## Downloads ``url`` and saves it to ``outputFilename``
+                   sslContext: PSSLContext = defaultSSLContext,
+                   timeout = -1, userAgent = defUserAgent) =
+  ## | Downloads ``url`` and saves it to ``outputFilename``
+  ## | An optional timeout can be specified in miliseconds, if reading from the
+  ## server takes longer than specified an ETimeout exception will be raised.
   var f: TFile
   if open(f, outputFilename, fmWrite):
-    f.write(getContent(url, sslContext))
+    f.write(getContent(url, sslContext = sslContext, timeout = timeout,
+            userAgent = userAgent))
     f.close()
   else:
     fileError("Unable to open file")
diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim
index f233e53c8..e70fbd09a 100755
--- a/lib/pure/sockets.nim
+++ b/lib/pure/sockets.nim
@@ -1,18 +1,28 @@
 #
 #
 #            Nimrod's Runtime Library
-#        (c) Copyright 2013 Andreas Rumpf
+#        (c) Copyright 2013 Andreas Rumpf, Dominik Picheta
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
 #
 
-## This module implements a simple portable type-safe sockets layer.
+## This module implements portable sockets, it supports a mix of different types
+## of sockets. Sockets are buffered by default meaning that data will be
+## received in ``BufferSize`` (4000) sized chunks, buffering
+## behaviour can be disabled by setting the ``buffered`` parameter when calling
+## the ``socket`` function to `False`. Be aware that some functions may not yet
+## support buffered sockets (mainly the recvFrom function).
 ##
-## Most procedures raise EOS on error.
+## Most procedures raise EOS on error, but some may return ``-1`` or a boolean
+## ``False``.
 ##
-## For OpenSSL support compile with ``-d:ssl``. When using SSL be aware that
-## most functions will then raise ``ESSL`` on SSL errors.
+## SSL is supported through the OpenSSL library. This support can be activated
+## by compiling with the ``-d:ssl`` switch. When an SSL socket is used it will
+## raise ESSL exceptions when SSL errors occur.
+##
+## Asynchronous sockets are supported, however a better alternative is to use
+## the `asyncio <asyncio.html>`_ module.
 
 {.deadCodeElim: on.}
 
@@ -68,6 +78,7 @@ type
         sslHasPeekChar: bool
         sslPeekChar: char
       of false: nil
+    nonblocking: bool
   
   TSocket* = ref TSocketImpl
   
@@ -118,6 +129,7 @@ proc newTSocket(fd: int32, isBuff: bool): TSocket =
   result.isBuffered = isBuff
   if isBuff:
     result.currPos = 0
+  result.nonblocking = false
 
 let
   InvalidSocket*: TSocket = nil ## invalid socket
@@ -196,7 +208,7 @@ else:
 
 proc socket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
              protocol: TProtocol = IPPROTO_TCP, buffered = true): TSocket =
-  ## creates a new socket; returns `InvalidSocket` if an error occurs.  
+  ## Creates a new socket; returns `InvalidSocket` if an error occurs.  
   when defined(Windows):
     result = newTSocket(winlean.socket(ord(domain), ord(typ), ord(protocol)), buffered)
   else:
@@ -711,10 +723,10 @@ proc setSockOptInt*(socket: TSocket, level, optname, optval: int) {.
                 sizeof(value).TSockLen) < 0'i32:
     OSError()
 
-proc connect*(socket: TSocket, name: string, port = TPort(0), 
+proc connect*(socket: TSocket, address: string, port = TPort(0), 
               af: TDomain = AF_INET) {.tags: [FReadIO].} =
-  ## Connects socket to ``name``:``port``. ``Name`` can be an IP address or a
-  ## host name. If ``name`` is a host name, this function will try each IP
+  ## Connects socket to ``address``:``port``. ``Address`` can be an IP address or a
+  ## host name. If ``address`` is a host name, this function will try each IP
   ## of that host name. ``htons`` is already performed on ``port`` so you must
   ## not do it.
   ##
@@ -724,8 +736,7 @@ proc connect*(socket: TSocket, name: string, port = TPort(0),
   hints.ai_family = toInt(af)
   hints.ai_socktype = toInt(SOCK_STREAM)
   hints.ai_protocol = toInt(IPPROTO_TCP)
-  gaiNim(name, port, hints, aiList)
-  
+  gaiNim(address, port, hints, aiList)
   # try all possibilities:
   var success = false
   var it = aiList
@@ -758,7 +769,7 @@ proc connect*(socket: TSocket, name: string, port = TPort(0),
         
   when false:
     var s: TSockAddrIn
-    s.sin_addr.s_addr = inet_addr(name)
+    s.sin_addr.s_addr = inet_addr(address)
     s.sin_port = sockets.htons(int16(port))
     when defined(windows):
       s.sin_family = toU16(ord(af))
@@ -891,6 +902,10 @@ proc hasDataBuffered*(s: TSocket): bool =
   if s.isBuffered:
     result = s.bufLen > 0 and s.currPos != s.bufLen
 
+  when defined(ssl):
+    if s.isSSL and not result:
+      result = s.sslHasPeekChar
+
 proc checkBuffer(readfds: var seq[TSocket]): int =
   ## Checks the buffer of each socket in ``readfds`` to see whether there is data.
   ## Removes the sockets from ``readfds`` and returns the count of removed sockets.
@@ -906,8 +921,8 @@ proc checkBuffer(readfds: var seq[TSocket]): int =
 proc select*(readfds, writefds, exceptfds: var seq[TSocket], 
              timeout = 500): int {.tags: [FReadIO].} = 
   ## Traditional select function. This function will return the number of
-  ## sockets that are ready to be read from, written to, or which have errors
-  ## if there are none; 0 is returned. 
+  ## sockets that are ready to be read from, written to, or which have errors.
+  ## If there are none; 0 is returned. 
   ## ``Timeout`` is in miliseconds and -1 can be specified for no timeout.
   ## 
   ## A socket is removed from the specific ``seq`` when it has data waiting to
@@ -935,7 +950,7 @@ proc select*(readfds, writefds, exceptfds: var seq[TSocket],
 
 proc select*(readfds, writefds: var seq[TSocket], 
              timeout = 500): int {.tags: [FReadIO].} =
-  ## variant of select with only a read and write list.
+  ## Variant of select with only a read and write list.
   let buffersFilled = checkBuffer(readfds)
   if buffersFilled > 0:
     return buffersFilled
@@ -1019,7 +1034,10 @@ template retRead(flags, readBytes: int) =
       return res
 
 proc recv*(socket: TSocket, data: pointer, size: int): int {.tags: [FReadIO].} =
-  ## receives data from a socket
+  ## Receives data from a socket.
+  ##
+  ## **Note**: This is a low-level function, you may be interested in the higher
+  ## level versions of this function which are also named ``recv``.
   if size == 0: return
   if socket.isBuffered:
     if socket.bufLen == 0:
@@ -1055,51 +1073,39 @@ proc recv*(socket: TSocket, data: pointer, size: int): int {.tags: [FReadIO].} =
     else:
       result = recv(socket.fd, data, size.cint, 0'i32)
 
-proc recv*(socket: TSocket, data: var string, size: int): int =
-  ## higher-level version of the above
-  ##
-  ## When 0 is returned the socket's connection has been closed.
-  ##
-  ## This function will throw an EOS exception when an error occurs. A value
-  ## lower than 0 is never returned.
-  ##
-  ## **Note**: ``data`` must be initialised.
-  data.setLen(size)
-  result = recv(socket, cstring(data), size)
-  if result < 0:
-    data.setLen(0)
-    socket.SocketError(result)
-  data.setLen(result)
-
-proc recvAsync*(socket: TSocket, data: var string, size: int): int =
-  ## Async version of the above.
-  ##
-  ## When socket is non-blocking and no data is available on the socket,
-  ## ``-1`` will be returned and ``data`` will be ``""``.
+proc waitFor(socket: TSocket, waited: var float, timeout, size: int,
+             funcName: string): int {.tags: [FTime].} =
+  ## determines the amount of characters that can be read. Result will never
+  ## be larger than ``size``. For unbuffered sockets this will be ``1``.
+  ## For buffered sockets it can be as big as ``BufferSize``.
   ##
-  ## **Note**: ``data`` must be initialised.
-  data.setLen(size)
-  result = recv(socket, cstring(data), size)
-  if result < 0:
-    data.setLen(0)
-    socket.SocketError(async = true)
-    result = -1
-  data.setLen(result)
-
-proc waitFor(socket: TSocket, waited: var float, timeout: int): int {.
-  tags: [FTime].} =
-  ## returns the number of characters available to be read. In unbuffered
-  ## sockets this is always 1, otherwise this may as big as ``BufferSize``.
+  ## If this function does not determine that there is data on the socket
+  ## within ``timeout`` ms, an ETimeout error will be raised.
   result = 1
+  if size <= 0: assert false
+  if timeout == -1: return size
   if socket.isBuffered and socket.bufLen != 0 and socket.bufLen != socket.currPos:
     result = socket.bufLen - socket.currPos
+    result = min(result, size)
   else:
     if timeout - int(waited * 1000.0) < 1:
-      raise newException(ETimeout, "Call to recv() timed out.")
+      raise newException(ETimeout, "Call to '" & funcName & "' timed out.")
+    
+    when defined(ssl):
+      if socket.isSSL:
+        if socket.hasDataBuffered:
+          # sslPeekChar is present.
+          return 1
+        let sslPending = SSLPending(socket.sslHandle)
+        if sslPending != 0:
+          return sslPending
+    
     var s = @[socket]
     var startTime = epochTime()
-    if select(s, timeout - int(waited * 1000.0)) != 1:
-      raise newException(ETimeout, "Call to recv() timed out.")
+    let selRet = select(s, timeout - int(waited * 1000.0))
+    if selRet < 0: OSError()
+    if selRet != 1:
+      raise newException(ETimeout, "Call to '" & funcName & "' timed out.")
     waited += (epochTime() - startTime)
 
 proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int {.
@@ -1109,7 +1115,7 @@ proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int {.
   
   var read = 0
   while read < size:
-    let avail = waitFor(socket, waited, timeout)
+    let avail = waitFor(socket, waited, timeout, size-read, "recv")
     var d = cast[cstring](data)
     result = recv(socket, addr(d[read]), avail)
     if result == 0: break
@@ -1119,16 +1125,38 @@ proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int {.
   
   result = read
 
-proc recv*(socket: TSocket, data: var string, size: int, timeout: int): int =
-  ## higher-level version of the above.
+proc recv*(socket: TSocket, data: var string, size: int, timeout = -1): int =
+  ## Higher-level version of ``recv``.
+  ##
+  ## When 0 is returned the socket's connection has been closed.
+  ##
+  ## This function will throw an EOS exception when an error occurs. A value
+  ## lower than 0 is never returned.
+  ##
+  ## A timeout may be specified in miliseconds, if enough data is not received
+  ## within the time specified an ETimeout exception will be raised.
   ##
-  ## Similar to the non-timeout version this will throw an EOS exception
-  ## when an error occurs.
+  ## **Note**: ``data`` must be initialised.
   data.setLen(size)
   result = recv(socket, cstring(data), size, timeout)
   if result < 0:
     data.setLen(0)
-    socket.SocketError()
+    socket.SocketError(result)
+  data.setLen(result)
+
+proc recvAsync*(socket: TSocket, data: var string, size: int): int =
+  ## Async version of ``recv``.
+  ##
+  ## When socket is non-blocking and no data is available on the socket,
+  ## ``-1`` will be returned and ``data`` will be ``""``.
+  ##
+  ## **Note**: ``data`` must be initialised.
+  data.setLen(size)
+  result = recv(socket, cstring(data), size)
+  if result < 0:
+    data.setLen(0)
+    socket.SocketError(async = true)
+    result = -1
   data.setLen(result)
 
 proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} =
@@ -1151,9 +1179,11 @@ proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} =
         return
     result = recv(socket.fd, addr(c), 1, MSG_PEEK)
 
-proc recvLine*(socket: TSocket, line: var TaintedString): bool {.
-  tags: [FReadIO].} =
-  ## retrieves a line from ``socket``. If a full line is received ``\r\L`` is not
+proc recvLine*(socket: TSocket, line: var TaintedString, timeout = -1): bool {.
+  tags: [FReadIO, FTime].} =
+  ## Receive a line of data from ``socket``.
+  ##
+  ## If a full line is received ``\r\L`` is not
   ## added to ``line``, however if solely ``\r\L`` is received then ``line``
   ## will be set to it.
   ## 
@@ -1163,49 +1193,24 @@ proc recvLine*(socket: TSocket, line: var TaintedString): bool {.
   ## 
   ## If the socket is disconnected, ``line`` will be set to ``""`` and ``True``
   ## will be returned.
+  ##
+  ## A timeout can be specified in miliseconds, if data is not received within
+  ## the specified time an ETimeout exception will be raised.
   template addNLIfEmpty(): stmt =
     if line.len == 0:
       line.add("\c\L")
 
-  setLen(line.string, 0)
-  while true:
-    var c: char
-    var n = recv(socket, addr(c), 1)
-    if n < 0: return
-    elif n == 0: return true
-    if c == '\r':
-      n = peekChar(socket, c)
-      if n > 0 and c == '\L':
-        discard recv(socket, addr(c), 1)
-      elif n <= 0: return false
-      addNlIfEmpty()
-      return true
-    elif c == '\L': 
-      addNlIfEmpty()
-      return true
-    add(line.string, c)
+  var waited = 0.0
 
-proc recvLine*(socket: TSocket, line: var TaintedString, timeout: int): bool {.
-  tags: [FReadIO, FTime].} =
-  ## variant with a ``timeout`` parameter, the timeout parameter specifies
-  ## how many miliseconds to wait for data.
-  ##
-  ## ``ETimeout`` will be raised if ``timeout`` is exceeded.
-  template addNLIfEmpty(): stmt =
-    if line.len == 0:
-      line.add("\c\L")
-  
-  var waited = 0.0 # number of seconds already waited
-  
   setLen(line.string, 0)
   while true:
     var c: char
-    discard waitFor(socket, waited, timeout)
+    discard waitFor(socket, waited, timeout, 1, "recvLine")
     var n = recv(socket, addr(c), 1)
     if n < 0: return
     elif n == 0: return true
     if c == '\r':
-      discard waitFor(socket, waited, timeout)
+      discard waitFor(socket, waited, timeout, 1, "recvLine")
       n = peekChar(socket, c)
       if n > 0 and c == '\L':
         discard recv(socket, addr(c), 1)
@@ -1219,12 +1224,14 @@ proc recvLine*(socket: TSocket, line: var TaintedString, timeout: int): bool {.
 
 proc recvLineAsync*(socket: TSocket, 
   line: var TaintedString): TRecvLineResult {.tags: [FReadIO].} =
-  ## similar to ``recvLine`` but for non-blocking sockets.
+  ## Similar to ``recvLine`` but designed for non-blocking sockets.
+  ##
   ## The values of the returned enum should be pretty self explanatory:
-  ## If a full line has been retrieved; ``RecvFullLine`` is returned.
-  ## If some data has been retrieved; ``RecvPartialLine`` is returned.
-  ## If the socket has been disconnected; ``RecvDisconnected`` is returned.
-  ## If call to ``recv`` failed; ``RecvFail`` is returned.
+  ##
+  ##   * If a full line has been retrieved; ``RecvFullLine`` is returned.
+  ##   * If some data has been retrieved; ``RecvPartialLine`` is returned.
+  ##   * If the socket has been disconnected; ``RecvDisconnected`` is returned.
+  ##   * If call to ``recv`` failed; ``RecvFail`` is returned.
   setLen(line.string, 0)
   while true:
     var c: char
@@ -1348,6 +1355,9 @@ proc recvFrom*(socket: TSocket, data: var string, length: int,
   ## Receives data from ``socket``. This function should normally be used with
   ## connection-less sockets (UDP sockets).
   ##
+  ## If an error occurs the return value will be ``-1``. Otherwise the return
+  ## value will be the length of data received.
+  ##
   ## **Warning:** This function does not yet have a buffered implementation,
   ## so when ``socket`` is buffered the non-buffered implementation will be
   ## used. Therefore if ``socket`` contains something in its buffer this
@@ -1368,9 +1378,10 @@ proc recvFrom*(socket: TSocket, data: var string, length: int,
 proc recvFromAsync*(socket: TSocket, data: var String, length: int,
                     address: var string, port: var TPort, 
                     flags = 0'i32): bool {.tags: [FReadIO].} =
-  ## Similar to ``recvFrom`` but raises an EOS error when an error occurs and
-  ## is also meant for non-blocking sockets.
-  ## Returns False if no messages could be received from ``socket``.
+  ## Variant of ``recvFrom`` for non-blocking sockets. Unlike ``recvFrom``,
+  ## this function will raise an EOS error whenever a socket error occurs.
+  ##
+  ## If there is no data to be read from the socket ``False`` will be returned.
   result = true
   var callRes = recvFrom(socket, data, length, address, port, flags)
   if callRes < 0:
@@ -1394,14 +1405,19 @@ proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} =
   while recv(socket, buf, bufSize) == bufSize: nil
   dealloc(buf)
 
-proc skip*(socket: TSocket, size: int) =
+proc skip*(socket: TSocket, size: int, timeout = -1) =
   ## Skips ``size`` amount of bytes.
   ##
+  ## An optional timeout can be specified in miliseconds, if skipping the
+  ## bytes takes longer than specified an ETimeout exception will be raised.
+  ##
   ## Returns the number of skipped bytes.
+  var waited = 0.0
   var dummy = alloc(size)
   var bytesSkipped = 0
   while bytesSkipped != size:
-    bytesSkipped += recv(socket, dummy, size-bytesSkipped)
+    let avail = waitFor(socket, waited, timeout, size-bytesSkipped, "skip")
+    bytesSkipped += recv(socket, dummy, avail)
   dealloc(dummy)
 
 proc send*(socket: TSocket, data: pointer, size: int): int {.
@@ -1529,21 +1545,27 @@ proc setBlocking(s: TSocket, blocking: bool) =
       var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK
       if fcntl(s.fd, F_SETFL, mode) == -1:
         OSError()
-
-proc connect*(socket: TSocket, timeout: int, name: string, port = TPort(0),
-             af: TDomain = AF_INET) {.tags: [FReadIO].} =
-  ## Overload for ``connect`` to support timeouts. The ``timeout`` parameter 
-  ## specifies the time in miliseconds of how long to wait for a connection
-  ## to be made.
+  s.nonblocking = not blocking
+  
+proc connect*(socket: TSocket, address: string, port = TPort(0), timeout: int,
+             af: TDomain = AF_INET) {.tags: [FReadIO, FWriteIO].} =
+  ## Connects to server as specified by ``address`` on port specified by ``port``.
   ##
-  ## **Warning:** If ``socket`` is non-blocking then
-  ## this function will set blocking mode on ``socket`` to true.
-  socket.setBlocking(true)
+  ## The ``timeout`` paremeter specifies the time in miliseconds to allow for
+  ## the connection to the server to be made.
+  let originalStatus = not socket.nonblocking
+  socket.setBlocking(false)
   
-  socket.connectAsync(name, port, af)
+  socket.connectAsync(address, port, af)
   var s: seq[TSocket] = @[socket]
   if selectWrite(s, timeout) != 1:
-    raise newException(ETimeout, "Call to connect() timed out.")
+    raise newException(ETimeout, "Call to 'connect' timed out.")
+  else:
+    when defined(ssl):
+      if socket.isSSL:
+        socket.setBlocking(true)
+        doAssert socket.handshake()
+  socket.setBlocking(originalStatus)
 
 proc isSSL*(socket: TSocket): bool = return socket.isSSL
   ## Determines whether ``socket`` is a SSL socket.
diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim
index 438774a15..ca4ad6a99 100755
--- a/lib/wrappers/openssl.nim
+++ b/lib/wrappers/openssl.nim
@@ -233,6 +233,7 @@ proc SSL_read*(ssl: PSSL, buf: pointer, num: int): cint{.cdecl, dynlib: DLLSSLNa
 proc SSL_write*(ssl: PSSL, buf: cstring, num: int): cint{.cdecl, dynlib: DLLSSLName, importc.}
 proc SSL_get_error*(s: PSSL, ret_code: cInt): cInt{.cdecl, dynlib: DLLSSLName, importc.}
 proc SSL_accept*(ssl: PSSL): cInt{.cdecl, dynlib: DLLSSLName, importc.}
+proc SSL_pending*(ssl: PSSL): cInt{.cdecl, dynlib: DLLSSLName, importc.}
 
 proc BIO_new_ssl_connect*(ctx: PSSL_CTX): PBIO{.cdecl,
     dynlib: DLLSSLName, importc.}
@@ -323,7 +324,6 @@ else:
       dynlib: DLLSSLName, importc.}
   proc SslWrite*(ssl: PSSL, buf: SslPtr, num: cInt): cInt{.cdecl, 
       dynlib: DLLSSLName, importc.}
-  proc SslPending*(ssl: PSSL): cInt{.cdecl, dynlib: DLLSSLName, importc.}
   proc SslGetVersion*(ssl: PSSL): cstring{.cdecl, dynlib: DLLSSLName, importc.}
   proc SslGetPeerCertificate*(ssl: PSSL): PX509{.cdecl, dynlib: DLLSSLName, 
       importc.}