Merge branch 'devel' into unify_waitpid_handling

This commit is contained in:
Johannes Hofmann 2016-09-30 08:37:09 +02:00
commit 72ee6f78c5
42 changed files with 988 additions and 833 deletions

View file

@ -18,19 +18,6 @@ stages:
tags: tags:
- windows - windows
build-linux:
stage: build
script:
- sh ci/build.sh
artifacts:
paths:
- bin/nim
- bin/nimd
- compiler/nim
- koch
expire_in: 1 week
tags:
- linux
build-windows: build-windows:
stage: build stage: build
@ -58,15 +45,7 @@ deploy-windows:
- windows - windows
- fast - fast
test-linux:
stage: test
<<: *linux_set_path_def
script:
- sh ci/deps.sh
- nim c --taintMode:on tests/testament/tester
- tests/testament/tester --pedantic all
tags:
- linux
test-windows: test-windows:
stage: test stage: test
@ -79,18 +58,3 @@ test-windows:
- windows - windows
- fast - fast
.csources: &csources_definition
stage: test
script:
- apt-get update -qq
- apt-get install -y -qq build-essential git
- nim -v
- ./koch csources
- ./koch xz
artifacts:
paths:
- build/c_code
csources-linux:
<<: *csources_definition
<<: *linux_set_path_def

View file

@ -28,7 +28,7 @@ script:
- nimble install zip - nimble install zip
- nimble install opengl - nimble install opengl
- nimble install sdl1 - nimble install sdl1
- nimble install jester - nimble install jester@#head
- nimble install niminst - nimble install niminst
- nim c --taintMode:on tests/testament/tester - nim c --taintMode:on tests/testament/tester
- tests/testament/tester --pedantic all - tests/testament/tester --pedantic all

3
build_tools.sh Normal file
View file

@ -0,0 +1,3 @@
./bin/nim c --noNimblePath -p:compiler -o:./bin/nimble dist/nimble/src/nimble.nim
./bin/nim c --noNimblePath -p:compiler -o:./bin/nimsuggest dist/nimsuggest/nimsuggest.nim
./bin/nim c -o:./bin/nimgrep tools/nimgrep.nim

View file

@ -1,4 +1,4 @@
nim e install_nimble.nims nim e install_nimble.nims
nim e tests/test_nimscript.nims nim e tests/test_nimscript.nims
nimble update nimble update
nimble install -y zip opengl sdl1 jester niminst nimble install -y zip opengl sdl1 jester@#head niminst

View file

@ -14,4 +14,4 @@ export PATH=$(pwd)/bin:$PATH
nim e install_nimble.nims nim e install_nimble.nims
nim e tests/test_nimscript.nims nim e tests/test_nimscript.nims
nimble update nimble update
nimble install zip opengl sdl1 jester niminst nimble install zip opengl sdl1 jester@#head niminst

View file

@ -29,7 +29,6 @@ rem move /y build\nim-%NIMVER%.zip web\upload\download
rem Grab C sources and nimsuggest rem Grab C sources and nimsuggest
git clone --depth 1 https://github.com/nim-lang/csources.git git clone --depth 1 https://github.com/nim-lang/csources.git
git clone --depth 1 https://github.com/nim-lang/nimsuggest.git
set PATH=%CD%\bin;%PATH% set PATH=%CD%\bin;%PATH%
@ -39,13 +38,11 @@ set PATH=C:\Users\araq\projects\mingw32\bin;%PATH%
cd csources cd csources
call build.bat call build.bat
cd .. cd ..
nim c koch || exit /b ReM Rebuilding koch is necessary because it uses its pointer size to determine
koch boot -d:release || exit /b ReM which mingw link to put in the NSIS installer.
cd nimsuggest nim c --out:koch_temp koch || exit /b
nim c -d:release --noNimblePath --path:.. nimsuggest || exit /b koch_temp boot -d:release || exit /b
copy /y nimsuggest.exe ..\bin || exit /b koch_temp nsis -d:release || exit /b
cd ..
koch nsis -d:release || exit /b
dir build dir build
move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x32.exe || exit /b move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x32.exe || exit /b
@ -55,11 +52,7 @@ set PATH=C:\Users\araq\projects\mingw64\bin;%PATH%
cd csources cd csources
call build64.bat call build64.bat
cd .. cd ..
nim c koch || exit /b nim c --out:koch_temp koch || exit /b
koch boot -d:release || exit /b koch_temp boot -d:release || exit /b
cd nimsuggest koch_temp nsis -d:release || exit /b
nim c -d:release --noNimblePath --path:.. nimsuggest || exit /b
copy /y nimsuggest.exe ..\bin || exit /b
cd ..
koch nsis -d:release || exit /b
move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x64.exe || exit /b move /y build\nim_%NIMVER%.exe build\nim-%NIMVER%_x64.exe || exit /b

View file

@ -1408,6 +1408,7 @@ proc copyNode*(src: PNode): PNode =
result.info = src.info result.info = src.info
result.typ = src.typ result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags result.flags = src.flags * PersistentNodeFlags
result.comment = src.comment
when defined(useNodeIds): when defined(useNodeIds):
if result.id == nodeIdToDebug: if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id echo "COMES FROM ", src.id
@ -1426,6 +1427,7 @@ proc shallowCopy*(src: PNode): PNode =
result.info = src.info result.info = src.info
result.typ = src.typ result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags result.flags = src.flags * PersistentNodeFlags
result.comment = src.comment
when defined(useNodeIds): when defined(useNodeIds):
if result.id == nodeIdToDebug: if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id echo "COMES FROM ", src.id
@ -1445,6 +1447,7 @@ proc copyTree*(src: PNode): PNode =
result.info = src.info result.info = src.info
result.typ = src.typ result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags result.flags = src.flags * PersistentNodeFlags
result.comment = src.comment
when defined(useNodeIds): when defined(useNodeIds):
if result.id == nodeIdToDebug: if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id echo "COMES FROM ", src.id

View file

@ -50,6 +50,7 @@ Files: "readme.txt;install.txt;contributors.txt;copying.txt"
Files: "makefile" Files: "makefile"
Files: "koch.nim" Files: "koch.nim"
Files: "install_nimble.nims" Files: "install_nimble.nims"
Files: "install_tools.nims"
Files: "icons/nim.ico" Files: "icons/nim.ico"
Files: "icons/nim.rc" Files: "icons/nim.rc"
@ -77,7 +78,8 @@ Files: "lib"
[Other] [Other]
Files: "examples" Files: "examples"
#Files: "dist/nimble" Files: "dist/nimble"
Files: "dist/nimsuggest"
Files: "tests" Files: "tests"
@ -98,8 +100,8 @@ BinPath: r"bin;dist\mingw\bin;dist"
; Section | dir | zipFile | size hint (in KB) | url | exe start menu entry ; Section | dir | zipFile | size hint (in KB) | url | exe start menu entry
Download: r"Documentation|doc|docs.zip|13824|http://nim-lang.org/download/docs-${version}.zip|overview.html" Download: r"Documentation|doc|docs.zip|13824|http://nim-lang.org/download/docs-${version}.zip|overview.html"
Download: r"C Compiler (MingW)|dist|mingw.zip|82944|http://nim-lang.org/download/${mingw}.zip" Download: r"C Compiler (MingW)|dist|mingw.zip|82944|http://nim-lang.org/download/${mingw}.zip"
Download: r"Support DLL's|bin|nim_dlls.zip|479|http://nim-lang.org/download/dlls.zip" Download: r"Support DLLs|bin|nim_dlls.zip|479|http://nim-lang.org/download/dlls.zip"
Download: r"Aporia IDE|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.4.0.zip|aporia-0.4.0\bin\aporia.exe" Download: r"Aporia Text Editor|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.4.0.zip|aporia-0.4.0\bin\aporia.exe"
; for now only NSIS supports optional downloads ; for now only NSIS supports optional downloads
[UnixBin] [UnixBin]

View file

@ -155,7 +155,10 @@ proc discardCheck(c: PContext, result: PNode) =
else: else:
var n = result var n = result
while n.kind in skipForDiscardable: n = n.lastSon while n.kind in skipForDiscardable: n = n.lastSon
localError(n.info, errDiscardValueX, result.typ.typeToString) if result.typ.kind == tyProc:
localError(n.info, "value of type '" & result.typ.typeToString & "' has to be discarded; for a function call use ()")
else:
localError(n.info, errDiscardValueX, result.typ.typeToString)
proc semIf(c: PContext, n: PNode): PNode = proc semIf(c: PContext, n: PNode): PNode =
result = n result = n

View file

@ -171,6 +171,7 @@ proc copyValue(src: PNode): PNode =
result.info = src.info result.info = src.info
result.typ = src.typ result.typ = src.typ
result.flags = src.flags * PersistentNodeFlags result.flags = src.flags * PersistentNodeFlags
result.comment = src.comment
when defined(useNodeIds): when defined(useNodeIds):
if result.id == nodeIdToDebug: if result.id == nodeIdToDebug:
echo "COMES FROM ", src.id echo "COMES FROM ", src.id
@ -1071,7 +1072,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
of opcRepr: of opcRepr:
decodeB(rkNode) decodeB(rkNode)
createStr regs[ra] createStr regs[ra]
regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments}) regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments, renderDocComments})
of opcQuit: of opcQuit:
if c.mode in {emRepl, emStaticExpr, emStaticStmt}: if c.mode in {emRepl, emStaticExpr, emStaticStmt}:
message(c.debug[pc], hintQuitCalled) message(c.debug[pc], hintQuitCalled)

View file

@ -66,7 +66,7 @@ path="$lib/pure"
@end @end
@if unix: @if unix:
@if not bsd: @if not bsd or haiku:
# -fopenmp # -fopenmp
gcc.options.linker = "-ldl" gcc.options.linker = "-ldl"
gcc.cpp.options.linker = "-ldl" gcc.cpp.options.linker = "-ldl"
@ -80,6 +80,14 @@ path="$lib/pure"
# at least NetBSD has problems with thread local storage: # at least NetBSD has problems with thread local storage:
tlsEmulation:on tlsEmulation:on
@end @end
@if haiku:
# -fopenmp
gcc.options.linker = "-lroot -lnetwork"
gcc.cpp.options.linker = "-lroot -lnetwork"
clang.options.linker = "-lroot -lnetwork"
clang.cpp.options.linker = "-lroot -lnetwork"
tcc.options.linker = "-lroot -lnetwork"
@end
@end @end
# Configuration for the Intel C/C++ compiler: # Configuration for the Intel C/C++ compiler:

View file

@ -30,6 +30,15 @@ There are also ``install.sh`` and ``deinstall.sh`` scripts for distributing
the files over the UNIX hierarchy. However, updating your Nim installation the files over the UNIX hierarchy. However, updating your Nim installation
is more cumbersome then. is more cumbersome then.
To complete the installation you should also build Nim's tools like
``nimsuggest``, ``nimble`` or ``nimgrep`` via::
nim e install_tools.nims
Note that these tools should also end up in your ``PATH`` so adding
``$your_install_dir/bin/nim`` to your ``PATH`` is preferred over the symlink
solution.
Installation on the Macintosh Installation on the Macintosh
----------------------------- -----------------------------

15
install_tools.nims Normal file
View file

@ -0,0 +1,15 @@
import ospaths
mode = ScriptMode.Verbose
let nimbleExe = "./bin/nimble".toExe
selfExec "c --noNimblePath -p:compiler -o:" & nimbleExe &
" dist/nimble/src/nimble.nim"
let nimsugExe = "./bin/nimsuggest".toExe
selfExec "c --noNimblePath -p:compiler -o:" & nimsugExe &
" dist/nimsuggest/nimsuggest.nim"
let nimgrepExe = "./bin/nimgrep".toExe
selfExec "c -o:./bin/nimgrep tools/nimgrep.nim"

View file

@ -123,6 +123,8 @@ proc testUnixInstall() =
execCleanPath("./koch boot -d:release", destDir / "bin") execCleanPath("./koch boot -d:release", destDir / "bin")
# check the docs build: # check the docs build:
execCleanPath("./koch web", destDir / "bin") execCleanPath("./koch web", destDir / "bin")
# check nimble builds:
execCleanPath("./bin/nim e install_tools.nims")
# check the tests work: # check the tests work:
execCleanPath("./koch tests", destDir / "bin") execCleanPath("./koch tests", destDir / "bin")
else: else:
@ -149,7 +151,7 @@ proc csource(args: string) =
exec("$4 cc $1 -r $3 --var:version=$2 --var:mingw=none csource --main:compiler/nim.nim compiler/installer.ini $1" % exec("$4 cc $1 -r $3 --var:version=$2 --var:mingw=none csource --main:compiler/nim.nim compiler/installer.ini $1" %
[args, VersionAsString, compileNimInst, findNim()]) [args, VersionAsString, compileNimInst, findNim()])
proc bundleNimble() = proc bundleNimbleSrc() =
if dirExists("dist/nimble/.git"): if dirExists("dist/nimble/.git"):
exec("git --git-dir dist/nimble/.git pull") exec("git --git-dir dist/nimble/.git pull")
else: else:
@ -157,20 +159,33 @@ proc bundleNimble() =
let tags = execProcess("git --git-dir dist/nimble/.git tag -l v*").splitLines let tags = execProcess("git --git-dir dist/nimble/.git tag -l v*").splitLines
let tag = tags[^1] let tag = tags[^1]
exec("git --git-dir dist/nimble/.git checkout " & tag) exec("git --git-dir dist/nimble/.git checkout " & tag)
proc bundleNimbleExe() =
bundleNimbleSrc()
# now compile Nimble and copy it to $nim/bin for the installer.ini # now compile Nimble and copy it to $nim/bin for the installer.ini
# to pick it up: # to pick it up:
exec(findNim() & " c dist/nimble/src/nimble.nim") exec(findNim() & " c dist/nimble/src/nimble.nim")
copyExe("dist/nimble/src/nimble".exe, "bin/nimble".exe) copyExe("dist/nimble/src/nimble".exe, "bin/nimble".exe)
proc bundleNimsuggest(buildExe: bool) =
if dirExists("dist/nimsuggest/.git"):
exec("git --git-dir dist/nimsuggest/.git pull")
else:
exec("git clone https://github.com/nim-lang/nimsuggest.git dist/nimsuggest")
if buildExe:
exec(findNim() & " c --noNimblePath -p:compiler dist/nimsuggest/nimsuggest.nim")
copyExe("dist/nimsuggest/nimsuggest".exe, "bin/nimsuggest".exe)
proc zip(args: string) = proc zip(args: string) =
bundleNimble() bundleNimbleSrc()
exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" % exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
[VersionAsString, compileNimInst, findNim()]) [VersionAsString, compileNimInst, findNim()])
exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim zip compiler/installer.ini" % exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim zip compiler/installer.ini" %
["tools/niminst/niminst".exe, VersionAsString]) ["tools/niminst/niminst".exe, VersionAsString])
proc xz(args: string) = proc xz(args: string) =
bundleNimble() bundleNimbleSrc()
bundleNimsuggest(false)
exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" % exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" %
[VersionAsString, compileNimInst, findNim()]) [VersionAsString, compileNimInst, findNim()])
exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim xz compiler/installer.ini" % exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim xz compiler/installer.ini" %
@ -181,7 +196,8 @@ proc buildTool(toolname, args: string) =
copyFile(dest="bin"/ splitFile(toolname).name.exe, source=toolname.exe) copyFile(dest="bin"/ splitFile(toolname).name.exe, source=toolname.exe)
proc nsis(args: string) = proc nsis(args: string) =
bundleNimble() bundleNimbleExe()
bundleNimsuggest(true)
# make sure we have generated the niminst executables: # make sure we have generated the niminst executables:
buildTool("tools/niminst/niminst", args) buildTool("tools/niminst/niminst", args)
#buildTool("tools/nimgrep", args) #buildTool("tools/nimgrep", args)

