Code Monkey home page Code Monkey logo

jester's Introduction

🃏 Jester 🃏

The sinatra-like web framework for Nim. Jester provides a DSL for quickly creating web applications in Nim.

# example.nim
import htmlgen
import jester

routes:
  get "/":
    resp h1("Hello world")

Compile and run with:

  cd tests/example
  nim c -r example.nim

View at: localhost:5000

Before deploying to production ensure you run your application behind a reverse proxy. This library is not yet hardened against HTTP security exploits so applications written in it should not be exposed to the public internet.

Routes

routes:
  get "/":
    # do something here.

All routes must be inside a routes block.

Routes will be executed in the order that they are declared. So be careful when halting.

The route path may contain a special pattern or just a static string. Special patterns are almost identical to Sinatra's, the only real difference is the use of @ instead of the :.

get "/hello/@name":
  # This matches "/hello/fred" and "/hello/bob".
  # In the route ``@"name"`` will be either "fred" or "bob".
  # This can of course match any value which does not contain '/'.
  resp "Hello " & @"name"

The patterns in Jester are currently a bit more limited, there is no wildcard patterns.

You can use the '?' character to signify optional path parts.

get "/hello/@name?":
  # This will match what the previous code example matches but will also match
  # "/hello/".
  if @"name" == "":
    resp "No name received :("
  else:
    resp "Hello " & @"name"

In this case you might want to make the leading '/' optional too, you can do this by changing the pattern to "/hello/?@name?". This is useful because Jester will not match "/hello" if the leading '/' is not made optional.

Regex

Regex can also be used as a route pattern. The subpattern captures will be placed in request.matches when a route is matched. For example:

get re"^\/([0-9]{2})\.html$":
  resp request.matches[0]

This will match URLs of the form /15.html. In this case request.matches[0] will be 15.

Conditions

Jester supports conditions, however they are limited to a simple cond template.

routes:
  get "/@name":
    cond @"name" == "daniel"
    # ``cond`` will pass execution to the next matching route if @"name" is not
    # "daniel".
    resp "Correct, my name is daniel."

  get "/@name":
    # This will be the next route that is matched.
    resp "No, that's not my name."

Return values

Route bodies all have an implicit request object. This object is documented in jester.nim and documentation can be generated by executing nim doc jester.nim.

Returning a response from a route should be done using one of the following functions:

  • One of the resp functions.
  • By setting body, headers and/or status and calling return.
  • redirect function
  • attachment function

There might be more. Take a look at the documentation of jester.nim for more info.

Manual routing

It is possible not to use the routes macro and to do the routing yourself.

You can do this by writing your own match procedure. Take a look at example2 for an example on how to do this.

Static files

By default Jester looks for static files in ./public. This can be overriden using the setStaticDir function. Files will be served like so:

./public/css/style.css -> http://example.com/css/style.css

Note: Jester will only serve files, that are readable by others. On Unix/Linux you can ensure this with chmod o+r ./public/css/style.css.

Cookies

Cookies can be set using the setCookie function.

get "/":
  # Set a cookie "test:value" and make it expire in 5 days.
  setCookie("test", @"value", daysForward(5))

They can then be accessed using the request.cookies procedure which returns a Table[string, string].

Request object

The request object holds all the information about the current request. You can access it from a route using the request variable. It is defined as:

Request* = ref object
  params*: StringTableRef       ## Parameters from the pattern, but also the
                                ## query string.
  matches*: array[MaxSubpatterns, string] ## Matches if this is a regex
                                          ## pattern.
  body*: string                 ## Body of the request, only for POST.
                                ## You're probably looking for ``formData``
                                ## instead.
  headers*: StringTableRef      ## Headers received with the request.
                                ## Retrieving these is case insensitive.
  formData*: MultiData          ## Form data; only present for
                                ## multipart/form-data
  port*: int
  host*: string
  appName*: string              ## This is set by the user in ``run``, it is
                                ## overriden by the "SCRIPT_NAME" scgi
                                ## parameter.
  pathInfo*: string             ## This is ``.path`` without ``.appName``.
  secure*: bool
  path*: string                 ## Path of request.
  query*: string                ## Query string of request.
  cookies*: StringTableRef      ## Cookies from the browser.
  ip*: string                   ## IP address of the requesting client.
  reqMeth*: HttpMethod          ## Request method, eg. HttpGet, HttpPost
  settings*: Settings

Examples

Custom router

A custom router allows running your own initialization code and pass dynamic settings to Jester before starting the async loop.

import asyncdispatch, jester, os, strutils

router myrouter:
  get "/":
    resp "It's alive!"

