Intro

Web-based applications are all the rage these days. You can apply your Ruby knowledge to build useful applications easily with a great DSL called Sinatra.

With Sinatra, you basically associate Ruby code blocks with URL routes. For example, run some method whenever someone requests “/” on your website.

For our example, we’re going to write a simple web application that just returns the client’s IP address. You’ve likely seen this functionality elsewhere on the web. It’s really useful for testing that a VPN is working, or figuring out what IP address you need to allow through a firewall.

Let’s get started!

Setup The Environment

You should already have a working Ruby installed with rubygems and bundler setup. I always like to make a gem for my applications, so let’s create one. You can call yours whatever you want, but I’m going to call mine ipecho:

$ bundle gem ipecho

Hop into the gem directory and take a look at what we’ve got:

$ cd ./ipecho
$ tree
.
├── bin
│   ├── console
│   └── setup
├── Gemfile
├── ipecho.gemspec
├── lib
│   ├── ipecho
│   │   └── version.rb
│   └── ipecho.rb
├── Rakefile
└── README.md

3 directories, 8 files

Edit the Gemspec

The first thing I do after creating a new gem, is update the gemspec file. We need to fill out some metadata and add some runtime dependencies to build or gem. You need to fill out the TODO fields for rubygems to build your gem. Obviously, you can change the name and metadata from what I have here. The most important thing is the add_runtime_dependency. In production, you’ll want to be more specific about the version to avoid compatibility and security problems. This simple specification will work fine for development:

# ipecho.gemspec

require_relative 'lib/ipecho/version'

Gem::Specification.new do |spec|
  spec.name          = "ipecho"
  spec.version       = Ipecho::VERSION
  spec.authors       = ["Michael Rodrigues"]
  spec.email         = ["michael@mrod.space"]

  spec.summary       = %q{Echo Your IP Address}
  spec.description   = %q{Example Sinatra Application for a blog post.}
  spec.homepage      = "https://mrod.space"
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")


  # Specify which files should be added to the gem when it is released.
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
    `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  end
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.add_runtime_dependency "sinatra"
end

Creating the App

Let’s start creating the actual app. Create a file called app.rb in the gem directory. We’ll step through piece-by-piece and show you the entire app.rb file at the end:

Require Sinatra

# app.rb
require 'sinatra'

Rather straightforward, but we need to require Sinatra before we can use it. You can add other gems here when you extend this example.

Subclassing Sinatra::Base

# app.rb
class Ipecho::App < Sinatra::Base

So first, we create a subclass of Sinatra::Base for our app. We’ll stick it in the namespace of our ipecho gem, so we’ll call it Ipecho::App.

Within this class, we define our app.

Sinatra Configuration

# app.rb
  set :bind, '0.0.0.0' # This is the default, you can change it

First, we set a basic :bind config to tell Ruby to bind our server to 0.0.0.0 when running the app. Sinatra has a lot of other configuration directives. It turns out that 0.0.0.0 is the default value for :bind anyways, but it’s there for illustration.

Route Lambdas

# app.rb
  echo_ip_address = lambda do
    request.ip
  end

Next, we’ll dig into the meat of the app.

We define a lambda and assign it to a variable, echo_ip_address. The lambda is just a block of code that will run when the user makes a request. This one is dead simple, it just fetches the ip attribute out of request, and returns that string. The user will see this string (their IP address) in the browser.

request is a Rack Request object with other interesting information you can print out if you want.

You can make more route lambdas, but this is all we need for the demo. Now let’s hook them up to requests.

Hooking up the Lambdas

# app.rb
  get '/', &echo_ip_address
end 

You can see at the very bottom of the Ipecho::App class, we assign the echo_ip_address lambda to any GET requests for /. You can assign the same lambda to multiple requestes if you want, and you can use other verbs like put and post as well.

Putting it All Together

OK, here it is altogether in one app.rb file:

# app.rb
require 'sinatra'

class Ipecho::App < Sinatra::Base
  set :bind, '0.0.0.0' # This is the default, you can change it

  echo_ip_address = lambda do
    request.ip
  end

  get '/', &echo_ip_address
end 

Running the App

Rackup Configuration

First, we need to create our rackup config file, config.ru:

# require our app.rb file
require File.dirname(__FILE__) + '/app'

# run the app
run Ipecho::App

Now, just execute rackup in your app folder and it should start up.

Open up http://localhost:9292 and you should see your IP address. If your browser is running on the same computer as your server, you’ll likely see 127.0.0.1, but if your server is in the cloud you should see your public address. Members on your LAN will also able to see their addresses if they can reach your port 9292.

Anyways, I hope you had fun creating this simple Sinatra application. I’ve had a similar application running at portscan.io that does a port scan on the requesting client’s IP address. I’m sure you can think of other more interesting things to do with the Sinatra DSL. If you build something cool or need a hand, feel free to drop me a line.

Thanks for reading!