View file

@ -6,6 +6,9 @@
# 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.
# #
# Architecture-specific optimizations and features.
# arch.nim can be imported by only a subset of the
# architectures supported by Nim.
when defined(windows): when defined(windows):
const const

View file

@ -359,7 +359,7 @@ proc parallelReplace*(s: string, subs: openArray[
proc transformFile*(infile, outfile: string, proc transformFile*(infile, outfile: string,
subs: openArray[tuple[pattern: Regex, repl: string]]) = subs: openArray[tuple[pattern: Regex, repl: string]]) =
## reads in the file `infile`, performs a parallel replacement (calls ## reads in the file `infile`, performs a parallel replacement (calls
## `parallelReplace`) and writes back to `outfile`. Raises ``EIO`` if an ## `parallelReplace`) and writes back to `outfile`. Raises ``IOError`` if an
## error occurs. This is supposed to be used for quick scripting. ## error occurs. This is supposed to be used for quick scripting.
var x = readFile(infile).string var x = readFile(infile).string
writeFile(outfile, x.parallelReplace(subs)) writeFile(outfile, x.parallelReplace(subs))

View file

@ -156,290 +156,10 @@ export Port, SocketFlag
## * Can't await in a ``except`` body ## * Can't await in a ``except`` body
## * Forward declarations for async procs are broken, ## * Forward declarations for async procs are broken,
## link includes workaround: https://github.com/nim-lang/Nim/issues/3182. ## link includes workaround: https://github.com/nim-lang/Nim/issues/3182.
## * FutureVar[T] needs to be completed manually.
# TODO: Check if yielded future is nil and throw a more meaningful exception # TODO: Check if yielded future is nil and throw a more meaningful exception
# -- Futures include includes/asyncfutures
type
FutureBase* = ref object of RootObj ## Untyped future.
cb: proc () {.closure,gcsafe.}
finished: bool
error*: ref Exception ## Stored exception
errorStackTrace*: string
when not defined(release):
stackTrace: string ## For debugging purposes only.
id: int
fromProc: string
Future*[T] = ref object of FutureBase ## Typed future.
value: T ## Stored value
FutureVar*[T] = distinct Future[T]
FutureError* = object of Exception
cause*: FutureBase
{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
when not defined(release):
var currentID = 0
proc callSoon*(cbproc: proc ()) {.gcsafe.}
proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
## Creates a new future.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
new(result)
result.finished = false
when not defined(release):
result.stackTrace = getStackTrace()
result.id = currentID
result.fromProc = fromProc
currentID.inc()
proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
## Create a new ``FutureVar``. This Future type is ideally suited for
## situations where you want to avoid unnecessary allocations of Futures.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
result = FutureVar[T](newFuture[T](fromProc))
proc clean*[T](future: FutureVar[T]) =
## Resets the ``finished`` status of ``future``.
Future[T](future).finished = false
Future[T](future).error = nil
proc checkFinished[T](future: Future[T]) =
## Checks whether `future` is finished. If it is then raises a
## ``FutureError``.
when not defined(release):
if future.finished:
var msg = ""
msg.add("An attempt was made to complete a Future more than once. ")
msg.add("Details:")
msg.add("\n Future ID: " & $future.id)
msg.add("\n Created in proc: " & future.fromProc)
msg.add("\n Stack trace to moment of creation:")
msg.add("\n" & indent(future.stackTrace.strip(), 4))
when T is string:
msg.add("\n Contents (string): ")
msg.add("\n" & indent(future.value.repr, 4))
msg.add("\n Stack trace to moment of secondary completion:")
msg.add("\n" & indent(getStackTrace().strip(), 4))
var err = newException(FutureError, msg)
err.cause = future
raise err
proc complete*[T](future: Future[T], val: T) =
## Completes ``future`` with value ``val``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
assert(future.error == nil)
future.value = val
future.finished = true
if future.cb != nil:
future.cb()
proc complete*(future: Future[void]) =
## Completes a void ``future``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
assert(future.error == nil)
future.finished = true
if future.cb != nil:
future.cb()
proc complete*[T](future: FutureVar[T]) =
## Completes a ``FutureVar``.
template fut: expr = Future[T](future)
checkFinished(fut)
assert(fut.error == nil)
fut.finished = true
if fut.cb != nil:
fut.cb()
proc fail*[T](future: Future[T], error: ref Exception) =
## Completes ``future`` with ``error``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
future.finished = true
future.error = error
future.errorStackTrace =
if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error)
if future.cb != nil:
future.cb()
else:
# This is to prevent exceptions from being silently ignored when a future
# is discarded.
# TODO: This may turn out to be a bad idea.
# Turns out this is a bad idea.
#raise error
discard
proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) =
## Sets the callback proc to be called when the future completes.
##
## If future has already completed then ``cb`` will be called immediately.
##
## **Note**: You most likely want the other ``callback`` setter which
## passes ``future`` as a param to the callback.
future.cb = cb
if future.finished:
callSoon(future.cb)
proc `callback=`*[T](future: Future[T],
cb: proc (future: Future[T]) {.closure,gcsafe.}) =
## Sets the callback proc to be called when the future completes.
##
## If future has already completed then ``cb`` will be called immediately.
future.callback = proc () = cb(future)
proc injectStacktrace[T](future: Future[T]) =
# TODO: Come up with something better.
when not defined(release):
var msg = ""
msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
if not future.errorStackTrace.isNil and future.errorStackTrace != "":
msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
else:
msg.add("\n Empty or nil stack trace.")
future.error.msg.add(msg)
proc read*[T](future: Future[T]): T =
## Retrieves the value of ``future``. Future must be finished otherwise
## this function will fail with a ``ValueError`` exception.
##
## If the result of the future is an error then that error will be raised.
if future.finished:
if future.error != nil:
injectStacktrace(future)
raise future.error
when T isnot void:
return future.value
else:
# TODO: Make a custom exception type for this?
raise newException(ValueError, "Future still in progress.")
proc readError*[T](future: Future[T]): ref Exception =
## Retrieves the exception stored in ``future``.
##
## An ``ValueError`` exception will be thrown if no exception exists
## in the specified Future.
if future.error != nil: return future.error
else:
raise newException(ValueError, "No error in future.")
proc mget*[T](future: FutureVar[T]): var T =
## Returns a mutable value stored in ``future``.
##
## Unlike ``read``, this function will not raise an exception if the
## Future has not been finished.
result = Future[T](future).value
proc finished*[T](future: Future[T]): bool =
## Determines whether ``future`` has completed.
##
## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
future.finished
proc failed*(future: FutureBase): bool =
## Determines whether ``future`` completed with an error.
return future.error != nil
proc asyncCheck*[T](future: Future[T]) =
## Sets a callback on ``future`` which raises an exception if the future
## finished with an error.
##
## This should be used instead of ``discard`` to discard void futures.
future.callback =
proc () =
if future.failed:
injectStacktrace(future)
raise future.error
proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
## Returns a future which will complete once both ``fut1`` and ``fut2``
## complete.
var retFuture = newFuture[void]("asyncdispatch.`and`")
fut1.callback =
proc () =
if not retFuture.finished:
if fut1.failed: retFuture.fail(fut1.error)
elif fut2.finished: retFuture.complete()
fut2.callback =
proc () =
if not retFuture.finished:
if fut2.failed: retFuture.fail(fut2.error)
elif fut1.finished: retFuture.complete()
return retFuture
proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
## Returns a future which will complete once either ``fut1`` or ``fut2``
## complete.
var retFuture = newFuture[void]("asyncdispatch.`or`")
proc cb[X](fut: Future[X]) =
if fut.failed: retFuture.fail(fut.error)
if not retFuture.finished: retFuture.complete()
fut1.callback = cb[T]
fut2.callback = cb[Y]
return retFuture
proc all*[T](futs: varargs[Future[T]]): auto =
## Returns a future which will complete once
## all futures in ``futs`` complete.
##
## If the awaited futures are not ``Future[void]``, the returned future
## will hold the values of all awaited futures in a sequence.
##
## If the awaited futures *are* ``Future[void]``,
## this proc returns ``Future[void]``.
when T is void:
var
retFuture = newFuture[void]("asyncdispatch.all")
completedFutures = 0
let totalFutures = len(futs)
for fut in futs:
fut.callback = proc(f: Future[T]) =
if f.failed:
retFuture.fail(f.error)
elif not retFuture.finished:
inc(completedFutures)
if completedFutures == totalFutures:
retFuture.complete()
return retFuture
else:
var
retFuture = newFuture[seq[T]]("asyncdispatch.all")
retValues = newSeq[T](len(futs))
completedFutures = 0
for i, fut in futs:
proc setCallback(i: int) =
fut.callback = proc(f: Future[T]) =
if f.failed:
retFuture.fail(f.error)
elif not retFuture.finished:
retValues[i] = f.read()
inc(completedFutures)
if completedFutures == len(retValues):
retFuture.complete(retValues)
setCallback(i)
return retFuture
type type
PDispatcherBase = ref object of RootRef PDispatcherBase = ref object of RootRef
@ -1646,7 +1366,7 @@ proc send*(socket: AsyncFD, data: string,
# -- Await Macro # -- Await Macro
include asyncmacro include asyncmacro
proc recvLine*(socket: AsyncFD): Future[string] {.async.} = proc recvLine*(socket: AsyncFD): Future[string] {.async, deprecated.} =
## Reads a line of data from ``socket``. Returned future will complete once ## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs. ## a full line is read or an error occurs.
## ##
@ -1664,6 +1384,8 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
## ##
## **Note**: This procedure is mostly used for testing. You likely want to ## **Note**: This procedure is mostly used for testing. You likely want to
## use ``asyncnet.recvLine`` instead. ## use ``asyncnet.recvLine`` instead.
##
## **Deprecated since version 0.15.0**: Use ``asyncnet.recvLine()`` instead.
template addNLIfEmpty(): stmt = template addNLIfEmpty(): stmt =
if result.len == 0: if result.len == 0:

View file

@ -360,6 +360,14 @@ proc rename*(ftp: AsyncFtpClient, nameFrom: string, nameTo: string) {.async.} =
assertReply(await ftp.send("RNFR " & name_from), "350") assertReply(await ftp.send("RNFR " & name_from), "350")
assertReply(await ftp.send("RNTO " & name_to), "250") assertReply(await ftp.send("RNTO " & name_to), "250")
proc removeFile*(ftp: AsyncFtpClient, filename: string) {.async.} =
## Delete a file ``filename`` on the remote FTP server
assertReply(await ftp.send("DELE " & filename), "250")
proc removeDir*(ftp: AsyncFtpClient, dir: string) {.async.} =
## Delete a directory ``dir`` on the remote FTP server
assertReply(await ftp.send("RMD " & dir), "250")
proc newAsyncFtpClient*(address: string, port = Port(21), proc newAsyncFtpClient*(address: string, port = Port(21),
user, pass = ""): AsyncFtpClient = user, pass = ""): AsyncFtpClient =
## Creates a new ``AsyncFtpClient`` object. ## Creates a new ``AsyncFtpClient`` object.
@ -380,6 +388,10 @@ when not defined(testing) and isMainModule:
await ftp.store("payload.jpg", "payload.jpg") await ftp.store("payload.jpg", "payload.jpg")
await ftp.retrFile("payload.jpg", "payload2.jpg") await ftp.retrFile("payload.jpg", "payload2.jpg")
await ftp.rename("payload.jpg", "payload_renamed.jpg") await ftp.rename("payload.jpg", "payload_renamed.jpg")
await ftp.store("payload.jpg", "payload_remove.jpg")
await ftp.removeFile("payload_remove.jpg")
await ftp.createDir("deleteme")
await ftp.removeDir("deleteme")
echo("Finished") echo("Finished")
waitFor main(ftp) waitFor main(ftp)

View file

@ -9,6 +9,12 @@
## This module implements a high performance asynchronous HTTP server. ## This module implements a high performance asynchronous HTTP server.
## ##
## This HTTP server has not been designed to be used in production, but
## for testing applications locally. Because of this, when deploying your
## application you should use a reverse proxy (for example nginx) instead of
## allowing users to connect directly to this server.
##
##
## Examples ## Examples
## -------- ## --------
## ##
@ -38,7 +44,7 @@ export httpcore except parseHeader
type type
Request* = object Request* = object
client*: AsyncSocket # TODO: Separate this into a Response object? client*: AsyncSocket # TODO: Separate this into a Response object?
reqMethod*: string reqMethod*: HttpMethod
headers*: HttpHeaders headers*: HttpHeaders
protocol*: tuple[orig: string, major, minor: int] protocol*: tuple[orig: string, major, minor: int]
url*: Uri url*: Uri
@ -127,7 +133,14 @@ proc processClient(client: AsyncSocket, address: string,
var i = 0 var i = 0
for linePart in lineFut.mget.split(' '): for linePart in lineFut.mget.split(' '):
case i case i
of 0: request.reqMethod.shallowCopy(linePart.normalize) of 0:
try:
# TODO: this is likely slow.
request.reqMethod = parseEnum[HttpMethod]("http" & linePart)
except ValueError:
asyncCheck request.respond(Http400, "Invalid request method. Got: " &
linePart)
continue
of 1: parseUri(linePart, request.url) of 1: parseUri(linePart, request.url)
of 2: of 2:
try: try:
@ -159,7 +172,7 @@ proc processClient(client: AsyncSocket, address: string,
request.client.close() request.client.close()
return return
if request.reqMethod == "post": if request.reqMethod == HttpPost:
# Check for Expect header # Check for Expect header
if request.headers.hasKey("Expect"): if request.headers.hasKey("Expect"):
if "100-continue" in request.headers["Expect"]: if "100-continue" in request.headers["Expect"]:
@ -178,17 +191,12 @@ proc processClient(client: AsyncSocket, address: string,
else: else:
request.body = await client.recv(contentLength) request.body = await client.recv(contentLength)
assert request.body.len == contentLength assert request.body.len == contentLength
elif request.reqMethod == "post": elif request.reqMethod == HttpPost:
await request.respond(Http400, "Bad Request. No Content-Length.") await request.respond(Http400, "Bad Request. No Content-Length.")
continue continue
case request.reqMethod # Call the user's callback.
of "get", "post", "head", "put", "delete", "trace", "options", await callback(request)
"connect", "patch":
await callback(request)
else:
await request.respond(Http400, "Invalid request method. Got: " &
request.reqMethod)
if "upgrade" in request.headers.getOrDefault("connection"): if "upgrade" in request.headers.getOrDefault("connection"):
return return

View file

@ -25,7 +25,7 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
result = node[0] result = node[0]
template createCb(retFutureSym, iteratorNameSym, template createCb(retFutureSym, iteratorNameSym,
name: untyped) = name, futureVarCompletions: untyped) =
var nameIterVar = iteratorNameSym var nameIterVar = iteratorNameSym
#{.push stackTrace: off.} #{.push stackTrace: off.}
proc cb {.closure,gcsafe.} = proc cb {.closure,gcsafe.} =
@ -44,6 +44,8 @@ template createCb(retFutureSym, iteratorNameSym,
raise raise
else: else:
retFutureSym.fail(getCurrentException()) retFutureSym.fail(getCurrentException())
futureVarCompletions
cb() cb()
#{.pop.} #{.pop.}
proc generateExceptionCheck(futSym, proc generateExceptionCheck(futSym,
@ -119,8 +121,22 @@ template createVar(result: var NimNode, futSymName: string,
result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
useVar(result, futSym, valueReceiver, rootReceiver, fromNode) useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
proc createFutureVarCompletions(futureVarIdents: seq[NimNode]): NimNode
{.compileTime.} =
result = newStmtList()
# Add calls to complete each FutureVar parameter.
for ident in futureVarIdents:
# Only complete them if they have not been completed already by the user.
result.add newIfStmt(
(
newCall(newIdentNode("not"),
newDotExpr(ident, newIdentNode("finished"))),
newCall(newIdentNode("complete"), ident)
)
)
proc processBody(node, retFutureSym: NimNode, proc processBody(node, retFutureSym: NimNode,
subTypeIsVoid: bool, subTypeIsVoid: bool, futureVarIdents: seq[NimNode],
tryStmt: NimNode): NimNode {.compileTime.} = tryStmt: NimNode): NimNode {.compileTime.} =
#echo(node.treeRepr) #echo(node.treeRepr)
result = node result = node
@ -134,11 +150,14 @@ proc processBody(node, retFutureSym: NimNode,
else: else:
result.add newCall(newIdentNode("complete"), retFutureSym) result.add newCall(newIdentNode("complete"), retFutureSym)
else: else:
let x = node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt) let x = node[0].processBody(retFutureSym, subTypeIsVoid,
futureVarIdents, tryStmt)
if x.kind == nnkYieldStmt: result.add x if x.kind == nnkYieldStmt: result.add x
else: else:
result.add newCall(newIdentNode("complete"), retFutureSym, x) result.add newCall(newIdentNode("complete"), retFutureSym, x)
result.add createFutureVarCompletions(futureVarIdents)
result.add newNimNode(nnkReturnStmt, node).add(newNilLit()) result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
return # Don't process the children of this return stmt return # Don't process the children of this return stmt
of nnkCommand, nnkCall: of nnkCommand, nnkCall:
@ -196,7 +215,8 @@ proc processBody(node, retFutureSym: NimNode,
# Transform ``except`` body. # Transform ``except`` body.
# TODO: Could we perform some ``await`` transformation here to get it # TODO: Could we perform some ``await`` transformation here to get it
# working in ``except``? # working in ``except``?
tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, nil) tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid,
futureVarIdents, nil)
proc processForTry(n: NimNode, i: var int, proc processForTry(n: NimNode, i: var int,
res: NimNode): bool {.compileTime.} = res: NimNode): bool {.compileTime.} =
@ -207,7 +227,7 @@ proc processBody(node, retFutureSym: NimNode,
var skipped = n.skipStmtList() var skipped = n.skipStmtList()
while i < skipped.len: while i < skipped.len:
var processed = processBody(skipped[i], retFutureSym, var processed = processBody(skipped[i], retFutureSym,
subTypeIsVoid, n) subTypeIsVoid, futureVarIdents, n)
# Check if we transformed the node into an exception check. # Check if we transformed the node into an exception check.
# This suggests skipped[i] contains ``await``. # This suggests skipped[i] contains ``await``.
@ -239,7 +259,8 @@ proc processBody(node, retFutureSym: NimNode,
else: discard else: discard
for i in 0 .. <result.len: for i in 0 .. <result.len:
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, nil) result[i] = processBody(result[i], retFutureSym, subTypeIsVoid,
futureVarIdents, nil)
proc getName(node: NimNode): string {.compileTime.} = proc getName(node: NimNode): string {.compileTime.} =
case node.kind case node.kind
@ -252,6 +273,14 @@ proc getName(node: NimNode): string {.compileTime.} =
else: else:
error("Unknown name.") error("Unknown name.")
proc getFutureVarIdents(params: NimNode): seq[NimNode] {.compileTime.} =
result = @[]
for i in 1 .. <len(params):
expectKind(params[i], nnkIdentDefs)
if params[i][1].kind == nnkBracketExpr and
($params[i][1][0].ident).normalize == "futurevar":
result.add(params[i][0])
proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
## This macro transforms a single procedure into a closure iterator. ## This macro transforms a single procedure into a closure iterator.
## The ``async`` macro supports a stmtList holding multiple async procedures. ## The ``async`` macro supports a stmtList holding multiple async procedures.
@ -282,6 +311,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
let subtypeIsVoid = returnType.kind == nnkEmpty or let subtypeIsVoid = returnType.kind == nnkEmpty or
(baseType.kind == nnkIdent and returnType[1].ident == !"void") (baseType.kind == nnkIdent and returnType[1].ident == !"void")
let futureVarIdents = getFutureVarIdents(prc[3])
var outerProcBody = newNimNode(nnkStmtList, prc[6]) var outerProcBody = newNimNode(nnkStmtList, prc[6])
# -> var retFuture = newFuture[T]() # -> var retFuture = newFuture[T]()
@ -304,7 +335,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
# -> <proc_body> # -> <proc_body>
# -> complete(retFuture, result) # -> complete(retFuture, result)
var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter") var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil) var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid,
futureVarIdents, nil)
# don't do anything with forward bodies (empty) # don't do anything with forward bodies (empty)
if procBody.kind != nnkEmpty: if procBody.kind != nnkEmpty:
if not subtypeIsVoid: if not subtypeIsVoid:
@ -326,6 +358,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
# -> complete(retFuture) # -> complete(retFuture)
procBody.add(newCall(newIdentNode("complete"), retFutureSym)) procBody.add(newCall(newIdentNode("complete"), retFutureSym))
procBody.add(createFutureVarCompletions(futureVarIdents))
var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")], var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
procBody, nnkIteratorDef) procBody, nnkIteratorDef)
closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure")) closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
@ -334,7 +368,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
# -> createCb(retFuture) # -> createCb(retFuture)
#var cbName = newIdentNode("cb") #var cbName = newIdentNode("cb")
var procCb = getAst createCb(retFutureSym, iteratorNameSym, var procCb = getAst createCb(retFutureSym, iteratorNameSym,
newStrLitNode(prc[0].getName)) newStrLitNode(prc[0].getName),
createFutureVarCompletions(futureVarIdents))
outerProcBody.add procCb outerProcBody.add procCb
# -> return retFuture # -> return retFuture

View file

@ -159,7 +159,9 @@ when defineSsl:
await socket.fd.AsyncFd.send(data, flags) await socket.fd.AsyncFd.send(data, flags)
proc appeaseSsl(socket: AsyncSocket, flags: set[SocketFlag], proc appeaseSsl(socket: AsyncSocket, flags: set[SocketFlag],
sslError: cint) {.async.} = sslError: cint): Future[bool] {.async.} =
## Returns ``true`` if ``socket`` is still connected, otherwise ``false``.
result = true
case sslError case sslError
of SSL_ERROR_WANT_WRITE: of SSL_ERROR_WANT_WRITE:
await sendPendingSslData(socket, flags) await sendPendingSslData(socket, flags)
@ -173,6 +175,7 @@ when defineSsl:
elif length == 0: elif length == 0:
# connection not properly closed by remote side or connection dropped # connection not properly closed by remote side or connection dropped
SSL_set_shutdown(socket.sslHandle, SSL_RECEIVED_SHUTDOWN) SSL_set_shutdown(socket.sslHandle, SSL_RECEIVED_SHUTDOWN)
result = false
else: else:
raiseSSLError("Cannot appease SSL.") raiseSSLError("Cannot appease SSL.")
@ -180,13 +183,27 @@ when defineSsl:
op: expr) = op: expr) =
var opResult {.inject.} = -1.cint var opResult {.inject.} = -1.cint
while opResult < 0: while opResult < 0:
# Call the desired operation.
opResult = op opResult = op
# Bit hackish here. # Bit hackish here.
# TODO: Introduce an async template transformation pragma? # TODO: Introduce an async template transformation pragma?
# Send any remaining pending SSL data.
yield sendPendingSslData(socket, flags) yield sendPendingSslData(socket, flags)
# If the operation failed, try to see if SSL has some data to read
# or write.
if opResult < 0: if opResult < 0:
let err = getSslError(socket.sslHandle, opResult.cint) let err = getSslError(socket.sslHandle, opResult.cint)
yield appeaseSsl(socket, flags, err.cint) let fut = appeaseSsl(socket, flags, err.cint)
yield fut
if not fut.read():
# Socket disconnected.
if SocketFlag.SafeDisconn in flags:
break
else:
raiseSSLError("Socket has been disconnected")
proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} = proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
## Connects ``socket`` to server at ``address:port``. ## Connects ``socket`` to server at ``address:port``.
@ -388,7 +405,7 @@ proc accept*(socket: AsyncSocket,
return retFut return retFut
proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string], proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
flags = {SocketFlag.SafeDisconn}) {.async.} = flags = {SocketFlag.SafeDisconn}, maxLength = MaxLineLength) {.async.} =
## Reads a line of data from ``socket`` into ``resString``. ## Reads a line of data from ``socket`` into ``resString``.
## ##
## If a full line is read ``\r\L`` is not ## If a full line is read ``\r\L`` is not
@ -401,13 +418,14 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
## is read) then line will be set to ``""``. ## is read) then line will be set to ``""``.
## The partial line **will be lost**. ## The partial line **will be lost**.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial
## of Service (DOS) attacks.
##
## **Warning**: The ``Peek`` flag is not yet implemented. ## **Warning**: The ``Peek`` flag is not yet implemented.
## ##
## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the ## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the
## protocol uses ``\r\L`` to delimit a new line. ## protocol uses ``\r\L`` to delimit a new line.
##
## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for
## performance reasons. This will likely change soon to use FutureVars.
assert SocketFlag.Peek notin flags ## TODO: assert SocketFlag.Peek notin flags ## TODO:
assert(not resString.mget.isNil(), assert(not resString.mget.isNil(),
"String inside resString future needs to be initialised") "String inside resString future needs to be initialised")
@ -454,6 +472,12 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
else: else:
resString.mget.add socket.buffer[socket.currPos] resString.mget.add socket.buffer[socket.currPos]
socket.currPos.inc() socket.currPos.inc()
# Verify that this isn't a DOS attack: #3847.
if resString.mget.len > maxLength:
let msg = "recvLine received more than the specified `maxLength` " &
"allowed."
raise newException(ValueError, msg)
else: else:
var c = "" var c = ""
while true: while true:
@ -475,10 +499,17 @@ proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string],
resString.complete() resString.complete()
return return
resString.mget.add c resString.mget.add c
# Verify that this isn't a DOS attack: #3847.
if resString.mget.len > maxLength:
let msg = "recvLine received more than the specified `maxLength` " &
"allowed."
raise newException(ValueError, msg)
resString.complete() resString.complete()
proc recvLine*(socket: AsyncSocket, proc recvLine*(socket: AsyncSocket,
flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} = flags = {SocketFlag.SafeDisconn},
maxLength = MaxLineLength): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once ## Reads a line of data from ``socket``. Returned future will complete once
## a full line is read or an error occurs. ## a full line is read or an error occurs.
## ##
@ -492,6 +523,10 @@ proc recvLine*(socket: AsyncSocket,
## is read) then line will be set to ``""``. ## is read) then line will be set to ``""``.
## The partial line **will be lost**. ## The partial line **will be lost**.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial
## of Service (DOS) attacks.
##
## **Warning**: The ``Peek`` flag is not yet implemented. ## **Warning**: The ``Peek`` flag is not yet implemented.
## ##
## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol ## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol
@ -501,7 +536,7 @@ proc recvLine*(socket: AsyncSocket,
# TODO: Optimise this # TODO: Optimise this
var resString = newFutureVar[string]("asyncnet.recvLine") var resString = newFutureVar[string]("asyncnet.recvLine")
resString.mget() = "" resString.mget() = ""
await socket.recvLineInto(resString, flags) await socket.recvLineInto(resString, flags, maxLength)
result = resString.mget() result = resString.mget()
proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} = proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =

View file

@ -117,13 +117,13 @@ proc safeArccos(v:float):float=
template makeBinOpVector(s:expr)= template makeBinOpVector(s:expr)=
## implements binary operators + , - , * and / for vectors ## implements binary operators ``+``, ``-``, ``*`` and ``/`` for vectors
proc s*(a,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y)) proc s*(a,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a.x,b.x),s(a.y,b.y))
proc s*(a:Vector2d,b:float):Vector2d {.inline,noInit.} = vector2d(s(a.x,b),s(a.y,b)) proc s*(a:Vector2d,b:float):Vector2d {.inline,noInit.} = vector2d(s(a.x,b),s(a.y,b))
proc s*(a:float,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a,b.x),s(a,b.y)) proc s*(a:float,b:Vector2d):Vector2d {.inline,noInit.} = vector2d(s(a,b.x),s(a,b.y))
template makeBinOpAssignVector(s:expr)= template makeBinOpAssignVector(s:expr)=
## implements inplace binary operators += , -= , /= and *= for vectors ## implements inplace binary operators ``+=``, ``-=``, ``/=`` and ``*=`` for vectors
proc s*(a:var Vector2d,b:Vector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y) proc s*(a:var Vector2d,b:Vector2d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y)
proc s*(a:var Vector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b) proc s*(a:var Vector2d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b)

