07 Static - kjellhex/diode GitHub Wiki

Motivation

Diode makes it easy to build data-driven REST services, but often those services are consumed by a web application. Demonstrating the services then might involve a web page fetching the data, populating a form, and posting the changes back to the server. While Diode is designed to operate behind a web server that can serve static content, leaving the diode-powered server to handle rest services only, there may be times (such as a quick demo) when it would be convenient for the server to serve up some limited static content as well.

For example, if you want to provide some demonstration code to someone who does not have the ability or time to install and configure a web server and deploy the static content needed to connect with the diode services, just have the diode serve also provide the HTML, CSS, and JavaScript directly.

Code

Server

Copy the files in the examples/07-static folder into a working directory and run the server from there:

require 'diode/server'
routing = [
  [%r{^/curr$}, "CurrencyService"],
  [%r{^/img/\w+\.(jpg|png)$},  "Diode::Static", "."],
  [%r{^/},      "Diode::Static"]
]
load './currency-service.rb'
Diode::Server.new(3999, routing).start()

and start the server:

$ ruby static-server.rb

then visit http://127.0.0.1:3999/ to see the demonstration page. Edit the data and save your changes.

screenshot

How does it work?

Routing for static content

The CurrencyService is mounted as normal on a path of /curr. Note that the pattern for images restricts requests to only those matching the pattern, demonstrating how you can securely expose only certain files (this is just an demo example - in this case it is actually obviated by the next rule). Finally, we have a catch-all routing rule to try to serve static content for anything else, including /index.html.

routing = [
  [%r{^/curr$}, "CurrencyService"],
  [%r{^/img/\w+\.(jpg|png)$},  "Diode::Static", "."],
  [%r{^/},      "Diode::Static"]
]

Note how the last routing rule does not provide a specific directory as the document root for Diode::Static. That means it will default to the current working directory. Supply the document root directory as a third element of the rule to make it independent of the current working directory.

Diode::Static

The Static class is very simple and limited. To expect the full power of nginx from 60 lines of ruby code is not realistic. If the request is for a directory, Static will attempt to serve index.html for that folder if it exists. More than 30 common mimetypes are covered, so there should be no problem serving HTML, JavaScript, CSS, and images and even PDFs. If you need a mimetype that is not currently in the list, just add it:

Diode::Static.mimetypes["xyz"] = "weird/type"

The Diode::Static service assumes the current working directory is its document root unless explicitly instructed otherwise.

# lib/diode/static.rb
require 'pathname'
class Static

  def initialize(docRoot=".")
    @root = Pathname.new(Pathname.new(docRoot).cleanpath()).realpath()
    raise("Cannot serve static files from path #{@root}") unless @root.directory? and @root.readable?
  end

  def serve(request)
    return Diode::Response.new(405, "Method not allowed", {"Content-type" => "text/plain"}) unless request.method == "GET"
    path = Pathname.new(request.path).cleanpath.sub(request.pattern, "")  # remove the leading portion matching the mount pattern
    filepath = Pathname.new(File.expand_path(path, @root))
    return Diode::Response.new(404, "<html><body>File not found</body></html>", {"Content-type" => "text/html"}) unless filepath.exist?
    mimetype = @@mimetypes[filepath.extname[1..-1]] || "application/octet-stream"
    return Diode::Response.new(200, IO.read(filepath.to_s).b, {"Content-Type"=>mimetype, "Cache-Control" => "no-cache"})
  end

  @@mimetypes = {
    # over 30 common mimetypes
    # "jpg"   => "image/jpeg",
    ...
  }

  def self.mimetypes
    @@mimetypes
  end

end

The Diode::Static service does not generate an index listing for a directory - this is by design. That is a nice feature of web servers and Static is not a web server, it is just a quick mechanism to serve some supporting static content for situations like demonstrations.

⚠️ **GitHub.com Fallback** ⚠️