Syro

Simple router for web applications.

Community

Meet us on IRC: #syro on freenode.net.

Description

Syro is a very simple router for web applications. It was created in the tradition of libraries like Rum and Cuba, but it promotes a less flexible usage pattern. The design is inspired by the way some Cuba applications are architected: modularity is encouraged and sub-applications can be dispatched without any significant performance overhead.

Check the website for more information, and follow the tutorial for a step by step introduction.

Usage

An example of a modular application would look like this:

admin = Syro.new {
  get {
    res.write "Hello from admin!"
  }
}

app = Syro.new {
  on("admin") {
    run(admin)
  }
}

The block is evaluated in a sandbox where the following methods are available: env, req, res, path, inbox, call, run, halt, match, on, root?, root,get, put, post, patch and delete.

As a recommendation, user created variables should be instance variables. That way they won't mix with the API methods defined in the sandbox. All the internal instance variables defined by Syro are prefixed by syro_, like in @syro_inbox.

API

env: Environment variables for the request.

req: Helper object for accessing the request variables. It's an instance of Rack::Request.

res: Helper object for creating the response. It's an instance of Syro::Response.

path: Helper object that tracks the previous and current path.

inbox: Hash with captures and potentially other variables local to the request.

call: Entry point for the application. It receives the environment and optionally an inbox.

run: Runs a sub app, and accepts an inbox as an optional second argument.

halt: Terminates the request. It receives an array with the response as per Rack's specification.

match: Receives a String, a Symbol or a boolean, and returns true if it matches the request.

on: Receives a value to be matched, and a block that will be executed only if the request is matched.

root?: Returns true if the path yet to be consumed is empty.

root: Receives a block and calls it only if root? is true.

get: Receives a block and calls it only if root? and req.get? are true.

put: Receives a block and calls it only if root? and req.put? are true.

post: Receives a block and calls it only if root? and req.post? are true.

patch: Receives a block and calls it only if root? and req.patch? are true.

delete: Receives a block and calls it only if root? and req.delete? are true.

Decks

The sandbox where the application is evaluated is an instance of Syro::Deck, and it provides the API described earlier. You can define your own Deck and pass it to the Syro constructor. All the methods defined in there will be accessible from your routes. Here's an example:

class TextualDeck < Syro::Deck
  def text(str)
    res[Rack::CONTENT_TYPE] = "text/plain"
    res.write(str)
  end
end

App = Syro.new(TextualDeck) {
  get {
    text("hello world")
  }
}

The example is simple enough to showcase the concept, but maybe too simple to be meaningful. The idea is that you can create your own specialized decks and reuse them in different applications. You can also define modules and later include them in your decks: for example, you can write modules for rendering or serializing data, and then you can combine those modules in your custom decks.

Examples

In the following examples, the response string represents the request path that was sent.

app = Syro.new {
  get {
    res.write "GET /"
  }

  post {
    res.write "POST /"
  }

  on("users") {
    on(:id) {

      # Captured values go to the inbox
      @user = User[inbox[:id]]

      get {
        res.write "GET /users/42"
      }

      put {
        res.write "PUT /users/42"
      }

      patch {
        res.write "PATCH /users/42"
      }

      delete {
        res.write "DELETE /users/42"
      }
    }

    get {
      res.write "GET /users"
    }

    post {
      res.write "POST /users"
    }
  }
}

Matches

The on method can receive a String to perform path matches; a Symbol to perform path captures; and a boolean to match any true values.

Each time on matches or captures a segment of the PATH, that part of the path is consumed. The current and previous paths can be queried by calling prev and curr on the path object: path.prev returns the part of the path already consumed, and path.curr provides the current version of the path.

Any expression that evaluates to a boolean can also be used as a matcher. For example, a common pattern is to follow some route only if a user is authenticated. That can be accomplished with on(authenticated(User)). That example assumes there's a method called authenticated that returns true or false depending on whether or not an instance of User is authenticated. As a side note, Shield is a library that provides just that.

Captures

When a symbol is provided, on will try to consume a segment of the path. A segment is defined as any sequence of characters after a slash and until either another slash or the end of the string. The captured value is stored in the inbox hash under the key that was provided as the argument to on. For example, after a call to on(:user_id), the value for the segment will be stored at inbox[:user_id]. When mounting an application called users with the command run(users), an inbox can be provided as the second argument: run(users, inbox). That allows apps to share previous captures.

Security

There are no security features built into this routing library. A framework using this library should implement the security layer.

Rendering

There are no rendering features built into this routing library. A framework that uses this routing library can easily implement helpers for rendering.

Middleware

Syro doesn't support Rack middleware out of the box. If you need them, just use Rack::Builder:

app = Rack::Builder.new do

  use Rack::Session::Cookie, secret: "..."

  run Syro.new {
    get {
      res.write("Hello, world")
    }
  }

end

Trivia

An initial idea was to release a new version of Cuba that broke backward compatibility, but in the end my friends suggested to release this as a separate library. In the future, some ideas of this library could be included in Cuba as well.

Installation

$ gem install syro