Handler

class
Superclass
Strelka::App
Extended With
Loggability
Configurability
Strelka::MethodUtilities

Network-accessable datastore service

Public Class Methods

anchor
configure( config=nil )

Configurability API – install the configuration

# File lib/thingfish/handler.rb, line 77
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.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
anchor
load_processors( processor_list )

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

# File lib/thingfish/handler.rb, line 94
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

anchor
restart()

Restart handler hook.

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

        super
end
anchor
run()

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

# File lib/thingfish/handler.rb, line 138
def run
        self.setup_event_socket
        super
end
anchor
setup_event_socket()

Set up the event socket.

# File lib/thingfish/handler.rb, line 145
def setup_event_socket
        if self.class.event_socket_uri && ! @event_socket
                @event_socket = Mongrel2.zmq_context.socket( :PUB )
                @event_socket.linger = 0
                @event_socket.bind( self.class.event_socket_uri )
        end
end
anchor
shutdown()

Shutdown handler hook.

# File lib/thingfish/handler.rb, line 155
def shutdown
        self.event_socket.close if self.event_socket
        super
end
anchor
thingfish()

Configurability API

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

Protected Instance Methods

anchor
add_content_disposition( res, metadata )

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

# File lib/thingfish/handler.rb, line 807
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
anchor
add_etag_headers( request, metadata )

Add browser cache headers for resources. This requires the sha256 processor plugin to be enabled for stored resources.

# File lib/thingfish/handler.rb, line 790
def add_etag_headers( request, metadata )
        response = request.response
        checksum = metadata[ 'checksum' ]
        return unless 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
        return
end
anchor anchor
check_resource_permissions( request, uuid=nil, metadata=nil )

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 818
def check_resource_permissions( request, uuid=nil, metadata=nil ); end
anchor
extract_connection_metadata( request )

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

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

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

# File lib/thingfish/handler.rb, line 714
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
anchor
extract_header_metadata( request )

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

# File lib/thingfish/handler.rb, line 743
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
anchor
extract_metadata( req )

Extract and validate supplied metadata from the request.

# File lib/thingfish/handler.rb, line 734
def extract_metadata( req )
        new_metadata = req.params.fields.dup
        new_metadata = self.remove_operational_metadata( new_metadata )
        return new_metadata
end
anchor
handle_async_upload_start( request )

Overridden from the base handler class to allow spooled uploads.

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

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

# File lib/thingfish/handler.rb, line 114
def initialize( * ) # :notnew:
        super

        @datastore = Thingfish::Datastore.create( self.class.datastore )
        @metastore = Thingfish::Metastore.create( self.class.metastore )
        @event_socket = nil
end
anchor
normalized_metadata_for( uuid )

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

# File lib/thingfish/handler.rb, line 758
def normalized_metadata_for( uuid )
        return self.metastore.fetch(uuid).merge( 'uuid' => uuid )
end
anchor
remove_operational_metadata( metadata )

Prune operational metadata from the provided hash.

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

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

# File lib/thingfish/handler.rb, line 635
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
anchor
send_event( type, msg )

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

# File lib/thingfish/handler.rb, line 780
def send_event( type, msg )
        esock = self.event_socket or return
        self.log.debug "Publishing %p event: %p" % [ type, msg ]
        esock.sendm( type.to_s )
        esock.send( Yajl.dump(msg) )
end
anchor
verify_operational_metadata( metadata )

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

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