proc main() =
  let port = paramStr(1).parseInt().Port
  let settings = newSettings(port=port)
  var jester = initJester(myrouter, settings=settings)
  jester.serve()

when isMainModule:
  main()

Github service hooks

The code for this is pretty similar to the code for Sinatra given here: http://help.github.com/post-receive-hooks/

import jester, json

routes:
  post "/":
    var push = parseJson(@"payload")
    resp "I got some JSON: " & $push

jester's People

Contributors

0atman avatar ajusa avatar alaviss avatar allan avatar araq avatar dom96 avatar federicoceratto avatar glademiller avatar hiteshjasani avatar iffy avatar its5q avatar itsumura-h avatar moigagoo avatar nanoant avatar narimiran avatar nigredo-tori avatar noirscape avatar onionhammer avatar pmunch avatar rosetree avatar runvnc avatar shinriyo avatar silent-observer avatar thomastjdev avatar timotheecour avatar windgo-inc avatar xland avatar xmonader avatar yglukhov avatar zedeus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jester's Issues

Template processing in a route is broken

https://gist.github.com/Heartmender/59250eddb8e20252541c for my code and the compiler output.

My goal is to have things like materialize be slipstreamed into the server binary with the correct cache-control flags set so that it only has to serve the base css/javascript files once.

replacing:

const
  materializeCSS = staticRead "public/css/materialize.min.css"

routes:
  get "/static/css/materialize.min.css":
    headers["Cache-Control"] = "public, max-age=31536000"
    resp materializeCSS, "text/css"

with:

const
  materializeCSS = staticRead "public/css/materialize.min.css"

template cachedFile(path, contents, mimetype: string) =
  get path:
    headers["Cache-Control"] = "public, mapath-age=31536000"
    resp contents, mimetype

routes:
  cachedFile "/static/css/materialize.min.css", materializeCSS, "text/css"

The template gets discarded and the resulting static constants get thrown out of the binary at compile.

Error running the example code

I tried to run the very first code example in the wiki, and got the following error on my macbook pro,

/.nimble/pkgs/jester-0.1.0/private/errorpages.nim(7, 22) Error: got 0, but expected 1 argument(s)

any hint?

Moustachu lib for your views

I'm trying your framework with the moustachu lib and a html file and it's cool.
Indeed, moustachu is an implementation of mustach that allow you to pass parameters on rendering.
Furthermore the developers could use javascript or css (for exemple) into the rendering file.

Sorry for my english

How fast is this?

How fast is this compared to the original sinatra framework in ruby since this is compiled to c it should atleast be 1000x faster right?

Memory consumption seems high

May I'm doing something wrong but when I serve big files (< 1GB) the memory consumption raises very high very fast.
Example code:

    get "/@filename":
      var filename = @"filename"
      var file = readFile(filename)
      resp(file, "application")

This seems also the case when I serve the big files inside the public folder.

Jester resp block problem

Me to do a program with the jester, in get block using resp to the client returns the data, when a small amount of data, the client can receive a normal. Why the amount of data is very big, such as a tens of KB, the client will not receive?

Assert failing on start

Jester is making jokes at http://localhost:8080
GET /
Traceback (most recent call last)
main.nim(78) main
main.nim(56) updateLoop
asyncio.nim(631) poll
asyncio.nim(214) asyncSockHandleRead
httpserver.nim(495) :anonymous
jester.nim(429) :anonymous
jester.nim(348) handleHTTPRequest
jester.nim(249) handleRequest
jester.nim(124) statusContent
jester.nim(119) sendHeaders
jester.nim(104) trySendEx
system.nim(2555) hiddenRaiseAssert
system.nim(1844) raiseAssert
Error: unhandled exception: not j.isAsync [EAssertionFailed]
Jester finishes his performance.

support async mode

dom96, I haven't seen jester before. very cool!

There is one problem with the current internal design tho - since handlers have to return the response body, it's not possible for a handler to complete asynchronously. Imagine what will happen if a handler needs to talk to several other services over the network to build the response - this communication must be blocking, jester needs to spawn 1 thread per request (not cool) and ultimately it's better if the requests are carried out in parallel.

To allow async mode, it's better if the handler type is proc (TRequest, TResponse), where the TResponse holds a reference to the output stream. Ideally, the handler will be written as a coroutine once we have these in nimrod. This will be similar to the deferred generators in python's twisted framework and the new await keyword in C# 5 (await is really just a syntax sugar for a framework built around async futures and coroutines), but even this will be based on the same low-level proc (req, res) handlers.

Problem with devel version of Nim

