class Rackful::Server

Rack compliant server class for implementing RESTful web services.

Public Class Methods

new(&resource_registry) click to toggle source

Constructor.

This generic server class has no knowledge, and makes no presumptions, about your URI namespace. It depends on the code block you provide here to produce the {Resource} object which lives at a certain URI. This block will be called with a {URI::Generic#normalize! normalized} URI, and must return a {Resource}, or ‘nil` if there’s no resource at the given URI.

If there’s no resource at the given URI, but you’d still like to respond to ‘POST` or `PUT` requests to this URI, you can return an {Resource#empty? empty resource}.

The provided code block must be thread-safe and reentrant. @yieldparam uri [URI::Generic] The {URI::Generic::normalize! normalized}

URI of the requested resource.

@yieldreturn [Resource] A (possibly {Resource#empty? empty}) resource, or nil.

# File lib/rackful/server.rb, line 34
def initialize &resource_registry
  @resource_registry = resource_registry
end

Public Instance Methods

call( env ) click to toggle source

As required by the Rack specification.

@param env [{String => Mixed}] @return [(status_code, response_headers, response_body)]

# File lib/rackful/server.rb, line 43
def call( env )
  env['rackful.resource_registry'] ||= @resource_registry
  request = Request.new( env )
  response = Rack::Response.new
  begin
    resource = request.resource
    if request.url != request.canonical_uri.to_s
      if %w{HEAD GET}.include?( request.request_method )
        raise HTTP404NotFound if resource.empty?
        raise HTTP301MovedPermanently, request.canonical_uri
      end
      response.header['Content-Location'] = request.canonical_uri.to_s
    end
    request.assert_if_headers
    if %w{HEAD GET OPTIONS PATCH POST PUT DELETE}.include?( request.request_method )
      resource.__send__( :"http_#{request.request_method}", request, response )
    else
      resource.http_method request, response
    end
  rescue HTTPStatus => e
    serializer = e.serializer(request, false)
    response = Rack::Response.new
    response['Content-Type'] = serializer.content_type
    response.status = e.status
    if serializer.respond_to? :headers
      response.headers.merge!( serializer.headers )
    end
    response.body = serializer
  end
  # The next line fixes a small peculiarity in RFC2616: the response body of
  # a `HEAD` request _must_ be empty, even for responses outside 2xx.
  if request.head?
    response.body = []
  end
  begin
    if  201 == response.status &&
        ( location = response['Location'] ) &&
        ( new_resource = request.resource_at( location ) ) &&
        ! new_resource.empty? \
    or  ( (200...300) === response.status ||
           304        ==  response.status ) &&
        ! response['Location'] &&
        ( new_resource = request.resource_at( request.canonical_uri ) ) &&
        ! new_resource.empty?
      response.headers.merge! new_resource.default_headers
    end
    # Make sure the Location: response header contains an absolute URI:
    if  response['Location'] and response['Location'][0] == ?/
      response['Location'] = request.canonical_uri + response['Location']
    end
  rescue HTTP404NotFound => e
  end
  response.finish
end