webmachine 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +11 -3
- data/README.md +55 -27
- data/lib/webmachine/adapters/mongrel.rb +84 -0
- data/lib/webmachine/adapters/webrick.rb +12 -3
- data/lib/webmachine/adapters.rb +1 -7
- data/lib/webmachine/configuration.rb +30 -0
- data/lib/webmachine/decision/conneg.rb +7 -72
- data/lib/webmachine/decision/flow.rb +13 -11
- data/lib/webmachine/decision/fsm.rb +1 -9
- data/lib/webmachine/decision/helpers.rb +27 -7
- data/lib/webmachine/errors.rb +1 -0
- data/lib/webmachine/headers.rb +12 -3
- data/lib/webmachine/locale/en.yml +2 -2
- data/lib/webmachine/media_type.rb +117 -0
- data/lib/webmachine/resource/callbacks.rb +9 -0
- data/lib/webmachine/streaming.rb +3 -3
- data/lib/webmachine/version.rb +1 -1
- data/lib/webmachine.rb +3 -1
- data/pkg/webmachine-0.1.0/Gemfile +16 -0
- data/pkg/webmachine-0.1.0/Guardfile +11 -0
- data/pkg/webmachine-0.1.0/README.md +90 -0
- data/pkg/webmachine-0.1.0/Rakefile +31 -0
- data/pkg/webmachine-0.1.0/examples/webrick.rb +19 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/adapters/webrick.rb +74 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/adapters.rb +15 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/decision/conneg.rb +304 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/decision/flow.rb +502 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/decision/fsm.rb +79 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/decision/helpers.rb +80 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/decision.rb +12 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/dispatcher/route.rb +85 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/dispatcher.rb +40 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/errors.rb +37 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/headers.rb +16 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/locale/en.yml +28 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/request.rb +56 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/resource/callbacks.rb +362 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/resource/encodings.rb +36 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/resource.rb +48 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/response.rb +49 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/streaming.rb +27 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/translation.rb +11 -0
- data/pkg/webmachine-0.1.0/lib/webmachine/version.rb +4 -0
- data/pkg/webmachine-0.1.0/lib/webmachine.rb +19 -0
- data/pkg/webmachine-0.1.0/spec/spec_helper.rb +13 -0
- data/pkg/webmachine-0.1.0/spec/tests.org +57 -0
- data/pkg/webmachine-0.1.0/spec/webmachine/decision/conneg_spec.rb +152 -0
- data/pkg/webmachine-0.1.0/spec/webmachine/decision/flow_spec.rb +1030 -0
- data/pkg/webmachine-0.1.0/spec/webmachine/dispatcher/route_spec.rb +109 -0
- data/pkg/webmachine-0.1.0/spec/webmachine/dispatcher_spec.rb +34 -0
- data/pkg/webmachine-0.1.0/spec/webmachine/headers_spec.rb +19 -0
- data/pkg/webmachine-0.1.0/spec/webmachine/request_spec.rb +24 -0
- data/pkg/webmachine-0.1.0/webmachine.gemspec +44 -0
- data/pkg/webmachine-0.1.0.gem +0 -0
- data/spec/webmachine/configuration_spec.rb +27 -0
- data/spec/webmachine/decision/conneg_spec.rb +18 -11
- data/spec/webmachine/decision/flow_spec.rb +2 -0
- data/spec/webmachine/decision/helpers_spec.rb +105 -0
- data/spec/webmachine/errors_spec.rb +13 -0
- data/spec/webmachine/headers_spec.rb +2 -1
- data/spec/webmachine/media_type_spec.rb +78 -0
- data/webmachine.gemspec +4 -1
- metadata +69 -11
data/Gemfile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
1
3
|
source :rubygems
|
2
4
|
|
3
5
|
gemspec
|
@@ -6,9 +8,15 @@ gem 'bundler'
|
|
6
8
|
|
7
9
|
unless ENV['TRAVIS']
|
8
10
|
gem 'guard-rspec'
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
|
12
|
+
case RbConfig::CONFIG['host_os']
|
13
|
+
when /darwin/
|
14
|
+
gem 'rb-fsevent'
|
15
|
+
gem 'growl_notify'
|
16
|
+
when /linux/
|
17
|
+
gem 'rb-inotify'
|
18
|
+
gem 'libnotify'
|
19
|
+
end
|
12
20
|
end
|
13
21
|
|
14
22
|
platforms :jruby do
|
data/README.md
CHANGED
@@ -26,25 +26,25 @@ Webmachine is very young, but it's still easy to construct an
|
|
26
26
|
application for it!
|
27
27
|
|
28
28
|
```ruby
|
29
|
-
require 'webmachine'
|
30
|
-
# Require any of the files that contain your resources here
|
31
|
-
require 'my_resource'
|
32
|
-
|
33
|
-
# Point all URIs at the MyResource class
|
34
|
-
Webmachine::Dispatcher.add_route(['*'], MyResource)
|
35
|
-
|
36
|
-
# Start the server, binds to port 3000 using WEBrick
|
37
|
-
Webmachine.run
|
29
|
+
require 'webmachine'
|
30
|
+
# Require any of the files that contain your resources here
|
31
|
+
require 'my_resource'
|
32
|
+
|
33
|
+
# Point all URIs at the MyResource class
|
34
|
+
Webmachine::Dispatcher.add_route(['*'], MyResource)
|
35
|
+
|
36
|
+
# Start the server, binds to port 3000 using WEBrick
|
37
|
+
Webmachine.run
|
38
38
|
```
|
39
39
|
|
40
40
|
Your resource will look something like this:
|
41
41
|
|
42
42
|
```ruby
|
43
|
-
class MyResource < Webmachine::Resource
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
43
|
+
class MyResource < Webmachine::Resource
|
44
|
+
def to_html
|
45
|
+
"<html><body>Hello, world!</body></html>"
|
46
|
+
end
|
47
|
+
end
|
48
48
|
```
|
49
49
|
|
50
50
|
Run the first file and your application is up. That's all there is to
|
@@ -54,15 +54,15 @@ might want to enable "gzip" compression on your resource, for which
|
|
54
54
|
you can simply add an `encodings_provided` callback method:
|
55
55
|
|
56
56
|
```ruby
|
57
|
-
class MyResource < Webmachine::Resource
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
57
|
+
class MyResource < Webmachine::Resource
|
58
|
+
def encodings_provided
|
59
|
+
{"gzip" => :encode_gzip, "identity" => :encode_identity}
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_html
|
63
|
+
"<html><body>Hello, world!</body></html>"
|
64
|
+
end
|
65
|
+
end
|
66
66
|
```
|
67
67
|
|
68
68
|
There are many other HTTP features exposed to your resource through
|
@@ -75,15 +75,43 @@ callbacks. Give them a try!
|
|
75
75
|
* Most callbacks can interrupt the decision flow by returning an
|
76
76
|
integer response code. You generally only want to do this when new
|
77
77
|
information comes to light, requiring a modification of the response.
|
78
|
-
*
|
78
|
+
* Supports WEBrick and Mongrel (1.2pre+). Other host servers are being
|
79
|
+
investigated.
|
79
80
|
* Streaming/chunked response bodies are permitted as Enumerables or Procs.
|
81
|
+
* Unlike the Erlang original, it does real Language negotiation.
|
80
82
|
|
81
83
|
## Problems/TODOs
|
82
84
|
|
83
85
|
* Support streamed responses as Fibers.
|
84
|
-
*
|
85
|
-
* An effort has been made to make the code feel as Ruby-ish as
|
86
|
-
possible, but there is still work to do.
|
86
|
+
* Command-line tools, and general polish.
|
87
87
|
* Tracing is exposed as an Array of decisions visited on the response
|
88
88
|
object. You should be able to turn this off and on, and visualize
|
89
89
|
the decisions on the sequence diagram.
|
90
|
+
|
91
|
+
## Changelog
|
92
|
+
|
93
|
+
### 0.2.0 September 11, 2011
|
94
|
+
|
95
|
+
0.2.0 includes an adapter for Mongrel and a central place for
|
96
|
+
configuration as well as numerous bugfixes. Added Ian Plosker and
|
97
|
+
Bernd Ahlers as committers. Thank you for your contributions!
|
98
|
+
|
99
|
+
* Acceptable media types are matched less strictly, which has
|
100
|
+
implications on both responses and PUT requests. See the
|
101
|
+
[discussion on the commit](https://github.com/seancribbs/webmachine-ruby/commit/3686d0d9ff77fc98aff59f89478e9c6c18844ca1).
|
102
|
+
* Resources now receive a callback after the language has been
|
103
|
+
negotiated, so they can decide what to do with it.
|
104
|
+
* Added `Webmachine::Configuration` so we can more easily support more
|
105
|
+
than one host server/adapter.
|
106
|
+
* Added Mongrel adapter, supporting 1.2pre+.
|
107
|
+
* Media type headers are more lax about whitespace following
|
108
|
+
semicolons.
|
109
|
+
* Fix some problems with callable response bodies.
|
110
|
+
* Make sure String response bodies get a Content-Length header added
|
111
|
+
and streaming responses get chunked encoding.
|
112
|
+
* Numerous refactorings, including extracting `MediaType` into its own
|
113
|
+
top-level class.
|
114
|
+
|
115
|
+
### 0.1.0 August 25, 2011
|
116
|
+
|
117
|
+
This is the initial release. Most things work, but only WEBrick is supported.
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'mongrel'
|
2
|
+
require 'webmachine/version'
|
3
|
+
require 'webmachine/headers'
|
4
|
+
require 'webmachine/request'
|
5
|
+
require 'webmachine/response'
|
6
|
+
require 'webmachine/dispatcher'
|
7
|
+
|
8
|
+
module Webmachine
|
9
|
+
module Adapters
|
10
|
+
# Connects Webmachine to Mongrel.
|
11
|
+
module Mongrel
|
12
|
+
# Starts the Mongrel adapter
|
13
|
+
def self.run
|
14
|
+
c = Webmachine.configuration
|
15
|
+
options = {
|
16
|
+
:port => c.port,
|
17
|
+
:host => c.ip
|
18
|
+
}.merge(c.adapter_options)
|
19
|
+
config = ::Mongrel::Configurator.new(options) do
|
20
|
+
listener do
|
21
|
+
uri '/', :handler => Webmachine::Adapters::Mongrel::Handler.new
|
22
|
+
end
|
23
|
+
trap("INT") { stop }
|
24
|
+
run
|
25
|
+
end
|
26
|
+
config.join
|
27
|
+
end
|
28
|
+
|
29
|
+
class Handler < ::Mongrel::HttpHandler
|
30
|
+
def process(wreq, wres)
|
31
|
+
header = http_headers(wreq.params, Webmachine::Headers.new)
|
32
|
+
|
33
|
+
request = Webmachine::Request.new(wreq.params["REQUEST_METHOD"],
|
34
|
+
URI.parse(wreq.params["REQUEST_URI"]),
|
35
|
+
header,
|
36
|
+
wreq.body || StringIO.new(''))
|
37
|
+
|
38
|
+
response = Webmachine::Response.new
|
39
|
+
Webmachine::Dispatcher.dispatch(request, response)
|
40
|
+
|
41
|
+
begin
|
42
|
+
wres.status = response.code.to_i
|
43
|
+
wres.send_status(nil)
|
44
|
+
|
45
|
+
response.headers.each { |k, vs|
|
46
|
+
vs.split("\n").each { |v|
|
47
|
+
wres.header[k] = v
|
48
|
+
}
|
49
|
+
}
|
50
|
+
wres.header['Server'] = [Webmachine::SERVER_STRING, "Mongrel/#{::Mongrel::Const::MONGREL_VERSION}"].join(" ")
|
51
|
+
wres.send_header
|
52
|
+
|
53
|
+
case response.body
|
54
|
+
when String
|
55
|
+
wres.write response.body
|
56
|
+
wres.socket.flush
|
57
|
+
when Enumerable
|
58
|
+
response.body.each { |part|
|
59
|
+
wres.write part
|
60
|
+
wres.socket.flush
|
61
|
+
}
|
62
|
+
else
|
63
|
+
if response.body.respond_to?(:call)
|
64
|
+
wres.write part
|
65
|
+
wres.socket.flush
|
66
|
+
end
|
67
|
+
end
|
68
|
+
ensure
|
69
|
+
response.body.close if response.body.respond_to? :close
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def http_headers(env, headers)
|
74
|
+
env.inject(headers) do |h,(k,v)|
|
75
|
+
if k =~ /^HTTP_(\w+)$/
|
76
|
+
h[$1.tr("_", "-")] = v
|
77
|
+
end
|
78
|
+
h
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -11,7 +11,12 @@ module Webmachine
|
|
11
11
|
module WEBrick
|
12
12
|
# Starts the WEBrick adapter
|
13
13
|
def self.run
|
14
|
-
|
14
|
+
c = Webmachine.configuration
|
15
|
+
options = {
|
16
|
+
:Port => c.port,
|
17
|
+
:BindAddress => c.ip
|
18
|
+
}.merge(c.adapter_options)
|
19
|
+
server = Webmachine::Adapters::WEBrick::Server.new options
|
15
20
|
trap("INT"){ server.shutdown }
|
16
21
|
Thread.new { server.start }.join
|
17
22
|
end
|
@@ -35,9 +40,13 @@ module Webmachine
|
|
35
40
|
when String
|
36
41
|
wres.body << response.body
|
37
42
|
when Enumerable
|
43
|
+
wres.chunked = true
|
38
44
|
response.body.each {|part| wres.body << part }
|
39
|
-
|
40
|
-
|
45
|
+
else
|
46
|
+
if response.body.respond_to?(:call)
|
47
|
+
wres.chunked = true
|
48
|
+
wres.body << response.body.call
|
49
|
+
end
|
41
50
|
end
|
42
51
|
end
|
43
52
|
end
|
data/lib/webmachine/adapters.rb
CHANGED
@@ -4,12 +4,6 @@ module Webmachine
|
|
4
4
|
# Contains classes and modules that connect Webmachine to Ruby
|
5
5
|
# application servers.
|
6
6
|
module Adapters
|
7
|
+
autoload :Mongrel, 'webmachine/adapters/mongrel'
|
7
8
|
end
|
8
|
-
|
9
|
-
class << self
|
10
|
-
# @return [Symbol] the current webserver adapter
|
11
|
-
attr_accessor :adapter
|
12
|
-
end
|
13
|
-
|
14
|
-
self.adapter = :WEBrick
|
15
9
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Webmachine
|
2
|
+
# A simple configuration container for items that are used across
|
3
|
+
# multiple web server adapters. Typically set using
|
4
|
+
# {Webmachine::configure}. If not set by your application, the
|
5
|
+
# defaults will be filled in when {Webmachine::run} is called.
|
6
|
+
# @attr [String] ip the interface to bind to, defaults to "0.0.0.0"
|
7
|
+
# (all interfaces)
|
8
|
+
# @attr [Fixnum] port the port to bind to, defaults to 8080
|
9
|
+
# @attr [Symbol] adapter the adapter to use, defaults to :WEBrick
|
10
|
+
# @attr [Hash] adapter_options adapter-specific options, defaults to {}
|
11
|
+
Configuration = Struct.new(:ip, :port, :adapter, :adapter_options)
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# @return [Configuration] the current configuration
|
15
|
+
attr_accessor :configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sets configuration for the web server via the passed
|
19
|
+
# block. Returns Webmachine so you can chain it with
|
20
|
+
# Webmachine.run.
|
21
|
+
# @yield [config] a block in which to set configuration values
|
22
|
+
# @yieldparam [Configuration] config the Configuration instance
|
23
|
+
# @return [Webmachine]
|
24
|
+
def self.configure
|
25
|
+
@configuration ||= Configuration.new("0.0.0.0", 8080, :WEBrick, {})
|
26
|
+
yield @configuration if block_given?
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'webmachine/translation'
|
2
|
+
require 'webmachine/media_type'
|
2
3
|
|
3
4
|
module Webmachine
|
4
5
|
module Decision
|
@@ -14,7 +15,7 @@ module Webmachine
|
|
14
15
|
def choose_media_type(provided, header)
|
15
16
|
requested = MediaTypeList.build(header.split(/\s*,\s*/))
|
16
17
|
provided = provided.map do |p| # normalize_provided
|
17
|
-
MediaType.
|
18
|
+
MediaType.parse(p)
|
18
19
|
end
|
19
20
|
# choose_media_type1
|
20
21
|
chosen = nil
|
@@ -79,7 +80,8 @@ module Webmachine
|
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
82
|
-
#
|
83
|
+
# Implements language-negotation matching as described in
|
84
|
+
# RFC2616, section 14.14.
|
83
85
|
#
|
84
86
|
# A language-range matches a language-tag if it exactly
|
85
87
|
# equals the tag, or if it exactly equals a prefix of the
|
@@ -116,74 +118,6 @@ module Webmachine
|
|
116
118
|
# Matches acceptable items that include 'q' values
|
117
119
|
CONNEG_REGEX = /^\s*(\S+);\s*q=(\S*)\s*$/
|
118
120
|
|
119
|
-
# Matches sub-type parameters
|
120
|
-
PARAMS_REGEX = /;([^=]+)=([^;=\s]+)/
|
121
|
-
|
122
|
-
# Matches valid media types
|
123
|
-
MEDIA_TYPE_REGEX = /^\s*([^;\s]+)\s*((?:;\S+\s*)*)\s*$/
|
124
|
-
|
125
|
-
# Encapsulates a MIME media type, with logic for matching types.
|
126
|
-
class MediaType
|
127
|
-
# Creates a new MediaType by parsing its string representation.
|
128
|
-
def self.parse(str)
|
129
|
-
if str =~ MEDIA_TYPE_REGEX
|
130
|
-
type, raw_params = $1, $2
|
131
|
-
params = Hash[raw_params.scan(PARAMS_REGEX)]
|
132
|
-
new(type, params)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# @return [String] the MIME media type
|
137
|
-
attr_accessor :type
|
138
|
-
|
139
|
-
# @return [Hash] any type parameters, e.g. charset
|
140
|
-
attr_accessor :params
|
141
|
-
|
142
|
-
def initialize(type, params={})
|
143
|
-
@type, @params = type, params
|
144
|
-
end
|
145
|
-
|
146
|
-
# Detects whether the {MediaType} represents an open wildcard
|
147
|
-
# type, that is, "*/*" without any {#params}.
|
148
|
-
def matches_all?
|
149
|
-
@type == "*/*" && @params.empty?
|
150
|
-
end
|
151
|
-
|
152
|
-
def ==(other)
|
153
|
-
other = self.class.parse(other) if String === other
|
154
|
-
other.type == type && other.params == params
|
155
|
-
end
|
156
|
-
|
157
|
-
# Detects whether this {MediaType} matches the other {MediaType},
|
158
|
-
# taking into account wildcards.
|
159
|
-
def match?(other)
|
160
|
-
type_matches?(other) && other.params == params
|
161
|
-
end
|
162
|
-
|
163
|
-
# Reconstitutes the type into a String
|
164
|
-
def to_s
|
165
|
-
[type, *params.map {|k,v| "#{k}=#{v}" }].join(";")
|
166
|
-
end
|
167
|
-
|
168
|
-
# @return [String] The major type, e.g. "application", "text", "image"
|
169
|
-
def major
|
170
|
-
type.split("/").first
|
171
|
-
end
|
172
|
-
|
173
|
-
# @return [String] the minor or sub-type, e.g. "json", "html", "jpeg"
|
174
|
-
def minor
|
175
|
-
type.split("/").last
|
176
|
-
end
|
177
|
-
|
178
|
-
def type_matches?(other)
|
179
|
-
if ["*", "*/*", type].include?(other.type)
|
180
|
-
true
|
181
|
-
else
|
182
|
-
other.major == major && other.minor == "*"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
121
|
# Matches the requested media type (with potential modifiers)
|
188
122
|
# against the provided types (with potential modifiers).
|
189
123
|
# @param [MediaType] requested the requested media type
|
@@ -291,10 +225,11 @@ module Webmachine
|
|
291
225
|
# {MediaType} items instead of Strings.
|
292
226
|
# @see PriorityList#add_header_val
|
293
227
|
def add_header_val(c)
|
294
|
-
|
228
|
+
begin
|
229
|
+
mt = MediaType.parse(c)
|
295
230
|
q = mt.params.delete('q') || 1.0
|
296
231
|
add(q.to_f, mt)
|
297
|
-
|
232
|
+
rescue ArgumentError
|
298
233
|
raise MalformedRequest, t('invalid_media_type', :type => c)
|
299
234
|
end
|
300
235
|
end
|
@@ -163,7 +163,12 @@ module Webmachine
|
|
163
163
|
# Accept-Language exists?
|
164
164
|
def d4
|
165
165
|
if !request.accept_language
|
166
|
-
choose_language(resource.languages_provided, "*")
|
166
|
+
if language = choose_language(resource.languages_provided, "*")
|
167
|
+
resource.language_chosen(language)
|
168
|
+
:e5
|
169
|
+
else
|
170
|
+
406
|
171
|
+
end
|
167
172
|
else
|
168
173
|
:d5
|
169
174
|
end
|
@@ -171,7 +176,12 @@ module Webmachine
|
|
171
176
|
|
172
177
|
# Acceptable language available?
|
173
178
|
def d5
|
174
|
-
choose_language(resource.languages_provided, request.accept_language)
|
179
|
+
if language = choose_language(resource.languages_provided, request.accept_language)
|
180
|
+
resource.language_chosen(language)
|
181
|
+
:e5
|
182
|
+
else
|
183
|
+
406
|
184
|
+
end
|
175
185
|
end
|
176
186
|
|
177
187
|
# Accept-Charset exists?
|
@@ -449,15 +459,7 @@ module Webmachine
|
|
449
459
|
# Also where body generation for GET and HEAD is done.
|
450
460
|
def o18
|
451
461
|
if request.method =~ /^(GET|HEAD)$/
|
452
|
-
|
453
|
-
response.headers['ETag'] = ensure_quoted_header(etag)
|
454
|
-
end
|
455
|
-
if last_modified = resource.last_modified
|
456
|
-
response.headers['Last-Modified'] = last_modified.httpdate
|
457
|
-
end
|
458
|
-
if expires = resource.expires
|
459
|
-
response.headers['Expires'] = expires.httpdate
|
460
|
-
end
|
462
|
+
add_caching_headers
|
461
463
|
content_type = metadata['Content-Type']
|
462
464
|
handler = resource.content_types_provided.find {|ct, _| content_type.type_matches?(MediaType.parse(ct)) }.last
|
463
465
|
result = resource.send(handler)
|