It seems that Jester does not work with the devel version of nim (pulled from devel branch of github). Trying to start example (from readme.md of Jester) I got:
Original stack trace in serve:
Traceback (most recent call last)
macros.nim(309) example
jester.nim(326) serve
asyncdispatch.nim(1225) serve
asyncdispatch.nim(1212) cb
asynchttpserver.nim(254) serveIter
asyncnet.nim(443) bindAddr
rawsockets.nim(210) getAddrInfo
os.nim(280) raiseOSError

Continuing...
Traceback (most recent call last)
macros.nim(309) example
jester.nim(326) serve
asyncdispatch.nim(282) asyncCheck
asyncdispatch.nim(224) callback=
asyncdispatch.nim(286) :anonymous
Error: unhandled exception: Invalid value for ai_flags No such file or directory [Exception]

I'm on FreeBSD 10.1 x64, Jester is reinstalled using nimble - no changes.

Response hooks. Cross-origin requests.

I haven't found a good way to serve static files with Access-Control-Allow-Origin header. Would be nice to have some hooks to post-process response along with its headers.

an error has occured when i try to reply to "http://forum.nim-lang.org/t/1991?action=reply#reply"

An error has occured in one of your routes.

Detail: Traceback (most recent call last)
forum.nim(1328) forum
asyncdispatch.nim(1617) runForever
asyncdispatch.nim(1032) poll
asyncdispatch.nim(1149) cb
asyncdispatch.nim(213) complete
asyncdispatch.nim(1257) cb
os.nim(1159) recvLineIntoIter
asyncdispatch.nim(222) complete
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1257) cb
asynchttpserver.nim(238) processClientIter
jester.nim(328) :anonymous
jester.nim(299) handleHTTPRequest
asyncdispatch.nim(1270) handleRequest
asyncdispatch.nim(1257) cb
jester.nim(263) handleRequestIter
asyncdispatch.nim(1270) match
asyncdispatch.nim(1257) cb
forum.nim(1095) matchIter
forum.nim(458) reply
strtabs.nim(109) []
key not found: subject

