You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by mf...@redhat.com on 2011/01/07 14:24:31 UTC
[PATCH core] Added ETag HTTP header to give client opportunity to check if content was changed. Added X-Runtime and X-Backend-Runtime headers for benchmarking.
From: Michal Fojtik <mf...@redhat.com>
---
client/lib/deltacloud.rb | 17 +++++-
.../lib/deltacloud/helpers/application_helper.rb | 11 +++-
server/lib/sinatra/rack_driver_select.rb | 48 ++++++++--------
server/lib/sinatra/rack_etag.rb | 60 ++++++++++++++++++++
server/lib/sinatra/rack_runtime.rb | 28 +++++++++
server/server.rb | 6 ++-
6 files changed, 141 insertions(+), 29 deletions(-)
create mode 100644 server/lib/sinatra/rack_etag.rb
create mode 100644 server/lib/sinatra/rack_runtime.rb
diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb
index 6985d5c..e8d6375 100644
--- a/client/lib/deltacloud.rb
+++ b/client/lib/deltacloud.rb
@@ -34,9 +34,15 @@ module DeltaCloud
# @param [String, password] API password
# @param [String, user_name] API URL (eg. http://localhost:3001/api)
# @return [DeltaCloud::API]
- def self.new(user_name, password, api_url, opts={}, &block)
- opts ||= {}
- API.new(user_name, password, api_url, opts, &block)
+ #def self.new(user_name, password, api_url, opts={}, &block)
+ # opts ||= {}
+ # API.new(user_name, password, api_url, opts, &block)
+ #end
+
+ def self.new(opts={}, &block)
+ opts ||={}
+ client = API.new(opts[:username], opts[:password], opts[:url], opts, &block)
+ client.use_driver(opts[:driver], opts) if opts[:driver]
end
# Check given credentials if their are valid against
@@ -256,6 +262,11 @@ module DeltaCloud
return self
end
+ def use_config!(opts={})
+ @api_uri = URI.parse(opts[:url]) if opts[:url]
+ use_driver(opts[:driver], opts)
+ end
+
def extended_headers
headers = {}
headers["X-Deltacloud-Driver"] = @api_driver.to_s if @api_driver
diff --git a/server/lib/deltacloud/helpers/application_helper.rb b/server/lib/deltacloud/helpers/application_helper.rb
index a734387..b018876 100644
--- a/server/lib/deltacloud/helpers/application_helper.rb
+++ b/server/lib/deltacloud/helpers/application_helper.rb
@@ -17,6 +17,8 @@
# Methods added to this helper will be available to all templates in the application.
+require 'benchmark'
+
module ApplicationHelper
def bread_crumb
@@ -74,7 +76,10 @@ module ApplicationHelper
filter.merge!(:state => params[:state]) if params[:state]
filter = nil if filter.keys.size.eql?(0)
singular = model.to_s.singularize.to_sym
- @elements = driver.send(model.to_sym, credentials, filter)
+ @benchmark = Benchmark.measure do
+ @elements = driver.send(model.to_sym, credentials, filter)
+ end
+ headers['X-Backend-Runtime'] = @benchmark.real.to_s
instance_variable_set(:"@#{model}", @elements)
respond_to do |format|
format.html { haml :"#{model}/index" }
@@ -84,7 +89,9 @@ module ApplicationHelper
end
def show(model)
- @element = driver.send(model, credentials, { :id => params[:id]} )
+ @benchmark = Benchmark.measure do
+ @element = driver.send(model, credentials, { :id => params[:id]} )
+ end
instance_variable_set("@#{model}", @element)
if @element
respond_to do |format|
diff --git a/server/lib/sinatra/rack_driver_select.rb b/server/lib/sinatra/rack_driver_select.rb
index 3e7d038..7b43d81 100644
--- a/server/lib/sinatra/rack_driver_select.rb
+++ b/server/lib/sinatra/rack_driver_select.rb
@@ -1,29 +1,31 @@
-class RackDriverSelect
+module Rack
+ class DriverSelect
- def initialize(app, opts={})
- @app = app
- @opts = opts
- end
+ def initialize(app, opts={})
+ @app = app
+ @opts = opts
+ end
- HEADER_TO_ENV_MAP = {
- 'HTTP_X_DELTACLOUD_DRIVER' => :driver,
- 'HTTP_X_DELTACLOUD_PROVIDER' => :provider
- }
-
- def call(env)
- original_settings = { }
- HEADER_TO_ENV_MAP.each do |header, name|
- original_settings[name] = Thread.current[name]
- new_setting = extract_header(env, header)
- Thread.current[name] = new_setting if new_setting
+ HEADER_TO_ENV_MAP = {
+ 'HTTP_X_DELTACLOUD_DRIVER' => :driver,
+ 'HTTP_X_DELTACLOUD_PROVIDER' => :provider
+ }
+
+ def call(env)
+ original_settings = { }
+ HEADER_TO_ENV_MAP.each do |header, name|
+ original_settings[name] = Thread.current[name]
+ new_setting = extract_header(env, header)
+ Thread.current[name] = new_setting if new_setting
+ end
+ @app.call(env)
+ ensure
+ original_settings.each { |name, value| Thread.current[name] = value }
end
- @app.call(env)
- ensure
- original_settings.each { |name, value| Thread.current[name] = value }
- end
- def extract_header(env, header)
- env[header].downcase if env[header]
- end
+ def extract_header(env, header)
+ env[header].downcase if env[header]
+ end
+ end
end
diff --git a/server/lib/sinatra/rack_etag.rb b/server/lib/sinatra/rack_etag.rb
new file mode 100644
index 0000000..fc5cf0a
--- /dev/null
+++ b/server/lib/sinatra/rack_etag.rb
@@ -0,0 +1,60 @@
+require 'digest/md5'
+
+module Rack
+ # Automatically sets the ETag header on all String bodies.
+ #
+ # The ETag header is skipped if ETag or Last-Modified headers are sent or if
+ # a sendfile body (body.responds_to :to_path) is given (since such cases
+ # should be handled by apache/nginx).
+ #
+ # On initialization, you can pass two parameters: a Cache-Control directive
+ # used when Etag is absent and a directive when it is present. The first
+ # defaults to nil, while the second defaults to "max-age=0, privaute, must-revalidate"
+ class ETag
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
+
+ def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
+ @app = app
+ @cache_control = cache_control
+ @no_cache_control = no_cache_control
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+
+ if etag_status?(status) && etag_body?(body) && !http_caching?(headers)
+ digest, body = digest_body(body)
+ headers['ETag'] = %("#{digest}") if digest
+ end
+
+ unless headers['Cache-Control']
+ headers['Cache-Control'] = digest ? @cache_control : @no_cache_control
+ end
+
+ [status, headers, body]
+ end
+
+ private
+
+ def etag_status?(status)
+ status == 200 || status == 201
+ end
+
+ def etag_body?(body)
+ !body.respond_to?(:to_path)
+ end
+
+ def http_caching?(headers)
+ headers.key?('ETag') || headers.key?('Last-Modified')
+ end
+
+ def digest_body(body)
+ parts = []
+ body.each { |part| parts << part }
+ string_body = parts.join
+ digest = Digest::MD5.hexdigest(string_body) unless string_body.empty?
+ [digest, parts]
+ end
+ end
+end
+
diff --git a/server/lib/sinatra/rack_runtime.rb b/server/lib/sinatra/rack_runtime.rb
new file mode 100644
index 0000000..5da3940
--- /dev/null
+++ b/server/lib/sinatra/rack_runtime.rb
@@ -0,0 +1,28 @@
+module Rack
+ # Sets an "X-Runtime" response header, indicating the response
+ # time of the request, in seconds
+ #
+ # You can put it right before the application to see the processing
+ # time, or before all the other middlewares to include time for them,
+ # too.
+ class Runtime
+ def initialize(app, name = nil)
+ @app = app
+ @header_name = "X-Runtime"
+ @header_name << "-#{name}" if name
+ end
+
+ def call(env)
+ start_time = Time.now
+ status, headers, body = @app.call(env)
+ request_time = Time.now - start_time
+
+ if !headers.has_key?(@header_name)
+ headers[@header_name] = "%0.6f" % request_time
+ end
+
+ [status, headers, body]
+ end
+ end
+end
+
diff --git a/server/server.rb b/server/server.rb
index 8c3b72c..097ff0f 100644
--- a/server/server.rb
+++ b/server/server.rb
@@ -28,11 +28,15 @@ require 'haml'
require 'open3'
require 'lib/deltacloud/helpers/blob_stream'
require 'sinatra/rack_driver_select'
+require 'sinatra/rack_runtime'
+require 'sinatra/rack_etag'
set :version, '0.1.0'
-use RackDriverSelect
+use Rack::DriverSelect
+use Rack::ETag
+use Rack::Runtime
configure do
set :raise_errors => false
--
1.7.3.4
Re: [PATCH core] Added ETag HTTP header to give client opportunity
to check if content was changed. Added X-Runtime and X-Backend-Runtime
headers for benchmarking.
Posted by Michal Fojtik <mf...@redhat.com>.
On 07/01/11 14:24 +0100, mfojtik@redhat.com wrote:
Pushing this to master.
-- Michal
>From: Michal Fojtik <mf...@redhat.com>
>
>---
> client/lib/deltacloud.rb | 17 +++++-
> .../lib/deltacloud/helpers/application_helper.rb | 11 +++-
> server/lib/sinatra/rack_driver_select.rb | 48 ++++++++--------
> server/lib/sinatra/rack_etag.rb | 60 ++++++++++++++++++++
> server/lib/sinatra/rack_runtime.rb | 28 +++++++++
> server/server.rb | 6 ++-
> 6 files changed, 141 insertions(+), 29 deletions(-)
> create mode 100644 server/lib/sinatra/rack_etag.rb
> create mode 100644 server/lib/sinatra/rack_runtime.rb
>
>diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb
>index 6985d5c..e8d6375 100644
>--- a/client/lib/deltacloud.rb
>+++ b/client/lib/deltacloud.rb
>@@ -34,9 +34,15 @@ module DeltaCloud
> # @param [String, password] API password
> # @param [String, user_name] API URL (eg. http://localhost:3001/api)
> # @return [DeltaCloud::API]
>- def self.new(user_name, password, api_url, opts={}, &block)
>- opts ||= {}
>- API.new(user_name, password, api_url, opts, &block)
>+ #def self.new(user_name, password, api_url, opts={}, &block)
>+ # opts ||= {}
>+ # API.new(user_name, password, api_url, opts, &block)
>+ #end
>+
>+ def self.new(opts={}, &block)
>+ opts ||={}
>+ client = API.new(opts[:username], opts[:password], opts[:url], opts, &block)
>+ client.use_driver(opts[:driver], opts) if opts[:driver]
> end
>
> # Check given credentials if their are valid against
>@@ -256,6 +262,11 @@ module DeltaCloud
> return self
> end
>
>+ def use_config!(opts={})
>+ @api_uri = URI.parse(opts[:url]) if opts[:url]
>+ use_driver(opts[:driver], opts)
>+ end
>+
> def extended_headers
> headers = {}
> headers["X-Deltacloud-Driver"] = @api_driver.to_s if @api_driver
>diff --git a/server/lib/deltacloud/helpers/application_helper.rb b/server/lib/deltacloud/helpers/application_helper.rb
>index a734387..b018876 100644
>--- a/server/lib/deltacloud/helpers/application_helper.rb
>+++ b/server/lib/deltacloud/helpers/application_helper.rb
>@@ -17,6 +17,8 @@
>
> # Methods added to this helper will be available to all templates in the application.
>
>+require 'benchmark'
>+
> module ApplicationHelper
>
> def bread_crumb
>@@ -74,7 +76,10 @@ module ApplicationHelper
> filter.merge!(:state => params[:state]) if params[:state]
> filter = nil if filter.keys.size.eql?(0)
> singular = model.to_s.singularize.to_sym
>- @elements = driver.send(model.to_sym, credentials, filter)
>+ @benchmark = Benchmark.measure do
>+ @elements = driver.send(model.to_sym, credentials, filter)
>+ end
>+ headers['X-Backend-Runtime'] = @benchmark.real.to_s
> instance_variable_set(:"@#{model}", @elements)
> respond_to do |format|
> format.html { haml :"#{model}/index" }
>@@ -84,7 +89,9 @@ module ApplicationHelper
> end
>
> def show(model)
>- @element = driver.send(model, credentials, { :id => params[:id]} )
>+ @benchmark = Benchmark.measure do
>+ @element = driver.send(model, credentials, { :id => params[:id]} )
>+ end
> instance_variable_set("@#{model}", @element)
> if @element
> respond_to do |format|
>diff --git a/server/lib/sinatra/rack_driver_select.rb b/server/lib/sinatra/rack_driver_select.rb
>index 3e7d038..7b43d81 100644
>--- a/server/lib/sinatra/rack_driver_select.rb
>+++ b/server/lib/sinatra/rack_driver_select.rb
>@@ -1,29 +1,31 @@
>-class RackDriverSelect
>+module Rack
>+ class DriverSelect
>
>- def initialize(app, opts={})
>- @app = app
>- @opts = opts
>- end
>+ def initialize(app, opts={})
>+ @app = app
>+ @opts = opts
>+ end
>
>- HEADER_TO_ENV_MAP = {
>- 'HTTP_X_DELTACLOUD_DRIVER' => :driver,
>- 'HTTP_X_DELTACLOUD_PROVIDER' => :provider
>- }
>-
>- def call(env)
>- original_settings = { }
>- HEADER_TO_ENV_MAP.each do |header, name|
>- original_settings[name] = Thread.current[name]
>- new_setting = extract_header(env, header)
>- Thread.current[name] = new_setting if new_setting
>+ HEADER_TO_ENV_MAP = {
>+ 'HTTP_X_DELTACLOUD_DRIVER' => :driver,
>+ 'HTTP_X_DELTACLOUD_PROVIDER' => :provider
>+ }
>+
>+ def call(env)
>+ original_settings = { }
>+ HEADER_TO_ENV_MAP.each do |header, name|
>+ original_settings[name] = Thread.current[name]
>+ new_setting = extract_header(env, header)
>+ Thread.current[name] = new_setting if new_setting
>+ end
>+ @app.call(env)
>+ ensure
>+ original_settings.each { |name, value| Thread.current[name] = value }
> end
>- @app.call(env)
>- ensure
>- original_settings.each { |name, value| Thread.current[name] = value }
>- end
>
>- def extract_header(env, header)
>- env[header].downcase if env[header]
>- end
>+ def extract_header(env, header)
>+ env[header].downcase if env[header]
>+ end
>
>+ end
> end
>diff --git a/server/lib/sinatra/rack_etag.rb b/server/lib/sinatra/rack_etag.rb
>new file mode 100644
>index 0000000..fc5cf0a
>--- /dev/null
>+++ b/server/lib/sinatra/rack_etag.rb
>@@ -0,0 +1,60 @@
>+require 'digest/md5'
>+
>+module Rack
>+ # Automatically sets the ETag header on all String bodies.
>+ #
>+ # The ETag header is skipped if ETag or Last-Modified headers are sent or if
>+ # a sendfile body (body.responds_to :to_path) is given (since such cases
>+ # should be handled by apache/nginx).
>+ #
>+ # On initialization, you can pass two parameters: a Cache-Control directive
>+ # used when Etag is absent and a directive when it is present. The first
>+ # defaults to nil, while the second defaults to "max-age=0, privaute, must-revalidate"
>+ class ETag
>+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
>+
>+ def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
>+ @app = app
>+ @cache_control = cache_control
>+ @no_cache_control = no_cache_control
>+ end
>+
>+ def call(env)
>+ status, headers, body = @app.call(env)
>+
>+ if etag_status?(status) && etag_body?(body) && !http_caching?(headers)
>+ digest, body = digest_body(body)
>+ headers['ETag'] = %("#{digest}") if digest
>+ end
>+
>+ unless headers['Cache-Control']
>+ headers['Cache-Control'] = digest ? @cache_control : @no_cache_control
>+ end
>+
>+ [status, headers, body]
>+ end
>+
>+ private
>+
>+ def etag_status?(status)
>+ status == 200 || status == 201
>+ end
>+
>+ def etag_body?(body)
>+ !body.respond_to?(:to_path)
>+ end
>+
>+ def http_caching?(headers)
>+ headers.key?('ETag') || headers.key?('Last-Modified')
>+ end
>+
>+ def digest_body(body)
>+ parts = []
>+ body.each { |part| parts << part }
>+ string_body = parts.join
>+ digest = Digest::MD5.hexdigest(string_body) unless string_body.empty?
>+ [digest, parts]
>+ end
>+ end
>+end
>+
>diff --git a/server/lib/sinatra/rack_runtime.rb b/server/lib/sinatra/rack_runtime.rb
>new file mode 100644
>index 0000000..5da3940
>--- /dev/null
>+++ b/server/lib/sinatra/rack_runtime.rb
>@@ -0,0 +1,28 @@
>+module Rack
>+ # Sets an "X-Runtime" response header, indicating the response
>+ # time of the request, in seconds
>+ #
>+ # You can put it right before the application to see the processing
>+ # time, or before all the other middlewares to include time for them,
>+ # too.
>+ class Runtime
>+ def initialize(app, name = nil)
>+ @app = app
>+ @header_name = "X-Runtime"
>+ @header_name << "-#{name}" if name
>+ end
>+
>+ def call(env)
>+ start_time = Time.now
>+ status, headers, body = @app.call(env)
>+ request_time = Time.now - start_time
>+
>+ if !headers.has_key?(@header_name)
>+ headers[@header_name] = "%0.6f" % request_time
>+ end
>+
>+ [status, headers, body]
>+ end
>+ end
>+end
>+
>diff --git a/server/server.rb b/server/server.rb
>index 8c3b72c..097ff0f 100644
>--- a/server/server.rb
>+++ b/server/server.rb
>@@ -28,11 +28,15 @@ require 'haml'
> require 'open3'
> require 'lib/deltacloud/helpers/blob_stream'
> require 'sinatra/rack_driver_select'
>+require 'sinatra/rack_runtime'
>+require 'sinatra/rack_etag'
>
> set :version, '0.1.0'
>
>
>-use RackDriverSelect
>+use Rack::DriverSelect
>+use Rack::ETag
>+use Rack::Runtime
>
> configure do
> set :raise_errors => false
>--
>1.7.3.4
>
--
--------------------------------------------------------
Michal Fojtik, mfojtik@redhat.com
Deltacloud API: http://deltacloud.org
--------------------------------------------------------