class Thingfish::Handler
Network-accessable datastore service
Constants
- CONFIG_DEFAULTS
Configurability API – set config defaults
- 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.
- REQUIRED_RELATED_METADATA_KEYS
Metadata keys that must be provided by plugins for related resources
Attributes
The datastore
The PUB socket on which resource events are published
The metastore
Public Class Methods
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 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 handler hook.
# 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 the handler – overridden to set up the event socket on startup.
# File lib/thingfish/handler.rb, line 140 def run self.setup_event_socket super end
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 handler hook.
# File lib/thingfish/handler.rb, line 157 def shutdown self.event_socket.close if self.event_socket super end
Configurability API
# File lib/thingfish/handler.rb, line 61 config_key :thingfish
Protected Instance Methods
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 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
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
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
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 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 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
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
Set up the metastore, datastore, and event socket when the handler is created.
# 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
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
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 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 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
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