class Thingfish::Handler

Network-accessable datastore service

Constants

CONFIG_DEFAULTS

Configurability API – set config defaults

ID

Strelka App ID

OPERATIONAL_METADATA_KEYS

Metadata keys which aren't directly modifiable via the REST API :todo: Consider making either all of these or a subset of them

be immutable.

Metadata keys that must be provided by plugins for related resources

Attributes

datastore[R]

The datastore

event_socket[R]

The PUB socket on which resource events are published

metastore[R]

The metastore

Public Class Methods

configure( config=nil ) click to toggle source

Configurability API – install the configuration

# File lib/thingfish/handler.rb, line 97
def self::configure( config=nil )
        config = self.defaults.merge( config || {} )

        self.datastore        = config[:datastore]
        self.metastore        = config[:metastore]
        self.event_socket_uri = config[:event_socket_uri]

        self.plugin( :filters ) # pre-load the filters plugin for deferred config

        self.processors = self.load_processors( config[:processors] )
        self.processors.each do |processor|
                self.filter( :request, &processor.method(:process_request) )
                self.filter( :response, &processor.method(:process_response) )
        end
end
load_processors( processor_list ) click to toggle source

Load the Thingfish::Processors in the given processor_list and return an instance of each one.

# File lib/thingfish/handler.rb, line 78
def self::load_processors( processor_list )
        self.log.info "Loading processors"
        processors = []

        processor_list.each do |processor_type|
                begin
                        processors << Thingfish::Processor.create( processor_type )
                        self.log.debug "  loaded %s: %p" % [ processor_type, processors.last ]
                rescue LoadError => err
                        self.log.error "%p: %s while loading the %s processor" %
                                [ err.class, err.message, processor_type ]
                end
        end

        return processors
end

Public Instance Methods

restart() click to toggle source

Restart handler hook.

Calls superclass method
# File lib/thingfish/handler.rb, line 164
def restart
        if self.event_socket
                oldsock = @event_socket
                @event_socket = @event_socket.dup
                oldsock.close
        end

        super
end
run() click to toggle source

Run the handler – overridden to set up the event socket on startup.

Calls superclass method
# File lib/thingfish/handler.rb, line 140
def run
        self.setup_event_socket
        super
end
setup_event_socket() click to toggle source

Set up the event socket.

# File lib/thingfish/handler.rb, line 147
def setup_event_socket
        if self.class.event_socket_uri && ! @event_socket
                @event_socket = CZTop::Socket::PUB.new
                @event_socket.options.linger = 0
                @event_socket.bind( self.class.event_socket_uri )
        end
end
shutdown() click to toggle source

Shutdown handler hook.

Calls superclass method
# File lib/thingfish/handler.rb, line 157
def shutdown
        self.event_socket.close if self.event_socket
        super
end
thingfish() click to toggle source

Configurability API

# File lib/thingfish/handler.rb, line 61
config_key :thingfish

Protected Instance Methods

add_cache_headers( request, metadata ) click to toggle source

Add browser cache headers for resources. Last-Modified is always added. ETag support requires the sha256 processor plugin to be enabled for stored resources.

# File lib/thingfish/handler.rb, line 795
def add_cache_headers( request, metadata )
        response = request.response

        # ETag takes precedence if available.
        #
        if (( checksum = metadata['checksum'] ))
                if (( match = request.headers[ :if_none_match ] ))
                        match = match.gsub( '"', '' ).split( /,\s*/ )
                        finish_with( HTTP::NOT_MODIFIED ) if match.include?( checksum )
                end
                response.headers[ :etag ] = checksum
        end

        return unless metadata[ 'created' ]

        if (( modified = request.headers[ :if_modified_since ] ))
                finish_with( HTTP::NOT_MODIFIED ) if Time.parse( modified ) <= metadata['created'].round
        end
        response.headers[ :last_modified ] = metadata[ 'created' ].httpdate

        return
end
add_content_disposition( res, metadata ) click to toggle source

Add a filename “hint” for browsers, if the resource being fetched has a 'title' attribute.

# File lib/thingfish/handler.rb, line 821
def add_content_disposition( res, metadata )
        return unless metadata[ 'title' ]
        title = metadata[ 'title' ].encode( 'us-ascii', :undef => :replace )
        res.headers[ :content_disposition ] = "filename=%p" % [ title ]
end
check_resource_permissions( request, uuid=nil, metadata=nil ) click to toggle source

Supply a method that child handlers can override. The regular auth plugin runs too early (but can also be used), this hook allows child handlers to make access decisions based on the request object, uuid of the resource, or metadata of the fetched resource.

# File lib/thingfish/handler.rb, line 832
def check_resource_permissions( request, uuid=nil, metadata=nil ); end
extract_connection_metadata( request ) click to toggle source

