lib/httpimagestore/configuration/handler.rb in httpimagestore-1.8.1 vs lib/httpimagestore/configuration/handler.rb in httpimagestore-1.9.0
- old
+ new
@@ -1,8 +1,7 @@
-require 'mime/types'
-require 'digest/sha2'
-require 'securerandom'
+require 'httpimagestore/configuration/request_state'
+require 'httpimagestore/ruby_string_template'
module Configuration
class ImageNotLoadedError < ConfigurationError
def initialize(image_name)
super "image '#{image_name}' not loaded"
@@ -37,137 +36,27 @@
def initialize(image_name, meta_value)
super "image '#{image_name}' does not have data for variable '#{meta_value}'"
end
end
- class RequestState < Hash
- include ClassLogging
-
- class Images < Hash
- def initialize(memory_limit)
- @memory_limit = memory_limit
- super
- end
-
- def []=(name, image)
- if member?(name)
- @memory_limit.return fetch(name).data.bytesize
- end
- super
- end
-
- def [](name)
- fetch(name){|image_name| raise ImageNotLoadedError.new(image_name)}
- end
- end
-
- def initialize(body = '', matches = {}, path = '', query_string = {}, memory_limit = MemoryLimit.new, headers = {})
- super() do |request_state, name|
- # note that request_state may be different object when useing with_locals that creates duplicate
- request_state[name] = request_state.generate_meta_variable(name) or raise VariableNotDefinedError.new(name)
- end
-
- self[:path] = path
- merge! matches
- self[:query_string_options] = query_string.sort.map{|kv| kv.join(':')}.join(',')
-
- log.debug "processing request with body length: #{body.bytesize} bytes and variables: #{map{|k,v| "#{k}: '#{v}'"}.join(', ')}"
-
- @body = body
- @images = Images.new(memory_limit)
- @memory_limit = memory_limit
- @output_callback = nil
-
- @headers = headers
- end
-
- attr_reader :body
- attr_reader :images
- attr_reader :memory_limit
- attr_reader :headers
-
- def with_locals(*locals)
- locals = locals.reduce{|a, b| a.merge(b)}
- log.debug "using additional local variables: #{locals}"
- self.dup.merge!(locals)
- end
-
- def output(&callback)
- @output_callback = callback
- end
-
- def output_callback
- @output_callback or fail 'no output callback'
- end
-
- def fetch_base_variable(name, base_name)
- fetch(base_name, nil) or generate_meta_variable(base_name) or raise NoVariableToGenerateMetaVariableError.new(base_name, name)
- end
-
- def generate_meta_variable(name)
- log.debug "generating meta variable: #{name}"
- val = case name
- when :basename
- path = Pathname.new(fetch_base_variable(name, :path))
- path.basename(path.extname).to_s
- when :dirname
- Pathname.new(fetch_base_variable(name, :path)).dirname.to_s
- when :extension
- Pathname.new(fetch_base_variable(name, :path)).extname.delete('.')
- when :digest # deprecated
- @body.empty? and raise NoRequestBodyToGenerateMetaVariableError.new(name)
- Digest::SHA2.new.update(@body).to_s[0,16]
- when :input_digest
- @body.empty? and raise NoRequestBodyToGenerateMetaVariableError.new(name)
- Digest::SHA2.new.update(@body).to_s[0,16]
- when :input_sha256
- @body.empty? and raise NoRequestBodyToGenerateMetaVariableError.new(name)
- Digest::SHA2.new.update(@body).to_s
- when :input_image_width
- @images['input'].width or raise NoImageDataForVariableError.new('input', name)
- when :input_image_height
- @images['input'].height or raise NoImageDataForVariableError.new('input', name)
- when :input_image_mime_extension
- @images['input'].mime_extension or raise NoImageDataForVariableError.new('input', name)
- when :image_digest
- Digest::SHA2.new.update(@images[fetch_base_variable(name, :image_name)].data).to_s[0,16]
- when :image_sha256
- Digest::SHA2.new.update(@images[fetch_base_variable(name, :image_name)].data).to_s
- when :mimeextension # deprecated
- image_name = fetch_base_variable(name, :image_name)
- @images[image_name].mime_extension or raise NoImageDataForVariableError.new(image_name, name)
- when :image_mime_extension
- image_name = fetch_base_variable(name, :image_name)
- @images[image_name].mime_extension or raise NoImageDataForVariableError.new(image_name, name)
- when :image_width
- image_name = fetch_base_variable(name, :image_name)
- @images[image_name].width or raise NoImageDataForVariableError.new(image_name, name)
- when :image_height
- image_name = fetch_base_variable(name, :image_name)
- @images[image_name].height or raise NoImageDataForVariableError.new(image_name, name)
- when :uuid
- SecureRandom.uuid
- end
- if val
- log.debug "generated meta variable '#{name}': #{val}"
- else
- log.debug "could not generated meta variable '#{name}'"
- end
- val
- end
- end
-
module ImageMetaData
attr_accessor :source_path
attr_accessor :source_url
attr_accessor :store_path
attr_accessor :store_url
def mime_extension
- return nil unless mime_type
- mime = MIME::Types[mime_type].first
- mime.extensions.select{|e| e.length == 3}.first or mime.extensions.first
+ case mime_type
+ when nil then nil
+ when 'image/jpeg' then 'jpg'
+ when 'image/png' then 'png'
+ when 'image/gif' then 'gif'
+ else
+ # TODO: this does not work well; the resoult may not the most common extension (like 'jpeg')
+ mime = MIME::Types[mime_type].first or return nil
+ mime.preferred_extension
+ end
end
end
class Image < Struct.new(:data, :mime_type, :width, :height)
include ImageMetaData
@@ -178,144 +67,61 @@
request_state.body.empty? and raise ZeroBodyLengthError
request_state.images['input'] = Image.new(request_state.body)
end
end
- class InclusionMatcher
- def initialize(value, template)
- @value = value
- @template = RubyStringTemplate.new(template) if template
- end
-
- def included?(request_state)
- return true if not @template
- @template.render(request_state).split(',').include? @value
- end
- end
-
- class HandlerStatement
- module ImageName
- attr_reader :image_name
-
- def initialize(global, *args)
- @image_name = args.pop
-
- super(global, *args)
-
- config_local :imagename, @image_name # deprecated
- config_local :image_name, @image_name
- end
- end
-
- module PathSpec
- attr_reader :path_spec
-
- def initialize(global, *args)
- @path_spec = args.pop
- super(global, *args)
- end
-
- def path_template
- @global.paths[@path_spec]
- end
- end
-
- module ConditionalInclusion
- def initialize(global, *args)
- @matchers = []
- matcher = args.pop
- @matchers << matcher if matcher
- super(global, *args)
- end
-
- def inclusion_matcher(matcher)
- @matchers << matcher
- end
-
- def included?(request_state)
- return true if @matchers.empty?
- @matchers.any? do |matcher|
- matcher.included?(request_state)
- end
- end
-
- def excluded?(request_state)
- not included? request_state
- end
- end
-
- def initialize(global, *args)
- @global = global
- @config_locals = {}
- @module_args = args
- end
-
- attr_reader :config_locals
- def config_local(name, value)
- @config_locals[name] = value
- end
-
- def path_template(path_spec)
- @global.paths[path_spec]
- end
- end
-
- class SourceStoreBase < HandlerStatement
- include ImageName
- include PathSpec
- include ConditionalInclusion
-
- def initialize(global, image_name, matcher, path_spec)
- super(global, image_name, path_spec, matcher)
- end
-
- private
-
- def put_sourced_named_image(request_state)
- rendered_path = path_template.render(request_state.with_locals(config_locals))
-
- image = yield @image_name, rendered_path
-
- image.source_path = rendered_path
- request_state.images[@image_name] = image
- end
-
- def get_named_image_for_storage(request_state)
- image = request_state.images[@image_name]
- rendered_path = path_template.render(request_state.with_locals(config_locals))
- image.store_path = rendered_path
-
- yield @image_name, image, rendered_path
- end
- end
-
class Matcher
def initialize(names, debug_type = '', debug_value = '', &matcher)
@names = names
@matcher = matcher
@debug_type = debug_type
@debug_value = case debug_value
when Regexp
"/#{debug_value.source}/"
+ when nil
+ nil
else
- debug_value.inspect
+ debug_value.to_s
end
end
attr_reader :names
attr_reader :matcher
def to_s
- if @names.empty?
- "#{@debug_type}(#{@debug_value})"
+ if @debug_value
+ if @names.empty?
+ "#{@debug_type}(#{@debug_value})"
+ else
+ "#{@debug_type}(#{@names.join(',')} => #{@debug_value})"
+ end
else
- "#{@debug_type}(#{@names.join(',')} => #{@debug_value})"
+ @debug_type
end
end
end
class Handler < Scope
+ class HandlerConfiguration
+ def initialize(global, http_method, uri_matchers)
+ @global = global
+ @http_method = http_method
+ @uri_matchers = uri_matchers
+ @validators = []
+ @sources = []
+ @processors = []
+ @stores = []
+ @output = nil
+ end
+
+ attr_accessor :global, :http_method, :uri_matchers, :validators, :sources, :processors, :stores, :output
+
+ def to_s
+ "#{@http_method} #{@uri_matchers.join(', ')}"
+ end
+ end
+
def self.match(node)
node.name == 'put' or
node.name == 'post' or
node.name == 'get'
end
@@ -323,24 +129,11 @@
def self.pre(configuration)
configuration.handlers ||= []
end
def self.parse(configuration, node)
- handler_configuration =
- Struct.new(
- :global,
- :http_method,
- :uri_matchers,
- :sources,
- :processors,
- :stores,
- :output
- ).new
-
- handler_configuration.global = configuration
- handler_configuration.http_method = node.name
- handler_configuration.uri_matchers = node.values.map do |matcher|
+ uri_matchers = node.values.map do |matcher|
case matcher
# URI matchers
when %r{^:([^/]+)/(.*)/$} # :foobar/.*/
name = $1.to_sym
_regexp = Regexp.new($2)
@@ -379,36 +172,34 @@
end
# Query string matchers
when /^\&([^=]+)=(.+)$/# ?foo=bar
name = $1.to_sym
value = $2
- Matcher.new([name], 'QueryKeyValue', "#{name}=#{value}") do
+ Matcher.new([name], 'QueryKeyValue', "#{value}") do
->{req.GET[name.to_s] && req.GET[name.to_s] == value && captures.push(req.GET[name.to_s])}
end
when /^\&:(.+)\?(.*)$/# &:foo?bar
name = $1.to_sym
default = $2
- Matcher.new([name], 'QueryKeyDefault', "#{name}=<key>|#{default}") do
+ Matcher.new([name], 'QueryKeyDefault', "<key>|#{default}") do
->{captures.push(req.GET[name.to_s] || default)}
end
when /^\&:(.+)$/# &:foo
name = $1.to_sym
- Matcher.new([name], 'QueryKey', "#{name}=<key>") do
+ Matcher.new([name], 'QueryKey', "<key>") do
->{req.GET[name.to_s] && captures.push(req.GET[name.to_s])}
end
# Literal URI segment matcher
else # foobar
- Matcher.new([], "Literal", matcher) do
+ Matcher.new([], matcher, nil) do
Regexp.escape(matcher)
end
end
end
- handler_configuration.sources = []
- handler_configuration.processors = []
- handler_configuration.stores = []
- handler_configuration.output = nil
+ handler_configuration = HandlerConfiguration.new(configuration, node.name, uri_matchers)
+
node.grab_attributes
if handler_configuration.http_method != 'get'
handler_configuration.sources << InputSource.new
end
@@ -424,8 +215,56 @@
log.warn 'no handlers configured' if configuration.handlers.empty?
end
end
RequestState.logger = Global.logger_for(RequestState)
+ class OutputText < Scope
+ def self.match(node)
+ node.name == 'output_text'
+ end
+
+ def self.parse(configuration, node)
+ configuration.output and raise StatementCollisionError.new(node, 'output')
+ text = node.grab_values('text').first
+ status, cache_control = *node.grab_attributes('status', 'cache-control')
+ configuration.output = OutputText.new(text, status || 200, cache_control)
+ end
+
+ def initialize(text, status, cache_control)
+ @text = RubyStringTemplate.new(text || fail("no text?!"))
+ @status = status || 200
+ @cache_control = cache_control
+ end
+
+ def realize(request_state)
+ # make sure variables are available in request context
+ status = @status
+ text = @text.render(request_state)
+ cache_control = @cache_control
+ request_state.output do
+ res['Cache-Control'] = cache_control if cache_control
+ write_plain status.to_i, text.to_s
+ end
+ end
+ end
+
+ class OutputOK < OutputText
+ def self.match(node)
+ node.name == 'output_ok'
+ end
+
+ def self.parse(configuration, node)
+ configuration.output and raise StatementCollisionError.new(node, 'output')
+ cache_control = node.grab_attributes('cache-control').first
+ configuration.output = OutputOK.new(cache_control)
+ end
+
+ def initialize(cache_control = nil)
+ super 'OK', 200, cache_control
+ end
+ end
+
Global.register_node_parser Handler
+ Handler::register_node_parser OutputText
+ Handler::register_node_parser OutputOK
end