View file

@ -117,7 +117,6 @@ proc safeArccos(v:float):float=
return arccos(clamp(v,-1.0,1.0)) return arccos(clamp(v,-1.0,1.0))
template makeBinOpVector(s:expr)= template makeBinOpVector(s:expr)=
## implements binary operators + , - , * and / for vectors
proc s*(a,b:Vector3d):Vector3d {.inline,noInit.} = proc s*(a,b:Vector3d):Vector3d {.inline,noInit.} =
vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z)) vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z))
proc s*(a:Vector3d,b:float):Vector3d {.inline,noInit.} = proc s*(a:Vector3d,b:float):Vector3d {.inline,noInit.} =
@ -126,11 +125,10 @@ template makeBinOpVector(s:expr)=
vector3d(s(a,b.x),s(a,b.y),s(a,b.z)) vector3d(s(a,b.x),s(a,b.y),s(a,b.z))
template makeBinOpAssignVector(s:expr)= template makeBinOpAssignVector(s:expr)=
## implements inplace binary operators += , -= , /= and *= for vectors
proc s*(a:var Vector3d,b:Vector3d) {.inline.} = proc s*(a:var Vector3d,b:Vector3d) {.inline.} =
s(a.x,b.x) ; s(a.y,b.y) ; s(a.z,b.z) s(a.x,b.x); s(a.y,b.y); s(a.z,b.z)
proc s*(a:var Vector3d,b:float) {.inline.} = proc s*(a:var Vector3d,b:float) {.inline.} =
s(a.x,b) ; s(a.y,b) ; s(a.z,b) s(a.x,b); s(a.y,b); s(a.z,b)

