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.
Files changed (63) hide show
  1. data/Gemfile +11 -3
  2. data/README.md +55 -27
  3. data/lib/webmachine/adapters/mongrel.rb +84 -0
  4. data/lib/webmachine/adapters/webrick.rb +12 -3
  5. data/lib/webmachine/adapters.rb +1 -7
  6. data/lib/webmachine/configuration.rb +30 -0
  7. data/lib/webmachine/decision/conneg.rb +7 -72
  8. data/lib/webmachine/decision/flow.rb +13 -11
  9. data/lib/webmachine/decision/fsm.rb +1 -9
  10. data/lib/webmachine/decision/helpers.rb +27 -7
  11. data/lib/webmachine/errors.rb +1 -0
  12. data/lib/webmachine/headers.rb +12 -3
  13. data/lib/webmachine/locale/en.yml +2 -2
  14. data/lib/webmachine/media_type.rb +117 -0
  15. data/lib/webmachine/resource/callbacks.rb +9 -0
  16. data/lib/webmachine/streaming.rb +3 -3
  17. data/lib/webmachine/version.rb +1 -1
  18. data/lib/webmachine.rb +3 -1
  19. data/pkg/webmachine-0.1.0/Gemfile +16 -0
  20. data/pkg/webmachine-0.1.0/Guardfile +11 -0
  21. data/pkg/webmachine-0.1.0/README.md +90 -0
  22. data/pkg/webmachine-0.1.0/Rakefile +31 -0
  23. data/pkg/webmachine-0.1.0/examples/webrick.rb +19 -0
  24. data/pkg/webmachine-0.1.0/lib/webmachine/adapters/webrick.rb +74 -0
  25. data/pkg/webmachine-0.1.0/lib/webmachine/adapters.rb +15 -0
  26. data/pkg/webmachine-0.1.0/lib/webmachine/decision/conneg.rb +304 -0
  27. data/pkg/webmachine-0.1.0/lib/webmachine/decision/flow.rb +502 -0
  28. data/pkg/webmachine-0.1.0/lib/webmachine/decision/fsm.rb +79 -0
  29. data/pkg/webmachine-0.1.0/lib/webmachine/decision/helpers.rb +80 -0
  30. data/pkg/webmachine-0.1.0/lib/webmachine/decision.rb +12 -0
  31. data/pkg/webmachine-0.1.0/lib/webmachine/dispatcher/route.rb +85 -0
  32. data/pkg/webmachine-0.1.0/lib/webmachine/dispatcher.rb +40 -0
  33. data/pkg/webmachine-0.1.0/lib/webmachine/errors.rb +37 -0
  34. data/pkg/webmachine-0.1.0/lib/webmachine/headers.rb +16 -0
  35. data/pkg/webmachine-0.1.0/lib/webmachine/locale/en.yml +28 -0
  36. data/pkg/webmachine-0.1.0/lib/webmachine/request.rb +56 -0
  37. data/pkg/webmachine-0.1.0/lib/webmachine/resource/callbacks.rb +362 -0
  38. data/pkg/webmachine-0.1.0/lib/webmachine/resource/encodings.rb +36 -0
  39. data/pkg/webmachine-0.1.0/lib/webmachine/resource.rb +48 -0
  40. data/pkg/webmachine-0.1.0/lib/webmachine/response.rb +49 -0
  41. data/pkg/webmachine-0.1.0/lib/webmachine/streaming.rb +27 -0
  42. data/pkg/webmachine-0.1.0/lib/webmachine/translation.rb +11 -0
  43. data/pkg/webmachine-0.1.0/lib/webmachine/version.rb +4 -0
  44. data/pkg/webmachine-0.1.0/lib/webmachine.rb +19 -0
  45. data/pkg/webmachine-0.1.0/spec/spec_helper.rb +13 -0
  46. data/pkg/webmachine-0.1.0/spec/tests.org +57 -0
  47. data/pkg/webmachine-0.1.0/spec/webmachine/decision/conneg_spec.rb +152 -0
  48. data/pkg/webmachine-0.1.0/spec/webmachine/decision/flow_spec.rb +1030 -0
  49. data/pkg/webmachine-0.1.0/spec/webmachine/dispatcher/route_spec.rb +109 -0
  50. data/pkg/webmachine-0.1.0/spec/webmachine/dispatcher_spec.rb +34 -0
  51. data/pkg/webmachine-0.1.0/spec/webmachine/headers_spec.rb +19 -0
  52. data/pkg/webmachine-0.1.0/spec/webmachine/request_spec.rb +24 -0
  53. data/pkg/webmachine-0.1.0/webmachine.gemspec +44 -0
  54. data/pkg/webmachine-0.1.0.gem +0 -0
  55. data/spec/webmachine/configuration_spec.rb +27 -0
  56. data/spec/webmachine/decision/conneg_spec.rb +18 -11
  57. data/spec/webmachine/decision/flow_spec.rb +2 -0
  58. data/spec/webmachine/decision/helpers_spec.rb +105 -0
  59. data/spec/webmachine/errors_spec.rb +13 -0
  60. data/spec/webmachine/headers_spec.rb +2 -1
  61. data/spec/webmachine/media_type_spec.rb +78 -0
  62. data/webmachine.gemspec +4 -1
  63. metadata +69 -11
