GithubHelp home page GithubHelp logo

planety / prologue Goto Github PK

View Code? Open in Web Editor NEW
1.2K 26.0 47.0 5.75 MB

Powerful and flexible web framework written in Nim

Home Page: https://planety.github.io/prologue

License: Apache License 2.0

Nim 100.00%
web prologue webframework jester nim full-stack web-development webdev async async-web-applications

prologue's People

Contributors

bung87 avatar david-kunz avatar dependabot[bot] avatar digitalcraftsman avatar dlcmh avatar enthus1ast avatar fbpyr avatar ire4ever1190 avatar jhgalino avatar jiro4989 avatar jivank avatar keshon avatar konradmb avatar lmn avatar lolgab avatar marco4413 avatar mildred avatar mr-thack avatar philippmdoerner avatar ranok avatar ringabout avatar tbdsux avatar unicodex avatar xyb avatar zr0z 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

prologue's Issues

Can't compile

Trying to compile the simple app

import prologue                                                                                                                                                 
proc hello*(ctx: Context) {.async.} =                                                                                                                           
  resp "<h1>Hello, world!</h1>"                                                                                                                                                                                                                                                                                                   

let settings = newSettings()                                                                                                                                    
var app = newApp(settings = settings)                                                                                                                             
app.addRouter("/", helloworld)                                                                                                                                  
app.run() 

nim js hello_world.nim
Hint: used config file '/Users/build/Nim/config/nim.cfg' [Conf]
Hint: used config file '/Users/build/Nim/config/config.nims' [Conf]
Hint: system [Processing]
Hint: widestrs [Processing]
Hint: io [Processing]
Hint: formatfloat [Processing]
Hint: hello_world [Processing]
Hint: prologue [Processing]
Hint: application [Processing]
Hint: asyncdispatch [Processing]
Hint: os [Processing]
Hint: strutils [Processing]
Hint: parseutils [Processing]
Hint: math [Processing]
Hint: bitops [Processing]
Hint: macros [Processing]
Hint: algorithm [Processing]
Hint: unicode [Processing]
Hint: pathnorm [Processing]
Hint: osseps [Processing]
Hint: tables [Processing]
Hint: hashes [Processing]
Hint: times [Processing]
Hint: options [Processing]
Hint: typetraits [Processing]
Hint: jscore [Processing]
Hint: heapqueue [Processing]
Hint: lists [Processing]
Hint: asyncstreams [Processing]
Hint: asyncfutures [Processing]
Hint: deques [Processing]
Hint: cstrutils [Processing]
Hint: monotimes [Processing]
Hint: nativesockets [Processing]
Hint: posix [Processing]
/Users/aaronm/build/Nim/lib/pure/nativesockets.nim(115, 6) Error: type mismatch: got
but expected one of:
proc $(p: Port): string
first type mismatch at position: 1
required type for p: Port
but expression '' is of type: uint16
proc $[T, IDX](x: array[IDX, T]): string
first type mismatch at position: 1
required type for x: array[IDX, T]
but expression '' is of type: uint16
proc $[T](x: openArray[T]): string
first type mismatch at position: 1
required type for x: openArray[T]
but expression '' is of type: uint16
proc $(x: float): string
first type mismatch at position: 1
required type for x: float
but expression '' is of type: uint16
proc $(x: string): string
first type mismatch at position: 1
required type for x: string
but expression '' is of type: uint16
proc $[T, U](x: HSlice[T, U]): string
first type mismatch at position: 1
required type for x: HSlice[$.T, $.U]
but expression '' is of type: uint16
proc $(w: WideCString; estimate: int; replacement: int = 0x0000FFFD): string
first type mismatch at position: 1
required type for w: WideCString
but expression '' is of type: uint16
proc $(s: WideCString): string
first type mismatch at position: 1
required type for s: WideCString
but expression '' is of type: uint16
proc $(x: char): string
first type mismatch at position: 1
required type for x: char
but expression '' is of type: uint16
proc $(x: int): string
first type mismatch at position: 1
required type for x: int
but expression '' is of type: uint16
proc $(t: typedesc): string
first type mismatch at position: 1
required type for t: typedesc
but expression '' is of type: uint16
proc $(x: bool): string
first type mismatch at position: 1
required type for x: bool
but expression '' is of type: uint16
proc $[T: tuple |
object](x: T): string
first type mismatch at position: 1
required type for x: T: tuple or object
but expression '' is of type: uint16
proc $(err: OSErrorCode): string
first type mismatch at position: 1
required type for err: OSErrorCode
but expression '' is of type: uint16
proc $(x: cstring): string
first type mismatch at position: 1
required type for x: cstring
but expression '' is of type: uint16
proc $[T](x: seq[T]): string
first type mismatch at position: 1
required type for x: seq[T]
but expression '' is of type: uint16
proc $[T](x: set[T]): string
first type mismatch at position: 1
required type for x: set[T]
but expression '' is of type: uint16
proc $(x: int64): string
first type mismatch at position: 1
required type for x: int64
but expression '' is of type: uint16
proc $[Enum: enum](x: Enum): string
first type mismatch at position: 1
required type for x: Enum: enum
but expression '' is of type: uint16
proc $[T](self: Option[T]): string
first type mismatch at position: 1
required type for self: Option[$.T]
but expression '' is of type: uint16

