Skip to content
kjellhex edited this page Apr 17, 2024 · 27 revisions

Diode

A fast, simple, pure-ruby application server.

Why Diode?

While there are several wonderful and complex ruby application servers, sometimes you don't want the complexity. Rack is simple and it has plugins, but sometimes you want more control and performance. You might want Diode: especially if you want to start very simply, add more features and not be constrained.

What is Diode?

Diode is only 4 classes: a fast Server built on the brilliant async gem and Fibers, an HTTP Request class, an HTTP Response class, and a Static class to support serving static content. It gives you what you need and gets out of the way. Your services are yours - any class as long as it has a serve() method. Diode is designed to make it easy to build and deploy an HTTP service quickly - perhaps for a demo or a test.

Getting started

class Hello
  def serve(request)
    body = JSON.dump({ "message": "Hello World!" })
    Diode::Response.new(200, body)
  end
end

require 'diode/server'
routing = [
  [%r{^/}, "Hello"]
]
Diode::Server.new(3999, routing).start
# visit http://localhost:3999/

Concepts

Only two concepts are necessary.

Structure of a service

A service has a serve() method that is given a request as an argument, and must return a response. A response can be created from an HTTP status code and the response body and an optional hash of headers (essential standard HTTP headers will be automatically added for you).

class SomeService
  def serve(request)
    # do something
    return Diode::Response.new(200, "Hello", {})
  end
end

Routing

When you have more than one service, you want to designate which paths or endpoints will be served by which service. This is commonly referred to as Routing, and is defined by a list of arrays. Most of these arrays have two items: a regular expression which matches the desired paths, and the fully-qualified name of the class of the service. When the incoming request has a URL path that matches the pattern, then an instance of that class is created and the request is passed as an argument to that instance's serve() method. The first match in the list wins, so you may want to put the most specific patterns first and more generic patterns later in the list.

Any third or more arguments after the class name are passed to the constructor of the class so they can serve as configuration settings for that service. That means you can create a parametric or generic service, and use several different instances of it. For example, a translation service that is used several times, each time configured to translate a different language.

These concepts are illustrated in the tutorials.

Tutorials

  1. Hello World
  2. Configuration - application-wide settings and configuring an individual service
  3. Rest - handle both GET and POST on the same URL path
  4. Database - a database-driven response content
  5. Filters - request pre-processing, response post-processing, and authentication
  6. Validation + logging - suggestions to address common needs
  7. Static - serve static data

Security

Diode reduces the attack surface by supporting just what is needed and no more:

  • only supports GET and POST
  • provides helpers to reject unwanted query parameters, or unwanted JSON or XML fields within the body
  • only binds to 127.0.0.1 - Diode servers should be proxied behind a real webserver like nginx
  • only supports HTTP version 1.0 and 1.1 (until the list of vulnerabilities for HTTP/2 shrinks)
  • serves static content, but this should be limited to a bit of html, css, javascript, and some images and not much else, or else you might have misunderstood the intention of Diode
  • behaves well as a unix daemon (eg. stops gracefully on SIGINT)
  • insists on a very precise format for XML which allows for very strict parsing without the need for a large vulnerable xml library.