Inspired by Rack, empowers mruby, a work in progress!
Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
The exact details of this are described in the Rack specification, which all Rack applications should conform to.
Shelf::Builder.app do
run ->(env) { [200, {}, ['A barebones shelf app']] }
end
Add the line below to your build_config.rb
:
MRuby::Build.new do |conf|
# ... (snip) ...
conf.gem 'mruby-shelf'
end
Or add this line to your aplication's mrbgem.rake
:
MRuby::Gem::Specification.new('your-mrbgem') do |spec|
# ... (snip) ...
spec.add_dependency 'mruby-shelf'
end
The Rack::Builder DSL is compatible with Shelf::Builder. Shelf uses mruby-r3 for the path dispatching to add some nice extras.
app = Shelf::Builder.app do
run ->(env) { [200, { 'content-type' => 'text/plain' }, ['A barebones shelf app']] }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
# => [200, { 'content-type' => 'text/plain' }, ['A barebones shelf app']]
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/info')
# => [404, { 'content-type' => 'text/plain', 'X-Cascade' => 'pass' }, ['Not Found']]
Using middleware layers is dead simple:
class NoContent
def initialize(app)
@app = app
end
def call(env)
[204, @app.call(env)[1], []]
end
end
app = Shelf::Builder.app do
use NoContent
run ->(env) { [200, { ... }, ['A barebones shelf app']] }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
# => [204, { ... }, []]
Mounted routes may contain slugs and can be restricted to a certain HTTP method:
app = Shelf::Builder.app do
get('/users/{id}') { run ->(env) { [200, { ... }, [env['shelf.request.query_hash'][:id]]] } }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/users/1')
# => [200, { ... }, ['1']]
app.call('REQUEST_METHOD' => 'PUT', 'PATH_INFO' => '/users/1')
# => [405, { ... }, ['Method Not Allowed']]
Routes can store any kind of additional data:
app = Shelf::Builder.app do
get('data', [Object.new]) { run ->(env) { [200, { ... }, env['shelf.r3.data']] } }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/data')
# => [200, { ... }, ['#<Object:0x007fd5739dfe40>']]
The Rack::Handler class is mostly compatible with Shelf::Handler except that it takes the handler class instead of the path string.
Shelf::Handler.register 'h2o', H2O::Shelf::Handler
Per default Shelf uses its built-in handler for mruby-simplehttpserver:
Shelf::Handler.default
# => Shelf::Handler::SimpleHttpServer
Howver its possible to customize that:
ENV['SHELF_HANDLER'] = 'h2o'
The Rack::Server API is mostly compatible with Shelf::Server except that there's no config.ru file, built-in opt parser. Only the main options (:app, :port, :host, ...) are supported. Also note that :host and :port are written downcase!
Shelf::Server.start(
app: ->(e) {
[200, { 'Content-Type' => 'text/html' }, ['hello world']]
},
server: 'simplehttpserver'
)
The default middleware stack can be extended per environment:
Shelf::Server.middleware[:production] << MyCustomMiddleware
Shelf comes with some useful middlewares. These can be defined by app or by environment.
ContentLength
app = Shelf::Builder.app do
use Shelf::ContentLength
run ->(env) { [200, {}, ['A barebones shelf app']] }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
# => [200, { 'Content-Length' => 21 }, ['A barebones shelf app']]
ContentType
app = Shelf::Builder.app do
use Shelf::ContentLength
use Shelf::ContentType, 'text/plain'
run ->(env) { [200, {}, ['A barebones shelf app']] }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
# => [200, { 'Content-Length' => 21, 'Content-Type' => 'text/plain' }, ['A barebones shelf app']]
QueryParser
app = Shelf::Builder.app do
map('/users/{id}') do
use Shelf::QueryParser
run ->(env) { [200, env['shelf.request.query_hash'], []] }
end
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/users/1', 'QUERY_STRING' => 'field=age&field=name')
# => [200, { 'id' => '1', 'field' => ['age', 'name'] }, []]
Head
app = Shelf::Builder.app do
use Shelf::Head
run ->(env) { [200, {}, ['A barebones shelf app']] }
end
app.call('REQUEST_METHOD' => 'HEAD', 'PATH_INFO' => '/')
# => [200, { 'Content-Length' => 21 }, []]
Static
app = Shelf::Builder.app do
use Shelf::Static, urls: { '/' => 'index.html' }, root: 'public'
run ->(env) { [200, {}, ['A barebones shelf app']] }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
# => [200, { 'Content-Length' => xxx, 'Content-Type' => 'text/html; charset=utf-8' }, ['<html>...</html>']]
Logger
app = Shelf::Builder.app do
use Shelf::Logger, Logger::INFO
run ->(env) { [200, {}, [Log-Level: "#{env['shelf.logger'].level}"] }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
# => [200, {}, ['Log-Level: 1']]
env[SHELF_ERRORS]
which is $stderr by defaultCommonLogger
app = Shelf::Builder.app do
use Shelf::CommonLogger, Logger.new
run ->(env) { [200, {}, ['A barebones shelf app']] }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/index.html')
# => 127.0.0.1 - [23/05/2017:18:03:36 +0200] "GET /index.html HTTP/1.1" 200 2326
CatchError
app = Shelf::Builder.app do
use Shelf::CatchError
run ->(env) { undef_method_call }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
# => [500, { 'Content-Length' => 21, 'Content-Type' => 'text/plain' }, ['Internal Server Error']]
env[SHELF_ERRORS]
Deflater
app = Shelf::Builder.app do
use Shelf::Deflater
run ->(env) { [200, {}, ['A barebones shelf app']] }
end
app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/', 'Accept-Encoding' => 'gzip')
# => [200, { 'Content-Encoding' => 'gzip', ... }, ['...']]
gzip
, deflate
and identity
Clone the repo:
$ git clone https://github.com/katzer/mruby-shelf.git && cd mruby-shelf/
Compile the source:
$ rake compile
Run the tests:
$ rake test
Bug reports and pull requests are welcome on GitHub at https://github.com/katzer/mruby-shelf.
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)The mgem is available as open source under the terms of the MIT License.
Made with :yum: in Leipzig
© 2017 appPlant GmbH