expression: $()

Optimization

The following code using Prologue gets around 27000 requests per second when tested with wrk

import prologue

let
  # debug attributes must be false
  settings = newSettings(
    appName = "Prologue",
    debug = false,
    port = 8000,
    staticDirs = "static",
    secretKey = ""
  )

proc index*(ctx: Context) {.async.} =
  resp "Welcome!"

var app = newApp(settings = settings)
app.addRoute("/", index)
app.run()

Whereas this code for Jester gets around 55000 requests per second.

import jester

settings:
  port = 8000.Port

routes:
  get "/":
    resp "Welcome!"

Both are built and tested with -d:release -d:danger flags.

I'm evaluating frameworks for use in a production app. The more efficient the better for this use case. Any insight on how to improve performance with Prologue?

techempower benchmark

first of all I would say nice work !! Your web framework looks great

is it possible you to implement techempower benchs for prologue??? today we have only jester and httpbeast there, and no bench related with any database, like postgresql or mysql.

I confess i am really disappointed with nim, because I can not find any good async driver for access relational databases. Do you have any good to indicate????

[Crash] using a flag as queryParam crashes the app

With the URI RFC it is perfectly valid to to this:

http://example.com/?json

If i do this with prologue I get the following error:

/home/pyloor/dmso/src/ipmngsrv.nim(18) ipmngsrv
/home/pyloor/.nimble/pkgs/prologue-0.2.2/prologue/core/application.nim(269) run
/home/pyloor/.nimble/pkgs/prologue-0.2.2/prologue/core/beast/server.nim(24) serve
/home/pyloor/.nimble/pkgs/httpbeast-0.2.2/httpbeast.nim(423) run
/home/pyloor/.nimble/pkgs/httpbeast-0.2.2/httpbeast.nim(287) eventLoop
/home/pyloor/.nimble/pkgs/httpbeast-0.2.2/httpbeast.nim(219) processEvents
/home/pyloor/.choosenim/toolchains/nim-1.2.2/lib/pure/asyncmacro.nim(319) handleRequest
/home/pyloor/.choosenim/toolchains/nim-1.2.2/lib/pure/asyncmacro.nim(34) handleRequestNimAsyncContinue
/home/pyloor/.nimble/pkgs/prologue-0.2.2/prologue/core/application.nim(215) handleRequestIter
/home/pyloor/.choosenim/toolchains/nim-1.2.2/lib/pure/cgi.nim(114) parseFormParams
/home/pyloor/.choosenim/toolchains/nim-1.2.2/lib/pure/cgi.nim(72) cgiError
[[reraised from:
/home/pyloor/dmso/src/ipmngsrv.nim(18) ipmngsrv
/home/pyloor/.nimble/pkgs/prologue-0.2.2/prologue/core/application.nim(269) run
/home/pyloor/.nimble/pkgs/prologue-0.2.2/prologue/core/beast/server.nim(24) serve
/home/pyloor/.nimble/pkgs/httpbeast-0.2.2/httpbeast.nim(423) run
/home/pyloor/.nimble/pkgs/httpbeast-0.2.2/httpbeast.nim(294) eventLoop
/home/pyloor/.choosenim/toolchains/nim-1.2.2/lib/pure/asyncdispatch.nim(1577) poll
/home/pyloor/.choosenim/toolchains/nim-1.2.2/lib/pure/asyncdispatch.nim(1341) runOnce
/home/pyloor/.choosenim/toolchains/nim-1.2.2/lib/pure/asyncdispatch.nim(210) processPendingCallbacks
/home/pyloor/.choosenim/toolchains/nim-1.2.2/lib/pure/asyncfutures.nim(297) :anonymous
/home/pyloor/.nimble/pkgs/httpbeast-0.2.2/httpbeast.nim(223) :anonymous
/home/pyloor/.nimble/pkgs/httpbeast-0.2.2/httpbeast.nim(94) onRequestFutureComplete

the problem occours in application.nim line 215:
https://github.com/planety/prologue/blob/master/src/prologue/core/application.nim#L215

request.parseFormParams(contentType)

the proc parseFormParams is from cgi of nim stdlib. in line 114 the expectation of an existing "=" is set:
https://github.com/nim-lang/Nim/blob/version-1-2/lib/pure/cgi.nim#L114

if i >= data.len or data[i] != '=': cgiError("'=' expected")

Documentations and API Reference

Hi,

Where can I find the generated API reference?

On which points do you need help with further development.

I'm asking because this project seems to be the most mature Nam web framework.

A sleep in one request handler stops other handlers from serving

As mentioned in this thread on Reddit, putting a sleep in one async handler stops another from handling a request. Example code:

import os
import prologue
import prologue/middlewares/middlewares


proc home*(ctx: Context) {.async.} =
  resp "<h1>Home</h1>"

proc sleepPage*(ctx: Context) {.async.} =
  sleep(10000)
  resp "<h1>Sleep!</h1>"


let settings = newSettings(appName = "MyApp")
var app = newApp(settings = settings, middlewares = @[debugRequestMiddleware()])
app.addRoute("/", home, @[HttpGet, HttpPost])
app.addRoute("/home", home, HttpGet)
app.addRoute("/sleep", sleepPage, HttpGet)
app.run()

If you browse to the /sleep page, and then the / home page, the loading of the home page will block. If the code is built with threads enabled, the blocking behavior is not present.

Add listen address / bind interface to settings

I'd like to be able to specify a custom address for the server. It appears to be set to 0.0.0.0 which means all interfaces will be listened on.

For example, I have a VPS with a public IP but also with private IP (via VPN like wireguard/tinc/zerotier) where I'd like my service not to be exposed to the public. There doesn't seem to be a way to pass the address to serve*().

I'd suggest something like:

debug=false # change this
port=8787
appName=HelloWorld
staticDir=/static
secretKey=Pr435ol67ogue
address=0.0.0.0 # or 127.0.0.1 or 10.0.0.1

do not hijack poll loop

currently app.run calls waitFor to drive async handlers.
This prevents user to run other async code.
My proposal is to give users the option to drive their own async poll loop.

This is not possible right now:

# app.nim
import prologue


proc hello*(ctx: Context) {.async.} =
  resp "<h1>Hello, Prologue!</h1>"

proc someOtherAsyncWork() {.async.} =
  while true:
    echo "some other async work"
    await sleepAsync(1_000)

let settings = newSettings()
var app = newApp(settings = settings)
app.addRoute("/", hello)
asyncCheck someOtherAsyncWork()
app.run()

I think something like this should be possible:

# app.nim
import prologue


proc hello*(ctx: Context) {.async.} =
  resp "<h1>Hello, Prologue!</h1>"

proc someOtherAsyncWork() {.async.} =
  while true:
    echo "some other async work"
    await sleepAsync(1_000)

let settings = newSettings()
var app = newApp(settings = settings)
app.addRoute("/", hello)
asyncCheck someOtherAsyncWork()
waitFor app.runAsync() # maybe introduce runAsync? or make `run` multisync?

Examples - todoapp 404 error

I have noticed that todoapp and todolist both receive a 404 error. Are these examples still in the works?

I'm looking for web framework I can use with nim, do you have any other examples you can show that you haven't posted in the repo ?

DEBUG Prologue is serving at 0.0.0.0:8787 Starting 1 threads DEBUG GET / DEBUG 404 Not Found {"content-type": @["text/html; charset=UTF-8"]}
Same issue with TodoList
DEBUG Prologue is serving at 0.0.0.0:8787 TodoList Starting 8 threads Listening on port 8787
Screen Shot 2020-09-10 at 2 16 41 PM

JWT middleware

Is there any JWT middleware or guidience of wiring other third party jwt lib into the prologue framework?

Custom Error Pages

First of all, this framework is pretty dope. I'm looking through the source code and I've come across the pages.nimfor handling 404's, 500's etc. What's the best method to override these pages and build custom ones for my own purposes — without altering the library?

jsonResponse -- "application/json"

Why does the code in prologue/core/response refer to ## Content-Type: application/json, but then actually return a Content-Type of text/json?

func jsonResponse*(text: JsonNode, code = Http200, headers = newHttpHeaders(),
                   version = HttpVer11): Response {.inline.} =
  ## Content-Type: application/json.
  result = initResponse(version, code, headers, body = $text)
  if unlikely(result.headers == nil):
    result.headers = newHttpHeaders()
  result.headers["Content-Type"] = "text/json"

ApacheBench timesout on prologue hello world

First time trying Prologue, so I give it a quick bench to see how web scale (:laughing:) it is, and I trip up immediately:

$ ab localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)...

apr_pollset_poll: The timeout specified has expired (70007)

Looking at the output, prologue recieves exactly ONE request:

DEBUG GET /
DEBUG 200 OK {"content-type": @["text/html; charset=UTF-8"]}

and then no more.

Here's the code, unmodified from the docs:

import prologue

proc hello*(ctx: Context) {.async.} =
  resp "<h1>Hello, Prologue!</h1>"

let settings = newSettings()
var app = newApp(settings = settings)
app.addRoute("/", hello)
app.run()

Which I compiled simply as a release binary:

$ nim c -d:release pl.nim

What's up?

"Another example" does not compile (does not find middlewares)

The example on the readme.md page does not compile:

installed prologue:

nimble install prologue

nim --version
Nim Compiler Version 1.3.5 [Windows: amd64]
Compiled at 2020-09-13
Copyright (c) 2006-2020 by Andreas Rumpf

active boot switches: -d:release

# app.nim
import prologue
import prologue/middlewares


# Async Function
proc home*(ctx: Context) {.async.} =
  resp "<h1>Home</h1>"

proc helloName*(ctx: Context) {.async.} =
  resp "<h1>Hello, " & ctx.getPathParams("name", "Prologue") & "</h1>"

proc doRedirect*(ctx: Context) {.async.} =
  resp redirect("/hello")

proc login*(ctx: Context) {.async.} =
  resp loginPage()

proc do_login*(ctx: Context) {.async.} =
  resp redirect("/hello/Nim")


let settings = newSettings(appName = "StarLight")
var app = newApp(settings = settings, middlewares = @[debugRequestMiddleware()])
app.addRoute("/", home, @[HttpGet, HttpPost])
app.addRoute("/home", home, HttpGet)
app.addRoute("/redirect", doRedirect, HttpGet)
app.addRoute("/login", login, HttpGet)
app.addRoute("/login", do_login, HttpPost, middlewares = @[debugRequestMiddleware()])
app.addRoute("/hello/{name}", helloName, HttpGet)
app.run()

error:

prologuet.nim(3, 16) Error: cannot open file: prologue/middlewares

Unable to use CSRF middleware

Maybe I'm doing it wrong.

In my form generation I put the following code (it's a Source Code Filter):

#? stdtmpl | standard
#import prologue/middlewares/csrf
#
#proc alignForm(ctx: Context): string =                                         
#  result = "" 
<form method="post">
  csrfToken(ctx)
<-- All other inputs -->
</form>
#end proc

This fails to create the CSRF token with

index 32 not in 0 .. 31
[...]
  /home/xbello/.nimble/pkgs/prologue-0.3.2/prologue/middlewares/csrf.nim(52)     makeToken
  /home/xbello/.choosenim/toolchains/nim-1.2.6/lib/system/fatal.nim(49)          sysFatal
[...]

So this traced the index failure to the line 52 in prologue/middlewares/csrf.nim, that is:

token[idx] = mask[idx] + secret[idx]

but it turns out that both mask and secret have a length of 32 elements, as both of them are created with size DefaultSecretSize (32).

I changed the above line to:

token[idx] = mask[idx - DefaultSecretSize] + secret[idx - DefaultSecretSize]

And it seems to work now. But I'm no expert in security, so I'm not 100% sure it it makes sense. The CSRF cookie seems to be correctly created and set.

the 2nd example in the readme doesn't compiles

On compiling the "Another Example" I get this error

C:\Users\risharan\DevSpace\nim\prolly\app.nim(29, 4) Error: type mismatch: got <Prologue, string, proc (ctx: Context): Future[system.void]{.gcsafe, locks: <unknown>.}, HttpMethod, seq[HandlerAsync]>
but expected one of:
proc addRoute(app: Prologue; patterns: seq[UrlPattern]; baseRoute = "";
             settings: Settings = nil)
  first type mismatch at position: 2
  required type for patterns: seq[UrlPattern]
  but expression '"/login"' is of type: string
proc addRoute(app: Prologue; route: Regex; handler: HandlerAsync;
             httpMethod: seq[HttpMethod]; middlewares: seq[HandlerAsync] = @[];
             settings: Settings = nil)
  first type mismatch at position: 2
  required type for route: Regex
  but expression '"/login"' is of type: string
proc addRoute(app: Prologue; route: Regex; handler: HandlerAsync; httpMethod = HttpGet;
             middlewares: seq[HandlerAsync] = @[]; settings: Settings = nil)
  first type mismatch at position: 2
  required type for route: Regex
  but expression '"/login"' is of type: string
proc addRoute(app: Prologue; route: string; handler: HandlerAsync;
             httpMethod = HttpGet; name = ""; middlewares: seq[HandlerAsync] = @[];
             settings: Settings = nil)
  first type mismatch at position: 5
  required type for name: string
  but expression '@[debugRequestMiddleware("Starlight")]' is of type: seq[HandlerAsync]
proc addRoute(app: Prologue; route: string; handler: HandlerAsync;
             httpMethod: seq[HttpMethod]; name = "";
             middlewares: seq[HandlerAsync] = @[]; settings: Settings = nil)
  first type mismatch at position: 4
  required type for httpMethod: seq[HttpMethod]
  but expression 'HttpPost' is of type: HttpMethod

expression: addRoute(app, "/login", do_login, HttpPost, @[debugRequestMiddleware("Starlight")])

I am on nim 1.2

Session object not copied to new context

Hey I just tried the blog example and I noticed the session object gets refreshed for every context ie for every request. i tested by registering a user and logging in with that user and my userid is added to the context's session object on the login handler.
When I make another request my session object is empty and I have to log in again. Shouldn't the session object be copied to every new context?

Streaming file support

I am wondering if you will drop support for httpbeast for netkit?

I have a pull request that will stream large files, its a simple change but it won't fix the memory leak with httpbeast. Additionally the content-length is sent twice because it appears httpbeast is loading the body data into its own buffer before sending it, so I corrected this with when defined, but the content length shows as 0.

On macOS it will serve a 600mb and the executable will use 4.9MB memory afterwards.

Please let me know if you would like me to create a pull request.
context.nim

  else:
    when defined(windows) or defined(usestd):
      ctx.response.setHeader("Content-Length", $contentLength)
    await ctx.respond(Http200, "", headers)
    var
      file = openAsync(filePath, fmRead)

    while true:
      let value = await file.read(4096)
      if value.len > 0:
        await ctx.send(value)
      else:
        break

    file.close()
    ctx.handled = true

use "wrk" tool to test it for prologue.

This is a wrk test of httpbeast hello word:wrk -c1000 -d10s http://192.12.1.22:8080/

Running 10s test @ http://192.12.1.22:8080/
2 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 8.01ms 12.16ms 215.10ms 99.05%
Req/Sec 71.46k 5.27k 76.81k 81.31%
1407514 requests in 10.05s, 154.37MB read
Non-2xx or 3xx responses: 1407514
Requests/sec: 140036.79
Transfer/sec: 15.36MB

This is a wrk test of prologue hello word.:wrk -c1000 -d10s http://192.12.1.22:8080/

Running 10s test @ http://192.12.1.22:8080/
2 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 34.58ms 0.96ms 39.56ms 78.50%
Req/Sec 14.51k 1.18k 16.28k 85.35%
286065 requests in 10.02s, 43.65MB read
Requests/sec: 28538.37
Transfer/sec: 4.35MB

Why is the difference so big?which place cause it so low, suggest to optimize.

Does ctx.session actually work?

Hi! I'm trying to finish blog example to make it complete but I struggled with authentication specifically with context session. I did not find any documentation about it but in blog example it is used when user successfully logs in:

ctx.session["userId"] = id

I thought that if we store values to ctx.session we can read them later on in our business logic for example in our templates to check if user is logged on or not. But it's not working: the ctx.session is always empty if I try to read it outside the proc it was assigned to.

How do we pass any data we want via ctx between procs?

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.