kemalcr / kemal Goto Github PK
View Code? Open in Web Editor NEWFast, Effective, Simple Web Framework
Home Page: https://kemalcr.com
License: MIT License
Fast, Effective, Simple Web Framework
Home Page: https://kemalcr.com
License: MIT License
I have a problem with css. In the app directory I have public/styles.css
, views/index.ecr
and src/app.cr
app.cr
looks like
require "kemal"
module App::Crystal
get "/" do
render "views/index.ecr"
end
end
index.ecr
has link-tag <link rel="stylesheet" href="/styles.css">
When open localhost:3000
html renders well but css request has 404. Trying different pathes in link
like public/styles.css
, /public/styles.css
, styles.css
, /styles.css
doesn't work.
There's a lot of bugs related to the new context object in the http server refactoring for crystal 0.11.0.
Error in ./libs/kemal/kemal.cr:35: instantiating 'HTTP::Server#listen()'
server.listen
^~~~~~
in macro 'spawn' /opt/crystal/src/concurrent/concurrent.cr:37, line 8:
1.
2. ->(
3.
4. __arg0 : typeof(server.accept),
5.
6. ) {
7. spawn do
8. handle_client(
9.
10. __arg0,
11.
12. )
13. end
14. }.call(server.accept)
15.
handle_client(
^~~~~~~~~~~~~
instantiating 'handle_client(TCPSocket+)'
in /opt/crystal/src/http/server/server.cr:157: instantiating 'HTTP::Handler+#call(HTTP::Server::Context)'
@handler.call(context)
^~~~
in ./libs/kemal/kemal/middleware/http_basic_auth.cr:20: undefined method 'headers' for HTTP::Server::Context
if request.headers[AUTH]?
^~~~~~~
================================================================================
HTTP::Server::Context trace:
./libs/kemal/kemal/middleware/http_basic_auth.cr:19
def call(request)
In the example for rendering ecr views on http://kemalcr.com/docs/views/ the path to the view is specified as views/hello.ecr
.
I followed the example, created my_app/src/views
and put hello.ecr
there. When you try to build from the project root, following the example on http://kemalcr.com/docs/getting_started/, this won't work because the path needs to be src/views/hello.ecr
.
I didn't just fixed this in case I did something wrong. So, am I right? xD
require "kemal"
get "/a" do
"a"
end
post "/b" do
"b"
end
curl http://localhost:3000/a
curl http://localhost:3000/b -X POST -d '{"bla":12}'
2016-01-25 22:45:33 +0300 | 200 | GET /a - 91µs
2016-01-25 22:47:08 +0300 | 404 | POST /b - 65µs
Hi,
I benchmarked kemal on my laptop and I'm getting only 5000 req/s for a hello world application. Building in release mode results in about 7200 req/s.
For a comparision, using moonshine which is another sinatra clone I get 13000 in dev mode and 19000 in release mode.
require "kemal"
require "json"
get "/api/v1/ping" do
{:ping => "pong"}.to_json
end
require "moonshine"
require "json"
include Moonshine
include Moonshine::Utils::Shortcuts
app = App.new
app.get "/api/v1/ping", do |request|
ok({:ping => "pong"}.to_json)
end
port = ENV.has_key?("PORT") ? ENV["PORT"] : "5000"
app.run(port.to_i)
$ crystal --version
Crystal 0.10.1 (Fri Jan 8 21:13:35 UTC 2016)
$ uname -s
Darwin
$ crystal build kemal.cr -o bin/kemal
$ bin/kemal -p 5000
$ wrk -c 800 -t 100 -d20s http://127.0.0.1:5000/api/v1/ping
Running 20s test @ http://127.0.0.1:5000/api/v1/ping
100 threads and 800 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 8.09ms 55.57ms 1.61s 98.71%
Req/Sec 544.39 484.16 3.21k 68.63%
101412 requests in 20.10s, 9.96MB read
Socket errors: connect 0, read 517, write 0, timeout 76
Requests/sec: 5044.91
Transfer/sec: 507.45KB
$ crystal build --release kemal.cr -o bin/kemal
$ bin/kemal -p 5000
$ wrk -c 800 -t 100 -d20s http://127.0.0.1:5000/api/v1/ping
Running 20s test @ http://127.0.0.1:5000/api/v1/ping
100 threads and 800 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 8.20ms 68.49ms 1.84s 98.82%
Req/Sec 0.94k 789.94 6.55k 71.84%
146596 requests in 20.10s, 14.40MB read
Socket errors: connect 0, read 17, write 0, timeout 126
Requests/sec: 7294.91
Transfer/sec: 733.77KB
$ crystal build moonshine.cr -o bin/moonshine
$ ./bin/moonshine
$ wrk -c 800 -t 100 -d20s http://127.0.0.1:5000/api/v1/ping
Running 20s test @ http://127.0.0.1:5000/api/v1/ping
100 threads and 800 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 57.72ms 10.70ms 108.12ms 75.91%
Req/Sec 132.25 41.11 0.91k 73.79%
262255 requests in 20.10s, 21.01MB read
Socket errors: connect 0, read 623, write 0, timeout 0
Requests/sec: 13044.96
Transfer/sec: 1.05MB
$ cr build --release moonshine.cr -o bin/moonshine
$ ./bin/moonshine
$ wrk -c 800 -t 100 -d20s http://127.0.0.1:5000/api/v1/ping
Running 20s test @ http://127.0.0.1:5000/api/v1/ping
100 threads and 800 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 39.22ms 6.82ms 103.44ms 78.84%
Req/Sec 198.26 36.60 480.00 69.29%
395779 requests in 20.10s, 31.71MB read
Socket errors: connect 0, read 423, write 0, timeout 0
Requests/sec: 19690.01
Transfer/sec: 1.58MB
As a comparision, running sinatra with puma (1 worker, 16 threads):
$ PORT=5000 WEB_CONCURRENCY=1 puma -C config/puma.rb
$ wrk -c 800 -t 100 -d20s http://127.0.0.1:5000/api/v1/ping
Running 20s test @ http://127.0.0.1:5000/api/v1/ping
100 threads and 800 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 7.47ms 3.76ms 60.84ms 76.13%
Req/Sec 1.07k 111.97 1.25k 77.86%
43017 requests in 20.09s, 7.71MB read
Socket errors: connect 0, read 501, write 0, timeout 0
Requests/sec: 2140.76
Transfer/sec: 393.07KB
I have measured performance by ab
command.
Here is the code.
require "kemal"
get "/" do
"test"
end
$ crystal build --release server.cr
$ ./server -e production &
$ ab -n 10000 -c 100 http://localhost:3000
...
Requests per second: 1144.30 [#/sec] (mean)
Time per request: 87.390 [ms] (mean)
Time per request: 0.874 [ms] (mean, across all concurrent requests)
Transfer rate: 74.87 [Kbytes/sec] received
...
Using macbook pro (CPU: 3.1 GHz Intel Core i7, MEMORY: 16 GB 1867 MHz DDR3)
The result is about 100 times slower than expected.
Are there missing configuration options?
Hello;
I want to seperate my logic according to http request type(GET, POST, PUT etc.). But i couldn't make it.
for example,
get "/" do |env|
"GET REQUEST"
end
post "/" do |env|
"POST REQUEST"
end
response is always "GET REQUEST"
also when i try to run only
post "/" do |env|
"POST REQUEST"
end
i can get same response with GET or PUT request.
App file looks like
require "kemal"
require "ambience"
module HotWater::Crystal
ENV["env"] ||= "development"
amb = Ambience::Application.new("~/hot_water-crystal/config/environment.yml", ENV["env"])
amb.load
get "/" do
# render "views/index.ecr"
p ENV
end
end
But localhost:3000/
returns | 404 | GET / - (50µs)
Hi again,
I was also looking at the Kemal::Middleware::Filter
implementation and the before
after
will only work if Kemal::Middleware::Filter
is the first middleware added. If another is added before this one it will silently fail.
I don't know if you wanted it specifically this way but if you want it to not be order dependent you could replace these lines in kemal/middleware/filters.cr
:
def add_filters
Kemal.config.add_handler Kemal::Middleware::Filter.new
end
def before(path = "*", options = {} of Symbol => String, &block : HTTP::Server::Context -> _)
filter = Kemal.config.handlers.first as Kemal::Middleware::Filter
filter.add :before, path, options, &block
end
def after(path = "*", options = {} of Symbol => String, &block : HTTP::Server::Context -> _)
filter = Kemal.config.handlers.first as Kemal::Middleware::Filter
filter.add :after, path, options, &block
end
With these (very little change but would make it order independent and would remove the need to call add_filters
explicitly instead it will be called lazily if it hasn't been called before) =>
def add_filters
unless filter = Kemal.config.handlers.any? { |handler| handler.is_a? Kemal::Middleware::Filter }
filter = Kemal::Middleware::Filter.new
Kemal.config.add_handler filter
end
filter
end
def before(path = "*", options = {} of Symbol => String, &block : HTTP::Server::Context -> _)
filter = (Kemal.config.handlers.find { |handler| handler.is_a? Kemal::Middleware::Filter } || add_filters) as Kemal::Middleware::Filter
filter.add :before, path, options, &block
end
def after(path = "*", options = {} of Symbol => String, &block : HTTP::Server::Context -> _)
filter = (Kemal.config.handlers.find { |handler| handler.is_a? Kemal::Middleware::Filter } || add_filters) as Kemal::Middleware::Filter
filter.add :after, path, options, &block
end
Again, if this is by design feel free to close this immediately, just thought I'd mention it. I doubt something as insignificant as this would be worthy of a pull request, but I could make one if necessary.
Error in ./libs/kemal/kemal/context.cr:30: undefined local variable or method 'status_code'
I am getting this error. Any idea why?
Currently we don't have any middleware ordering. This can lead to wrong execution flows.
We need to order the handlers while bootstrapping the application. Something like config.bootstrap_handlers
is required.
New to ruby/crystal ecosystem, so don't know about ecr template. Googled it, found nothing. Can you please give provide source for the template and maybe a some examples?
thanks.
Hi, I started writing a session plugin because I want to use Kemal for my applications and I need to be able to work with sessions: https://github.com/Thyra/kemal-session
It's quite crude yet and needs a lot of improvements but I thought you might be interested...
Hey @sdogruyol, I took your middleware for http auth and made it so it works per request...
https://github.com/ukd1/plusone/blob/master/src/plusone/authed.cr
My version is kinda shitty, I'd like to get it up to scratch to add to kemal itself.
Hi there! What am I doing wrong?
In view.cr
I see methods render_404
and other.
If I use code
get "/" do
render_404 #or return render_404
end
and then route it, kemal returnes me 200 and response #<HTTP::Response:xxxxxxxx>
.
Am I missing something?
I want to implement Radix without Beryl in the upcoming Kemal release.
//cc @luislavena , @f
Thought I'd give kemal
a quick test via the getting started information this evening, but ran into an apparent wall. When I get to the execution stage after a seemingly successful compilation, and execute the binary, I get no output whatsoever.
src/kemal_test.cr
contains:
require "kemal"
get "/" do
"Hello World!"
end
Any idea what may be going on here or where would I start to troubleshoot such a seemingly simplistic issue?
Related to crystal-lang/crystal#1781
From shards' usage:
When libraries are installed from Git repositories, the repository is expected to have version tags following the semver format, prefixed with a v. Examples: v1.2.3 or v2.0.0-rc1.
So, currently I can't refer to a specific version of Kemal:
dependencies:
kemal:
github: sdogruyol/kemal
version: 0.5.0
$ crystal deps
Updating https://github.com/sdogruyol/kemal.git
git command failed: git ls-tree -r --full-tree --name-only v0.5.0 -- shard.yml ()
Could you please add a support of semver format ?
Currently we only support parsing query parameters and nothing else.
We need to parse the body.
If you execute this short piece of code:
require "kemal"
get "/" do
"Hello, World!"
end
if true
raise "This is an Error!"
end
get "/hi" do
"Hello, World!"
end
the results are quite weird: There is nowhere a notice of the error (neither logs nor in the browser) and you can open /
but if you open /hi
in the browser window, kemal says Kemal doesn't know this way.
I understand this is because /
gets registered, then the program crashes and kemal's at_exit
is executed, so everything after the error simply gets dropped.
Wouldn't it be better if kemal would clearly report the error and abort? I think it would make debugging much easier and a half-working webserver which doesn't know half of its supposed pages is anyway not helpful ;-).
Hello:
How can i approach the implementation of jwt in Kemal? When to enode the jwt token and how to verify the jwt signature and respond with 401 if is invalid?
The code in the below repository is a fork from one of the jwt ruby libraries.
https://github.com/Ravenstine/jwt.cr
Hi @sdogruyol,
Thanks for writing such an awesome yet simple framework. I' am learning Crystal and I stumble upon kemal
while searching for some web development frameworks which I can use and contribute to. I just want to point-out a small thing in installation section. I didn't knew what shards
is until I see a video presentation from Eurucamp 2015
but even then I struggled a bit while installing kemal
. You mentioned in installation section to include
a code snippet in shard.yml
but putting that code was not running for me and I was getting following error:
Shards::ParseError: missing required attribute: name at 4:0
1. github: sdogruyol/kemal
2. branch: master`
I did a little research and found out that every shard.yml
must contain a name
attribute. It would be nice if we have this mentioned in installation section of kemal
as well so that beginners don't find it difficult to install it :-).
If you think adding that is not necessary then it's fine as well, I just wanted to get in-touch regarding this thing and I' am really looking forward to contribute to kemal
soon :-).
Thanks.
I tried to read the doc at kemalcr.com but it seems to be unresponsive :/
Hello,
I've been reading the documentation and browsing the code for a few days and it looks really cool ( 👍 ) so I was thinking of implementing a custom logger but I think a few things wouldn't work right now:
First here
config = Kemal.config
if config.logging
logger = Kemal::LogHandler.new
config.logger = logger
Could we maybe specify our own logger class in something like config.logger_class
(I don't even know if Crystal would support that) and then it would get instantiated? (Of course our logger will have a write
method)
And then here:
rescue ex
Kemal::LogHandler::INSTANCE.write "Exception: #{ex.to_s}\n"
return render_500(context, ex.to_s)
You call your logger explicitly instead of Kemal.config.logger.write
(I'm sure it's on purpose, I just don't see it, I'm still not very comfortable with Crystal)
Do you think I could achieve this without too much trouble or is it just not possible ?
Hello,
Could you include the wrk
command you used in the README.md
or an example/wrk.sh
or something?
It is always great to see what people are running to load test a framework, and use that same command to load test other frameworks as well and/or validate the claims.
This framework looks great. Thanks for putting this together.
I' am thinking of implementing HTTP HEAD
method to complete kemal
's supported HTTP methods set.
I run the command ./server -p 80 -e production
and it says im running on http://0.0.0.0:80, but when I try to access my website from the browser (stackin.money) it says Connection refused. I cURLed from my terminal to http://0.0.0.0:80 and it worked, but cURLing to http://stackin.money again shows connection refused. I tried sinatra and did ruby y.rb -p 80 -o 0.0.0.0
and I accessing my website through http://stackin.money worked, any fix to this?
I have been trying hard to remove env. And while doing that I found out that Kemal is not thread-safe .
Because HTTP::Server is not thread-safe by-default.
I have found a way to make it thread-safe by using most established technique i.e. creating a process for each incoming request.
This will not only make Kemal thread-safe which will be beneficial for us in future, but it will allow us to assign context as global variable and we can use that global variable everywhere like redirect, render.
We can also expose params as a method and this will completely eliminate need of having env.params.
I discovered this thread-safety issue while creating a global variable in requests. I was putting some sleep in requests as well to notice the behaviour of that global variable. And to my expectation, it did what I was anticipating. I stored content-type as text/plain for one of the requests in a global variable but due to sharing of global variable among all requests, it was being applied to all requests.
I tried the exact same thing with new forked-based server implementation and that issue vanished .
Please let me know what you think about this?
Hi,
I'm sorry to annoy you again but I couldn't resist, I spent a few hours on it but couldn't figure out how in this method:
def call(context)
context.response.content_type = "text/html"
response = process_request(context)
response || call_next(context) # <========== this line
end
From file kemal/route_handler.cr
line 16
The || call_next(context)
would ever be reached ?
The response
comes from process_request
:
def process_request(context)
url = context.request.path.not_nil!
Kemal::Route.check_for_method_override!(context.request)
lookup = @tree.find radix_path(context.request.override_method as String, context.request.path)
if lookup.found?
route = lookup.payload as Route
context.request.url_params = lookup.params
begin
body = route.handler.call(context).to_s
context.response.print body
return context # <================== this return
rescue ex
return render_500(context, ex.to_s) <================== this return
end
end
# Render 404 unless a route matches
return render_404(context) <================== this return
end
The 3 returns
above, all return the context
object that was passed as the arg to the process_request
method.
Also if the context
was Nil
the process_request
wouldn't be reached so it's safe to assume the context passed to process_request
is not Nil
so when this same context is returned from process_request
to call
the || call_next
part would never be reached either right?
Even if it was Nil
or False
what could there be after this since the RouteHandler
is added by Kemal in at_exit
as the last middleware just before launching the server?
I hope I'm not just wasting your time with useless questions but it's driving me crazy 😕
You may consider writing a test and submitting a PR to: https://github.com/TechEmpower/FrameworkBenchmarks
I think it would be very interesting to see Kemal getting some notoriety from such an effort!
How can I run tests when using kemal
which starts a server while running tests and does not exit?
Currently we are wrapping the exception and outputting it into the ecr
view.
However sometimes the stacktrace is needed for further debugging that's why we need to also output the exception to STDOUT.
Hello,
would nice to have an error when TCP 3000 is busy. Currently I had
$ crystal run src/chat.cr
[development] Kemal is ready to lead at http://0.0.0.0:3000
msa@Mag2 ~/prj/vid/app/chat (master)
$
HTTP server of Kemal always runs even if exit()
is called explicitly, because server.listen
is called in at_exit
.
However, sometimes I want Kemal to exit without server.listen
.
For example:
Do you think how about adding Kemal.config.run
property?
It should be true
by default to cause no change to default behavior.
When it is false
, Kemal silently exits without service.
if already_running?
Kemal.config.logger.write "[#{Kemal.config.env}] another process is running.\n" if Kemal.config.logger
Kemal.config.run = flase
exit(1)
end
It is possible by adding a few lines to "kemal.cr" and "kemal/config.cr" .
I have tested it in my forked branch.
Hi, I am trying run hello world example from tutorial, but I am getting such error:
➜ kemal_example git:(master) ✗ crystal build src/kemal_example.cr -d Error in ./libs/kemal/kemal.cr:21: instantiating 'HTTP::Server::Context#params()' image = env.params["image"] ^~~~~~ in ./libs/kemal/kemal/context.cr:6: instantiating 'Kemal::ParamParser#parse()' @params ||= Kemal::ParamParser.new(@request).parse ^~~~~ in ./libs/kemal/kemal/param_parser.cr:17: instantiating 'parse_request()' parse_request ^~~~~~~~~~~~~ in ./libs/kemal/kemal/param_parser.cr:21: instantiating 'parse_url_params()' parse_url_params ^~~~~~~~~~~~~~~~ in ./libs/kemal/kemal/param_parser.cr:38: undefined method 'url_params' for Nil if params = @request.url_params ^~~~~~~~~~ ================================================================================ Nil trace: ./libs/kemal/kemal/param_parser.cr:12 def initialize(@request) ^ ./libs/kemal/kemal/param_parser.cr:12 def initialize(@request) ^~~~~~~ ./libs/kemal/kemal/param_parser.cr:12 def initialize(@request)
Crystal version: Crystal 0.10.2 (Wed Jan 13 20:13:28 UTC 2016), OSX El Capitan
Crystal is quite a fun programming language to work with. I have been trying to remove env.redirect
etc. to simplify method calls to something like redirect
. I actually want to do this because in my opinion when kemal
has support for templates having template getting rendered as env.render
is not going to look cool.
Due to some core differences in Ruby and Crystal I' am unable to achieve this so far because there is no self
in Crystal inside blocks which makes sense but this also means that we cannot apart env
from method calls inside defined routes' callback.
However, there is a simple solution. Instead of passing something like env
, we should pass something like req
and res
. For example,
get "/" do |req, res|
# important stuff here
end
This will make method invocations more expressive and we can call appropriate methods on proper objects. For example to access params
we can call req.params
and to redirect we can call res.redirect
and to render we can do res.render
.
This is what Express.js is doing and I think it's quite elegant.
Waiting for crystal-lang/crystal#1832
./libs/kemal/kemal/context.cr:26: undefined local variable or method 'status_code'
delegate status_code, :"status_code=", :"content_type=", @response
^~~~~~~~~~~
has it got any new version ?
crystal run src/kemal_chat.cr
Error in ./libs/kemal/kemal.cr:5: instantiating 'OptionParser:Class#parse!()'
OptionParser.parse! do |opts|
^~~~~~
in /usr/local/Cellar/crystal-lang/0.11.1/src/option_parser.cr:54: instantiating 'parse(Array(String))'
parse(ARGV) { |parser| yield parser }
^~~~~
in /usr/local/Cellar/crystal-lang/0.11.1/src/option_parser.cr:54: instantiating 'parse(Array(String))'
parse(ARGV) { |parser| yield parser }
^~~~~
in ./libs/kemal/kemal.cr:5: instantiating 'OptionParser:Class#parse!()'
OptionParser.parse! do |opts|
^~~~~~
in ./libs/kemal/kemal.cr:7: instantiating 'Kemal:Module#config()'
Kemal.config.port = opt_port.to_i
^~~~~~
in ./libs/kemal/kemal/config.cr:5: instantiating 'Kemal::Config:Class#new()'
INSTANCE = Config.new
^~~
instantiating 'Kemal::Config#initialize()'
in ./libs/kemal/kemal/config.cr:13: instantiating 'read_file()'
read_file
^~~~~~~~
in ./libs/kemal/kemal/config.cr:37: undefined method 'working_directory' for Dir:Class
path = File.expand_path("config.yml", Dir.working_directory)
ws("/") do |socket|
^~
in ./libs/kemal/kemal/dsl.cr:9: undefined constant HTTP::WebSocketHandler::WebSocket
def ws(path, &block : HTTP::WebSocketHandler::WebSocket -> _)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using 0.8.0
.
This is actually needed for file uploads and multipart forms.
Currently we don't have any multipart form parsers for Crystal. Thinking of writing this as a seperate shard
and then making a plugin for Kemal
.
Add support for multiple query strings
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.