View file

@ -8,79 +8,102 @@
# #
## This module implements a simple HTTP client that can be used to retrieve ## This module implements a simple HTTP client that can be used to retrieve
## webpages/other data. ## webpages and other data.
##
##
## **Note**: This module is not ideal, connection is not kept alive so sites with
## many redirects are expensive. As such in the future this module may change,
## and the current procedures will be deprecated.
## ##
## Retrieving a website ## Retrieving a website
## ==================== ## ====================
## ##
## This example uses HTTP GET to retrieve ## This example uses HTTP GET to retrieve
## ``http://google.com`` ## ``http://google.com``:
## ##
## .. code-block:: Nim ## .. code-block:: Nim
## var client = newHttpClient()
## echo(getContent("http://google.com")) ## echo(getContent("http://google.com"))
## ##
## The same action can also be performed asynchronously, simply use the
## ``AsyncHttpClient``:
##
## .. code-block:: Nim
## var client = newAsyncHttpClient()
## echo(await getContent("http://google.com"))
##
## The functionality implemented by ``HttpClient`` and ``AsyncHttpClient``
## is the same, so you can use whichever one suits you best in the examples
## shown here.
##
## **Note:** You will need to run asynchronous examples in an async proc
## otherwise you will get an ``Undeclared identifier: 'await'`` error.
##
## Using HTTP POST ## Using HTTP POST
## =============== ## ===============
## ##
## This example demonstrates the usage of the W3 HTML Validator, it ## This example demonstrates the usage of the W3 HTML Validator, it
## uses ``multipart/form-data`` as the ``Content-Type`` to send the HTML to ## uses ``multipart/form-data`` as the ``Content-Type`` to send the HTML to be
## the server. ## validated to the server.
## ##
## .. code-block:: Nim ## .. code-block:: Nim
## var client = newHttpClient()
## var data = newMultipartData() ## var data = newMultipartData()
## data["output"] = "soap12" ## data["output"] = "soap12"
## data["uploaded_file"] = ("test.html", "text/html", ## data["uploaded_file"] = ("test.html", "text/html",
## "<html><head></head><body><p>test</p></body></html>") ## "<html><head></head><body><p>test</p></body></html>")
## ##
## echo postContent("http://validator.w3.org/check", multipart=data) ## echo client.postContent("http://validator.w3.org/check", multipart=data)
## ##
## Asynchronous HTTP requests ## Progress reporting
## ========================== ## ==================
## ##
## You simply have to create a new instance of the ``AsyncHttpClient`` object. ## You may specify a callback procedure to be called during an HTTP request.
## You may then use ``await`` on the functions defined for that object. ## This callback will be executed every second with information about the
## Keep in mind that the following code needs to be inside an asynchronous ## progress of the HTTP request.
## procedure.
##
## .. code-block::nim
## ##
## .. code-block:: Nim
## var client = newAsyncHttpClient() ## var client = newAsyncHttpClient()
## var resp = await client.request("http://google.com") ## proc onProgressChanged(total, progress, speed: BiggestInt) {.async.} =
## echo("Downloaded ", progress, " of ", total)
## echo("Current rate: ", speed div 1000, "kb/s")
## client.onProgressChanged = onProgressChanged
## discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test")
##
## If you would like to remove the callback simply set it to ``nil``.
##
## .. code-block:: Nim
## client.onProgressChanged = nil
## ##
## SSL/TLS support ## SSL/TLS support
## =============== ## ===============
## This requires the OpenSSL library, fortunately it's widely used and installed ## This requires the OpenSSL library, fortunately it's widely used and installed
## on many operating systems. httpclient will use SSL automatically if you give ## on many operating systems. httpclient will use SSL automatically if you give
## any of the functions a url with the ``https`` schema, for example: ## 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: ## ``https://github.com/``.
##
## You will also have to compile with ``ssl`` defined like so:
## ``nim c -d:ssl ...``. ## ``nim c -d:ssl ...``.
## ##
## Timeouts ## 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 ## Currently only the synchronous functions support a timeout.
## The timeout is
## measured in milliseconds, once it is set any call on a socket which may ## measured in milliseconds, once it is set any call on a socket which may
## block will be susceptible to this timeout, however please remember that the ## block will be susceptible to this timeout.
##
## It may be surprising but the
## function as a whole can take longer than the specified timeout, only ## function as a whole can take longer than the specified timeout, only
## individual internal calls on the socket are affected. In practice this means ## 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, ## 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 ## if however data does not reach the client within the specified timeout a
## exception will then be raised. ## ``TimeoutError`` exception will be raised.
## ##
## Proxy ## Proxy
## ===== ## =====
## ##
## A proxy can be specified as a param to any of these procedures, the ``newProxy`` ## A proxy can be specified as a param to any of the procedures defined in
## constructor should be used for this purpose. However, ## this module. To do this, use the ``newProxy`` constructor. Unfortunately,
## currently only basic authentication is supported. ## only basic authentication is supported at the moment.
import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes, import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes,
math, random, httpcore math, random, httpcore, times
import asyncnet, asyncdispatch import asyncnet, asyncdispatch
import nativesockets import nativesockets
@ -379,15 +402,18 @@ proc format(p: MultipartData): tuple[header, body: string] =
proc request*(url: string, httpMethod: string, extraHeaders = "", proc request*(url: string, httpMethod: string, extraHeaders = "",
body = "", sslContext = defaultSSLContext, timeout = -1, body = "", sslContext = defaultSSLContext, timeout = -1,
userAgent = defUserAgent, proxy: Proxy = nil): Response = userAgent = defUserAgent, proxy: Proxy = nil): Response
{.deprecated.} =
## | Requests ``url`` with the custom method string specified by the ## | Requests ``url`` with the custom method string specified by the
## | ``httpMethod`` parameter. ## | ``httpMethod`` parameter.
## | Extra headers can be specified and must be separated by ``\c\L`` ## | Extra headers can be specified and must be separated by ``\c\L``
## | An optional timeout can be specified in milliseconds, if reading from the ## | An optional timeout can be specified in milliseconds, 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.
##
## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead.
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")).toUpper() var headers = httpMethod.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()
@ -471,15 +497,18 @@ proc request*(url: string, httpMethod: string, extraHeaders = "",
if body != "": if body != "":
s.send(body) s.send(body)
result = parseResponse(s, httpMethod != "httpHEAD", timeout) result = parseResponse(s, httpMethod != "HEAD", timeout)
proc request*(url: string, httpMethod = httpGET, extraHeaders = "", proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
body = "", sslContext = defaultSSLContext, timeout = -1, body = "", sslContext = defaultSSLContext, timeout = -1,
userAgent = defUserAgent, proxy: Proxy = nil): Response = userAgent = defUserAgent, proxy: Proxy = nil): Response
{.deprecated.} =
## | Requests ``url`` with the specified ``httpMethod``. ## | Requests ``url`` with the specified ``httpMethod``.
## | Extra headers can be specified and must be separated by ``\c\L`` ## | Extra headers can be specified and must be separated by ``\c\L``
## | An optional timeout can be specified in milliseconds, if reading from the ## | An optional timeout can be specified in milliseconds, 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.
##
## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead.
result = request(url, $httpMethod, extraHeaders, body, sslContext, timeout, result = request(url, $httpMethod, extraHeaders, body, sslContext, timeout,
userAgent, proxy) userAgent, proxy)
@ -502,12 +531,14 @@ proc getNewLocation(lastURL: string, headers: HttpHeaders): string =
proc get*(url: string, extraHeaders = "", maxRedirects = 5, proc get*(url: string, extraHeaders = "", maxRedirects = 5,
sslContext: SSLContext = defaultSSLContext, sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent, timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil): Response = proxy: Proxy = nil): Response {.deprecated.} =
## | GETs the ``url`` and returns a ``Response`` object ## | GETs the ``url`` and returns a ``Response`` object
## | This proc also handles redirection ## | This proc also handles redirection
## | Extra headers can be specified and must be separated by ``\c\L``. ## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in milliseconds, if reading from the ## | An optional timeout can be specified in milliseconds, 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.
##
## ## **Deprecated since version 0.15.0**: use ``HttpClient.get`` instead.
result = request(url, httpGET, extraHeaders, "", sslContext, timeout, result = request(url, httpGET, extraHeaders, "", sslContext, timeout,
userAgent, proxy) userAgent, proxy)
var lastURL = url var lastURL = url
@ -521,12 +552,14 @@ proc get*(url: string, extraHeaders = "", maxRedirects = 5,
proc getContent*(url: string, extraHeaders = "", maxRedirects = 5, proc getContent*(url: string, extraHeaders = "", maxRedirects = 5,
sslContext: SSLContext = defaultSSLContext, sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent, timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil): string = proxy: Proxy = nil): string {.deprecated.} =
## | GETs the body and returns it as a string. ## | GETs the body and returns it as a string.
## | Raises exceptions for the status codes ``4xx`` and ``5xx`` ## | Raises exceptions for the status codes ``4xx`` and ``5xx``
## | Extra headers can be specified and must be separated by ``\c\L``. ## | Extra headers can be specified and must be separated by ``\c\L``.
## | An optional timeout can be specified in milliseconds, if reading from the ## | An optional timeout can be specified in milliseconds, 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.
##
## **Deprecated since version 0.15.0**: use ``HttpClient.getContent`` instead.
var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent, var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent,
proxy) proxy)
if r.status[0] in {'4','5'}: if r.status[0] in {'4','5'}:
@ -539,7 +572,7 @@ proc post*(url: string, extraHeaders = "", body = "",
sslContext: SSLContext = defaultSSLContext, sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent, timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil, proxy: Proxy = nil,
multipart: MultipartData = nil): Response = multipart: MultipartData = nil): Response {.deprecated.} =
## | POSTs ``body`` to the ``url`` and returns a ``Response`` object. ## | POSTs ``body`` to the ``url`` and returns a ``Response`` object.
## | This proc adds the necessary Content-Length header. ## | This proc adds the necessary Content-Length header.
## | This proc also handles redirection. ## | This proc also handles redirection.
@ -548,6 +581,8 @@ proc post*(url: string, extraHeaders = "", body = "",
## server takes longer than specified an ETimeout exception will be raised. ## server takes longer than specified an ETimeout exception will be raised.
## | The optional ``multipart`` parameter can be used to create ## | The optional ``multipart`` parameter can be used to create
## ``multipart/form-data`` POSTs comfortably. ## ``multipart/form-data`` POSTs comfortably.
##
## **Deprecated since version 0.15.0**: use ``HttpClient.post`` instead.
let (mpHeaders, mpBody) = format(multipart) let (mpHeaders, mpBody) = format(multipart)
template withNewLine(x): expr = template withNewLine(x): expr =
@ -577,7 +612,8 @@ proc postContent*(url: string, extraHeaders = "", body = "",
sslContext: SSLContext = defaultSSLContext, sslContext: SSLContext = defaultSSLContext,
timeout = -1, userAgent = defUserAgent, timeout = -1, userAgent = defUserAgent,
proxy: Proxy = nil, proxy: Proxy = nil,
multipart: MultipartData = nil): string = multipart: MultipartData = nil): string
{.deprecated.} =
## | POSTs ``body`` to ``url`` and returns the response's body as a string ## | POSTs ``body`` to ``url`` and returns the response's body as a string
## | Raises exceptions for the status codes ``4xx`` and ``5xx`` ## | Raises exceptions for the status codes ``4xx`` and ``5xx``
## | Extra headers can be specified and must be separated by ``\c\L``. ## | Extra headers can be specified and must be separated by ``\c\L``.
@ -585,6 +621,9 @@ proc postContent*(url: string, extraHeaders = "", body = "",
## server takes longer than specified an ETimeout exception will be raised. ## server takes longer than specified an ETimeout exception will be raised.
## | The optional ``multipart`` parameter can be used to create ## | The optional ``multipart`` parameter can be used to create
## ``multipart/form-data`` POSTs comfortably. ## ``multipart/form-data`` POSTs comfortably.
##
## **Deprecated since version 0.15.0**: use ``HttpClient.postContent``
## instead.
var r = post(url, extraHeaders, body, maxRedirects, sslContext, timeout, var r = post(url, extraHeaders, body, maxRedirects, sslContext, timeout,
userAgent, proxy, multipart) userAgent, proxy, multipart)
if r.status[0] in {'4','5'}: if r.status[0] in {'4','5'}:
@ -610,7 +649,7 @@ proc downloadFile*(url: string, outputFilename: string,
proc generateHeaders(requestUrl: Uri, httpMethod: string, proc generateHeaders(requestUrl: Uri, httpMethod: string,
headers: HttpHeaders, body: string, proxy: Proxy): string = headers: HttpHeaders, body: string, proxy: Proxy): string =
# GET # GET
result = substr(httpMethod, len("http")).toUpper() result = httpMethod.toUpper()
result.add ' ' result.add ' '
if proxy.isNil: if proxy.isNil:
@ -653,17 +692,30 @@ proc generateHeaders(requestUrl: Uri, httpMethod: string,
add(result, "\c\L") add(result, "\c\L")
type type
ProgressChangedProc*[ReturnType] =
proc (total, progress, speed: BiggestInt):
ReturnType {.closure, gcsafe.}
HttpClientBase*[SocketType] = ref object HttpClientBase*[SocketType] = ref object
socket: SocketType socket: SocketType
connected: bool connected: bool
currentURL: Uri ## Where we are currently connected. currentURL: Uri ## Where we are currently connected.
headers*: HttpHeaders headers*: HttpHeaders ## Headers to send in requests.
maxRedirects: int maxRedirects: int
userAgent: string userAgent: string
timeout: int ## Only used for blocking HttpClient for now. timeout: int ## Only used for blocking HttpClient for now.
proxy: Proxy proxy: Proxy
## ``nil`` or the callback to call when request progress changes.
when SocketType is Socket:
onProgressChanged*: ProgressChangedProc[void]
else:
onProgressChanged*: ProgressChangedProc[Future[void]]
when defined(ssl): when defined(ssl):
sslContext: net.SslContext sslContext: net.SslContext
contentTotal: BiggestInt
contentProgress: BiggestInt
oneSecondProgress: BiggestInt
lastProgressReport: float
type type
HttpClient* = HttpClientBase[Socket] HttpClient* = HttpClientBase[Socket]
@ -692,6 +744,7 @@ proc newHttpClient*(userAgent = defUserAgent,
result.maxRedirects = maxRedirects result.maxRedirects = maxRedirects
result.proxy = proxy result.proxy = proxy
result.timeout = timeout result.timeout = timeout
result.onProgressChanged = nil
when defined(ssl): when defined(ssl):
result.sslContext = sslContext result.sslContext = sslContext
@ -721,6 +774,7 @@ proc newAsyncHttpClient*(userAgent = defUserAgent,
result.maxRedirects = maxRedirects result.maxRedirects = maxRedirects
result.proxy = proxy result.proxy = proxy
result.timeout = -1 # TODO result.timeout = -1 # TODO
result.onProgressChanged = nil
when defined(ssl): when defined(ssl):
result.sslContext = sslContext result.sslContext = sslContext
@ -730,19 +784,37 @@ proc close*(client: HttpClient | AsyncHttpClient) =
client.socket.close() client.socket.close()
client.connected = false client.connected = false
proc recvFull(socket: Socket | AsyncSocket, proc reportProgress(client: HttpClient | AsyncHttpClient,
progress: BiggestInt) {.multisync.} =
client.contentProgress += progress
client.oneSecondProgress += progress
if epochTime() - client.lastProgressReport >= 1.0:
if not client.onProgressChanged.isNil:
await client.onProgressChanged(client.contentTotal,
client.contentProgress,
client.oneSecondProgress)
client.oneSecondProgress = 0
client.lastProgressReport = epochTime()
proc recvFull(client: HttpClient | AsyncHttpClient,
size: int, timeout: int): Future[string] {.multisync.} = size: int, timeout: 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:
if size == result.len: break if size == result.len: break
when socket is Socket:
let data = socket.recv(size - result.len, timeout) let remainingSize = size - result.len
let sizeToRecv = min(remainingSize, net.BufferSize)
when client.socket is Socket:
let data = client.socket.recv(sizeToRecv, timeout)
else: else:
let data = await socket.recv(size - result.len) let data = await client.socket.recv(sizeToRecv)
if data == "": break # We've been disconnected. if data == "": break # We've been disconnected.
result.add data result.add data
await reportProgress(client, data.len)
proc parseChunks(client: HttpClient | AsyncHttpClient): Future[string] proc parseChunks(client: HttpClient | AsyncHttpClient): Future[string]
{.multisync.} = {.multisync.} =
result = "" result = ""
@ -770,10 +842,10 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[string]
httpError("Invalid chunk size: " & chunkSizeStr) httpError("Invalid chunk size: " & chunkSizeStr)
inc(i) inc(i)
if chunkSize <= 0: if chunkSize <= 0:
discard await recvFull(client.socket, 2, client.timeout) # Skip \c\L discard await recvFull(client, 2, client.timeout) # Skip \c\L
break break
result.add await recvFull(client.socket, chunkSize, client.timeout) result.add await recvFull(client, chunkSize, client.timeout)
discard await recvFull(client.socket, 2, client.timeout) # Skip \c\L discard await recvFull(client, 2, client.timeout) # Skip \c\L
# 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
@ -781,6 +853,12 @@ proc parseBody(client: HttpClient | AsyncHttpClient,
headers: HttpHeaders, headers: HttpHeaders,
httpVersion: string): Future[string] {.multisync.} = httpVersion: string): Future[string] {.multisync.} =
result = "" result = ""
# Reset progress from previous requests.
client.contentTotal = 0
client.contentProgress = 0
client.oneSecondProgress = 0
client.lastProgressReport = 0
if headers.getOrDefault"Transfer-Encoding" == "chunked": if headers.getOrDefault"Transfer-Encoding" == "chunked":
result = await parseChunks(client) result = await parseChunks(client)
else: else:
@ -789,8 +867,9 @@ proc parseBody(client: HttpClient | AsyncHttpClient,
var contentLengthHeader = headers.getOrDefault"Content-Length" var contentLengthHeader = headers.getOrDefault"Content-Length"
if contentLengthHeader != "": if contentLengthHeader != "":
var length = contentLengthHeader.parseint() var length = contentLengthHeader.parseint()
client.contentTotal = length
if length > 0: if length > 0:
result = await client.socket.recvFull(length, client.timeout) result = await client.recvFull(length, client.timeout)
if result == "": if result == "":
httpError("Got disconnected while trying to read body.") httpError("Got disconnected while trying to read body.")
if result.len != length: if result.len != length:
@ -804,7 +883,7 @@ proc parseBody(client: HttpClient | AsyncHttpClient,
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0": if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0":
var buf = "" var buf = ""
while true: while true:
buf = await client.socket.recvFull(4000, client.timeout) buf = await client.recvFull(4000, client.timeout)
if buf == "": break if buf == "": break
result.add(buf) result.add(buf)
@ -902,8 +981,6 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
## Connection will kept alive. Further requests on the same ``client`` to ## Connection will kept alive. Further requests on the same ``client`` to
## the same hostname will not require a new connection to be made. The ## the same hostname will not require a new connection to be made. The
## 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.
let connectionUrl = let connectionUrl =
if client.proxy.isNil: parseUri(url) else: client.proxy.url if client.proxy.isNil: parseUri(url) else: client.proxy.url
let requestUrl = parseUri(url) let requestUrl = parseUri(url)
@ -933,14 +1010,15 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
if not client.headers.hasKey("user-agent") and client.userAgent != "": if not client.headers.hasKey("user-agent") and client.userAgent != "":
client.headers["User-Agent"] = client.userAgent client.headers["User-Agent"] = client.userAgent
var headers = generateHeaders(requestUrl, $httpMethod, var headers = generateHeaders(requestUrl, httpMethod,
client.headers, body, client.proxy) client.headers, body, client.proxy)
await client.socket.send(headers) await client.socket.send(headers)
if body != "": if body != "":
await client.socket.send(body) await client.socket.send(body)
result = await parseResponse(client, httpMethod notin {HttpHead, HttpConnect}) result = await parseResponse(client,
httpMethod.toLower() notin ["head", "connect"])
# Restore the clients proxy in case it was overwritten. # Restore the clients proxy in case it was overwritten.
client.proxy = savedProxy client.proxy = savedProxy
@ -950,11 +1028,12 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
## 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.
## ##
## Connection will kept alive. Further requests on the same ``client`` to ## Connection will be kept alive. Further requests on the same ``client`` to
## the same hostname will not require a new connection to be made. The ## the same hostname will not require a new connection to be made. The
## 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. ## When a request is made to a different hostname, the current connection will
## be closed.
result = await request(client, url, $httpMethod, body) result = await request(client, url, $httpMethod, body)
proc get*(client: HttpClient | AsyncHttpClient, proc get*(client: HttpClient | AsyncHttpClient,
@ -964,6 +1043,8 @@ proc get*(client: HttpClient | AsyncHttpClient,
## This procedure will follow redirects up to a maximum number of redirects ## This procedure will follow redirects up to a maximum number of redirects
## specified in ``client.maxRedirects``. ## specified in ``client.maxRedirects``.
result = await client.request(url, HttpGET) result = await client.request(url, HttpGET)
# Handle redirects.
var lastURL = url var lastURL = url
for i in 1..client.maxRedirects: for i in 1..client.maxRedirects:
if result.status.redirection(): if result.status.redirection():
@ -971,6 +1052,21 @@ proc get*(client: HttpClient | AsyncHttpClient,
result = await client.request(redirectTo, HttpGET) result = await client.request(redirectTo, HttpGET)
lastURL = redirectTo lastURL = redirectTo
proc getContent*(client: HttpClient | AsyncHttpClient,
url: string): Future[string] {.multisync.} =
## 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
## specified in ``client.maxRedirects``.
##
## A ``HttpRequestError`` will be raised if the server responds with a
## client error (status code 4xx) or a server error (status code 5xx).
let resp = await get(client, url)
if resp.code.is4xx or resp.code.is5xx:
raise newException(HttpRequestError, resp.status)
else:
return resp.body
proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "", proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "",
multipart: MultipartData = nil): Future[Response] {.multisync.} = 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.
@ -990,3 +1086,28 @@ proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "",
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)
# Handle redirects.
var lastURL = url
for i in 1..client.maxRedirects:
if result.status.redirection():
let redirectTo = getNewLocation(lastURL, result.headers)
var meth = if result.status != "307": HttpGet else: HttpPost
result = await client.request(redirectTo, meth, xb)
lastURL = redirectTo
proc postContent*(client: HttpClient | AsyncHttpClient, url: string,
body = "",
multipart: MultipartData = nil): Future[string]
{.multisync.} =
## 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
## specified in ``client.maxRedirects``.
##
## A ``HttpRequestError`` will be raised if the server responds with a
## client error (status code 4xx) or a server error (status code 5xx).
let resp = await post(client, url, body, multipart)
if resp.code.is4xx or resp.code.is5xx:
raise newException(HttpRequestError, resp.status)
else:
return resp.body

View file

@ -43,9 +43,8 @@ type
## for specified address. ## for specified address.
HttpConnect, ## Converts the request connection to a transparent HttpConnect, ## Converts the request connection to a transparent
## TCP/IP tunnel, usually used for proxies. ## TCP/IP tunnel, usually used for proxies.
HttpPatch ## Added in RFC 5789. Can be used to update partial HttpPatch ## Applies partial modifications to a resource.
## resources. The set of changes is represented in a
## format called a "patch document".
{.deprecated: [httpGet: HttpGet, httpHead: HttpHead, httpPost: HttpPost, {.deprecated: [httpGet: HttpGet, httpHead: HttpHead, httpPost: HttpPost,
httpPut: HttpPut, httpDelete: HttpDelete, httpTrace: HttpTrace, httpPut: HttpPut, httpDelete: HttpDelete, httpTrace: HttpTrace,
httpOptions: HttpOptions, httpConnect: HttpConnect].} httpOptions: HttpOptions, httpConnect: HttpConnect].}
@ -226,7 +225,7 @@ proc `$`*(code: HttpCode): string =
## For example: ## For example:
## ##
## .. code-block:: nim ## .. code-block:: nim
## doAssert(Http404.status == "404 Not Found") ## doAssert($Http404 == "404 Not Found")
case code.int case code.int
of 100: "100 Continue" of 100: "100 Continue"
of 101: "101 Switching Protocols" of 101: "101 Switching Protocols"
@ -298,6 +297,9 @@ proc is5xx*(code: HttpCode): bool =
## Determines whether ``code`` is a 5xx HTTP status code. ## Determines whether ``code`` is a 5xx HTTP status code.
return code.int in {500 .. 599} return code.int in {500 .. 599}
proc `$`*(httpMethod: HttpMethod): string =
return (system.`$`(httpMethod))[4 .. ^1].toUpper()
when isMainModule: when isMainModule:
var test = newHttpHeaders() var test = newHttpHeaders()
test["Connection"] = @["Upgrade", "Close"] test["Connection"] = @["Upgrade", "Close"]

View file

@ -0,0 +1,293 @@
# TODO: This shouldn't need to be included, but should ideally be exported.
type
FutureBase* = ref object of RootObj ## Untyped future.
cb: proc () {.closure,gcsafe.}
finished: bool
error*: ref Exception ## Stored exception
errorStackTrace*: string
when not defined(release):
stackTrace: string ## For debugging purposes only.
id: int
fromProc: string
Future*[T] = ref object of FutureBase ## Typed future.
value: T ## Stored value
FutureVar*[T] = distinct Future[T]
FutureError* = object of Exception
cause*: FutureBase
{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
when not defined(release):
var currentID = 0
proc callSoon*(cbproc: proc ()) {.gcsafe.}
proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
## Creates a new future.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
new(result)
result.finished = false
when not defined(release):
result.stackTrace = getStackTrace()
result.id = currentID
result.fromProc = fromProc
currentID.inc()
proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
## Create a new ``FutureVar``. This Future type is ideally suited for
## situations where you want to avoid unnecessary allocations of Futures.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
result = FutureVar[T](newFuture[T](fromProc))
proc clean*[T](future: FutureVar[T]) =
## Resets the ``finished`` status of ``future``.
Future[T](future).finished = false
Future[T](future).error = nil
proc checkFinished[T](future: Future[T]) =
## Checks whether `future` is finished. If it is then raises a
## ``FutureError``.
when not defined(release):
if future.finished:
var msg = ""
msg.add("An attempt was made to complete a Future more than once. ")
msg.add("Details:")
msg.add("\n Future ID: " & $future.id)
msg.add("\n Created in proc: " & future.fromProc)
msg.add("\n Stack trace to moment of creation:")
msg.add("\n" & indent(future.stackTrace.strip(), 4))
when T is string:
msg.add("\n Contents (string): ")
msg.add("\n" & indent(future.value.repr, 4))
msg.add("\n Stack trace to moment of secondary completion:")
msg.add("\n" & indent(getStackTrace().strip(), 4))
var err = newException(FutureError, msg)
err.cause = future
raise err
proc complete*[T](future: Future[T], val: T) =
## Completes ``future`` with value ``val``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
assert(future.error == nil)
future.value = val
future.finished = true
if future.cb != nil:
future.cb()
proc complete*(future: Future[void]) =
## Completes a void ``future``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
assert(future.error == nil)
future.finished = true
if future.cb != nil:
future.cb()
proc complete*[T](future: FutureVar[T]) =
## Completes a ``FutureVar``.
template fut: expr = Future[T](future)
checkFinished(fut)
assert(fut.error == nil)
fut.finished = true
if fut.cb != nil:
fut.cb()
proc complete*[T](future: FutureVar[T], val: T) =
## Completes a ``FutureVar`` with value ``val``.
##
## Any previously stored value will be overwritten.
template fut: expr = Future[T](future)
checkFinished(fut)
assert(fut.error == nil)
fut.finished = true
fut.value = val
if fut.cb != nil:
fut.cb()
proc fail*[T](future: Future[T], error: ref Exception) =
## Completes ``future`` with ``error``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
future.finished = true
future.error = error
future.errorStackTrace =
if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error)
if future.cb != nil:
future.cb()
else:
# This is to prevent exceptions from being silently ignored when a future
# is discarded.
# TODO: This may turn out to be a bad idea.
# Turns out this is a bad idea.
#raise error
discard
proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) =
## Sets the callback proc to be called when the future completes.
##
## If future has already completed then ``cb`` will be called immediately.
##
## **Note**: You most likely want the other ``callback`` setter which
## passes ``future`` as a param to the callback.
future.cb = cb
if future.finished:
callSoon(future.cb)
proc `callback=`*[T](future: Future[T],
cb: proc (future: Future[T]) {.closure,gcsafe.}) =
## Sets the callback proc to be called when the future completes.
##
## If future has already completed then ``cb`` will be called immediately.
future.callback = proc () = cb(future)
proc injectStacktrace[T](future: Future[T]) =
# TODO: Come up with something better.
when not defined(release):
var msg = ""
msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
if not future.errorStackTrace.isNil and future.errorStackTrace != "":
msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
else:
msg.add("\n Empty or nil stack trace.")
future.error.msg.add(msg)
proc read*[T](future: Future[T] | FutureVar[T]): T =
## Retrieves the value of ``future``. Future must be finished otherwise
## this function will fail with a ``ValueError`` exception.
##
## If the result of the future is an error then that error will be raised.
let fut = Future[T](future)
if fut.finished:
if fut.error != nil:
injectStacktrace(fut)
raise fut.error
when T isnot void:
return fut.value
else:
# TODO: Make a custom exception type for this?
raise newException(ValueError, "Future still in progress.")
proc readError*[T](future: Future[T]): ref Exception =
## Retrieves the exception stored in ``future``.
##
## An ``ValueError`` exception will be thrown if no exception exists
## in the specified Future.
if future.error != nil: return future.error
else:
raise newException(ValueError, "No error in future.")
proc mget*[T](future: FutureVar[T]): var T =
## Returns a mutable value stored in ``future``.
##
## Unlike ``read``, this function will not raise an exception if the
## Future has not been finished.
result = Future[T](future).value
proc finished*[T](future: Future[T] | FutureVar[T]): bool =
## Determines whether ``future`` has completed.
##
## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
(Future[T](future)).finished
proc failed*(future: FutureBase): bool =
## Determines whether ``future`` completed with an error.
return future.error != nil
proc asyncCheck*[T](future: Future[T]) =
## Sets a callback on ``future`` which raises an exception if the future
## finished with an error.
##
## This should be used instead of ``discard`` to discard void futures.
future.callback =
proc () =
if future.failed:
injectStacktrace(future)
raise future.error
proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
## Returns a future which will complete once both ``fut1`` and ``fut2``
## complete.
var retFuture = newFuture[void]("asyncdispatch.`and`")
fut1.callback =
proc () =
if not retFuture.finished:
if fut1.failed: retFuture.fail(fut1.error)
elif fut2.finished: retFuture.complete()
fut2.callback =
proc () =
if not retFuture.finished:
if fut2.failed: retFuture.fail(fut2.error)
elif fut1.finished: retFuture.complete()
return retFuture
proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
## Returns a future which will complete once either ``fut1`` or ``fut2``
## complete.
var retFuture = newFuture[void]("asyncdispatch.`or`")
proc cb[X](fut: Future[X]) =
if fut.failed: retFuture.fail(fut.error)
if not retFuture.finished: retFuture.complete()
fut1.callback = cb[T]
fut2.callback = cb[Y]
return retFuture
proc all*[T](futs: varargs[Future[T]]): auto =
## Returns a future which will complete once
## all futures in ``futs`` complete.
##
## If the awaited futures are not ``Future[void]``, the returned future
## will hold the values of all awaited futures in a sequence.
##
## If the awaited futures *are* ``Future[void]``,
## this proc returns ``Future[void]``.
when T is void:
var
retFuture = newFuture[void]("asyncdispatch.all")
completedFutures = 0
let totalFutures = len(futs)
for fut in futs:
fut.callback = proc(f: Future[T]) =
if f.failed:
retFuture.fail(f.error)
elif not retFuture.finished:
inc(completedFutures)
if completedFutures == totalFutures:
retFuture.complete()
return retFuture
else:
var
retFuture = newFuture[seq[T]]("asyncdispatch.all")
retValues = newSeq[T](len(futs))
completedFutures = 0
for i, fut in futs:
proc setCallback(i: int) =
fut.callback = proc(f: Future[T]) =
if f.failed:
retFuture.fail(f.error)
elif not retFuture.finished:
retValues[i] = f.read()
inc(completedFutures)
if completedFutures == len(retValues):
retFuture.complete(retValues)
setCallback(i)
return retFuture

View file

@ -24,6 +24,8 @@
## jobj["test"] = newJFloat(0.7) # create or update ## jobj["test"] = newJFloat(0.7) # create or update
## echo($jobj["test"].fnum) ## echo($jobj["test"].fnum)
## echo($jobj["key2"].bval) ## echo($jobj["key2"].bval)
## echo jobj{"missing key"}.getFNum(0.1) # read a float value using a default
## jobj{"a", "b", "c"} = newJFloat(3.3) # created nested keys
## ##
## Results in: ## Results in:
## ##

View file

@ -112,6 +112,7 @@ else:
const const
BufferSize*: int = 4000 ## size of a buffered socket's buffer BufferSize*: int = 4000 ## size of a buffered socket's buffer
MaxLineLength* = 1_000_000
type type
SocketImpl* = object ## socket type SocketImpl* = object ## socket type
@ -1006,7 +1007,7 @@ proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} =
result = recv(socket.fd, addr(c), 1, MSG_PEEK) result = recv(socket.fd, addr(c), 1, MSG_PEEK)
proc readLine*(socket: Socket, line: var TaintedString, timeout = -1, proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
flags = {SocketFlag.SafeDisconn}) {. flags = {SocketFlag.SafeDisconn}, maxLength = MaxLineLength) {.
tags: [ReadIOEffect, TimeEffect].} = tags: [ReadIOEffect, TimeEffect].} =
## Reads a line of data from ``socket``. ## Reads a line of data from ``socket``.
## ##
@ -1021,6 +1022,10 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
## A timeout can be specified in milliseconds, if data is not received within ## A timeout can be specified in milliseconds, if data is not received within
## the specified time an ETimeout exception will be raised. ## the specified time an ETimeout exception will be raised.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial
## of Service (DOS) attacks.
##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported. ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
template addNLIfEmpty() = template addNLIfEmpty() =
@ -1054,8 +1059,15 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
return return
add(line.string, c) add(line.string, c)
# Verify that this isn't a DOS attack: #3847.
if line.string.len > maxLength:
let msg = "recvLine received more than the specified `maxLength` " &
"allowed."
raise newException(ValueError, msg)
proc recvLine*(socket: Socket, timeout = -1, proc recvLine*(socket: Socket, timeout = -1,
flags = {SocketFlag.SafeDisconn}): TaintedString = flags = {SocketFlag.SafeDisconn},
maxLength = MaxLineLength): TaintedString =
## Reads a line of data from ``socket``. ## Reads a line of data from ``socket``.
## ##
## If a full line is read ``\r\L`` is not ## If a full line is read ``\r\L`` is not
@ -1069,9 +1081,13 @@ proc recvLine*(socket: Socket, timeout = -1,
## A timeout can be specified in milliseconds, if data is not received within ## A timeout can be specified in milliseconds, if data is not received within
## the specified time an ETimeout exception will be raised. ## the specified time an ETimeout exception will be raised.
## ##
## The ``maxLength`` parameter determines the maximum amount of characters
## that can be read before a ``ValueError`` is raised. This prevents Denial
## of Service (DOS) attacks.
##
## **Warning**: Only the ``SafeDisconn`` flag is currently supported. ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
result = "" result = ""
readLine(socket, result, timeout, flags) readLine(socket, result, timeout, flags, maxLength)
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 {.

View file

@ -377,7 +377,8 @@ proc contains*(s: Selector, key: SelectorKey): bool =
proc len*(s: Selector): int = proc len*(s: Selector): int =
## Retrieves the number of registered file descriptors in this Selector. ## Retrieves the number of registered file descriptors in this Selector.
return s.fds.len when not defined(nimdoc):
return s.fds.len
{.deprecated: [TEvent: Event, PSelectorKey: SelectorKey, {.deprecated: [TEvent: Event, PSelectorKey: SelectorKey,
TReadyInfo: ReadyInfo, PSelector: Selector].} TReadyInfo: ReadyInfo, PSelector: Selector].}

View file

@ -130,286 +130,7 @@ export Port, SocketFlag
# TODO: Check if yielded future is nil and throw a more meaningful exception # TODO: Check if yielded future is nil and throw a more meaningful exception
# -- Futures include "../includes/asyncfutures"
type
FutureBase* = ref object of RootObj ## Untyped future.
cb: proc () {.closure,gcsafe.}
finished: bool
error*: ref Exception ## Stored exception
errorStackTrace*: string
when not defined(release):
stackTrace: string ## For debugging purposes only.
id: int
fromProc: string
Future*[T] = ref object of FutureBase ## Typed future.
value: T ## Stored value
FutureVar*[T] = distinct Future[T]
FutureError* = object of Exception
cause*: FutureBase
{.deprecated: [PFutureBase: FutureBase, PFuture: Future].}
when not defined(release):
var currentID = 0
proc callSoon*(cbproc: proc ()) {.gcsafe.}
proc newFuture*[T](fromProc: string = "unspecified"): Future[T] =
## Creates a new future.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
new(result)
result.finished = false
when not defined(release):
result.stackTrace = getStackTrace()
result.id = currentID
result.fromProc = fromProc
currentID.inc()
proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] =
## Create a new ``FutureVar``. This Future type is ideally suited for
## situations where you want to avoid unnecessary allocations of Futures.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
result = FutureVar[T](newFuture[T](fromProc))
proc clean*[T](future: FutureVar[T]) =
## Resets the ``finished`` status of ``future``.
Future[T](future).finished = false
Future[T](future).error = nil
proc checkFinished[T](future: Future[T]) =
## Checks whether `future` is finished. If it is then raises a
## ``FutureError``.
when not defined(release):
if future.finished:
var msg = ""
msg.add("An attempt was made to complete a Future more than once. ")
msg.add("Details:")
msg.add("\n Future ID: " & $future.id)
msg.add("\n Created in proc: " & future.fromProc)
msg.add("\n Stack trace to moment of creation:")
msg.add("\n" & indent(future.stackTrace.strip(), 4))
when T is string:
msg.add("\n Contents (string): ")
msg.add("\n" & indent(future.value.repr, 4))
msg.add("\n Stack trace to moment of secondary completion:")
msg.add("\n" & indent(getStackTrace().strip(), 4))
var err = newException(FutureError, msg)
err.cause = future
raise err
proc complete*[T](future: Future[T], val: T) =
## Completes ``future`` with value ``val``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
assert(future.error == nil)
future.value = val
future.finished = true
if future.cb != nil:
future.cb()
proc complete*(future: Future[void]) =
## Completes a void ``future``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
assert(future.error == nil)
future.finished = true
if future.cb != nil:
future.cb()
proc complete*[T](future: FutureVar[T]) =
## Completes a ``FutureVar``.
template fut: expr = Future[T](future)
checkFinished(fut)
assert(fut.error == nil)
fut.finished = true
if fut.cb != nil:
fut.cb()
proc fail*[T](future: Future[T], error: ref Exception) =
## Completes ``future`` with ``error``.
#assert(not future.finished, "Future already finished, cannot finish twice.")
checkFinished(future)
future.finished = true
future.error = error
future.errorStackTrace =
if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error)
if future.cb != nil:
future.cb()
else:
# This is to prevent exceptions from being silently ignored when a future
# is discarded.
# TODO: This may turn out to be a bad idea.
# Turns out this is a bad idea.
#raise error
discard
proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) =
## Sets the callback proc to be called when the future completes.
##
## If future has already completed then ``cb`` will be called immediately.
##
## **Note**: You most likely want the other ``callback`` setter which
## passes ``future`` as a param to the callback.
future.cb = cb
if future.finished:
callSoon(future.cb)
proc `callback=`*[T](future: Future[T],
cb: proc (future: Future[T]) {.closure,gcsafe.}) =
## Sets the callback proc to be called when the future completes.
##
## If future has already completed then ``cb`` will be called immediately.
future.callback = proc () = cb(future)
proc injectStacktrace[T](future: Future[T]) =
# TODO: Come up with something better.
when not defined(release):
var msg = ""
msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
if not future.errorStackTrace.isNil and future.errorStackTrace != "":
msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
else:
msg.add("\n Empty or nil stack trace.")
future.error.msg.add(msg)
proc read*[T](future: Future[T]): T =
## Retrieves the value of ``future``. Future must be finished otherwise
## this function will fail with a ``ValueError`` exception.
##
## If the result of the future is an error then that error will be raised.
if future.finished:
if future.error != nil:
injectStacktrace(future)
raise future.error
when T isnot void:
return future.value
else:
# TODO: Make a custom exception type for this?
raise newException(ValueError, "Future still in progress.")
proc readError*[T](future: Future[T]): ref Exception =
## Retrieves the exception stored in ``future``.
##
## An ``ValueError`` exception will be thrown if no exception exists
## in the specified Future.
if future.error != nil: return future.error
else:
raise newException(ValueError, "No error in future.")
proc mget*[T](future: FutureVar[T]): var T =
## Returns a mutable value stored in ``future``.
##
## Unlike ``read``, this function will not raise an exception if the
## Future has not been finished.
result = Future[T](future).value
proc finished*[T](future: Future[T]): bool =
## Determines whether ``future`` has completed.
##
## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
future.finished
proc failed*(future: FutureBase): bool =
## Determines whether ``future`` completed with an error.
return future.error != nil
proc asyncCheck*[T](future: Future[T]) =
## Sets a callback on ``future`` which raises an exception if the future
## finished with an error.
##
## This should be used instead of ``discard`` to discard void futures.
future.callback =
proc () =
if future.failed:
injectStacktrace(future)
raise future.error
proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
## Returns a future which will complete once both ``fut1`` and ``fut2``
## complete.
var retFuture = newFuture[void]("asyncdispatch.`and`")
fut1.callback =
proc () =
if not retFuture.finished:
if fut1.failed: retFuture.fail(fut1.error)
elif fut2.finished: retFuture.complete()
fut2.callback =
proc () =
if not retFuture.finished:
if fut2.failed: retFuture.fail(fut2.error)
elif fut1.finished: retFuture.complete()
return retFuture
proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] =
## Returns a future which will complete once either ``fut1`` or ``fut2``
## complete.
var retFuture = newFuture[void]("asyncdispatch.`or`")
proc cb[X](fut: Future[X]) =
if fut.failed: retFuture.fail(fut.error)
if not retFuture.finished: retFuture.complete()
fut1.callback = cb[T]
fut2.callback = cb[Y]
return retFuture
proc all*[T](futs: varargs[Future[T]]): auto =
## Returns a future which will complete once
## all futures in ``futs`` complete.
##
## If the awaited futures are not ``Future[void]``, the returned future
## will hold the values of all awaited futures in a sequence.
##
## If the awaited futures *are* ``Future[void]``,
## this proc returns ``Future[void]``.
when T is void:
var
retFuture = newFuture[void]("asyncdispatch.all")
completedFutures = 0
let totalFutures = len(futs)
for fut in futs:
fut.callback = proc(f: Future[T]) =
if f.failed:
retFuture.fail(f.error)
elif not retFuture.finished:
inc(completedFutures)
if completedFutures == totalFutures:
retFuture.complete()
return retFuture
else:
var
retFuture = newFuture[seq[T]]("asyncdispatch.all")
retValues = newSeq[T](len(futs))
completedFutures = 0
for i, fut in futs:
proc setCallback(i: int) =
fut.callback = proc(f: Future[T]) =
if f.failed:
retFuture.fail(f.error)
elif not retFuture.finished:
retValues[i] = f.read()
inc(completedFutures)
if completedFutures == len(retValues):
retFuture.complete(retValues)
setCallback(i)
return retFuture
type type
PDispatcherBase = ref object of RootRef PDispatcherBase = ref object of RootRef
@ -522,43 +243,44 @@ when defined(windows) or defined(nimdoc):
if at == -1: winlean.INFINITE if at == -1: winlean.INFINITE
else: at.int32 else: at.int32
var lpNumberOfBytesTransferred: Dword if p.handles.len != 0:
var lpCompletionKey: ULONG_PTR var lpNumberOfBytesTransferred: Dword
var customOverlapped: PCustomOverlapped var lpCompletionKey: ULONG_PTR
let res = getQueuedCompletionStatus(p.ioPort, var customOverlapped: PCustomOverlapped
addr lpNumberOfBytesTransferred, addr lpCompletionKey, let res = getQueuedCompletionStatus(p.ioPort,
cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool addr lpNumberOfBytesTransferred, addr lpCompletionKey,
cast[ptr POVERLAPPED](addr customOverlapped), llTimeout).bool
# http://stackoverflow.com/a/12277264/492186 # http://stackoverflow.com/a/12277264/492186
# TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html # TODO: http://www.serverframework.com/handling-multiple-pending-socket-read-and-write-operations.html
if res: if res:
# This is useful for ensuring the reliability of the overlapped struct. # This is useful for ensuring the reliability of the overlapped struct.
assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, OSErrorCode(-1))
# If cell.data != nil, then system.protect(rawEnv(cb)) was called,
# so we need to dispose our `cb` environment, because it is not needed
# anymore.
if customOverlapped.data.cell.data != nil:
system.dispose(customOverlapped.data.cell)
GC_unref(customOverlapped)
else:
let errCode = osLastError()
if customOverlapped != nil:
assert customOverlapped.data.fd == lpCompletionKey.AsyncFD assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
customOverlapped.data.cb(customOverlapped.data.fd, customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, errCode) lpNumberOfBytesTransferred, OSErrorCode(-1))
# If cell.data != nil, then system.protect(rawEnv(cb)) was called,
# so we need to dispose our `cb` environment, because it is not needed
# anymore.
if customOverlapped.data.cell.data != nil: if customOverlapped.data.cell.data != nil:
system.dispose(customOverlapped.data.cell) system.dispose(customOverlapped.data.cell)
GC_unref(customOverlapped) GC_unref(customOverlapped)
else: else:
if errCode.int32 == WAIT_TIMEOUT: let errCode = osLastError()
# Timed out if customOverlapped != nil:
discard assert customOverlapped.data.fd == lpCompletionKey.AsyncFD
else: raiseOSError(errCode) customOverlapped.data.cb(customOverlapped.data.fd,
lpNumberOfBytesTransferred, errCode)
if customOverlapped.data.cell.data != nil:
system.dispose(customOverlapped.data.cell)
GC_unref(customOverlapped)
else:
if errCode.int32 == WAIT_TIMEOUT:
# Timed out
discard
else: raiseOSError(errCode)
# Timer processing. # Timer processing.
processTimers(p) processTimers(p)

View file

@ -116,7 +116,9 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#include "clinenoise.h" #ifndef __LINENOISE_H
# include "clinenoise.h"
#endif
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096 #define LINENOISE_MAX_LINE 4096

View file

@ -14,7 +14,8 @@ type
CompletionCallback* = proc (a2: cstring; a3: ptr Completions) {.cdecl.} CompletionCallback* = proc (a2: cstring; a3: ptr Completions) {.cdecl.}
{.compile: "clinenoise.c".} {.emit: staticRead"clinenoise.h".}
{.emit: staticRead"clinenoise.c".}
proc setCompletionCallback*(a2: ptr CompletionCallback) {. proc setCompletionCallback*(a2: ptr CompletionCallback) {.
importc: "linenoiseSetCompletionCallback".} importc: "linenoiseSetCompletionCallback".}

View file

@ -51,6 +51,14 @@ instead of ``build.sh``.
The ``koch`` tool is the Nim build tool, more ``koch`` related options are The ``koch`` tool is the Nim build tool, more ``koch`` related options are
documented in [doc/koch.rst](doc/koch.rst). documented in [doc/koch.rst](doc/koch.rst).
To complete the installation you should also build Nim's tools like
``nimsuggest``, ``nimble`` or ``nimgrep``:
```
nim e install_tools.nims
```
## Nimble ## Nimble
[Nimble](https://github.com/nim-lang/nimble) is Nim's package manager. For the [Nimble](https://github.com/nim-lang/nimble) is Nim's package manager. For the
source based installations where you added Nim's ``bin`` directory to your PATH source based installations where you added Nim's ``bin`` directory to your PATH

View file

@ -0,0 +1,47 @@
import asyncdispatch
proc completeOnReturn(fut: FutureVar[string], x: bool) {.async.} =
if x:
fut.mget() = ""
fut.mget.add("foobar")
return
proc completeOnImplicitReturn(fut: FutureVar[string], x: bool) {.async.} =
if x:
fut.mget() = ""
fut.mget.add("foobar")
proc failureTest(fut: FutureVar[string], x: bool) {.async.} =
if x:
raise newException(Exception, "Test")
proc manualComplete(fut: FutureVar[string], x: bool) {.async.} =
if x:
fut.mget() = "Hello World"
fut.complete()
return
proc main() {.async.} =
var fut: FutureVar[string]
fut = newFutureVar[string]()
await completeOnReturn(fut, true)
doAssert(fut.read() == "foobar")
fut = newFutureVar[string]()
await completeOnImplicitReturn(fut, true)
doAssert(fut.read() == "foobar")
fut = newFutureVar[string]()
let retFut = failureTest(fut, true)
yield retFut
doAssert(fut.read().isNil)
doAssert(fut.finished)
fut = newFutureVar[string]()
await manualComplete(fut, true)
doAssert(fut.read() == "Hello World")
waitFor main()

View file

@ -44,19 +44,8 @@ when defined(upcoming):
ev.setEvent() ev.setEvent()
proc timerTest() = proc timerTest() =
var timeout = 200
var errorRate = 40.0
var start = epochTime()
waitFor(waitTimer(200)) waitFor(waitTimer(200))
var finish = epochTime() echo "OK"
var lowlimit = float(timeout) - float(timeout) * errorRate / 100.0
var highlimit = float(timeout) + float(timeout) * errorRate / 100.0
var elapsed = (finish - start) * 1_000 # convert to milliseconds
if elapsed >= lowlimit and elapsed < highlimit:
echo "OK"
else:
echo "timerTest: Timeout = " & $(elapsed) & ", but must be inside of [" &
$lowlimit & ", " & $highlimit & ")"
proc eventTest() = proc eventTest() =
var event = newAsyncEvent() var event = newAsyncEvent()

View file

@ -7,6 +7,8 @@ from net import TimeoutError
import httpclient, asyncdispatch import httpclient, asyncdispatch
const manualTests = false
proc asyncTest() {.async.} = proc asyncTest() {.async.} =
var client = newAsyncHttpClient() var client = newAsyncHttpClient()
var resp = await client.request("http://example.com/") var resp = await client.request("http://example.com/")
@ -20,12 +22,40 @@ proc asyncTest() {.async.} =
resp = await client.request("https://google.com/") resp = await client.request("https://google.com/")
doAssert(resp.code.is2xx or resp.code.is3xx) doAssert(resp.code.is2xx or resp.code.is3xx)
# getContent
try:
discard await client.getContent("https://google.com/404")
doAssert(false, "HttpRequestError should have been raised")
except HttpRequestError:
discard
except:
doAssert(false, "HttpRequestError should have been raised")
# Multipart test.
var data = newMultipartData()
data["output"] = "soap12"
data["uploaded_file"] = ("test.html", "text/html",
"<html><head></head><body><p>test</p></body></html>")
resp = await client.post("http://validator.w3.org/check", multipart=data)
doAssert(resp.code.is2xx)
# onProgressChanged
when manualTests:
proc onProgressChanged(total, progress, speed: BiggestInt) {.async.} =
echo("Downloaded ", progress, " of ", total)
echo("Current rate: ", speed div 1000, "kb/s")
client.onProgressChanged = onProgressChanged
discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test")
client.close() client.close()
# Proxy test # Proxy test
#client = newAsyncHttpClient(proxy = newProxy("http://51.254.106.76:80/")) #when manualTests:
#var resp = await client.request("https://github.com") # client = newAsyncHttpClient(proxy = newProxy("http://51.254.106.76:80/"))
#echo resp # var resp = await client.request("https://github.com")
# echo resp
proc syncTest() = proc syncTest() =
var client = newHttpClient() var client = newHttpClient()
@ -41,6 +71,31 @@ proc syncTest() =
resp = client.request("https://google.com/") resp = client.request("https://google.com/")
doAssert(resp.code.is2xx or resp.code.is3xx) doAssert(resp.code.is2xx or resp.code.is3xx)
# getContent
try:
discard client.getContent("https://google.com/404")
doAssert(false, "HttpRequestError should have been raised")
except HttpRequestError:
discard
except:
doAssert(false, "HttpRequestError should have been raised")
# Multipart test.
var data = newMultipartData()
data["output"] = "soap12"
data["uploaded_file"] = ("test.html", "text/html",
"<html><head></head><body><p>test</p></body></html>")
resp = client.post("http://validator.w3.org/check", multipart=data)
doAssert(resp.code.is2xx)
# onProgressChanged
when manualTests:
proc onProgressChanged(total, progress, speed: BiggestInt) =
echo("Downloaded ", progress, " of ", total)
echo("Current rate: ", speed div 1000, "kb/s")
client.onProgressChanged = onProgressChanged
discard client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test")
client.close() client.close()
# Timeout test. # Timeout test.
@ -56,21 +111,3 @@ proc syncTest() =
syncTest() syncTest()
waitFor(asyncTest()) 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)]#

View file

@ -135,7 +135,7 @@ proc processFile(filename: string) =
if optVerbose in options: if optVerbose in options:
stdout.writeLine(filename) stdout.writeLine(filename)
stdout.flushFile() stdout.flushFile()
var pegp: TPeg var pegp: Peg
var rep: Regex var rep: Regex
var result: string var result: string
@ -213,7 +213,7 @@ proc hasRightExt(filename: string, exts: seq[string]): bool =
if os.cmpPaths(x, y) == 0: return true if os.cmpPaths(x, y) == 0: return true
proc styleInsensitive(s: string): string = proc styleInsensitive(s: string): string =
template addx: stmt = template addx =
result.add(s[i]) result.add(s[i])
inc(i) inc(i)
result = "" result = ""

View file

@ -92,6 +92,7 @@ case $uos in
;; ;;
*haiku* ) *haiku* )
myos="haiku" myos="haiku"
LINK_FLAGS="$LINK_FLAGS -lroot -lnetwork"
;; ;;
*) *)
echo 2>&1 "Error: unknown operating system: $uos" echo 2>&1 "Error: unknown operating system: $uos"

