Network-accessable datastore service
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
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
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
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
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
Shutdown handler hook.
# File lib/thingfish/handler.rb, line 155
def shutdown
self.event_socket.close if self.event_socket
super
end
Configurability API
# File lib/thingfish/handler.rb, line 61
config_key :thingfish
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
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
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
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
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
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
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
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
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
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
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
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
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
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