planety / prologue Goto Github PK
View Code? Open in Web Editor NEWPowerful and flexible web framework written in Nim
Home Page: https://planety.github.io/prologue
License: Apache License 2.0
Powerful and flexible web framework written in Nim
Home Page: https://planety.github.io/prologue
License: Apache License 2.0
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: $
()
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?
update examples like gin web framework
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????
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")
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.
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.
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
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?
https://planety.github.io/prologue/application/#startup-and-shutup
Startup and Shutdown aren't fully documented (and there aren't any examples).
This would greatly help usage.
What are the differences with Jester?
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
Is there any JWT middleware or guidience of wiring other third party jwt lib into the prologue framework?
First of all, this framework is pretty dope. I'm looking through the source code and I've come across the pages.nim
for 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?
the name of route
the HTTP methods
the name of handler
the name of middleware
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"
systemd
add bufferSize Parameters
prologue/src/prologue/core/context.nim
Line 295 in f5975c0
replace 'Starlight' to 'Prologue' in source files.
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?
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
I noticed there is no route attached for blog templates in example/blog
: http://127.0.0.1:8787/blog/index gives 404.
I am sure I did something wrong can't see where.
I tried localhost:8080/Blog but get a 404
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.
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
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?
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
prologue/src/prologue/core/context.nim
Line 225 in a4b6ed7
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.
Hi @planety,
Do you want to add prologue
in https://github.com/the-benchmarker/web-frameworks.
The purpose of this benchmark is to compare frameworks on a simple (an homogeneous) way.
We have already :
jester
httpbeast
Wdyt ?
Regards,
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:
prologue/examples/blog/views.nim
Line 36 in a66699f
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.