View file

@ -447,7 +447,8 @@ proc readCFiles(c: var ConfigData, osA, cpuA: int) =
# HACK: we conditionally add ``-lm -ldl``, so remove them from the # HACK: we conditionally add ``-lm -ldl``, so remove them from the
# linker flags: # linker flags:
c.linker.flags = c.linker.flags.replaceWord("-lm").replaceWord( c.linker.flags = c.linker.flags.replaceWord("-lm").replaceWord(
"-ldl").strip "-ldl").replaceWord("-lroot").replaceWord(
"-lnetwork").strip
else: else:
if cmpIgnoreStyle(k.key, "libpath") == 0: if cmpIgnoreStyle(k.key, "libpath") == 0:
c.libpath = k.value c.libpath = k.value

View file

@ -35,16 +35,16 @@
; Default installation folder ; Default installation folder
; This is changed later (in .onInit) to the root directory, if possible. ; This is changed later (in .onInit) to the root directory, if possible.
InstallDir "$PROGRAMFILES?{when sizeof(int) == 8: "64" else: ""}\?{c.name}-?{c.version}" InstallDir "$PROGRAMFILES?{when sizeof(int) == 8: "64" else: ""}\?{c.name}-?{c.version}\"
; Get installation folder from registry if available ; Get installation folder from registry if available
InstallDirRegKey HKCU "Software\c.name\c.version" "" InstallDirRegKey HKCU "Software\c.name\c.version" ""
; Request user level application privileges. ; Request admin level application privileges.
RequestExecutionLevel user RequestExecutionLevel admin
; Allow installation to the root drive directory. ; Allow installation to the root drive directory.
AllowRootDirInstall true AllowRootDirInstall false
; Maximum compression! ; Maximum compression!
SetCompressor /SOLID /FINAL lzma SetCompressor /SOLID /FINAL lzma
@ -159,7 +159,7 @@
${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\mingw" ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\mingw"
${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\mingw\bin" ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\mingw\bin"
${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\bin" ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\bin"
${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\babel" ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$PROFILE\.nimble\bin"
SectionEnd SectionEnd
; The downloadable sections. These sections are automatically generated by ; The downloadable sections. These sections are automatically generated by
@ -246,7 +246,7 @@
${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\mingw" ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\mingw"
${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\mingw\bin" ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\mingw\bin"
${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\bin" ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\bin"
${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\babel" ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$PROFILE\.nimble\bin"
SectionEnd SectionEnd
;-------------------------------- ;--------------------------------
@ -254,5 +254,5 @@
Function .onInit Function .onInit
${GetRoot} "$EXEDIR" $R0 ${GetRoot} "$EXEDIR" $R0
strCpy $INSTDIR "$R0\?{c.name}" ;strCpy $INSTDIR "$R0\?{c.name}"
FunctionEnd FunctionEnd

View file

@ -45,6 +45,7 @@ Now open a terminal and follow these instructions:
``cd ~/programs/nim``. ``cd ~/programs/nim``.
* run ``sh build.sh``. * run ``sh build.sh``.
* Add ``$your_install_dir/bin`` to your PATH. * Add ``$your_install_dir/bin`` to your PATH.
* To build associated tools like ``nimble`` and ``nimsuggest`` run ``nim e install_tools.nims``.
After restarting your terminal, you should be able to run ``nim -v`` After restarting your terminal, you should be able to run ``nim -v``
which should show you the version of Nim you just installed. which should show you the version of Nim you just installed.

View file

@ -3,24 +3,23 @@ Version 0.15.0 released
.. container:: metadata .. container:: metadata
Posted by Dominik Picheta on 17/09/2016 Posted by Dominik Picheta and Andreas Rumpf on 17/09/2016
Some text here. Some text here.
Changes affecting backwards compatibility Changes affecting backwards compatibility
----------------------------------------- -----------------------------------------
- The ``json`` module uses an ``OrderedTable`` rather than a ``Table`` - The ``json`` module now uses an ``OrderedTable`` rather than a ``Table``
for JSON objects. for JSON objects.
- De-deprecated ``re.nim`` because we have too much code using it - The ``split`` `(doc) <http://nim-lang.org/docs/strutils.html#split,string,set[char],int>`_
and it got the basic API right. procedure in the ``strutils`` module (with a delimiter of type
``set[char]``) no longer strips and splits characters out of the target string
- ``split`` with ``set[char]`` as a delimiter in ``strutils.nim``
no longer strips and splits characters out of the target string
by the entire set of characters. Instead, it now behaves in a by the entire set of characters. Instead, it now behaves in a
similar fashion to ``split`` with ``string`` and ``char`` similar fashion to ``split`` with ``string`` and ``char``
delimiters. Use ``splitWhitespace`` to get the old behaviour. delimiters. Use ``splitWhitespace`` to get the old behaviour.
- The command invocation syntax will soon apply to open brackets - The command invocation syntax will soon apply to open brackets
and curlies too. This means that code like ``a [i]`` will be and curlies too. This means that code like ``a [i]`` will be
interpreted as ``a([i])`` and not as ``a[i]`` anymore. Likewise interpreted as ``a([i])`` and not as ``a[i]`` anymore. Likewise
@ -29,92 +28,142 @@ Changes affecting backwards compatibility
Warning: a [b] will be parsed as command syntax; spacing is deprecated Warning: a [b] will be parsed as command syntax; spacing is deprecated
See `<https://github.com/nim-lang/Nim/issues/3898>`_ for the relevant See `Issue #3898 <https://github.com/nim-lang/Nim/issues/3898>`_ for the
discussion. relevant discussion.
- Overloading the special operators ``.``, ``.()``, ``.=``, ``()`` now - Overloading the special operators ``.``, ``.()``, ``.=``, ``()`` now
should be enabled via ``{.experimental.}``. needs to be enabled via the ``{.experimental.}`` pragma.
- ``immediate`` templates and macros are now deprecated. - ``immediate`` templates and macros are now deprecated.
Instead use ``untyped`` parameters. Use ``untyped`` `(doc) <http://nim-lang.org/docs/manual.html#templates-typed-vs-untyped-parameters>`_
- The metatype ``expr`` is deprecated. Use ``untyped`` instead. parameters instead.
- The metatype ``stmt`` is deprecated. Use ``typed`` instead.
- The metatype ``expr`` is deprecated. Use ``untyped``
`(doc) <http://nim-lang.org/docs/manual.html#templates-typed-vs-untyped-parameters>`_ instead.
- The metatype ``stmt`` is deprecated. Use ``typed``
`(doc) <http://nim-lang.org/docs/manual.html#templates-typed-vs-untyped-parameters>`_ instead.
- The compiler is now more picky when it comes to ``tuple`` types. The - The compiler is now more picky when it comes to ``tuple`` types. The
following code used to compile, now it's rejected: following code used to compile, now it's rejected:
.. code-block:: nim .. code-block:: nim
import tables import tables
var rocketaims = initOrderedTable[string, Table[tuple[k: int8, v: int8], int64] ]() var rocketaims = initOrderedTable[string, Table[tuple[k: int8, v: int8], int64]]()
rocketaims["hi"] = {(-1.int8, 0.int8): 0.int64}.toTable() rocketaims["hi"] = {(-1.int8, 0.int8): 0.int64}.toTable()
Instead be consistent in your tuple usage and use tuple names for tuples Instead be consistent in your tuple usage and use tuple names for named tuples:
that have tuple name:
.. code-block:: nim .. code-block:: nim
import tables import tables
var rocketaims = initOrderedTable[string, Table[tuple[k: int8, v: int8], int64] ]() var rocketaims = initOrderedTable[string, Table[tuple[k: int8, v: int8], int64]]()
rocketaims["hi"] = {(k: -1.int8, v: 0.int8): 0.int64}.toTable() rocketaims["hi"] = {(k: -1.int8, v: 0.int8): 0.int64}.toTable()
- Now when you compile console application for Windows, console output - Now when you compile console applications for Windows, console output
encoding is automatically set to UTF-8. encoding is automatically set to UTF-8.
- Unhandled exceptions in JavaScript are now thrown regardless ``noUnhandledHandler`` - Unhandled exceptions in JavaScript are now thrown regardless of whether
is defined. But now they do their best to provide a readable stack trace. ``noUnhandledHandler`` is defined. But the stack traces should be much more
readable now.
- In JavaScript ``system.alert`` is deprecated. Use ``dom.alert`` instead. - In JavaScript, the ``system.alert`` procedure has been deprecated.
Use ``dom.alert`` instead.
- ``AsyncHttpClient.headers`` type is now ``HttpHeaders``. - De-deprecated ``re.nim`` because there is too much code using it
and it got the basic API right.
- The type of ``headers`` field in the ``AsyncHttpClient`` type
`(doc) <http://nim-lang.org/docs/httpclient.html#AsyncHttpClient>`_
has been changed
from a string table to the specialised ``HttpHeaders`` type.
- The ``httpclient.request``
`(doc) <http://nim-lang.org/docs/httpclient.html#request,AsyncHttpClient,string,string,string>`_
procedure which takes the ``httpMethod`` as a string
value no longer requires it to be prefixed with ``"http"``
(or similar).
- Converting a ``HttpMethod``
`(doc) <nim-lang.org/docs/httpcore.html#HttpMethod>`_
value to a string using the ``$`` operator will
give string values without the ``"Http"`` prefix now.
- The ``Request``
`(doc) <http://nim-lang.org/docs/asynchttpserver.html#Request>`_
object defined in the ``asynchttpserver`` module now uses
the ``HttpMethod`` type for the request method.
Library Additions Library Additions
----------------- -----------------
- Added ``readHeaderRow`` and ``rowEntry`` to ``parsecsv.nim`` to provide - Added ``readHeaderRow`` and ``rowEntry`` to the ``parsecsv``
`(doc) <http://nim-lang.org/docs/parsecsv.html>`_ module
to provide
a lightweight alternative to python's ``csv.DictReader``. a lightweight alternative to python's ``csv.DictReader``.
- Added ``setStdIoUnbuffered`` proc to ``system.nim`` to enable unbuffered I/O.
- Added ``center`` and ``rsplit`` to ``strutils.nim`` to - Added ``setStdIoUnbuffered`` proc to the ``system`` module to enable
provide similar Python functionality for Nim's strings. unbuffered I/O.
- Added ``center`` and ``rsplit`` to the ``strutils``
`(doc) <http://nim-lang.org/docs/strutils.html>`_ module
to provide similar Python functionality for Nim's strings.
- Added ``isTitle``, ``title``, ``swapCase``, ``isUpper``, ``toUpper``, - Added ``isTitle``, ``title``, ``swapCase``, ``isUpper``, ``toUpper``,
``isLower``, ``toLower``, ``isAlpha``, ``isSpace``, and ``capitalize`` ``isLower``, ``toLower``, ``isAlpha``, ``isSpace``, and ``capitalize``
to ``unicode.nim`` to provide unicode aware case manipulation and case to the ``unicode.nim``
`(doc) <http://nim-lang.org/docs/unicode.html>`_ module
to provide unicode aware case manipulation and case
testing. testing.
- Added a new module ``lib/pure/strmisc.nim`` to hold uncommon string - Added a new module ``strmisc``
`(doc) <http://nim-lang.org/docs/strmisc.html>`_
to hold uncommon string
operations. Currently contains ``partition``, ``rpartition`` operations. Currently contains ``partition``, ``rpartition``
and ``expandTabs``. and ``expandTabs``.
- Split out ``walkFiles`` in ``os.nim`` to three separate procs in order - Split out ``walkFiles`` in the ``os``
to make a clear distinction of functionality. ``walkPattern`` iterates `(doc) <http://nim-lang.org/docs/os.html>`_ module to three separate procs
in order to make a clear distinction of functionality. ``walkPattern`` iterates
over both files and directories, while ``walkFiles`` now only iterates over both files and directories, while ``walkFiles`` now only iterates
over files and ``walkDirs`` only iterates over directories. over files and ``walkDirs`` only iterates over directories.
- Added synchronous ``HttpClient`` in the ``httpclient`` module. - Added a synchronous ``HttpClient`` in the ``httpclient``
`(doc) <http://nim-lang.org/docs/httpclient.html>`_
module. The old
``get``, ``post`` and similar procedures are now deprecated in favour of it.
- Added a new macro called ``multisync`` allowing you to write procedures for - Added a new macro called ``multisync`` allowing you to write procedures for
synchronous and asynchronous sockets with no duplication. synchronous and asynchronous sockets with no duplication.
- The ``async`` macro will now complete ``FutureVar[T]`` parameters
automatically unless they have been completed already.
Compiler Additions Compiler Additions
------------------ ------------------
- The ``-d/--define`` flag can now optionally take a value to be used - The ``-d/--define`` flag can now optionally take a value to be used
by code at compile time. by code at compile time.
`(doc) <http://nim-lang.org/docs/manual.html#implementation-specific-pragmas-compile-time-define-pragmas>`_
Nimscript Additions Nimscript Additions
------------------- -------------------
- Finally it's possible to dis/enable specific hints and warnings in - It's possible to enable and disable specific hints and warnings in
Nimscript via the procs ``warning`` and ``hint``. Nimscript via the ``warning`` and ``hint`` procedures.
- Nimscript exports a proc named ``patchFile`` which can be used to - Nimscript exports a proc named ``patchFile`` which can be used to
patch modules or include files for different Nimble packages, including patch modules or include files for different Nimble packages, including
the ``stdlib`` package. the ``stdlib`` package.
Language Additions Language Additions
------------------ ------------------
- Added ``{.intdefine.}`` and ``{.strdefine.}`` macros to make use of - Added ``{.intdefine.}`` and ``{.strdefine.}`` macros to make use of
(optional) compile time defines. (optional) compile time defines.
`(doc) <http://nim-lang.org/docs/manual.html#implementation-specific-pragmas-compile-time-define-pragmas>`_
- If the first statement is an ``import system`` statement then ``system`` - If the first statement is an ``import system`` statement then ``system``
is not imported implicitly anymore. This allows for code like is not imported implicitly anymore. This allows for code like
``import system except echo`` or ``from system import nil``. ``import system except echo`` or ``from system import nil``.
@ -122,6 +171,11 @@ Language Additions
Bugfixes Bugfixes
-------- --------
The list below has been generated based on the commits in Nim's git
repository. As such it lists only the issues which have been closed
via a commit, for a full list see
`this link on Github <https://github.com/nim-lang/Nim/issues?utf8=%E2%9C%93&q=is%3Aissue+closed%3A%222016-06-22+..+2016-09-28%22+>`_.
- Fixed "RFC: should startsWith and endsWith work with characters?" - Fixed "RFC: should startsWith and endsWith work with characters?"
(`#4252 <https://github.com/nim-lang/Nim/issues/4252>`_) (`#4252 <https://github.com/nim-lang/Nim/issues/4252>`_)
@ -347,3 +401,30 @@ Bugfixes
(`#4626 <https://github.com/nim-lang/Nim/issues/4626>`_) (`#4626 <https://github.com/nim-lang/Nim/issues/4626>`_)
- Fixed "module securehash not gcsafe" - Fixed "module securehash not gcsafe"
(`#4760 <https://github.com/nim-lang/Nim/issues/4760>`_) (`#4760 <https://github.com/nim-lang/Nim/issues/4760>`_)
- Fixed "Nimble installation failed on Windows x86."
(`#4764 <https://github.com/nim-lang/Nim/issues/4764>`_)
- Fixed "Recent changes to marshal module break old marshalled data"
(`#4779 <https://github.com/nim-lang/Nim/issues/4779>`_)
- Fixed "tnewasyncudp.nim test loops forever"
(`#4777 <https://github.com/nim-lang/Nim/issues/4777>`_)
- Fixed "Wrong poll timeout behavior in asyncdispatch"
(`#4262 <https://github.com/nim-lang/Nim/issues/4262>`_)
- Fixed "Standalone await shouldn't read future"
(`#4170 <https://github.com/nim-lang/Nim/issues/4170>`_)
- Fixed "Regression: httpclient fails to compile without -d:ssl"
(`#4797 <https://github.com/nim-lang/Nim/issues/4797>`_)
- Fixed "C Error on declaring array of heritable objects with bitfields"
(`#3567 <https://github.com/nim-lang/Nim/issues/3567>`_)
- Fixed "Corruption when using Channels and Threads"
(`#4776 <https://github.com/nim-lang/Nim/issues/4776>`_)
- Fixed "Sometimes Channel tryRecv() erroneously reports no messages available on the first call on Windows"
(`#4746 <https://github.com/nim-lang/Nim/issues/4746>`_)
- Fixed "Improve error message of functions called without parenthesis"
(`#4813 <https://github.com/nim-lang/Nim/issues/4813>`_)
- Fixed "Docgen doesn't find doc comments in macro generated procs"
(`#4803 <https://github.com/nim-lang/Nim/issues/4803>`_)
- Fixed "asynchttpserver may consume unbounded memory reading headers"
(`#3847 <https://github.com/nim-lang/Nim/issues/3847>`_)
- Fixed "TLS connection to api.clashofclans.com hangs forever."
(`#4587 <https://github.com/nim-lang/Nim/issues/4587>`_)