documentation :(

your documentation only shows basic examples for responding with "hello world" stuff, and your book focuses on server rendered applications. however, some people (such as i) would like to build single page apps and build a backend api with jester. can you provide some documentation examples for sending status codes for example, because i tried doing status = 200 and it failed which was very confusing because it requires an HttpCode which cannot be found on google. now i'm using status = Http400 but i'm getting a 404 instead. still struggling on to find out how to do a simple status code :( please try to cater for api developers rather than just for server rendered apps, i'm sure most of the apps built these days are SPAs anyway...

Custom 404 response?

I would like to use a custom 404 response.

Maybe like this?

import jester, asyncdispatch

errorpages:
  pagenotfound:
    resp "nothing found"

routes:
  get "/":
    resp "hello, world!"

runForever()

code gen error when using sequtils.mapIt()

Example code to illustrate (it fails for me on win7, don't know about other Linux/Mac)

import jester, asyncdispatch, htmlgen
import strutils, sequtils

var
  tseq = @[@["Hello","World"],@["bad","result"],@["Needs a","fix"]]

proc doTest(): string =
  var
    thed  = ""
    tbod = ""
  result = ""
  for rNr,r in pairs(tseq):       # for each row (a seq[string])
    var x: seq[string] = r
    if rNr == 0:                  # header row
      x = r.mapIt(th(it))  # THIS FAILS
      #x.applyIt(th(it))   # THIS WORKS
      thed = x.join(" ")  # str join wrapped th elements
    else:
      # add row wrapped in tr, with each element tt wrapped
      #x = r.mapIt(td(it))  # THIS FAILS
      x.applyIt(td(it))   # THIS WORKS
      tbod &= tr(x.join(" ")) 
  result = table(thead(thed), tbody(tbod))

proc test2(): string = "test data"

echo html(body(doTest()))

routes:
  get "/test2":
    resp html(body(test2()))    # works
  get "/test1":
    resp html(body(doTest()))   # doesn't work if contains mapIt()

runForever()

errors like:

nimcache\compiler_bug1.c: In function 'matchiter_250403':
nimcache\compiler_bug1.c:949:26: error: request for member 'ClEnv' in something not a structure or union
     LOC26 = dotest_250060.ClEnv? dotest_250060.ClPrc(dotest_250060.ClEnv):((TMP1315)(dotest_250060.ClPrc))();
                          ^
nimcache\compiler_bug1.c:949:47: error: request for member 'ClPrc' in something not a structure or union
     LOC26 = dotest_250060.ClEnv? dotest_250060.ClPrc(dotest_250060.ClEnv):((TMP1315)(dotest_250060.ClPrc))();
                                               ^
nimcache\compiler_bug1.c:949:67: error: request for member 'ClEnv' in something not a structure or union
     LOC26 = dotest_250060.ClEnv? dotest_250060.ClPrc(dotest_250060.ClEnv):((TMP1315)(dotest_250060.ClPrc))();
                                                                   ^
nimcache\compiler_bug1.c:949:99: error: request for member 'ClPrc' in something not a structure or union
     LOC26 = dotest_250060.ClEnv? dotest_250060.ClPrc(dotest_250060.ClEnv):((TMP1315)(dotest_250060.ClPrc))();

Top level exception handler

In many scenarios one has different exceptions that maps into say 400, 404 etc. It would be nice to see a way to maybe register a exceptionHandler or something at the top level.

Empty request params with XmlHTTP Post request

When I try to perform POST request with Ajax, the server hangs eating 94% to 100% of the CPU core on Ubuntu 14.10.

Here's client's script, which works (GET request, and if we change it to POST, jester hangs):

    jQuery.ajax({
        contentType: 'application/json',
        dataType: 'json',
        data: {
            userId: document.getElementById('user-id').value
        },
        type: 'GET',
        url: '/handle-this',

        success: function(data) {
            switch (data["result"]) {
            case -2:
                alert("malformed json");
            case -1:
                alert("Bad session id");
            case  0:
                alert("Success");
            }
        },

        error: function() {
            alert("Error saving cheat response");
        }
    });

and the server handler:

echo request.params    # Prints: {:}
try:
    sessId = request.params["userId"]
    echo sessId
except:
    respJson Http200, """{"result": -1}"""

Everything works fine with GET request (parameters are parsed and passed into handler), and does not work with POST request, parameters are empty and the server hangs at sessId = request.params["userId"] line.

`attachment` failed to reply

The followying code is built on nim 0.13:

import jester,asyncdispatch

settings:
    port = Port(8081)
    staticDir = "./static"

routes:
  get "/attachment/@id":
    echo "attachment=" & @"id"
    attachment "pages/login.html" #error occurred
    resp "done"

when isMainModule:
  echo "starting web server ..."
  runForever()

when browser:
http://localhost:8081/attachment/1
eror occurred on page:

Detail: Traceback (most recent call last)
gist.nim(18) gist
asyncdispatch.nim(1617) runForever
asyncdispatch.nim(446) poll
asyncdispatch.nim(722) :anonymous
asyncdispatch.nim(213) complete
asyncdispatch.nim(1257) cb
asyncnet.nim(320) recvLineIntoIter
asyncdispatch.nim(222) complete
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1262) cb
asyncdispatch.nim(260) callback=
asyncdispatch.nim(1257) cb
asynchttpserver.nim(238) processClientIter
jester.nim(330) :anonymous
jester.nim(301) handleHTTPRequest
asyncdispatch.nim(1270) handleRequest
asyncdispatch.nim(1257) cb
jester.nim(265) handleRequestIter
asyncdispatch.nim(1270) match
asyncdispatch.nim(1257) cb
jester.nim(444) matchIter
strtabs.nim(109) []
key not found: Content-Type

What happened?

Separation of jester routes between different modules or files.

Hello:

I would like to divide a rest api project with jester into different src files:

Ex:
main.nim

    import jester, asyncdispatch
    import another_part
    routes:
      get "/":
        resp """ {"status": "ok"} """, "application/json"

another_part.nim

    import jester, asyncdispatch
    routes:
      get "/anotherpart":
        resp """ {"status": "ok"} """, "application/json"

The current behaviour is that on running like this it will try to open two web servers with the same port.

Nitpick: required version note

The required version note in the README reads: Note: Jester requires Nim version 0.11.2.. This might be confusing as it suggests that Jester only works for that single version of Nim. Consider changing it to Note: Jester requires Nim version >0.11.2. or Note: Jester requires Nim version 0.11.2 or above.

resp may be problematic in templates

I have no idea how I managed to do this here:

DEBUG get /last_aired
DEBUG   200 OK {Content-Length: 16, Content-Type: {
    "episode":
    {
        "is_movie": false,
        "name": "Spice Up Your Life",
        "air_date": 1465659000,
        "episode": 12,
        "season": 6
    }
}}

Reading form data

Hi, is there a way to get the parameters of a request with type application/x-www-form-urlencoded? Or should one parse them by hand? I think that should a pretty standard way to pass form parameters

Routes and local values

Not sure whether this is Jester problem or Nim problem...
Compilation fails if routes macro is used inside a function, and a value, local to this function (or an argument), is used in one of the branches.
Source:

import jester, asyncdispatch

proc startServer() =
  let state = 1
  routes:
    get "/":
      echo state
      resp "OK"

results in this:

CC: test_jester
CC: stdlib_system
CC: jester_jester
CC: stdlib_asynchttpserver
Error: execution of an external compiler program 'gcc -c  -w  -I/home/dmitry/work/nim/lib -o /home/dmitry/work/tmp/nimcache/test_jester.o /home/dmitry/work/tmp/nimcache/test_jester.c' failed with exit code: 256

/home/dmitry/work/tmp/nimcache/test_jester.c: In function ‘startserver_240001’:
/home/dmitry/work/tmp/nimcache/test_jester.c:1066:2: error: incompatible type for argument 1 of ‘serve_238873’
  serve_238873(LOC3, settings);
  ^
In file included from /home/dmitry/work/tmp/nimcache/test_jester.c:9:0:
/home/dmitry/work/tmp/nimcache/test_jester.c:399:17: note: expected ‘TY238875’ but argument is of type ‘TY240205’
 N_NIMCALL(void, serve_238873)(TY238875 match, SettingsHEX3Aobjecttype237032* settings);
                 ^
/home/dmitry/work/nim/lib/nimbase.h:215:44: note: in definition of macro ‘N_NIMCALL’
 #  define N_NIMCALL(rettype, name) rettype name /* no modifier */

Using devel Nim version, Ubuntu, gcc 4.8.5

Transmit response headers (response.data[3]?) w/ sendHeaders

When sendHeaders is called, it currently expects an HTTP response code, and a PStringTable of headers; it would be nice if the response code defaulted to HTTP 200 OK, and there was an overload for "sendHeaders" that automatically sent up the built-up response headers (i.e. headers["content-type"] = "text/html") if the user does not provide a PStringTable.

Speed comparison with httpserver

I don't know how exactly this works behind the scenes, but it seems terribly slow at the moment, compared to a dumb httpserver. Here is the code tested, with nim [file] c -r --checks:off --opt:speed >/dev/null:

import jester, asyncdispatch, htmlgen

routes:
  const r = h1 "hello world"
  get "/":
    resp r

runForever()

and

import strutils, sockets, httpserver

proc handleRequest(client: Socket, path, query: string): bool {.procvar.} =
  client.send("<h1>hello world</h1>")
  client.close

run(handleRequest, Port(5000))

And here are the benchmarks for ab -n10000 -c400 http://localhost:5000/:

# Jester
Concurrency Level:      400
Time taken for tests:   13.004 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      840000 bytes
HTML transferred:       200000 bytes
Requests per second:    768.98 [#/sec] (mean)
Time per request:       520.171 [ms] (mean)
Time per request:       1.300 [ms] (mean, across all concurrent requests)
Transfer rate:          63.08 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   18 227.7      0    3004
Processing:    49   67 173.7     55   10387
Waiting:        6   67 173.6     55   10387
Total:         52   86 364.7     55   10387

Percentage of the requests served within a certain time (ms)
  50%     55
  66%     56
  75%     57
  80%     58
  90%     60
  95%     62
  98%     79
  99%    129
 100%  10387 (longest request)


# httpserver
Concurrency Level:      400
Time taken for tests:   0.438 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      200000 bytes
HTML transferred:       0 bytes
Requests per second:    22855.89 [#/sec] (mean)
Time per request:       17.501 [ms] (mean)
Time per request:       0.044 [ms] (mean, across all concurrent requests)
Transfer rate:          446.40 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    2   1.2      2      13
Processing:     1    3   0.9      3       9
Waiting:        1    3   0.8      3       7
Total:          3    6   1.7      5      18

Percentage of the requests served within a certain time (ms)
  50%      5
  66%      5
  75%      5
  80%      5
  90%      8
  95%      9
  98%     10
  99%     16
 100%     18 (longest request)

The mean time per request is 520ms vs 17ms, so a 30x slowdown. Now, I get that Jester is supposed to do much more than a dumb httpserver, but 30x seems way off for me. Do you know why this is happening or whether it's going to get better?

Readme example doesn't compile with nim 0.10.2.

After installing with nimble install jester the example mentioned in the readme fails to compile. Same with the git checkout:

jester.nim(183, 44) Info: instantiation from here
jester.nim(188, 73) Error: undeclared identifier: '^'

The readme mentions 0.10.0 should be enough. Why do I have the feeling your modules rarely work with stable compiler versions…

walkDirRec

Hi the walkDirRec function doesn't seem to work in a GET request.
If I do this

import os, re, jester, asyncdispatch, htmlgen, asyncnet


routes:

  get "/":
    var html = ""
    html.add "<form action=\"upload\" method=\"post\"enctype=\"multipart/form-data\">"
    html.add "<input type=\"file\" name=\"file\"value=\"file\">"
    html.add "<input type=\"submit\" value=\"Submit\" name=\"submit\">"
    html.add "</form>"
    html.add "<h3>Folder</h3>"
    for file in walkDirRec("./"):
      html.add "<li><a href=\"" &file & "\">" & file & "</li>"
    resp(html)

runForever()

i get

/nimcache/posix.c:103:18: error: invalid application of 'sizeof' to an incomplete type 'DIR' (aka 'struct __dirstream')
NTI103214.size = sizeof(DIR);
                 ^     ~~~~~
/usr/include/dirent.h:127:16: note: forward declaration of 'struct __dirstream'
typedef struct __dirstream DIR;
               ^
1 error generated.
Error: execution of an external program failed

Am I doing something wrong or is this a bug?

Greetings Jakob

Document filters

It would be nice to have an example and/or tutorial on how to use Jester with nimrod's filters for html (and CSS) templates.

why the browser window to send the request is the execution order, rather than asynchronous execution.

My code is as follows, open multiple browser windows, access program written by Jester the same address, submits the query to it, code to query the database statements, depending on the query condition, query time, ranging from the length of, why the browser window to send the request is the execution order, rather than asynchronous execution.

import db_odbc
import jester, asyncdispatch, htmlgen, asyncnet

settings:
  port = Port(5000)
  appName = "/book"
  bindAddr = "127.0.0.1"

routes:
  get "/":
    resp "Forbidden access!"

  post "/":
    resp "Forbidden access!"

  post "/cx":
    var mysql="select goods.goodsid,goods.categoryid,goods.barcode,goods.goodslname,goods.adviceprice,"
    mysql.add "goods.cbsj,goods.bb,shopstock.qty from goods left join shopstock on goods.goodsid=shopstock.goodsid where 1=1"
    var isbn,sm,cbs:string
    isbn=""
    sm=""
    cbs=""
    isbn=request.formData.getOrDefault("t_isbn").body
    sm=request.formData.getOrDefault("t_sm").body
    cbs=request.formData.getOrDefault("t_cbs").body
    if isbn=="" and sm=="" and cbs=="":
      resp "nokey","text/html;charset=gb2312"
    else:
      if isbn!="":
        mysql.add " and goods.barcode='" & isbn & "'"
      if sm!="":
        mysql.add " and goods.goodslname like '%" & sm & "%'"
      if cbs!="":
        mysql.add " and goods.bb like '%" & cbs & "%'"

      var theDb = open("dbpos", "dbcxj", "dbcxj", "dbpos")
      var myrows :seq[Row] = @[]
      myrows=theDb.getAllRows(sql(mysql))
      theDb.close()

      resp $myrows,"text/html;charset=gb2312"

runForever()

Jester Crashes when locks module imported after jester module

when hostOs == "linux":
    {.passL: "-pthread".}

import asyncdispatch
import jester
import locks

routes:
    get "/":

        let st = newStringTable()
        try:
            let x = st["x"]
        except:
            echo "NOt FOUND x"
            let mytab = newStringTable({"key1": "val1", "key2": "val2"},
                                       modeCaseInsensitive)
            echo "MYTAB: ", mytab

        try:
            let x = st["x"]
        except:
            echo "NOt FOUND x"
            let mytab = newStringTable({"key1": "val1", "key2": "val2"},
                                       modeCaseInsensitive)
            echo "MYTAB: ", mytab


        resp Http200, """{"result": -1}"""

    settings.port = Port(4000)

runForever()

Steps to reproduce:

  1. Run the example in order to reproduce the bug: nim c -r main.nim
  2. Enter localhost:4000/ in browser.

Binary File upload

Hi, I'm trying to build a simple file upload with jester and it works with text files but when I upload a image the image is corrupted through ascii conversion.
Here my code:

import os, re, jester, asyncdispatch, htmlgen, asyncnet

routes:
  get "/":
    var html = ""
    for file in walkFiles("*.*"):
      html.add "<li>" & file & "</li>"
    html.add "<form action=\"upload\" method=\"post\"enctype=\"multipart/form-data\">"
    html.add "<input type=\"file\" name=\"file\"value=\"file\">"
    html.add "<input type=\"submit\" value=\"Submit\" name=\"submit\">"
    html.add "</form>" 
    resp(html)

  post "/upload":
    writeFile("test.png", request.formData["file"].body)
    resp(request.formData["file"].body)
runForever()

Is there a way to save it in binary? Also I'm not shure if the conversion happens while I save the file or while the file gets transmitted.

Greetings Jakob

About `"Transfer-Encoding": "chunked"` and streaming response

@dom96

How about adding a sugar to write streaming response with "Transfer-Encoding": "chunked" ?

import strutils, math

proc sendChunk*(response: jester.Response, data: string): Future[void] =
  let dataLen = len(data)
  let sizeLen = Positive(floor(log2(toFloat(dataLen)) / 4 + 1))
  response.send(toHex(BiggestInt(dataLen), sizeLen) & "\r\n" & data & "\r\n")

proc endChunk*(response: jester.Response): Future[void] =
  response.send("0\r\n\r\n")

Test example:

routes:
  get "/":
    var headers = newStringTable({
      "Content-Type": "text/plain",
      "Transfer-Encoding": "chunked"
    })
    await response.sendHeaders(Http202, headers)
    await sleepAsync(1000)

    await response.sendChunk("hello")
    await sleepAsync(1000)

    await response.sendChunk(" world")
    await sleepAsync(1000)

    await response.sendChunk("AAAAAAAAAAAAAAAAAAAAA")
    await sleepAsync(1000)

    await response.endChunk()

Thoughts about Jester and practical header handling

Thoughts about Jester and practical header handling

I looked into the code and think that I would like to contribute some changes to add functionalities based around headers.

But at first: I am not sure if that is really in the scope of Jester (and where to stop). I don't know "sinatra". I just develop our companies own framework since 1997 and it is opinionated but very flexible about how stuff works. It evolved 18 years and as such is still very compact and backward compatible to the version from 10 years ago. Which I mention because "the scope" of Jester could be equal to that, which is based on practical experience with many mostly database focused projects.

Some Targets

My main targets would be extending the current code in a way that headers are not something which is handled "just in the end" of handling a request.

I also believe that "status code" has not really a lot to do with the content you send. Many "error code" responses could have a nice HTML page or for example json content.

As another example: There are a lot of different and meaningful ways of redirection besides 303 so this needs to be a parameter (with a sensible default).

Content-Type... well I love to server images, csv, pdf, json dynamically generated through the framework. HTML is just one thing which will profit from extended header handling (for example it is really nice to have e-tags to decide if an image needs to be recreated).

Header handling

Basically headers should be "collected" throughout the processing and there should be some practicable functionality implemented around HTTP "caching" (which does not cache anything). Disabling the default header handling should be optional, as there are always sensitive defaults!

content related

  • content-type is invaluable (should just "default" to text/html)
  • compression gzip and deflate come to mind
  • languages / encodings and other stuff maybe later...

caching related

  • if-modified-since: should by default work for static sources
  • last-modified: should be send per default for everything
  • expires: should be easily to set (like expires in timeframe or at given time)
  • A "nocache" mode (absolutely no!) could be implemented as some "magic" headers
  • e-tags: could be available with some nearly "raw" helpers and as functions about the content response. Later would not safe time for computing of the answer but skips the transfer (useful to safe bandwith)

Conclusion

If those changes are in the scope for Jester I can try to improve the headers and some other stuff. I am not sure how much time I can invest because I can't move our projects to Jester and Nim. Mostly because our codebase is just to big. But there may be a way to "integrate" stuff which I don't see right now. Besides that I may find a project which could be based on jester in the near future.

Jester cannot be compiled against C++

I'm not 100% sure, but it seems like that. I tried to compile just:

import jester

with

$ nim cpp test.nim

The error is:

SIGSEGV: Illegal storage access. (Attempt to read from nil?)

The full log is:

config/nim.cfg(53, 3) Hint: added path: '/root/.babel/pkgs/' [Path]
config/nim.cfg(54, 3) Hint: added path: '/root/.nimble/pkgs/nimble-0.6.0' [Path]
config/nim.cfg(54, 3) Hint: added path: '/root/.nimble/pkgs/jester-0.1.0' [Path]
config/nim.cfg(54, 3) Hint: added path: '/root/.nimble/pkgs/' [Path]
Hint: used config file '/opt/Nim/config/nim.cfg' [Conf]
Hint: system [Processing]
Hint: test [Processing]
Hint: jester [Processing]
Hint: asynchttpserver [Processing]
Hint: strtabs [Processing]
Hint: os [Processing]
Hint: strutils [Processing]
Hint: parseutils [Processing]
Hint: times [Processing]
Hint: posix [Processing]
Hint: hashes [Processing]
Hint: asyncnet [Processing]
Hint: asyncdispatch [Processing]
Hint: oids [Processing]
Hint: endians [Processing]
Hint: tables [Processing]
Hint: math [Processing]
Hint: macros [Processing]
Hint: rawsockets [Processing]
Hint: unsigned [Processing]
Hint: net [Processing]
Hint: selectors [Processing]
Hint: epoll [Processing]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing recvLine as an async proc. [User]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing connect as an async proc. [User]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing recv as an async proc. [User]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing send as an async proc. [User]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing recvLineInto as an async proc. [User]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing recvLine as an async proc. [User]
lib/pure/asyncnet.nim(405, 12) Hint: 'addNLIfEmpty' is declared but not used [XDeclaredButNotUsed]
Hint: uri [Processing]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing processClient as an async proc. [User]
lib/pure/asynchttpserver.nim(152, 12) Hint: 'value' is declared but not used [XDeclaredButNotUsed]
lib/pure/asynchttpserver.nim(152, 7) Hint: 'key' is declared but not used [XDeclaredButNotUsed]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing serve as an async proc. [User]
Hint: re [Processing]
Hint: pcre [Processing]
Hint: rtarrays [Processing]
Hint: scgi [Processing]
Hint: sockets [Processing]
lib/pure/scgi.nim(35, 8) Warning: sockets is deprecated [Deprecated]
Hint: asyncio [Processing]
lib/pure/asyncio.nim(11, 8) Warning: sockets is deprecated [Deprecated]
lib/pure/scgi.nim(35, 40) Warning: asyncio is deprecated [Deprecated]
Hint: cookies [Processing]
Hint: mimetypes [Processing]
Hint: md5 [Processing]
Hint: patterns [Processing]
Hint: errorpages [Processing]
Hint: htmlgen [Processing]
Hint: utils [Processing]
Hint: cgi [Processing]
lib/pure/asyncdispatch.nim(1410, 7) Hint: Processing sendHeaders as an async proc. [User]
SIGSEGV: Illegal storage access. (Attempt to read from nil?)

I use Docker container with nim built from master:

$ docker run -it --rm -v `pwd`:/src -p 5000:5000 coopernurse/docker-nim sh
/src # nimble install jester
/src # apk add --update g++ libgc++
/src # nim cpp test.nim

When I use nim c test.nim it works fine!

Request params persist after POST request

test code:

import jester, asyncdispatch

routes:
    get "/":
        let foo = $request.params
        echo foo
        resp """<form method=post action=/foo><input type=text name=bla><input type=submit value=.></form>"""
    post "/foo":
        let foo = $request.params
        echo foo
        resp """<p>Done. <a href="/">back</a></p>"""

runForever()

Steps to reproduce:

  1. open / in the browser
  2. enter something in the input field and press the button
  3. click back
  4. in the log, you will see that the GET request to / still gets the POST parameter of /foo.

Update for Nim 0.14

With the latest changes to HTTP headers, Jester does not work on latest Nim stable

Optional logging

Would be nice to silence logging, that's useful for example during test or benchmarks

runtime error on windows x64 system

Error:

could not load: pcre.dll
Error: execution of an external program failed

I have pcre64.dll in c:/Nim/bin/ directory, link this one should work.

Can't build example from the README

I get the following error when trying to build the very first example from the README:

/home/demizer/.babel/pkgs/jester-0.1.0/jester.nim(346, 15) Info: instantiation from here
/home/demizer/.babel/pkgs/jester-0.1.0/jester.nim(244, 44) Error: type mismatch: got (TSocket) but expected 'PAsyncSocket'

I'm new to nimrod so I have no idea how to get past this.

I am using nimrod, babel, and jester from git. All compiled a few hours ago on Arch Linux.

Error building project using jester with latest nim

Nim Compiler Version 0.13.1 (2016-02-24) [Linux: amd64]
jester-0.1.0

My error is

radio_server.nim(36, 1) template/generic instantiation from here
lib/core/macros.nim(316, 70) template/generic instantiation from here
lib/pure/asyncdispatch.nim(1254, 8) Error: 'cb' is not GC-safe as it accesses 'nameIterVar' which is a global using GC'ed memory

radio_server.nim is my own file which contains this string N36
routes:

I compiled with jester-0.1.0 and nim 0.12.1.
I didn't change my code since previous nim version.

Thank you

"?" symbol

I cannot route like this

post "something?action=ololo":

because ? is not valid escape sequence.
Triple string are not working also.
I looked at patterns.nim and it seems that it is really impossible to route something
like that.

middleware support

Request for feature: middlewares, much like other frameworks do them. The basic principle is:

You have a chain of handler instances, each responding to a method that takes, at the very least, the request, a way to respond to the request, and the next handler as parameters.

They are called in sequence; each handler is responsible for calling into the next one. This results in a nested call chain, and allows each registered handler to:

  • stop propagating the request and return data to the client - for example, to keep auth&auth in one place, or to add api/request throttling
  • modify the request or environment (for example, handle content-type and transform the body data from/to json, and handle errors pertaining to that transparent to the framework user)

And many more usecases. A good search term for more information and examples would be "rack middleware".

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.