@@ -0,0 +1,362 @@
1
+ module Webmachine
2
+ class Resource
3
+ # These
4
+ module Callbacks
5
+ # Does the resource exist? Returning a falsey value (false or nil)
6
+ # will result in a '404 Not Found' response. Defaults to true.
7
+ # @return [true,false] Whether the resource exists
8
+ # @api callback
9
+ def resource_exists?
10
+ true
11
+ end
12
+
13
+ # Is the resource available? Returning a falsey value (false or
14
+ # nil) will result in a '503 Service Not Available'
15
+ # response. Defaults to true. If the resource is only temporarily
16
+ # not available, add a 'Retry-After' response header in the body
17
+ # of the method.
18
+ # @return [true,false]
19
+ # @api callback
20
+ def service_available?
21
+ true
22
+ end
23
+
24
+ # Is the client or request authorized? Returning anything other than true
25
+ # will result in a '401 Unauthorized' response. Defaults to
26
+ # true. If a String is returned, it will be used as the value in
27
+ # the WWW-Authenticate header, which can also be set manually.
28
+ # @param [String] authorization_header The contents of the
29
+ # 'Authorization' header sent by the client, if present.
30
+ # @return [true,false,String] Whether the client is authorized,
31
+ # and if not, the WWW-Authenticate header when a String.
32
+ # @api callback
33
+ def is_authorized?(authorization_header = nil)
34
+ true
35
+ end
36
+
37
+ # Is the request or client forbidden? Returning a truthy value
38
+ # (true or non-nil) will result in a '403 Forbidden' response.
39
+ # Defaults to false.
40
+ # @return [true,false] Whether the client or request is forbidden.
41
+ # @api callback
42
+ def forbidden?
43
+ false
44
+ end
45
+
46
+ # If the resource accepts POST requests to nonexistent resources,
47
+ # then this should return true. Defaults to false.
48
+ # @return [true,false] Whether to accept POST requests to missing
49
+ # resources.
50
+ # @api callback
51
+ def allow_missing_post?
52
+ false
53
+ end
54
+
55
+ # If the request is malformed, this should return true, which will
56
+ # result in a '400 Malformed Request' response. Defaults to false.
57
+ # @return [true,false] Whether the request is malformed.
58
+ # @api callback
59
+ def malformed_request?
60
+ false
61
+ end
62
+
63
+ # If the URI is too long to be processed, this should return true,
64
+ # which will result in a '414 Request URI Too Long'
65
+ # response. Defaults to false.
66
+ # @param [URI] uri the request URI
67
+ # @return [true,false] Whether the request URI is too long.
68
+ # @api callback
69
+ def uri_too_long?(uri = nil)
70
+ false
71
+ end
72
+
73
+ # If the Content-Type on PUT or POST is unknown, this should
74
+ # return false, which will result in a '415 Unsupported Media
75
+ # Type' response. Defaults to true.
76
+ # @param [String] content_type the 'Content-Type' header sent by
77
+ # the client
78
+ # @return [true,false] Whether the passed media type is known or
79
+ # accepted
80
+ # @api callback
81
+ def known_content_type?(content_type = nil)
82
+ true
83
+ end
84
+
85
+ # If the request includes any invalid Content-* headers, this
86
+ # should return false, which will result in a '501 Not
87
+ # Implemented' response. Defaults to false.
88
+ # @param [Hash] content_headers Request headers that begin with
89
+ # 'Content-'
90
+ # @return [true,false] Whether the Content-* headers are invalid
91
+ # or unsupported
92
+ # @api callback
93
+ def valid_content_headers?(content_headers = nil)
94
+ true
95
+ end
96
+
97
+ # If the entity length on PUT or POST is invalid, this should
98
+ # return false, which will result in a '413 Request Entity Too
99
+ # Large' response. Defaults to true.
100
+ # @param [Fixnum] length the size of the request body (entity)
101
+ # @return [true,false] Whether the body is a valid length (not too
102
+ # large)
103
+ # @api callback
104
+ def valid_entity_length?(length = nil)
105
+ true
106
+ end
107
+
108
+ # If the OPTIONS method is supported and is used, this method
109
+ # should return a Hash of headers that should appear in the
110
+ # response. Defaults to {}.
111
+ # @return [Hash] headers to appear in the response
112
+ # @api callback
113
+ def options
114
+ {}
115
+ end
116
+
117
+ # HTTP methods that are allowed on this resource. This must return
118
+ # an Array of Strings in all capitals. Defaults to ['GET','HEAD'].
119
+ # @return [Array<String>] allowed methods on this resource
120
+ # @api callback
121
+ def allowed_methods
122
+ ['GET', 'HEAD']
123
+ end
124
+
125
+ # HTTP methods that are known to the resource. Like
126
+ # {#allowed_methods}, this must return an Array of Strings in
127
+ # all capitals. Default includes all standard HTTP methods. One
128
+ # could override this callback to allow additional methods,
129
+ # e.g. WebDAV.
130
+ # @return [Array<String>] known methods
131
+ # @api callback
132
+ def known_methods
133
+ ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS']
134
+ end
135
+
136
+ # This method is called when a DELETE request should be enacted,
137
+ # and should return true if the deletion succeeded. Defaults to false.
138
+ # @return [true,false] Whether the deletion succeeded.
139
+ # @api callback
140
+ def delete_resource
141
+ false
142
+ end
143
+
144
+ # This method is called after a successful call to
145
+ # {#delete_resource} and should return false if the deletion was
146
+ # accepted but cannot yet be guaranteed to have finished. Defaults
147
+ # to true.
148
+ # @return [true,false] Whether the deletion completed
149
+ # @api callback
150
+ def delete_completed?
151
+ true
152
+ end
153
+
154
+ # If POST requests should be treated as a request to put content
155
+ # into a (potentially new) resource as opposed to a generic
156
+ # submission for processing, then this method should return
157
+ # true. If it does return true, then {#create_path} will be called
158
+ # and the rest of the request will be treated much like a PUT to
159
+ # the path returned by that call. Default is false.
160
+ # @return [true,false] Whether POST creates a new resource
161
+ # @api callback
162
+ def post_is_create?
163
+ false
164
+ end
165
+
166
+ # This will be called on a POST request if post_is_create? returns
167
+ # true. The path returned should be a valid URI part following the
168
+ # dispatcher prefix. That path will replace the previous one in
169
+ # the return value of {Request#disp_path} for all subsequent
170
+ # resource function calls in the course of this request.
171
+ # @return [String, URI] the path to the new resource
172
+ # @api callback
173
+ def create_path
174
+ nil
175
+ end
176
+
177
+ # This will be called after {#create_path} but before setting the
178
+ # Location response header, and is used to determine the root
179
+ # URI of the new resource. Default is nil, which uses the URI of
180
+ # the request as the base.
181
+ # @return [String, URI, nil]
182
+ # @api callback
183
+ def base_uri
184
+ nil
185
+ end
186
+
187
+ # If post_is_create? returns false, then this will be called to
188
+ # process any POST request. If it succeeds, it should return true.
189
+ # @return [true,false,Fixnum] Whether the POST was successfully
190
+ # processed, or an alternate response code
191
+ # @api callback
192
+ def process_post
193
+ false
194
+ end
195
+
196
+ # This should return an array of pairs where each pair is of the
197
+ # form [mediatype, :handler] where mediatype is a String of
198
+ # Content-Type format and :handler is a Symbol naming the method
199
+ # which can provide a resource representation in that media
200
+ # type. For example, if a client request includes an 'Accept'
201
+ # header with a value that does not appear as a first element in
202
+ # any of the return pairs, then a '406 Not Acceptable' will be
203
+ # sent. Default is [['text/html', :to_html]].
204
+ # @return an array of mediatype/handler pairs
205
+ # @api callback
206
+ def content_types_provided
207
+ [['text/html', :to_html]]
208
+ end
209
+
210
+ # Similarly to content_types_provided, this should return an array
211
+ # of mediatype/handler pairs, except that it is for incoming
212
+ # resource representations -- for example, PUT requests. Handler
213
+ # functions usually want to use {Request#body} to access the
214
+ # incoming entity.
215
+ # @return [Array] an array of mediatype/handler pairs
216
+ # @api callback
217
+ def content_types_accepted
218
+ []
219
+ end
220
+
221
+ # If this is anything other than nil, it must be an array of pairs
222
+ # where each pair is of the form Charset, Converter where Charset
223
+ # is a string naming a charset and Converter is an arity-1 method
224
+ # in the resource which will be called on the produced body in a
225
+ # GET and ensure that it is in Charset.
226
+ # @return [nil, Array] The provided character sets and encoder
227
+ # methods, or nothing.
228
+ # @api callback
229
+ def charsets_provided
230
+ nil
231
+ end
232
+
233
+ # This should return a list of language tags provided by the
234
+ # resource. Default is the empty Array, in which the content is
235
+ # in no specific language.
236
+ # @return [Array<String>] a list of provided languages
237
+ # @api callback
238
+ def languages_provided
239
+ []
240
+ end
241
+
242
+ # This should return a hash of encodings mapped to encoding
243
+ # methods for Content-Encodings your resource wants to
244
+ # provide. The encoding will be applied to the response body
245
+ # automatically by Webmachine. A number of built-in encodings
246
+ # are provided in the {Encodings} module. Default includes only
247
+ # the 'identity' encoding.
248
+ # @return [Hash] a hash of encodings and encoder methods/procs
249
+ # @api callback
250
+ # @see Encodings
251
+ def encodings_provided
252
+ {"identity" => :encode_identity }
253
+ end
254
+
255
+ # If this method is implemented, it should return a list of
256
+ # strings with header names that should be included in a given
257
+ # response's Vary header. The standard conneg headers (Accept,
258
+ # Accept-Encoding, Accept-Charset, Accept-Language) do not need to
259
+ # be specified here as Webmachine will add the correct elements of
260
+ # those automatically depending on resource behavior. Default is
261
+ # [].
262
+ # @api callback
263
+ # @return [Array<String>] a list of variance headers
264
+ def variances
265
+ []
266
+ end
267
+
268
+ # If this returns true, the client will receive a '409 Conflict'
269
+ # response. This is only called for PUT requests. Default is false.
270
+ # @api callback
271
+ # @return [true,false] whether the submitted entity is in conflict
272
+ # with the current state of the resource
273
+ def is_conflict?
274
+ false
275
+ end
276
+
277
+ # If this returns true, then it is assumed that multiple
278
+ # representations of the response are possible and a single one
279
+ # cannot be automatically chosen, so a 300 Multiple Choices will
280
+ # be sent instead of a 200. Default is false.
281
+ # @api callback
282
+ # @return [true,false] whether the multiple representations are
283
+ # possible
284
+ def multiple_choices?
285
+ false
286
+ end
287
+
288
+ # If this resource is known to have existed previously, this
289
+ # method should return true. Default is false.
290
+ # @api callback
291
+ # @return [true,false] whether the resource existed previously
292
+ def previously_existed?
293
+ false
294
+ end
295
+
296
+ # If this resource has moved to a new location permanently, this
297
+ # method should return the new location as a String or
298
+ # URI. Default is to return false.
299
+ # @api callback
300
+ # @return [String,URI,false] the new location of the resource, or
301
+ # false
302
+ def moved_permanently?
303
+ false
304
+ end
305
+
306
+ # If this resource has moved to a new location temporarily, this
307
+ # method should return the new location as a String or
308
+ # URI. Default is to return false.
309
+ # @api callback
310
+ # @return [String,URI,false] the new location of the resource, or
311
+ # false
312
+ def moved_temporarily?
313
+ false
314
+ end
315
+
316
+ # This method should return the last modified date/time of the
317
+ # resource which will be added as the Last-Modified header in the
318
+ # response and used in negotiating conditional requests. Default
319
+ # is nil.
320
+ # @api callback
321
+ # @return [Time,DateTime,Date,nil] the last modified time
322
+ def last_modified
323
+ nil
324
+ end
325
+
326
+ # If the resource expires, this method should return the date/time
327
+ # it expires. Default is nil.
328
+ # @api callback
329
+ # @return [Time,DateTime,Date,nil] the expiration time
330
+ def expires
331
+ nil
332
+ end
333
+
334
+ # If this returns a value, it will be used as the value of the
335
+ # ETag header and for comparison in conditional requests. Default
336
+ # is nil.
337
+ # @api callback
338
+ # @return [String,nil] the entity tag for this resource
339
+ def generate_etag
340
+ nil
341
+ end
342
+
343
+ # This method is called just before the final response is
344
+ # constructed and sent. The return value is ignored, so any effect
345
+ # of this method must be by modifying the response.
346
+ # @api callback
347
+ def finish_request; end
348
+
349
+ # This method is called when verifying the Content-MD5 header
350
+ # against the request body. To do your own validation, implement
351
+ # it in this callback, returning true or false. To bypass header
352
+ # validation, simply return true. Default is nil, which will
353
+ # invoke Webmachine's default validation.
354
+ # @api callback
355
+ # @return [true,false,nil] Whether the Content-MD5 header
356
+ # validates against the request body
357
+ def validate_content_checksum
358
+ nil
359
+ end
360
+ end
361
+ end
362
+ end
@@ -0,0 +1,36 @@
1
+ require 'zlib'
2
+ require 'stringio'
3
+
4
+ module Webmachine
5
+ class Resource
6
+ # This module implements standard Content-Encodings that you might
7
+ # want to use in your {Resource}. To use one, simply return it in
8
+ # the hash from {Callbacks#encodings_provided}.
9
+ module Encodings
10
+ # The 'identity' encoding, which does no compression.
11
+ def encode_identity(data)
12
+ data
13
+ end
14
+
15
+ # The 'deflate' encoding, which uses libz's DEFLATE compression.
16
+ def encode_deflate(data)
17
+ # The deflate options were borrowed from Rack and Mongrel1.
18
+ Zlib::Deflate.deflate(data, *[Zlib::DEFAULT_COMPRESSION,
19
+ # drop the zlib header which causes both Safari and IE to choke
20
+ -Zlib::MAX_WBITS,
21
+ Zlib::DEF_MEM_LEVEL,
22
+ Zlib::DEFAULT_STRATEGY
23
+ ])
24
+ end
25
+
26
+ # The 'gzip' encoding, which uses GNU Zip (via libz).
27
+ # @note Because of the header/checksum requirements, gzip cannot
28
+ # be used on streamed responses.
29
+ def encode_gzip(data)
30
+ "".tap do |out|
31
+ Zlib::GzipWriter.wrap(StringIO.new(out)){|gz| gz << data }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ require 'webmachine/resource/callbacks'
2
+ require 'webmachine/resource/encodings'
3
+
4
+ module Webmachine
5
+ # Resource is the primary building block of Webmachine applications,
6
+ # and describes families of HTTP resources. It includes all of the
7
+ # methods you might want to override to customize the behavior of
8
+ # the resource. The simplest resource family you can implement
9
+ # looks like this:
10
+ #
11
+ # class HelloWorldResource < Webmachine::Resource
12
+ # def to_html
13
+ # "<html><body>Hello, world!</body></html>"
14
+ # end
15
+ # end
16
+ #
17
+ # For more information about how response decisions are made in
18
+ # Webmachine based on your resource class, refer to the diagram at
19
+ # {http://webmachine.basho.com/images/http-headers-status-v3.png}.
20
+ class Resource
21
+ include Callbacks
22
+ include Encodings
23
+
24
+ attr_reader :request, :response
25
+
26
+ # Creates a new {Resource}, initializing it with the request and
27
+ # response. Note that you may still override {#initialize} to
28
+ # initialize your resource. It will be called after the request
29
+ # and response ivars are set.
30
+ # @param [Request] request the request object
31
+ # @param [Response] response the response object
32
+ # @return [Resource] the new resource
33
+ def self.new(request, response)
34
+ instance = allocate
35
+ instance.instance_variable_set(:@request, request)
36
+ instance.instance_variable_set(:@response, response)
37
+ instance.send :initialize
38
+ instance
39
+ end
40
+
41
+ private
42
+ # When no specific charsets are provided, this acts as an identity
43
+ # on the response body. Probably deserves some refactoring.
44
+ def charset_nop(x)
45
+ x
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,49 @@
1
+ module Webmachine
2
+ # Represents an HTTP response from Webmachine.
3
+ class Response
4
+ # @return [Hash] Response headers that will be sent to the client
5
+ attr_reader :headers
6
+
7
+ # @return [Fixnum] The HTTP status code of the response
8
+ attr_accessor :code
9
+
10
+ # @return [String, #each] The response body
11
+ attr_accessor :body
12
+
13
+ # @return [true,false] Whether the response is a redirect
14
+ attr_accessor :redirect
15
+
16
+ # @return [Array] the list of states that were traversed
17
+ attr_reader :trace
18
+
19
+ # @return [Symbol] When an error has occurred, the last state the
20
+ # FSM was in
21
+ attr_accessor :end_state
22
+
23
+ # @return [String] The error message when responding with an error
24
+ # code
25
+ attr_accessor :error
26
+
27
+ # Creates a new Response object with the appropriate defaults.
28
+ def initialize
29
+ @headers = {}
30
+ @trace = []
31
+ self.code = 200
32
+ self.redirect = false
33
+ end
34
+
35
+ # Indicate that the response should be a redirect. This is only
36
+ # used when processing a POST request in {Callbacks#process_post}
37
+ # to indicate that the client should request another resource
38
+ # using GET. Either pass the URI of the target resource, or
39
+ # manually set the Location header using {#headers}.
40
+ # @param [String, URI] location the target of the redirection
41
+ def do_redirect(location=nil)
42
+ headers['Location'] = location.to_s if location
43
+ self.redirect = true
44
+ end
45
+
46
+ alias :is_redirect? :redirect
47
+ alias :redirect_to :do_redirect
48
+ end
49
+ end