Return a Hash of metadata extracted from the connection information of the given request.

# File lib/thingfish/handler.rb, line 729
def extract_connection_metadata( request )
        return {
                'useragent'     => request.headers.user_agent,
                'uploadaddress' => request.remote_ip,
        }
end
extract_default_metadata( request ) click to toggle source

Return a Hash of default metadata extracted from the given request.

# File lib/thingfish/handler.rb, line 718
def extract_default_metadata( request )
        return self.extract_connection_metadata( request ).merge(
                'extent'  => request.headers.content_length,
                'format'  => request.content_type,
                'created' => Time.now.getgm
        )
end
extract_header_metadata( request ) click to toggle source

Extract metadata from X-ThingFish-* headers from the given request and return them as a Hash.

# File lib/thingfish/handler.rb, line 747
def extract_header_metadata( request )
        self.log.debug "Extracting metadata from headers: %p" % [ request.headers ]
        metadata = {}
        request.headers.each do |header, value|
                name = header.downcase[ /^x_thingfish_(?<name>[[:alnum:]\-]+)$/i, :name ] or next
                self.log.debug "Found metadata header %p" % [ header ]
                metadata[ name ] = value
        end

        return metadata
end
extract_metadata( req ) click to toggle source

Extract and validate supplied metadata from the request.

# File lib/thingfish/handler.rb, line 738
def extract_metadata( req )
        new_metadata = req.params.fields.dup
        new_metadata = self.remove_operational_metadata( new_metadata )
        return new_metadata
end
handle_async_upload_start( request ) click to toggle source

Overridden from the base handler class to allow spooled uploads.

# File lib/thingfish/handler.rb, line 710
def handle_async_upload_start( request )
        self.log.info "Starting asynchronous upload: %s" %
                [ request.headers.x_mongrel2_upload_start ]
        return nil
end
initialize( * ) click to toggle source

Set up the metastore, datastore, and event socket when the handler is created.

Calls superclass method
# File lib/thingfish/handler.rb, line 116
def initialize( * ) # :notnew:
        super

        @datastore = Thingfish::Datastore.create( self.class.datastore )
        @metastore = Thingfish::Metastore.create( self.class.metastore )
        @event_socket = nil
end
normalized_metadata_for( uuid ) click to toggle source

Fetch the current metadata for uuid, altering it for easier round trips with REST.

# File lib/thingfish/handler.rb, line 762
def normalized_metadata_for( uuid )
        return self.metastore.fetch(uuid).merge( 'uuid' => uuid )
end
remove_operational_metadata( metadata ) click to toggle source

Prune operational metadata from the provided hash.

# File lib/thingfish/handler.rb, line 777
def remove_operational_metadata( metadata )
        operationals = OPERATIONAL_METADATA_KEYS + [ 'uuid' ]
        return metadata.reject{|key, _| operationals.include?(key) }
end
save_resource( request, uuid=nil ) click to toggle source

Save the resource in the given request's body and any associated metadata or additional resources.

# File lib/thingfish/handler.rb, line 639
def save_resource( request, uuid=nil )
        metadata = request.metadata
        metadata.merge!( self.extract_header_metadata(request) )
        metadata.merge!( self.extract_default_metadata(request) )

        self.check_resource_permissions( request, uuid, metadata )
        self.verify_operational_metadata( metadata )

        if uuid
                self.log.info "Replacing resource %s (encoding: %p)" %
                        [ uuid, request.headers.content_encoding ]
                self.datastore.replace( uuid, request.body )
                self.metastore.merge( uuid, metadata )
        else
                self.log.info "Saving new resource (encoding: %p)." %
                        [ request.headers.content_encoding ]
                uuid = self.datastore.save( request.body )
                self.metastore.save( uuid, metadata )
        end

        self.save_related_resources( request, uuid )

        return uuid, metadata
end
send_event( type, msg ) click to toggle source

Send an event of type with the given msg over the zmq event socket.

# File lib/thingfish/handler.rb, line 784
def send_event( type, msg )
        esock = self.event_socket or return
        self.log.debug "Publishing %p event: %p" % [ type, msg ]
        esock << CZTop::Message.new([ type.to_s, Yajl.dump(msg) ])
end
verify_operational_metadata( metadata ) click to toggle source

Check that the metadata provided contains valid values for the operational keys, before saving a resource to disk.

# File lib/thingfish/handler.rb, line 769
def verify_operational_metadata( metadata )
        if metadata.values_at( *OPERATIONAL_METADATA_KEYS ).any?( &:nil? )
                finish_with( HTTP::BAD_REQUEST, "Missing operational attribute." )
        end
end