Fixed httpclient bugs, fixed socket bugs and fixed sockets for windows.
This commit is contained in:
parent
ce933c90a4
commit
7364d72548
2 changed files with 117 additions and 93 deletions
|
|
@ -87,10 +87,11 @@ proc charAt(d: var string, i: var int, s: TSocket): char {.inline.} =
|
||||||
i = 0
|
i = 0
|
||||||
result = d[i]
|
result = d[i]
|
||||||
|
|
||||||
proc parseChunks(d: var string, start: int, s: TSocket): string =
|
proc parseChunks(s: TSocket): string =
|
||||||
# get chunks:
|
# get chunks:
|
||||||
var i = start
|
var i = 0
|
||||||
result = ""
|
result = ""
|
||||||
|
var d = s.recv().string
|
||||||
while true:
|
while true:
|
||||||
var chunkSize = 0
|
var chunkSize = 0
|
||||||
var digitFound = false
|
var digitFound = false
|
||||||
|
|
@ -136,88 +137,87 @@ proc parseChunks(d: var string, start: int, s: TSocket): string =
|
||||||
# skip trailing CR-LF:
|
# skip trailing CR-LF:
|
||||||
while charAt(d, i, s) in {'\C', '\L'}: inc(i)
|
while charAt(d, i, s) in {'\C', '\L'}: inc(i)
|
||||||
|
|
||||||
proc parseBody(d: var string, start: int, s: TSocket,
|
proc parseBody(s: TSocket,
|
||||||
headers: PStringTable): string =
|
headers: PStringTable): string =
|
||||||
|
result = ""
|
||||||
if headers["Transfer-Encoding"] == "chunked":
|
if headers["Transfer-Encoding"] == "chunked":
|
||||||
result = parseChunks(d, start, s)
|
result = parseChunks(s)
|
||||||
else:
|
else:
|
||||||
result = substr(d, start)
|
|
||||||
# -REGION- Content-Length
|
# -REGION- Content-Length
|
||||||
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.3
|
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.3
|
||||||
var contentLengthHeader = headers["Content-Length"]
|
var contentLengthHeader = headers["Content-Length"]
|
||||||
if contentLengthHeader != "":
|
if contentLengthHeader != "":
|
||||||
var length = contentLengthHeader.parseint()
|
var length = contentLengthHeader.parseint()
|
||||||
while result.len() < length: result.add(s.recv.string)
|
result = newString(length)
|
||||||
|
var received = 0
|
||||||
|
while true:
|
||||||
|
if received >= length: break
|
||||||
|
let r = s.recv(addr(result[received]), length-received)
|
||||||
|
if r == 0: break
|
||||||
|
received += r
|
||||||
|
if received != length:
|
||||||
|
httpError("Got invalid content length. Expected: " & $length &
|
||||||
|
" got: " & $received)
|
||||||
else:
|
else:
|
||||||
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.4 TODO
|
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.4 TODO
|
||||||
|
|
||||||
# -REGION- Connection: Close
|
# -REGION- Connection: Close
|
||||||
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
|
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
|
||||||
if headers["Connection"] == "close":
|
if headers["Connection"] == "close":
|
||||||
|
var buf = ""
|
||||||
while True:
|
while True:
|
||||||
var moreData = recv(s).string
|
buf = newString(4000)
|
||||||
if moreData.len == 0: break
|
let r = s.recv(addr(buf[0]), 4000)
|
||||||
result.add(moreData)
|
if r == 0: break
|
||||||
|
buf.setLen(r)
|
||||||
|
result.add(buf)
|
||||||
|
|
||||||
proc parseResponse(s: TSocket): TResponse =
|
proc parseResponse(s: TSocket, getBody: bool): TResponse =
|
||||||
var d = s.recv.string # Warning: without a Connection: Close header this will not work.
|
var parsedStatus = false
|
||||||
var i = 0
|
var linei = 0
|
||||||
|
var fullyRead = false
|
||||||
# Parse the version
|
var line = ""
|
||||||
# Parses the first line of the headers
|
|
||||||
# ``HTTP/1.1`` 200 OK
|
|
||||||
var L = skipIgnoreCase(d, "HTTP/1.1", i)
|
|
||||||
if L > 0:
|
|
||||||
result.version = "1.1"
|
|
||||||
inc(i, L)
|
|
||||||
else:
|
|
||||||
L = skipIgnoreCase(d, "HTTP/1.0", i)
|
|
||||||
if L > 0:
|
|
||||||
result.version = "1.0"
|
|
||||||
inc(i, L)
|
|
||||||
else:
|
|
||||||
httpError("invalid HTTP header")
|
|
||||||
L = skipWhiteSpace(d, i)
|
|
||||||
if L <= 0: httpError("invalid HTTP header")
|
|
||||||
inc(i, L)
|
|
||||||
|
|
||||||
result.status = ""
|
|
||||||
while d[i] notin {'\C', '\L', '\0'}:
|
|
||||||
result.status.add(d[i])
|
|
||||||
inc(i)
|
|
||||||
if d[i] == '\C': inc(i)
|
|
||||||
if d[i] == '\L': inc(i)
|
|
||||||
else: httpError("invalid HTTP header, CR-LF expected")
|
|
||||||
|
|
||||||
# Parse the headers
|
|
||||||
# Everything after the first line leading up to the body
|
|
||||||
# htype: hvalue
|
|
||||||
result.headers = newStringTable(modeCaseInsensitive)
|
result.headers = newStringTable(modeCaseInsensitive)
|
||||||
while true:
|
while True:
|
||||||
var key = ""
|
line = ""
|
||||||
while d[i] != ':':
|
linei = 0
|
||||||
if d[i] == '\0': httpError("invalid HTTP header, ':' expected")
|
if s.recvLine(line):
|
||||||
key.add(d[i])
|
if line == "": break # We've been disconnected.
|
||||||
inc(i)
|
if line == "\c\L":
|
||||||
inc(i) # skip ':'
|
fullyRead = true
|
||||||
if d[i] == ' ': inc(i) # skip if the character is a space
|
break
|
||||||
var val = ""
|
if not parsedStatus:
|
||||||
while d[i] notin {'\C', '\L', '\0'}:
|
# Parse HTTP version info and status code.
|
||||||
val.add(d[i])
|
var le = skipIgnoreCase(line, "HTTP/", linei)
|
||||||
inc(i)
|
if le <= 0: httpError("invalid http version")
|
||||||
|
inc(linei, le)
|
||||||
result.headers[key] = val
|
le = skipIgnoreCase(line, "1.1", linei)
|
||||||
|
if le > 0: result.version = "1.1"
|
||||||
if d[i] == '\C': inc(i)
|
else:
|
||||||
if d[i] == '\L': inc(i)
|
le = skipIgnoreCase(line, "1.0", linei)
|
||||||
else: httpError("invalid HTTP header, CR-LF expected")
|
if le <= 0: httpError("unsupported http version")
|
||||||
|
result.version = "1.0"
|
||||||
if d[i] == '\C': inc(i)
|
inc(linei, le)
|
||||||
if d[i] == '\L':
|
# Status code
|
||||||
inc(i)
|
linei.inc skipWhitespace(line, linei)
|
||||||
break
|
result.status = line[linei .. -1]
|
||||||
|
parsedStatus = true
|
||||||
result.body = parseBody(d, i, s, result.headers)
|
else:
|
||||||
|
# Parse headers
|
||||||
|
var name = ""
|
||||||
|
var le = parseUntil(line, name, ':', linei)
|
||||||
|
if le <= 0: httpError("invalid headers")
|
||||||
|
inc(linei, le)
|
||||||
|
if line[linei] != ':': httpError("invalid headers")
|
||||||
|
inc(linei) # Skip :
|
||||||
|
linei += skipWhitespace(line, linei)
|
||||||
|
|
||||||
|
result.headers[name] = line[linei.. -1]
|
||||||
|
if not fullyRead: httpError("Connection was closed before full request has been made")
|
||||||
|
if getBody:
|
||||||
|
result.body = parseBody(s, result.headers)
|
||||||
|
else:
|
||||||
|
result.body = ""
|
||||||
|
|
||||||
type
|
type
|
||||||
THttpMethod* = enum ## the requested HttpMethod
|
THttpMethod* = enum ## the requested HttpMethod
|
||||||
|
|
@ -246,10 +246,10 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
|
||||||
|
|
||||||
var headers = substr($httpMethod, len("http"))
|
var headers = substr($httpMethod, len("http"))
|
||||||
headers.add(" /" & r.path & r.query)
|
headers.add(" /" & r.path & r.query)
|
||||||
|
|
||||||
headers.add(" HTTP/1.1\c\L")
|
headers.add(" HTTP/1.1\c\L")
|
||||||
|
|
||||||
add(headers, "Host: " & r.hostname & "\c\L")
|
add(headers, "Host: " & r.hostname & "\c\L")
|
||||||
add(headers, "Connection: Close\c\L")
|
|
||||||
add(headers, extraHeaders)
|
add(headers, extraHeaders)
|
||||||
add(headers, "\c\L")
|
add(headers, "\c\L")
|
||||||
|
|
||||||
|
|
@ -258,6 +258,8 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
|
||||||
if r.scheme == "https":
|
if r.scheme == "https":
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
s.wrapSocket(verifyMode = CVerifyNone)
|
s.wrapSocket(verifyMode = CVerifyNone)
|
||||||
|
else:
|
||||||
|
raise newException(EHttpRequestErr, "SSL support was not compiled in. Cannot connect over SSL.")
|
||||||
port = TPort(443)
|
port = TPort(443)
|
||||||
if r.port != "":
|
if r.port != "":
|
||||||
port = TPort(r.port.parseInt)
|
port = TPort(r.port.parseInt)
|
||||||
|
|
@ -266,7 +268,7 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
|
||||||
if body != "":
|
if body != "":
|
||||||
s.send(body)
|
s.send(body)
|
||||||
|
|
||||||
result = parseResponse(s)
|
result = parseResponse(s, httpMethod != httpHEAD)
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
proc redirection(status: string): bool =
|
proc redirection(status: string): bool =
|
||||||
|
|
|
||||||
|
|
@ -407,7 +407,7 @@ proc acceptAddr*(server: TSocket): tuple[client: TSocket, address: string] =
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var err = WSAGetLastError()
|
var err = WSAGetLastError()
|
||||||
if err == WSAEINPROGRESS:
|
if err == WSAEINPROGRESS:
|
||||||
client = InvalidSocket
|
return (InvalidSocket, "")
|
||||||
else: OSError()
|
else: OSError()
|
||||||
else:
|
else:
|
||||||
if errno == EAGAIN or errno == EWOULDBLOCK:
|
if errno == EAGAIN or errno == EWOULDBLOCK:
|
||||||
|
|
@ -775,15 +775,18 @@ proc readIntoBuf(socket: TSocket, flags: int32): int =
|
||||||
result = recv(socket.fd, addr(socket.buffer), int(socket.buffer.high), flags)
|
result = recv(socket.fd, addr(socket.buffer), int(socket.buffer.high), flags)
|
||||||
else:
|
else:
|
||||||
result = recv(socket.fd, addr(socket.buffer), int(socket.buffer.high), flags)
|
result = recv(socket.fd, addr(socket.buffer), int(socket.buffer.high), flags)
|
||||||
if result <= 0: return
|
if result <= 0:
|
||||||
|
socket.buflen = 0
|
||||||
|
socket.currPos = 0
|
||||||
|
return result
|
||||||
socket.bufLen = result
|
socket.bufLen = result
|
||||||
socket.currPos = 0
|
socket.currPos = 0
|
||||||
|
|
||||||
template retRead(flags, read: int) =
|
template retRead(flags, readBytes: int) =
|
||||||
let res = socket.readIntoBuf(flags)
|
let res = socket.readIntoBuf(flags)
|
||||||
if res <= 0:
|
if res <= 0:
|
||||||
if read > 0:
|
if readBytes > 0:
|
||||||
return read
|
return readBytes
|
||||||
else:
|
else:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
@ -793,16 +796,27 @@ proc recv*(socket: TSocket, data: pointer, size: int): int =
|
||||||
if socket.bufLen == 0:
|
if socket.bufLen == 0:
|
||||||
retRead(0'i32, 0)
|
retRead(0'i32, 0)
|
||||||
|
|
||||||
var read = 0
|
when true:
|
||||||
while read < size:
|
var read = 0
|
||||||
if socket.currPos >= socket.bufLen:
|
while read < size:
|
||||||
retRead(0'i32, read)
|
if socket.currPos >= socket.bufLen:
|
||||||
|
retRead(0'i32, read)
|
||||||
let chunk = min(socket.bufLen, size-read)
|
|
||||||
var d = cast[cstring](data)
|
let chunk = min(socket.bufLen-socket.currPos, size-read)
|
||||||
copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk)
|
var d = cast[cstring](data)
|
||||||
read.inc(chunk)
|
copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk)
|
||||||
socket.currPos.inc(chunk)
|
read.inc(chunk)
|
||||||
|
socket.currPos.inc(chunk)
|
||||||
|
else:
|
||||||
|
var read = 0
|
||||||
|
while read < size:
|
||||||
|
if socket.currPos >= socket.bufLen:
|
||||||
|
retRead(0'i32, read)
|
||||||
|
|
||||||
|
var d = cast[cstring](data)
|
||||||
|
d[read] = socket.buffer[socket.currPos]
|
||||||
|
read.inc(1)
|
||||||
|
socket.currPos.inc(1)
|
||||||
|
|
||||||
result = read
|
result = read
|
||||||
else:
|
else:
|
||||||
|
|
@ -814,8 +828,14 @@ proc recv*(socket: TSocket, data: pointer, size: int): int =
|
||||||
else:
|
else:
|
||||||
result = recv(socket.fd, data, size, 0'i32)
|
result = recv(socket.fd, data, size, 0'i32)
|
||||||
|
|
||||||
proc waitFor(socket: TSocket, waited: var float, timeout: int) =
|
proc waitFor(socket: TSocket, waited: var float, timeout: int): int =
|
||||||
if socket.bufLen == 0:
|
## returns the number of characters available to be read. In unbuffered
|
||||||
|
## sockets this is always 1, otherwise this may as big as the buffer, currently
|
||||||
|
## 4000.
|
||||||
|
result = 1
|
||||||
|
if socket.isBuffered and socket.bufLen != 0 and socket.bufLen != socket.currPos:
|
||||||
|
result = socket.bufLen - socket.currPos
|
||||||
|
else:
|
||||||
if timeout - int(waited * 1000.0) < 1:
|
if timeout - int(waited * 1000.0) < 1:
|
||||||
raise newException(ETimeout, "Call to recv() timed out.")
|
raise newException(ETimeout, "Call to recv() timed out.")
|
||||||
var s = @[socket]
|
var s = @[socket]
|
||||||
|
|
@ -824,17 +844,19 @@ proc waitFor(socket: TSocket, waited: var float, timeout: int) =
|
||||||
raise newException(ETimeout, "Call to recv() timed out.")
|
raise newException(ETimeout, "Call to recv() timed out.")
|
||||||
waited += (epochTime() - startTime)
|
waited += (epochTime() - startTime)
|
||||||
|
|
||||||
proc recv*(socket: TSocket, data: var string, size: int, timeout: int): int =
|
proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int =
|
||||||
## overload with a ``timeout`` parameter in miliseconds.
|
## overload with a ``timeout`` parameter in miliseconds.
|
||||||
var waited = 0.0 # number of seconds already waited
|
var waited = 0.0 # number of seconds already waited
|
||||||
|
|
||||||
var read = 0
|
var read = 0
|
||||||
while read < size:
|
while read < size:
|
||||||
waitFor(socket, waited, timeout)
|
let avail = waitFor(socket, waited, timeout)
|
||||||
result = recv(socket, addr(data[read]), 1)
|
var d = cast[cstring](data)
|
||||||
|
result = recv(socket, addr(d[read]), avail)
|
||||||
|
if result == 0: break
|
||||||
if result < 0:
|
if result < 0:
|
||||||
return
|
return result
|
||||||
inc(read)
|
inc(read, result)
|
||||||
|
|
||||||
result = read
|
result = read
|
||||||
|
|
||||||
|
|
@ -901,12 +923,12 @@ proc recvLine*(socket: TSocket, line: var TaintedString, timeout: int): bool =
|
||||||
setLen(line.string, 0)
|
setLen(line.string, 0)
|
||||||
while true:
|
while true:
|
||||||
var c: char
|
var c: char
|
||||||
waitFor(socket, waited, timeout)
|
discard waitFor(socket, waited, timeout)
|
||||||
var n = recv(socket, addr(c), 1)
|
var n = recv(socket, addr(c), 1)
|
||||||
if n < 0: return
|
if n < 0: return
|
||||||
elif n == 0: return true
|
elif n == 0: return true
|
||||||
if c == '\r':
|
if c == '\r':
|
||||||
waitFor(socket, waited, timeout)
|
discard waitFor(socket, waited, timeout)
|
||||||
n = peekChar(socket, c)
|
n = peekChar(socket, c)
|
||||||
if n > 0 and c == '\L':
|
if n > 0 and c == '\L':
|
||||||
discard recv(socket, addr(c), 1)
|
discard recv(socket, addr(c), 1)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue