• Stars
    star
    135
  • Rank 269,240 (Top 6 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 9 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple router for web applications

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 do
  get do
    res.write "Hello from admin!"
  end
end

App = Syro.new do
  on "admin" do
    run(Admin)
  end
end

The block is evaluated in a sandbox where the following methods are available: env, req, res, path, inbox, call, run, halt, handle, finish!, consume, capture, root? match, default, on, root,get, put, head, post, patch, delete and options. Three other methods are available for customizations: default_headers, request_class and response_class.

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.

handle: Installs a handler for a given status code. It receives a status code and a block that will be executed from finish!.

finish!: Terminates the request by executing any installed handlers and then halting with the current value of res.finish.

consume: Match and consume a path segment.

capture: Match and capture a path segment. The value is stored in the inbox.

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

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

default: Receives a block that will be executed unconditionally.

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

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.

head: Receives a block and calls it only if root? and req.head? 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.

options: Receives a block and calls it only if root? and req.options? 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) do
  get do
    text("hello world")
  end
end

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 do
  get do
    res.write "GET /"
  end

  post do
    res.write "POST /"
  end

  on "users" do
    on :id do

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

      get do
        res.write "GET /users/42"
      end

      put do
        res.write "PUT /users/42"
      end

      patch do
        res.write "PATCH /users/42"
      end

      delete do
        res.write "DELETE /users/42"
      end
    end

    get do
      res.write "GET /users"
    end

    post do
      res.write "POST /users"
    end
  end
end

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.

Status code

By default the status code is set to 404. If both path and request method are matched, the status is automatically changed to 200. You can change the status code by assigning a number to res.status, for example:

post do
  ...
  res.status = 201
end

Handlers

Status code handlers can be installed with the handle command, which receives a status code and a block to be executed just before finishing the request.

By default, if there are no matches in a Syro application the response is a 404 with an empty body. If we decide to handle the 404 requests and return a string, we can do as follows:

App = Syro.new do
  handle 404 do
    res.text "Not found!"
  end

  get do
    res.text "Found!"
  end
end

In this example, a GET request to "/" will return a status 200 with the body "Found!". Any other request will return a 404 with the body "Not found!".

If a new handler is installed for the same status code, the previous handler is overwritten. A handler is valid in the current scope and in all its nested branches. Blocks that end before the handler is installed are not affected.

This is a contrived example that shows some edge cases when using handlers:

App = Syro.new do
  on "foo" do
    # 404, empty body
  end

  handle 404 do
    res.text "Not found!"
  end

  on "bar" do
    # 404, body is "Not found!"
  end

  on "baz" do
    # 404, body is "Couldn't find baz"

    handle 404 do
      res.text "Couldn't find baz"
    end
  end
end

A request to "/foo" will return a 404, because the request method was not matched. But as the on "foo" block ends before the handler is installed, the result will be a blank screen. On the other hand, a request to "/bar" will return a 404 with the plain text "Not found!".

Finally, a request to "/baz" will return a 404 with the plain text "Couldn't find baz", because by the time the on "baz" block ends a new handler is installed, and thus the previous one is overwritten.

Any status code can be handled this way, even status 200. In that case the handler will behave as a filter to be run after each successful request.

Content type

There's no default value for the content type header, but there's a handy way of setting the desired value.

In order to write the body of the response, the res.write method is used:

res.write "hello world"

It has the drawback of leaving the Content-Type header empty. Three alternative methods are provided, and more can be added by using custom Decks.

Setting the Content-Type as "text/plain":

res.text "hello world"

Setting the Content-Type as "text/html":

res.html "hello world"

Setting the Content-Type as "application/json":

res.json "hello world"

Note that aside from writing the response body and setting the value for the Content-Type header, no encoding or serialization takes place. If you want to return a JSON encoded response, make sure to encode the objects yourself (i.e., res.json JSON.dump(...)).

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 do
      res.write("Hello, world")
    end
  }
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

More Repositories

1

cuba

Rum based microframework for web development.
Ruby
1,438
star
2

ohm

Object-Hash Mapping for Redis
Ruby
1,390
star
3

micromachine

Minimal Finite State Machine
Ruby
522
star
4

clac

Command-line, stack-based calculator with postfix notation
C
350
star
5

map

Map lines from stdin to commands
C
221
star
6

mote

Minimum Operational Template
Ruby
217
star
7

nest

Generate nested namespaced keys for key-value databases.
Ruby
185
star
8

ost

Redis based queues and workers.
Ruby
166
star
9

