rdoc-image:https://roda.jeremyevans.net/images/roda-logo.svg

A routing tree web toolkit, designed for building fast and maintainable web applications in Ruby.

== Table of contents

- {Installation}[#label-Installation]
- {Resources}[#label-Resources]
- {Goals}[#label-Goals]
- {Usage}[#label-Usage]
- {Running the application}[#label-Running+the+Application]
- {The routing tree}[#label-The+Routing+Tree]
- {Matchers}[#label-Matchers]
- {Optional segments}[#label-Optional+segments]
- {Match/Route Block Return Values}[#label-Match-2FRoute+Block+Return+Values]
- {Status codes}[#label-Status+Codes]
- {Verb methods}[#label-Verb+Methods]
- {Root method}[#label-Root+Method]
- {Request and Response}[#label-Request+and+Response]
- {Pollution}[#label-Pollution]
- {Composition}[#label-Composition]
- {Testing}[#label-Testing]
- {Settings}[#label-Settings]
- {Rendering}[#label-Rendering]
- {Security}[#label-Security]
- {Code Reloading}[#label-Code+Reloading]
- {Plugins}[#label-Plugins]
- {No introspection}[#label-No+Introspection]
- {Inspiration}[#label-Inspiration]
- {Ruby Support Policy}[#label-Ruby+Support+Policy]

== Installation

  $ gem install roda

== Resources

Website :: http://roda.jeremyevans.net
Source :: http://github.com/jeremyevans/roda
Bugs :: http://github.com/jeremyevans/roda/issues
Discussion Forum (GitHub Discussions) :: https://github.com/jeremyevans/roda/discussions
Alternate Discussion Forum (Google Group) :: http://groups.google.com/group/ruby-roda

== Goals

Roda is designed with the following SUPER goals in mind:

* Simplicity
* Understandability
* Performance
* Extensibility
* Reliability

=== Simplicity

Roda is designed to be simple, both internally and externally.
It uses a routing tree to enable you to write simpler and DRYer
code.

=== Understandability

Roda is designed to avoid hidden control flow, so it is easy to
follow the control flow you write in your routing tree to see
the application's entire handling process for a given request.

=== Performance

Roda has low per-request overhead, and the use of a routing tree
and intelligent caching of internal datastructures makes it 
significantly faster than other popular ruby web frameworks.

=== Extensibility

Roda is built completely out of plugins, which makes it very
extensible.  You can override any part of Roda and call super
to get the default behavior.

=== Reliability

Roda supports and encourages immutability.  Roda apps are designed
to be frozen in production, which eliminates possible thread safety issues.
Additionally, Roda limits the instance variables, constants, and
methods that it uses, so that they do not conflict with the ones
you use for your application.

== Usage

Here's a simple application, showing how the routing tree works:

  # cat config.ru
  require "roda"

  class App < Roda
    route do |r|
      # GET / request
      r.root do
        r.redirect "/hello"
      end

      # /hello branch
      r.on "hello" do
        # Set variable for all routes in /hello branch
        @greeting = 'Hello'

        # GET /hello/world request
        r.get "world" do
          "#{@greeting} world!"
        end

        # /hello request
        r.is do
          # GET /hello request
          r.get do
            "#{@greeting}!"
          end

          # POST /hello request
          r.post do
            puts "Someone said #{@greeting}!"
            r.redirect
          end
        end
      end
    end
  end

  run App.freeze.app

Here's a breakdown of what is going on in the block above:

The +route+ block is called whenever a new request comes in. 
It is yielded an instance of a subclass of <tt>Rack::Request</tt>
with some additional methods for matching routes.
By convention, this argument should be named +r+.

The primary way routes are matched in Roda is by calling
+r.on+, +r.is+, +r.root+, +r.get+, or +r.post+.
Each of these "routing methods" takes a "match block".

Each routing method takes each of the arguments (called matchers)
that are given and tries to match it to the current request.
If the method is able to match all of the arguments, it yields to the match block;
otherwise, the block is skipped and execution continues.

- +r.on+ matches if all of the arguments match.
- +r.is+ matches if all of the arguments match and there are no
  further entries in the path after matching.
- +r.get+ matches any +GET+ request when called without arguments.
- +r.get+ (when called with any arguments) matches only if the
  current request is a +GET+ request and there are no further entries
  in the path after matching.
- +r.root+ only matches a +GET+ request where the current path is +/+.

If a routing method matches and control is yielded to the match block,
whenever the match block returns, Roda will return the Rack response array
(containing status, headers, and body) to the caller.

If the match block returns a string
and the response body hasn't already been written to,
the block return value will be interpreted as the body for the response.
If none of the routing methods match and the route block returns a string,
it will be interpreted as the body for the response.

+r.redirect+ immediately returns the response,
allowing for code such as <tt>r.redirect(path) if some_condition</tt>.
If +r.redirect+ is called without arguments
and the current request method is not +GET+, it redirects to the current path.

The +.freeze.app+ at the end is optional.  Freezing the app makes modifying
app-level settings raise an error, alerting you to possible thread-safety issues
in your application.  It is recommended to freeze the app in production and
during testing.  The +.app+ is an optimization, which saves a few method calls
for every request.

== Running the Application

Running a Roda application is similar to running any other rack-based application
that uses a +config.ru+ file.  You can start a basic server using +rackup+, +puma+,
+unicorn+, +passenger+, or any other webserver that can handle +config.ru+ files:

  $ rackup

== The Routing Tree

Roda is called a routing tree web toolkit because the way most sites are structured,
routing takes the form of a tree (based on the URL structure of the site).
In general:

- +r.on+ is used to split the tree into different branches.
- +r.is+ finalizes the routing path.
- +r.get+ and +r.post+ handle specific request methods.

So, a simple routing tree might look something like this:

  r.on "a" do           # /a branch
    r.on "b" do         # /a/b branch
      r.is "c" do       # /a/b/c request
        r.get do end    # GET  /a/b/c request
        r.post do end   # POST /a/b/c request
      end
      r.get "d" do end  # GET  /a/b/d request
      r.post "e" do end # POST /a/b/e request
    end
  end

It's also possible to handle the same requests,
but structure the routing tree by first branching on the request method:

  r.get do              # GET 
    r.on "a" do         # GET /a branch
      r.on "b" do       # GET /a/b branch
        r.is "c" do end # GET /a/b/c request
        r.is "d" do end # GET /a/b/d request
      end
    end
  end

  r.post do             # POST
    r.on "a" do         # POST /a branch
      r.on "b" do       # POST /a/b branch
        r.is "c" do end # POST /a/b/c request
        r.is "e" do end # POST /a/b/e request
      end
    end
  end

This allows you to easily separate your +GET+ request handling
from your +POST+ request handling.
If you only have a small number of +POST+ request URLs
and a large number of +GET+ request URLs, this may make things easier.

However, routing first by the path and last by the request method
is likely to lead to simpler and DRYer code.
This is because you can act on the request at any point during the routing.
For example, if all requests in the +/a+ branch need access permission +A+
and all requests in the +/a/b+ branch need access permission +B+,
you can easily handle this in the routing tree:

  r.on "a" do           # /a branch
    check_perm(:A)  
    r.on "b" do         # /a/b branch
      check_perm(:B)  
      r.is "c" do       # /a/b/c request
        r.get do end    # GET  /a/b/c request
        r.post do end   # POST /a/b/c request
      end
      r.get "d" do end  # GET  /a/b/d request
      r.post "e" do end # POST /a/b/e request
    end
  end

Being able to operate on the request at any point during the routing
is one of the major advantages of Roda.

== Matchers

Other than +r.root+, the routing methods all take arguments called matchers.
If all of the matchers match, the routing method yields to the match block.
Here's an example showcasing how different matchers work:

  class App < Roda
    route do |r|
      # GET /
      r.root do
        "Home"
      end

      # GET /about
      r.get "about" do
        "About"
      end

      # GET /post/2011/02/16/hello
      r.get "post", Integer, Integer, Integer, String do |year, month, day, slug|
        "#{year}-#{month}-#{day} #{slug}" #=> "2011-02-16 hello"
      end

      # GET /username/foobar branch
      r.on "username", String, method: :get do |username|
        user = User.find_by_username(username)

        # GET /username/foobar/posts
        r.is "posts" do
          # You can access user here, because the blocks are closures.
          "Total Posts: #{user.posts.size}" #=> "Total Posts: 6"
        end

        # GET /username/foobar/following
        r.is "following" do
          user.following.size.to_s #=> "1301"
        end
      end

      # /search?q=barbaz
      r.get "search" do
        "Searched for #{r.params['q']}" #=> "Searched for barbaz"
      end

      r.is "login" do
        # GET /login
        r.get do
          "Login"
        end

        # POST /login?user=foo&password=baz
        r.post do
          "#{r.params['user']}:#{r.params['password']}" #=> "foo:baz"
        end
      end
    end
  end

Here's a description of the matchers.
Note that "segment", as used here, means one part of the path preceded by a +/+.
So, a path such as +/foo/bar//baz+ has four segments: +/foo+, +/bar+, +/+, and +/baz+.
The +/+ here is considered the empty segment.

=== String

If a string does not contain a slash, it matches a single segment
containing the text of the string, preceded by a slash.

  ""    # matches "/"
  "foo" # matches "/foo"
  "foo" # does not match "/food"

If a string contains any slashes, it matches one additional segment for each slash:

  "foo/bar" # matches "/foo/bar"
  "foo/bar" # does not match "/foo/bard"

=== Regexp

Regexps match one or more segments by looking for the pattern,
preceded by a slash, and followed by a slash or the end of the path:

  /foo\w+/ # matches "/foobar"
  /foo\w+/ # does not match "/foo/bar"
  /foo/i # matches "/foo", "/Foo/"
  /foo/i # does not match "/food"

If any patterns are captured by the Regexp, they are yielded:

  /foo\w+/   # matches "/foobar", yields nothing
  /foo(\w+)/ # matches "/foobar", yields "bar" 

=== Class

There are two classes that are supported as matchers, String
and Integer.

String :: matches any non-empty segment, yielding the segment except for
          the preceding slash
Integer :: matches any segment of 0-9, returns matched values as integers

Using String and Integer is the recommended way to handle
arbitrary segments

  String # matches "/foo", yields "foo"
  String # matches "/1", yields "1"
  String # does not match "/"

  Integer # does not match "/foo"
  Integer # matches "/1", yields 1
  Integer # does not match "/"

=== Symbol

Symbols match any nonempty segment,
yielding the segment except for the preceding slash:

  :id # matches "/foo" yields "foo"
  :id # does not match "/"

Symbol matchers operate the same as the class String matcher,
and is the historical way to do arbitrary segment matching.
It is recommended to use the class String matcher in new code
as it is a bit more intuitive.

=== Proc

Procs match unless they return false or nil:

  proc{true}  # matches anything
  proc{false} # does not match anything

Procs don't capture anything by default,
but they can do so if you add the captured text to +r.captures+.

=== Arrays

Arrays match when any of their elements match.
If multiple matchers are given to +r.on+, they all must match (an AND condition).
If an array of matchers is given, only one needs to match (an OR condition).
Evaluation stops at the first matcher that matches.

Additionally, if the matched object is a String, the string is yielded.
This makes it easy to handle multiple strings without a Regexp:

  ['page1', 'page2'] # matches "/page1", "/page2"
  []                 # does not match anything

=== Sets

Sets match if the next segment in the request path matches one of the
elements in the set (non-String elements in the set are ignored). The matched
element is yielded.

  Set['page1', 'page2'] # matches "/page1", "/page2"
  Set[]                 # does not match anything

In general, a Set matcher has the same behavior as an Array matcher 
containing only the set's string elements, but will perform better for
when there are a large number of elements in the set.

=== Hash

Hashes allow easily calling specialized match methods on the request.
The default registered matchers included with Roda are documented below.
Some plugins add additional hash matchers, and the hash_matcher plugin
allows for easily defining your own:

  class App < Roda
    plugin :hash_matcher

    hash_matcher(:foo) do |v|
      # ...
    end
    
    route do |r|
      r.on foo: 'bar' do
        # ...
      end
    end
  end

==== :all

The +:all+ matcher matches if all of the entries in the given array match, so

  r.on all: [String, String] do
    # ...
  end

is the same as:

  r.on String, String do
    # ...
  end

The reason it also exists as a separate hash matcher
is so you can use it inside an array matcher, so:

  r.on ['foo', {all: ['foos', Integer]}] do
  end

would match +/foo+ and +/foos/10+, but not +/foos+.

==== :method

The +:method+ matcher matches the method of the request.
You can provide an array to specify multiple request methods and match on any of them:

  {method: :post}             # matches POST
  {method: ['post', 'patch']} # matches POST and PATCH

=== true

If +true+ is given directly as a matcher, it always matches.

=== false, nil

If +false+ or +nil+ is given directly as a matcher, it doesn't match anything.

=== Everything else

Everything else raises an error, unless support is specifically added for it
(some plugins add support for additional matcher types).

== Optional segments

There are multiple ways you can handle optional segments in Roda.  For example,
let's say you want to accept both +/items/123+ and +/items/123/456+, with 123 being
the item's id, and 456 being some optional data.

The simplest way to handle this is by treating this as two separate routes with a
shared branch:

  r.on "items", Integer do |item_id|
    # Shared code for branch here

    # /items/123/456
    r.is Integer do |optional_data|
    end

    # /items/123
    r.is do
    end
  end

This works well for many cases, but there are also cases where you really want to
treat it as one route with an optional segment.  One simple way to do that is to
use a parameter instead of an optional segment (e.g. <tt>/items/123?opt=456</tt>).

  r.is "items", Integer do |item_id|
    optional_data = r.params['opt'].to_s
  end

However, if you really do want to use a optional segment, there are a couple different
ways to use matchers to do so.  One is using an array matcher where the last element
is true:

  r.is "items", Integer, [String, true] do |item_id, optional_data|
  end

Note that this technically yields only one argument instead of two arguments if the
optional segment isn't provided.

An alternative way to implement this is via a regexp:

  r.is "items", /(\d+)(?:\/(\d+))?/ do |item_id, optional_data|
  end

== Match/Route Block Return Values

If the response body has already been written to by calling +response.write+
directly, then any return value of a match block or route block is ignored.

If the response body has not already been written to, then the match block
or route block return value is inspected:

String :: used as the response body
nil, false :: ignored
everything else :: raises an error

Plugins can add support for additional match block and route block return
values.  One example of this is the json plugin, which allows returning
arrays and hashes in match and route blocks and converts those directly
to JSON and uses the JSON as the response body.

== Status Codes

When it comes time to finalize a response,
if a status code has not been set manually and anything has been written to the response,
the response will use a 200 status code.
Otherwise, it will use a 404 status code.
This enables the principle of least surprise to work:
if you don't handle an action, a 404 response is assumed.

You can always set the status code manually,
via the +status+ attribute for the response.

  route do |r|
    r.get "hello" do
      response.status = 200
    end
  end

When redirecting, the response will use a 302 status code by default.
You can change this by passing a second argument to +r.redirect+:

  route do |r|
    r.get "hello" do
      r.redirect "/other", 301 # use 301 Moved Permanently
    end
  end

== Verb Methods

As displayed above, Roda has +r.get+ and +r.post+ methods
for matching based on the HTTP request method.  If you want
to match on other HTTP request methods, use the all_verbs
plugin.

When called without any arguments, these match as long
as the request has the appropriate method, so:

  r.get do end

matches any +GET+ request, and

  r.post do end

matches any +POST+ request

If any arguments are given to the method, these match only
if the request method matches, all arguments match, and
the path has been fully matched by the arguments, so:

  r.post "" do end

matches only +POST+ requests where the current path is +/+.

  r.get "a/b" do end

matches only +GET+ requests where the current path is +/a/b+.

The reason for this difference in behavior is that
if you are not providing any arguments, you probably don't want
to also test for an exact match with the current path.
If that is something you do want, you can provide +true+ as an argument:

  r.on "foo" do
    r.get true do # Matches GET /foo, not GET /foo/.*
    end
  end

If you want to match the request method
and do only a partial match on the request path,
you need to use +r.on+ with the <tt>:method</tt> hash matcher:

  r.on "foo", method: :get do # Matches GET /foo(/.*)?
  end

== Root Method

As displayed above, you can also use +r.root+ as a match method.
This method matches +GET+ requests where the current path is +/+.
+r.root+ is similar to <tt>r.get ""</tt>,
except that it does not consume the +/+ from the path.

Unlike the other matching methods, +r.root+ takes no arguments.

Note that +r.root+ does not match if the path is empty;
you should use <tt>r.get true</tt> for that.
If you want to match either the empty path or +/+,
you can use <tt>r.get ["", true]</tt>, or use the slash_path_empty
plugin.

Note that +r.root+ only matches +GET+ requests.
So, to handle <tt>POST /</tt> requests, use <tt>r.post ''</tt>.

== Request and Response

While the request object is yielded to the +route+ block,
it is also available via the +request+ method.
Likewise, the response object is available via the +response+ method.

The request object is an instance of a subclass of <tt>Rack::Request</tt>,
with some additional methods.

If you want to extend the request and response objects with additional modules,
you can use the module_include plugin.

== Pollution

Roda tries very hard to avoid polluting the scope of the +route+ block.
This should make it unlikely that Roda will cause namespace issues
with your application code.  Some of the things Roda does:

- The only instance variables defined by default in the scope of the +route+ block
  are <tt>@_request</tt> and <tt>@_response</tt>.  All instance variables in the
  scope of the +route+ block used by plugins that ship with Roda are prefixed
  with an underscore.
- The main methods defined, beyond the default methods for +Object+, are
  +env+, +opts+, +request+, +response+, and +session+.  +call+ and +_call+ are also
  defined, but are deprecated.  All other methods defined are prefixed with +_roda_+
- Constants inside the Roda namespace are all prefixed with +Roda+
  (e.g., <tt>Roda::RodaRequest</tt>).

== Composition

You can mount any Rack app (including another Roda app), with its own middlewares,
inside a Roda app, using +r.run+:

  class API < Roda
    route do |r|
      r.is do
        # ...
      end
    end
  end

  class App < Roda
    route do |r|
      r.on "api" do
        r.run API
      end
    end
  end

  run App.app

This will take any path starting with +/api+ and send it to +API+.
In this example, +API+ is a Roda app, but it could easily be
a Sinatra, Rails, or other Rack app.

When you use +r.run+, Roda calls the given Rack app (+API+ in this case);
whatever the Rack app returns will be returned
as the response for the current application.

If you have a lot of rack applications that you want to dispatch to, and
which one to dispatch to is based on the request path prefix, look into the
+multi_run+ plugin.

=== hash_branches plugin

If you are just looking to split up the main route block up by branches,
you should use the +hash_branches+ plugin,
which keeps the current scope of the +route+ block:

  class App < Roda
    plugin :hash_branches

    hash_branch "api" do |r|
      r.is do
        # ...
      end
    end

    route do |r|
      r.hash_branches
    end
  end

  run App.app

This allows you to set instance variables in the main +route+ block
and still have access to them inside the +api+ +route+ block.

== Testing

It is very easy to test Roda with {Rack::Test}[https://github.com/rack-test/rack-test]
or {Capybara}[https://github.com/teamcapybara/capybara].
Roda's own tests use {minitest/spec}[https://github.com/seattlerb/minitest].
The default Rake task will run the specs for Roda.

== Settings

Each Roda app can store settings in the +opts+ hash.
The settings are inherited by subclasses.

  Roda.opts[:layout] = "guest"

  class Users < Roda; end
  class Admin < Roda
    opts[:layout] = "admin"
  end

  Users.opts[:layout] # => 'guest'
  Admin.opts[:layout] # => 'admin'

Feel free to store whatever you find convenient.
Note that when subclassing, Roda only does a shallow clone of the settings.

If you store nested structures and plan to mutate them in subclasses,
it is your responsibility to dup the nested structures inside +Roda.inherited+
(making sure to call +super+).  This should be done so that modifications
to the parent class made after subclassing do _not_ affect the subclass, and
vice-versa.

The plugins that ship with Roda freeze their settings and only allow modification
to their settings by reloading the plugin, and external plugins are encouraged
to follow this approach.

The following options are respected by the default library or multiple plugins:

:add_script_name :: Prepend the SCRIPT_NAME for the request to paths.  This is
                    useful if you mount the app as a path under another app.
:check_arity :: Whether arity for blocks passed to Roda should be checked
                to determine if they can be used directly to define methods
                or need to be wrapped. For backwards compatibility, this can be
                set to +true+, so Roda will check blocks and handle cases where
                the arity of the block does not match the expected arity. The default 
                is +:warn+, which issues warnings whenever Roda detects an
                arity mismatch.  If set to +false+, Roda does not check the arity
                of blocks, which can result in failures at runtime if the arity
                of the block does not match what Roda expects.  Note that Roda
                does not check the arity for lambda blocks, as those are strict
                by default. In Roda 4, this option will be removed, and +false+
                will be the only behavior.
:check_dynamic_arity :: Similar to :check_arity, but used for checking blocks
                        where the number of arguments Roda will call the blocks
                        with is not possible to determine when defining the
                        method.  By default, Roda checks arity for such methods,
                        but doing so actually slows the method down even if the
                        number of arguments matches the expected number of arguments.
                        In Roda 4, Roda will not check dynamic arity, and arity
                        mismatches will result in exceptions.
:freeze_middleware :: Whether to freeze all middleware when building the rack app.
:json_parser :: A callable for parsing JSON (+JSON.parse+ in general used by
                default).
:json_serializer :: A callable for serializing JSON (+to_json+ in general used
                    by default).
:root :: Set the root path for the app.  This defaults to the current working
         directory of the process.
:sessions_convert_symbols :: This should be set to +true+ if the sessions in use
                             do not support roundtripping of symbols (for
                             example, when sessions are serialized via JSON).

There may be other options supported by individual plugins, if so it will be
mentioned in the documentation for the plugin.

== Rendering

Roda ships with a +render+ plugin that provides helpers for rendering templates.
It uses {Tilt}[https://github.com/rtomayko/tilt],
a gem that interfaces with many template engines.
The +erb+ engine is used by default.

Note that in order to use this plugin you need to have Tilt installed,
along with the templating engines you want to use.

This plugin adds the +render+ and +view+ methods, for rendering templates.
By default, +view+ will render the template inside the default layout template;
+render+ will just render the template.

  class App < Roda
    plugin :render

    route do |r|
      @var = '1'

      r.get "render" do
        # Renders the views/home.erb template, which will have access to
        # the instance variable @var, as well as local variable content.
        render("home", locals: {content: "hello, world"})
      end

      r.get "view" do
        @var2 = '1'

        # Renders the views/home.erb template, which will have access to the
        # instance variables @var and @var2, and takes the output of that and
        # renders it inside views/layout.erb (which should yield where the
        # content should be inserted).
        view("home")
      end
    end
  end

You can override the default rendering options by passing a hash to the plugin:

  class App < Roda
    plugin :render,
      escape: true, # Automatically escape output in erb templates using Erubi's escaping support
      views: 'admin_views', # Default views directory
      layout_opts: {template: 'admin_layout', engine: 'html.erb'},    # Default layout options
      template_opts: {default_encoding: 'UTF-8'} # Default template options
  end

== Security

Web application security is a very large topic,
but here are some things you can do with Roda
to prevent some common web application vulnerabilities.

=== Session Security

By default, Roda doesn't turn on sessions, and if you don't need sessions, you can
skip this section.  If you do need sessions, Roda offers two recommended ways to
implement cookie-based sessions.

If you do not need any session support in middleware, and only need session support
in the Roda application, then use the sessions plugin:

  require 'roda'
  class App < Roda
    plugin :sessions, secret: ENV['SESSION_SECRET']
  end

The +:secret+ option should be a randomly generated string of at least 64 bytes.

If you have middleware that need access to sessions, then use the +RodaSessionMiddleware+
that ships with Roda:

  require 'roda'
  require 'roda/session_middleware'
  class App < Roda
    use RodaSessionMiddleware, secret: ENV['SESSION_SECRET']
  end

If you need non-cookie based sessions (such as sessions stored in a database), you
should use an appropriate external middleware.
  
It is possible to use other session cookie middleware such as
<tt>Rack::Session::Cookie</tt>, but other middleware may not have the same security
features that Roda's session support does.  For example, the session cookies used by
the <tt>Rack::Session::Cookie</tt> middleware provided by Rack before Rack 3 are not
encrypted, just signed to prevent tampering.

For any cookie-based sessions, make sure that the necessary secrets (+:secret+ option)
are not disclosed to an attacker.  Knowledge of the
secret(s) can allow an attacker to inject arbitrary session values.  In the case of
<tt>Rack::Session::Cookie</tt>, that can also lead remote code execution.

=== Cross Site Request Forgery (CSRF)

CSRF can be prevented by using the +route_csrf+ plugin that ships with Roda.
The +route_csrf+ plugin uses modern security practices to create CSRF tokens,
requires request-specific tokens by default, and offers control to the user
over where in the routing tree that CSRF tokens are checked. For example, if
you are using the +public+ plugin to serve static files and the +assets+
plugin to serve assets, you wouldn't need to check for CSRF tokens for either
of those, so you could put the CSRF check after those in the routing tree,
but before handling other requests:

  route do |r|
    r.public
    r.assets

    check_csrf! # Must call this to check for valid CSRF tokens

    # ...
  end


=== Cross Site Scripting (XSS)

The easiest way to prevent XSS with Roda is to use a template library
that automatically escapes output by default.
The +:escape+ option to the +render+ plugin sets the ERB template processor
to escape by default, so that in your templates:

  <%= '<>' %>  # outputs &lt;&gt; 
  <%== '<>' %> # outputs <>

When using the +:escape+ option, you will need to ensure that your layouts
are not escaping the output of the content template:

  <%== yield %> # not <%= yield %>

This support requires {Erubi}[https://github.com/jeremyevans/erubi].

=== Unexpected Parameter Types

Rack converts submitted parameters into a hash of strings, arrays, and
nested hashes.  Since the user controls the submission of parameters, you
should treat any submission of parameters with caution, and should be
explicitly checking and/or converting types before using any submitted 
parameters.  One way to do this is explicitly after accessing them:

  # Convert foo_id parameter to an integer
  request.params['foo_id'].to_i

However, it is easy to forget to convert the type, and if the user
submits +foo_id+ as a hash or array, a NoMethodError will be raised.
Worse is if you do:

  some_method(request.params['bar'])

Where +some_method+ supports both a string argument and a hash
argument, and you expect the parameter will be submitted as a
string, and +some_method+'s handling of a hash argument performs
an unauthorized action.

Roda ships with a +typecast_params+ plugin that can easily handle
the typecasting of submitted parameters, and it is recommended
that all Roda applications that deal with parameters use it or
another tool to explicitly convert submitted parameters to the
expected types.

=== Content Security Policy

The Content-Security-Policy HTTP header can be used to instruct
the browser on what types of content to allow and where content
can be loaded from.  Roda ships with a +content_security_policy+
plugin that allows for the easy configuration of the content
security policy.  Here's an example of a fairly restrictive
content security policy configuration:

  class App < Roda
    plugin :content_security_policy do |csp|
      csp.default_src :none # deny everything by default
      csp.style_src :self
      csp.script_src :self
      csp.connect_src :self
      csp.img_src :self
      csp.font_src :self
      csp.form_action :self
      csp.base_uri :none
      csp.frame_ancestors :none
      csp.block_all_mixed_content
      csp.report_uri 'CSP_REPORT_URI'
    end
  end

=== Other Security Related HTTP Headers

You may want to look into setting the following HTTP headers, which
can be done at the web server level, but can also be done at the
application level using the +default_headers+ plugin:

Strict-Transport-Security :: Enforces SSL/TLS Connections to the application.
X-Content-Type-Options :: Forces some browsers to respect a declared Content-Type header.
X-Frame-Options :: Provides click-jacking protection by not allowing usage inside a frame.
                   Only include this if you want to support and protect old browsers that
                   do not support Content-Security-Policy.

Example:

  class App < Roda
    plugin :default_headers,
      'Content-Type'=>'text/html',
      'Strict-Transport-Security'=>'max-age=63072000; includeSubDomains',
      'X-Content-Type-Options'=>'nosniff',
      'X-Frame-Options'=>'deny'
  end

=== Rendering Templates Derived From User Input

Roda's rendering plugin by default checks that rendered templates are inside the views
directory.  This is because rendering templates outside the views directory is not
commonly needed, and it prevents a common attack (which is especially severe if there is any
location on the file system that users can write files to).

You can specify which directories are allowed using the +:allowed_paths+ render plugin
option. If you really want to turn path checking off, you can do so via the
<tt>check_paths: false</tt> render plugin option.

== Code Reloading

Roda does not ship with integrated support for code reloading, but there are
existing reloaders that will work with Roda apps:

{Zeitwerk}[https://github.com/fxn/zeitwerk] is the most popular Ruby code
reloading library.  It requires minimal setup and handles most cases. It
overrides +require+ when activated. It supports lazily loading code
(autoloading). It requires some file path conventions, and requires each file
define a single top-level constant. It doesn't fully support explicit requires
of dependencies.  Zeitwerk is probably the best choice if you prefer your code
to be automatically loaded and agree with the Zeitwerk code organization
conventions.

{rack-unreloader}[https://github.com/jeremyevans/rack-unreloader] implements a
Rack middleware that detects file changes and reloads only the files that
change.  It wraps a Rack app and records loaded constants per file so that it
knows which constants to unload when reloading a file.  It doesn't override
+require+ or apply any monkey patches, instead using its own API to require
files and specify dependencies. The main advantage over other reloaders is that
it only unloads constants that are required by the file that was modified (a
much faster approach), it supports splitting classes into multiple files (common
for Roda apps using the hash_branches plugin), and it does not require any file
path conventions. By default, it unloads newly detected constants, which can be
dangerous.  Users are advised to review the documentation before using it.  It
supports both eager loading (require) and lazy loading (autoloading).
rack-unreloader is probably the best choice if performance is important, you
have classes split into multiple files, or you would like to avoid monkey
patching +Kernel.require+.

{auto_reloader}[https://github.com/rosenfeld/auto_reloader] provides transparent
reloading for all files reached from one of the +reloadable_paths+ option
entries, by detecting new top-level constants and removing them when any of the
reloadable loaded files changes. It overrides +require+ and +require_relative+
when activated. No configurations other than +reloadable_paths+ are required.
Autoloading isn't supported by design.  auto_reloader may be the best choice if
you do not want to use Zeitwerk's conventions and are OK with monkey patching.

{rerun}[https://github.com/alexch/rerun] uses a fork/exec approach for loading
new versions of your app. It works without any changes to application code, but
may be slower as it has to reload the entire application on every change. rerun
is probably the best choice if you value simplicity over performance for code
reloading.

There is no one reloading solution that is the best for all applications and
development approaches.  Consider your needs and the tradeoffs of each of the
reloading approaches, and pick the one you think will work best.  If you are
unsure where to start, it may be best to start with Zeitwerk, and only consider
other options if it does not work well for you.

== Plugins

By design, Roda has a very small core, providing only the essentials.
All nonessential features are added via plugins.

Roda's plugins can override any Roda method and call +super+
to get the default behavior, which makes Roda very extensible.

{Roda ships with a large number of plugins}[http://roda.jeremyevans.net/documentation.html#included-plugins],
and {some other libraries ship with support for Roda}[http://roda.jeremyevans.net/documentation.html#external].

=== How to create plugins

Authoring your own plugins is pretty straightforward.
Plugins are just modules, which may contain any of the following modules:

InstanceMethods :: module included in the Roda class
ClassMethods :: module that extends the Roda class
RequestMethods :: module included in the class of the request
RequestClassMethods :: module extending the class of the request
ResponseMethods :: module included in the class of the response
ResponseClassMethods :: module extending the class of the response

If the plugin responds to +load_dependencies+, it will be called first,
and should be used if the plugin depends on another plugin.

If the plugin responds to +configure+, it will be called last,
and should be used to configure the plugin.

Both +load_dependencies+ and +configure+ are called
with the additional arguments and block that was given to the plugin call.

So, a simple plugin to add an instance method would be:

  module MarkdownHelper
    module InstanceMethods
      def markdown(str)
        BlueCloth.new(str).to_html
      end
    end
  end

  Roda.plugin MarkdownHelper

=== Registering plugins

If you want to ship a Roda plugin in a gem,
but still have Roda load it automatically via <tt>Roda.plugin :plugin_name</tt>,
you should place it where it can be required via +roda/plugins/plugin_name+
and then have the file register it as a plugin via
<tt>Roda::RodaPlugins.register_plugin</tt>.
It's recommended, but not required, that you store your plugin module
in the <tt>Roda::RodaPlugins</tt> namespace:

  class Roda
    module RodaPlugins
      module Markdown
        module InstanceMethods
          def markdown(str)
            BlueCloth.new(str).to_html
          end
        end
      end

      register_plugin :markdown, Markdown
    end
  end

To avoid namespace pollution,
you should avoid creating your module directly in the +Roda+ namespace.
Additionally, any instance variables created inside +InstanceMethods+
should be prefixed with an underscore (e.g., <tt>@_variable</tt>)
to avoid polluting the scope.  Finally, do not add any constants inside
the InstanceMethods module, add constants to the plugin module itself
(+Markdown+ in the above example).

If you are planning on shipping your plugin in an external gem, it is recommended that you follow 
{standard gem naming conventions for extensions}[http://guides.rubygems.org/name-your-gem/].
So if your plugin module is named +FooBar+, your gem name should be <tt>roda-foo_bar</tt>.

== No Introspection

Because a routing tree does not store the routes in a data structure, but
directly executes the routing tree block, you cannot introspect the routes
when using a routing tree.

If you would like to introspect your routes when using Roda, there is an
external plugin named {roda-route_list}[https://github.com/jeremyevans/roda-route_list],
which allows you to add appropriate comments to your routing files, and
has a parser that will parse those comments into routing metadata that
you can then introspect.

== Inspiration

Roda was inspired by {Sinatra}[http://www.sinatrarb.com] and {Cuba}[http://cuba.is].
It started out as a fork of Cuba, from which it borrows the idea of using a routing tree
(which Cuba in turn took from {Rum}[https://github.com/chneukirchen/rum]).
From Sinatra, it takes the ideas that route blocks should return the request bodies
and that routes should be canonical.
Roda's plugin system is based on the plugin system used by
{Sequel}[http://sequel.jeremyevans.net].

== Ruby Support Policy

Roda fully supports the currently supported versions of Ruby (MRI) and JRuby.  It may
support unsupported versions of Ruby or JRuby, but such support may be dropped in any
minor version if keeping it becomes a support issue.  The minimum Ruby version
required to run the current version of Roda is 1.9.2, and the minimum JRuby version is
9.0.0.0.

== License

MIT

== Maintainer

Jeremy Evans <code@jeremyevans.net>
