https://github.com/ysbaddaden/artanis
Sinatra-like DSL for the Crystal language (abusing macros)
https://github.com/ysbaddaden/artanis
crystal
Last synced: 10 months ago
JSON representation
Sinatra-like DSL for the Crystal language (abusing macros)
- Host: GitHub
- URL: https://github.com/ysbaddaden/artanis
- Owner: ysbaddaden
- License: mit
- Created: 2015-06-16T08:42:20.000Z (about 11 years ago)
- Default Branch: master
- Last Pushed: 2021-12-02T09:42:09.000Z (over 4 years ago)
- Last Synced: 2025-04-04T16:12:09.405Z (about 1 year ago)
- Topics: crystal
- Language: Crystal
- Size: 42 KB
- Stars: 58
- Watchers: 6
- Forks: 8
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Artanis
Crystal's metaprogramming macros to build a Sinatra-like DSL for the Crystal
language.
## Rationale
The DSL doesn't stock blocks to be invoked later on, but rather produces actual
methods using macros (`match` and the sugar `get`, `post`, etc.) where special
chars like `/`, `.`, `(` and `)` are replaced as `_SLASH_`, `_DOT_`, `_LPAREN_`
and `_RPAREN_`. Also, `:param` segments are replaced to `_PARAM_`.
Eventually methods look like:
get "/posts" | def match_GET__SLASH_posts
get "/posts.xml" | def match_GET__SLASH_posts_DOT_xml
Please read [dsl.cr](https://github.com/ysbaddaden/artanis/tree/master/src/dsl.cr)
for more details.
Eventually a call method is generated. It iterates over the class methods,
selects the generated `match_*` methods and generates a big `case` statement,
transforming the method names back to regular expressions, and eventually
calling the method with matched data (if any).
Please read [application.cr](https://github.com/ysbaddaden/artanis/tree/master/src/application.cr)
for more details.
## Usage
```crystal
require "http/server"
require "artanis"
class App < Artanis::Application
get "/" do
"ROOT"
end
get "/forbidden" do
403
end
get "/posts/:id.:format" do
p params["id"]
p params["format"]
"post"
end
get "/posts/:post_id/comments/:id(.:format)" do |post_id, id, format|
p params["format"]?
200
end
end
server = HTTP::Server.new(9292) do |context|
App.call(context)
end
puts "Listening on http://0.0.0.0:9292"
server.listen
```
## Benchmark
Running wrk against the above example (pointless hello world) gives the following
results (TL;DR 12µs per request):
```
$ wrk -c 1000 -t 2 -d 5 http://localhost:9292/fast
Running 5s test @ http://localhost:9292/fast
2 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 12.26ms 14.48ms 423.28ms 99.05%
Req/Sec 41.17k 2.93k 48.65k 76.00%
409663 requests in 5.01s, 25.79MB read
Requests/sec: 81722.08
Transfer/sec: 5.14MB
```
A better benchmark is available in `test/dsl_bench.cr` which monitors some
limits of the generated Crystal code, like going over all routes to find nothing
takes an awful lot of time, since it must build/execute a regular expression
against EVERY routes to eventually... find nothing.
```
$ crystal run --release test/dsl_bench.cr
get root: 0.84 µs
get param: 1.51 µs
get params (block args): 2.18 µs
get many params: 3.75 µs
get many params (block args): 2.59 µs
not found (method): 0.73 µs
not found (path): 15.93 µs
```
Keep in mind these numbers tell nothing about reality. They only measure how
fast the generated `Application.call(request)` method is in predefined cases.
## License
Licensed under the MIT License