toro

Tree oriented routing
Crystal
144
star
10

cargo

Require libraries without cluttering your namespace.
Ruby
127
star
11

scrivener

Validation frontend for models.
Ruby
124
star
12

clap

Command line argument parsing
Ruby
90
star
13

ohm-crystal

Ohm for Crystal
Crystal
71
star
14

disque-rb

Disque client for Ruby
Ruby
68
star
15

gs

Gemset management
Ruby
67
star
16

totp

Time-based One-Time Passwords
Ruby
44
star
17

mailcat

Fake SMTP server that prints emails to stdout
C
38
star
18

chen

Change directory entries with your text editor
C
37
star
19

spawn

A ridiculously simple fixtures replacement for your web framework of choice.
Ruby
36
star
20

finist

Redis based Finite State Machine
Ruby
36
star
21

redisurl

Connect to Redis using a REDIS_URL and the redigo client.
Go
33
star
22

hart

Hash router
Ruby
24
star
23

stal-ruby

Set algebra solver for Redis
Ruby
22
star
24

redisent

Sentinels aware Redis client.
Ruby
22
star
25

relay

Relay commands over SSH
Ruby
21
star
26

resp

Lightweight RESP client for Lua
Lua
21
star
27

lomo

Apply a lomo filter to your pictures from the command line using ImageMagick.
Ruby
19
star
28

terco

Obstinate DNS
Ruby
16
star
29

override

The as-simple-as-possible-but-not-simpler stubbing library.
Ruby
16
star
30

ox

Skeleton for a Cuba-based JSON API.
Ruby
16
star
31

nido

Structured keys helper
Ruby
14
star
32

syro-demo

Demo application with Syro
Ruby
14
star
33

resp-crystal

Lightweight RESP client
Crystal
14
star
34

mt

Mail tester daemon.
Ruby
13
star
35

stringent

Generate a string with a target entropy
Ruby
12
star
36

rediscan

Scanner for Redis keyspace
Ruby
12
star
37

hypertext

Hypertext authoring with Ruby
Ruby
11
star
38

filmo

A single page presentation tool.
11
star
39

cuba-book

Cuba Book
11
star
40

sc

List of HTTP status codes.
Ruby
11
star
41

finist.lua

Redis based Finite State Machine
Lua
10
star
42

drawer

Ultra slim file-based cache
Ruby
10
star
43

nest-crystal

Object Oriented Keys for Redis
Crystal
9
star
44

basica

Basic authentication library.
Ruby
8
star
45

rino

Remove a file by its inode number
C
7
star
46

seg.rb

Segment matcher for paths
Ruby
7
star
47

seg

Segment matcher for paths
Crystal
7
star
48

trim

Read from stdin and remove a prefix from each line
C
7
star
49

tas

Trees as strings
Ruby
6
star
50

stal

Set algebra solver for Redis
Lua
6
star
51

pac

Package management for Lua libraries.
Shell
6
star
52

prep

Read from stdin and prepend a string to each line while preserving identation.
C
6
star
53

m4s2

Static Site Generator
HTML
5
star
54

contract

Contract helper
Ruby
5
star
55

stal-crystal

Set algebra solver for Redis
Crystal
5
star
56

loco

Lines of code counter
C
4
star
57

ook

Object Oriented Keys for Redis
Ruby
4
star
58

walk

Walk a directory tree and print the name of every regular file
C
4
star
59

rel

Ruby client for the Bandicoot database.
Ruby
4
star
60

textorama

Text slides for the terminal
Shell
4
star
61

homebrew-tools

Formulae for Homebrew
Ruby
4
star
62

rediscan.lua

Scanner for Redis keyspace in Lua
Lua
3
star
63

seg.go

Segment matcher for paths
Go
3
star
64

look

Add a vendor directory to your load path.
Ruby
3
star
65

app

Cuba application template for gn, the file generator.
Ruby
2
star
66

packer

Require different versions of the same Cargo-compatible gem
Ruby
2
star
67

tele.sh

2
star
68

ers

AWK script easy replacements in templates
Awk
2
star
69

soveran.github.io

2
star
70

filter

Workflow template for gn.
Ruby
1
star
71

remoto

Ruby
1
star
72

gem

Gem template for gn, the file generator.
Ruby
1
star
73

ohm-scripts

Lua scripts for Ohm compatible libraries
Lua
1
star
74

ostgo

Ost client.
Go
1
star
75

miga

Breadcrumb name of the working directory
C
1
star