Sinatra is a F/OSS DSL, also referred to as a micro-framework, for quickly creating web applications in Ruby with minimal effort.

About Sinatra

Sinatra is tiny – its source code is less than 2000 lines. Small, but mighty, and very influential. Since its release in 2007, a partial list of other web frameworks that Sinatra has inspired and are currently active at the time of writing, includes Compojure, Express.js, Fiber, Finatra, Flask, Javalin, Padrino, Pedestal, Play Framework, Roda, Scalatra, Scotty, Spark, Spring Boot, and Trot.

Sinatra is the second most popular web framework in the Ruby ecosystem, after Ruby on Rails. Sinatra and Ruby on Rails are somewhat similar, because they both extend Rack. This means that middleware that conforms to the read the rack specification works with all web frameworks that extend rack.

Sinatra is a good choice for system integration, because it is simple, maleable, has few dependencies and is reasonably efficient. Sinatra webapps are a good mechanism for integrating web servers such as nginx and Apache httpd with other services, such as e-commerce payment gateways.

The following build on this Sinatra webapp:

If you want to learn Sinatra, here are two good references:

Automatic Restart

Sinatra applications can automatically be restarted when their source code changes. This requires a helper application, such as rerun; other projects also do similar things. To install rerun on Ubuntu:

Shell
$ sudo apt install rerun

Rerun is not currently compatible with WSL or WSL2. Future versions of WSL2 will likely work with rerun.

Setting Up a Ruby Development

Previously, I wrote about how to Setting Up a Ruby Development Environment. Follow those instructions first if you want to type along with this blog post.

Sinatra Request Explorer

Get the project:

Shell
$ git clone https://github.com/mslinn/SinatraRequestExplorer.git

$ cd SinatraRequestExplorer

Install the SinatraRequestExplorer gems:

Shell
$ bundle install

Run Sinatra Request Explorer

Development

Shell
$ rerun ruby main.rb
9:39:55 [rerun] Sinatrarequestexplorer launched
09:39:55 [rerun] Rerun (760521) running Sinatrarequestexplorer (760542)
== Sinatra (v3.0.3) has taken the stage on 9876 for development with backup from Puma
Puma starting in single mode...
* Puma version: 6.0.0 (ruby 3.1.0-p0) ("Sunflower")
*  Min threads: 0
*  Max threads: 5
*  Environment: development
*          PID: 760542
* Listening on http://127.0.0.1:9876
* Listening on http://[::1]:9876
Use Ctrl-C to stop
09:39:57 [rerun] Watching . for **/*.{rb,js,coffee,css,scss,sass,erb,html,haml,ru,yml,slim,md,feature,c,h} with Linux adapter 

Visit the SinatraRequestExplorer webapp at port 9876.

The web page displayed in response to the GET Test link above is:

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Method: GET
request.accept:
  */*
  application/signed-exchange
  application/xhtml+xml
  application/xml
  image/apng
  image/avif
  image/webp
  text/html

request.url=http://localhost:9876/dump?a=b&c=d
request.fullpath=/dump?a=b&c=d
request.path_info=/dump

request.params:
  a=b
  c=d

Computed Content-Type: plain

The web page displayed in response to the POST Test button above is:

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Method: POST
request.accept:
  */*
  application/signed-exchange
  application/xhtml+xml
  application/xml
  image/apng
  image/avif
  image/webp
  text/html

request.url=http://localhost:9876/dump
request.fullpath=/dump
request.path_info=/dump

request.params:

Computed Content-Type: plain

The web page displayed in response to the JSON Test button above is:

HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Method: POST
request.accept:
  */*
  application/signed-exchange
  application/xhtml+xml
  application/xml
  image/apng
  image/avif
  image/webp
  text/html

request.url=http://localhost:9876/dump
request.fullpath=/dump
request.path_info=/dump

request.params:
  field1=value1
  field2=value2

Computed Content-Type: plain

Using Curl

Use curl to invoke the SinatraRequestExplorer webapp:

Shell
$ URL=http://localhost:9876/dump

$ curl -d "param1=value1&param2=value2" -X POST "$URL"
<title>SinatraRequestExplorer dump</title>
<h2>Post Method</h2>
<p>Generated by the post /dump handler.</p>
<p>Go to <a href="/">Root</a>.</p>
<h2>Request Parameters</h2>
<pre>
request.url=http://localhost:9876/dump
request.fullpath=/dump
request.path_info=/dump
request.params=
  param1=value1
  param2=value2

HTTP_USER_AGENT=curl/7.85.0
</pre> 

The test script in SinatraRequestExplorer automates the above invocation and includes others.

test
#!/bin/bash

# Curl, and most browsers, default to sending Content-Type: application/x-www-form-urlencoded for a POST.
# Get requests should not specify Content-type because they do not have a body.

# Curl defaults to Accept: */*, however each browser defaults to a different default value.
# Chrome defaults to Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values

shopt -s expand_aliases
alias curl='curl -L --silent --show-error'

function label {
  printf "\n\n=== $@ ===\n"
}

URL=http://localhost:9876/dump

label "Send GET, accept any type of response"
curl "$URL?a=b&c=d"

label "Send GET, expect a JSON response"
curl -H "Accept: application/json" "$URL?a=b&c=d"

label "Send POST, accept any type of response"
curl -d "param1=value1&param2=value2" "$URL"

label "Send POST with a form-urlencoded body and a query string, accept any type of response"
curl -d "param1=value1&param2=value2" "$URL?a=b&c=d"

label "Send POST with a form-urlencoded body, expect a JSON response"
curl -d "param1=value1&param2=value2" \
  -H "Accept: application/json" \
  $URL

label "Send POST with a JSON body, expect a JSON response"
curl -d '{"param3":"value3", "param4":"value4"}' \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  $URL

Production

Rerun is not something you would want or need in production. Instead, you could launch Sinatra webapps such as SinatraRequestExplorer when the system boots with an entry in crontab, like this:

crontab
@reboot ruby /work/ruby/sinatra/SinatraRequestExplorer/main.rb