Nut

Miniature Reactive HTTP Server

Presentation

This library is an implementation of an HTTP Server using the Reactor Pattern as provided by RxIO. This allows easy development of fast, non-blocking web services.

Installation

Gemfile

gem 'nut'

Terminal

gem install -V nut

Usage

Introduction

Nut is a miniature pure-Ruby implementation of a reactive HTTP Server. Relying on RxIO, Nut is simple to use, fast and lightweight.

Creating a Service

Creating a service is as simple as:

# Create Nut Server
n = Nut::Service.new YourRequestHandler

The service can be made to listen on a specific address / port:

# Create Nut Server
n = Nut::Service.new 'localhost', 4444, YourRequestHandler

Finally, the service can also be made to serve HTTPS instead of HTTP:

# Create Nut Server
n = Nut::Service.new 'localhost', 4444, { cert: 'cert.pem', pkey: 'private.key' }, YourRequestHandler

Request Handler

Nut uses a request_handler module to service client requests. This module is to be implemented by the user. The request_handler module requires only one method: handle, which takes two Hashes as arguments: request and response.

# Request Handler Module
module ExampleRequestHandler
    def self.handle request, response
        # All processing done here...
    end
end

# Create Nut Server around Request Handler
n = Nut::Service.new ExampleRequestHandler

The Request Hash

The request argument to the handle method is a Hash containing information representing a client request.

The following fields are available:

  • :peer - RxIO Peer Information Hash such as { name: 'client1.example.com', addr: '51.254.97.136', port: 42432 }
  • :verb - HTTP Verb such as :get / :post
  • :uri - Request URI (without host and params)
  • :body - Complete Request Body (without headers)
  • :ctype - Content Type
  • :params - Request Params Hash such as { foo: 'bar', test_file: { filename: 'raccoon.txt', data: '...' } }
  • :headers - Request Headers Hash

The Response Hash

The response argument to the handle method is a Hash that should be filled by the user to represent a response. The following Hash keys are valid:

  • :code - HTTP Response Code (Numeric format) (200, 404, etc...) - If unspecified, defaults to 200 (unless a redirect is requested)
  • :body - Response Body
  • :type - Response Content-Type - If unspecified, will be automatically inferred from :body through Typor
  • :redirect - String (target of redirect) / Hash { to: 'http://url', permanent: true } (for a 301 Moved instead of 302 Found)

Rendering

Rendering any output is achieved by appending data to response[:body], as shown in the following example:

# Request Handler Module
module ExampleRequestHandler
    def self.handle request, response

        # Spit out some HTML
        response[:body] << "<html><body>Hello world!</body></html>"
    end
end

# Create Nut Server around Request Handler
n = Nut::Service.new ExampleRequestHandler

Content-Type

Nut will try to determine the appropriate content-type based on the response body. However, this can easily be bypassed by setting response[:type] to anything (other than nil). If response[:type] is a String, it will be used directly as the Content-Type.

# Request Handler Module
module ExampleRequestHandler
    def self.handle request, response

        # Respond with JSON
        response[:body] = { success: true, foo: :bar }.to_json
        response[:type] = 'application/json'
    end
end

# Create Nut Server around Request Handler
n = Nut::Service.new ExampleRequestHandler

If response[:type] is a Symbol, it will be matched against a set of shortcuts to determine the Content-Type. The available shortcuts are:

  • :json
  • :text
  • :html
  • :css
  • :js
# Request Handler Module
module ExampleRequestHandler
    def self.handle request, response

        # Respond with JSON
        response[:body] = { success: true, foo: :bar }.to_json
        response[:type] = :json
    end
end

# Create Nut Server around Request Handler
n = Nut::Service.new ExampleRequestHandler

Redirecting

Redirecting is achieved by setting response[:redirect] to either a String or a Hash. If response[:redirect] is a String, Nut will respond with a 302 Found "temporary" redirect.

# Request Handler Module
module ExampleRequestHandler
    def self.handle request, response

        # Temporarily Redirect to 'http://www.example.com'
        response[:redirect] = 'http://www.example.com'
    end
end

# Create Nut Server around Request Handler
n = Nut::Service.new ExampleRequestHandler

If response[:redirect] is a Hash, Nut will respond with either a 302 Found or a 301 Moved "permanent" redirect depending on the :permanent field.

# Request Handler Module
module ExampleRequestHandler
    def self.handle request, response

        # Permanently Redirect to 'http://www.eresse.net'
        response[:redirect] = { to: 'http://www.eresse.net', permanent: true }
    end
end

# Create Nut Server around Request Handler
n = Nut::Service.new ExampleRequestHandler

Handling Errors

When an exception is raised inside the handle method, Nut will catch it and immediately throw up a 500 Internal Server Error, with a description of the exception in the response body. While this is suitable for testing / debugging, production apps should overload this behaviour to serve a less 'verbose' error page. To overload the behaviour, simply define an on_err method in the Request Handler Module:

# Request Handler Module
module ExampleRequestHandler
    def self.handle request, response
        # ...
    end

    def self.on_err exception, request, response
        response[:code] = 500
        response[:body] = "Something went wrong: #{exception}"
    end
end

Best Practice

While anything is possible, the recommended pattern for using Nut is to extend the Nut Service class, and embed the Request Handler Module inside the new Service Class.

class ExampleService < Nut::Service
    def initialize
        super RequestHandler
    end

    module RequestHandler
        def self.handle request, response
            response[:body] << "You requested [#{request[:uri]}] as [#{request[:verb]}]"
        end
    end
end

Service Interface

The usual run / stop RxIO synchronous service interface is available, as well as the Runify-ed version ( startup / shutdown).

Running the server

# Create Nut Server
n = Nut::Service.new 'localhost', 23280, ExampleHandler

# Start
n.run
# Blocks until stop is called on the server...

Running in the background

# Create Nut Server
n = Nut::Service.new 'localhost', 23280, ExampleHandler

# Start
n.startup
# Server is running in the background...

# Stop
n.shutdown
# Server has stopped

License

The gem is available as open source under the terms of the MIT License.