bung87 / scorper

scorper is a micro and elegant web framework written in Nim
Apache License 2.0
81 stars 5 forks source link

SCORPER Build Status

scorper

Elegant, performant, asynchronous micro web framework written in Nim

Built using chronos and other powerful libraries.

Note: scorper is built using chronos which implements an async/await paradigm that is, at its core, different and therefore incompatible with the nim std library asyncdispatch's async; this conflict is irresolvable and you will have to ensure your project dependencies support chronos async/await (note: many libraries do offer this, often using a compiler switch).

scorper will self contain manageable dependency source code for accelerated development.

Compiler flags

-ssl

Compile with: -d:ssl

Then, using the proc newScorper, you will need to pass isSecurity = true,privateKey, certificate

Other

const GzipEnable {.booldefine.} = true 
const HttpRequestBufferSize* {.intdefine.} = 2.Kb
const HttpServer {.strdefine.} = "scorper"
const TimeOut {.intdefine.} = 300 # in seconds  
const HttpHeadersLength* {.intdefine.} = int(HttpRequestBufferSize / 32) 
# 32 is sizeof MofuHeader
const gzipMinLength* {.intdefine.} = 20

Usage

details see here usage

hello world

serve with callback

import scorper

const port{.intdefine.} = 8888

proc cb(req: Request) {.async.} =
  ## See Request type to see fields accessible by callbacks

  let headers = {"Content-type": "text/plain"}
  await req.resp("Hello, World!", headers.newHttpHeaders())

when isMainModule:
  let address = "127.0.0.1:" & $port

  waitFor serve(address, cb)
  ## waitFor is a chronos implementation
  ## See chronos for options around executing async procs

newScorper with router or callback

when isMainModule:
  let r = newRouter[ScorperCallback]()
  # Relys on `StaticDir` environment variable
  r.addRoute(serveStatic, "get", "/static/*$")
  let address = "127.0.0.1:8888"
  let flags = {ReuseAddr}
  var server = newScorper(address, r, flags)
  server.start()
  waitFor server.join()

use route pragma

proc handler(req: Request) {.route("get","/one"),async.} = discard
proc handler2(req: Request) {.route(["get","post"],"/multi"),async.} = discard
let r = newRouter[ScorperCallback]()
r.addRoute(handler)
r.addRoute(handler2)

responds depends on request mime

import scorper

proc cb(req: Request) {.async.} =
  var headers = newHttpHeaders()
  acceptMime(req, ext, headers):
    ## The template will automatically define the second parameter

    case ext
    of "html": await req.resp("Hello World", headers)
    of "txt": await req.resp("Hello World", headers)
    else:
      headers["Content-Type"] = "text/html"
      await req.resp("Hello World", headers)

when isMainModule:
  let address = "127.0.0.1:8888"
  waitFor serve(address, cb)

Middleware

Middleware can be implemented by importing scorper/scorpermacros (the import and definition of your middleware procedures MUST occur before importing scorper; see the note labeled IMPORTANT below)

preMiddleware

Procedure that is injected after the Request object fields are filled but BEFORE your callback.

postMiddleware

Procedure that is injected AFTER your callback (and therefore after your response).

Declaring middleware:

import scorper/scorpermacros

proc middleWare(req: Request): Future[bool] {.async, postMiddleware.} =
  ## Do things here
  return false
  ## return false to prevent calling the next middleware

Middleware procedures return bool to indicate whether to continue the call-chain to the next middleware.

see tmiddleware.nim for more

Types

type
  Request* = ref object
    meth*: HttpMethod
    headers*: HttpHeaders
    protocol*: tuple[major, minor: int]
    url*: Url
    hostname*: string
    ip*: string
    params*: Table[string, string]

  ScorperCallback* = proc (req: Request): Future[void] {.closure, gcsafe.}
  Scorper* = ref object of StreamServer
    # inherited (partial)
    sock*: AsyncFD                # Socket
    local*: TransportAddress      # Address
    status*: ServerStatus         # Current server status
    flags*: set[ServerFlags]      # Flags
    errorCode*: OSErrorCode
  MiddlewareProc* = proc (req: Request): Future[bool]

Error handling & Exception effects

As scorper make use of chronos for asynchronous procedures, the handling of errors and exceptions within this paradigm are of relevance.

chronos currently offers minimal support for exception effects and raises annotations. In general, during the async transformation, a generic except CatchableError handler is added around the entire function being transformed, in order to catch any exceptions and transfer them to the Future.

Effectively, this means that while code can be compiled with {.push raises: [Defect]}, the intended effect propagation and checking is disabled for async functions.

Todos

Contribution

check Todo list upon and issues

clone this repository, run nimble install stage , if you already have stage installed, run stage init
stage is used for integrating with git commit hook, doing checking and code formatting.

Benchmark

Web Frameworks Benchmark

TechEmpower benchmarks

requires wrk

nimble benchmark
nimble benchmarkserver

Report

runs on my MBP Dual-Core Intel Core i5 2.7 GHz ,8 GB memory.
scorper: 1.0.2
chronos: 3.0.1
nim version: 1.5.1, 1.4.4

Running 30s test @ http://127.0.0.1:8888/
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.51ms    1.00ms  66.66ms   92.16%
    Req/Sec     7.15k   518.66    10.94k    85.67%
  854619 requests in 30.05s, 93.73MB read
Requests/sec:  28441.14
Transfer/sec:      3.12MB

Conclusion

Scorper is almost ten thousands faster than jester with stdlib. It is even a thousand times faster than asynchttpserver

Misc features/limitations

The mofuparser use SIMD which relys on cpu support SSE or AVX instructions

Related projects

amysql Async MySQL Connector write in pure Nim. (support chronos with compile flag -d:ChronosAsync)

License

Apache License 2.0

The Scorper logo was created and released to the Scorper project under the CC BY-SA 4.0 by https://ITwrx.org and https://LuciusRafi.com