webmachine 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|