webmachine 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -9,13 +9,17 @@ gem 'bundler'
9
9
  unless ENV['TRAVIS']
10
10
  gem 'guard-rspec'
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'
12
+ platform :mri do
13
+ gem 'redcarpet'
14
+
15
+ case RbConfig::CONFIG['host_os']
16
+ when /darwin/
17
+ gem 'rb-fsevent'
18
+ gem 'growl_notify'
19
+ when /linux/
20
+ gem 'rb-inotify'
21
+ gem 'libnotify'
22
+ end
19
23
  end
20
24
  end
21
25
 
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  gemset = ENV['RVM_GEMSET'] || 'webmachine'
2
2
  gemset = "@#{gemset}" unless gemset.to_s == ''
3
3
 
4
- rvms = %W[ 1.9.2 ].map {|v| "#{v}#{gemset}" }
4
+ rvms = %W[ 1.8.7 1.9.2 jruby rbx ].map {|v| "#{v}#{gemset}" }
5
5
 
6
6
  guard 'rspec', :cli => "--color --profile", :growl => true, :rvm => rvms do
7
7
  watch(%r{^lib/webmachine/locale/.+$}) { "spec" }
data/README.md CHANGED
@@ -11,14 +11,14 @@ toolkit for building HTTP-friendly applications. For example, it does
11
11
  not provide a templating engine or a persistence layer; those choices
12
12
  are up to you.
13
13
 
14
- **NOTE**: _Webmachine is NOT compatible with Rack._ This is
15
- intentional! Rack obscures HTTP in a way that makes it hard for
16
- Webmachine to do its job properly, and encourages people to add
17
- middleware that might break Webmachine's behavior. Rack is also built
18
- on the tradition of CGI, which is nice for backwards compatibility but
19
- also an antiquated paradigm and should be scuttled (IMHO). _Rack may
20
- be supported in the future, but only as a shim to support other web
21
- application servers._
14
+ ## A Note about Rack
15
+
16
+ Webmachine has a Rack adapter -- thanks to Jamis Buck -- but when
17
+ using it, we recommend you ensure that NO middleware is used. The
18
+ behaviors that are encapsulated in Webmachine could be broken by
19
+ middlewares that sit above it, and there is no way to detect them at
20
+ runtime. _Caveat emptor_. That said, Webmachine should behave properly
21
+ when given a clear stack.
22
22
 
23
23
  ## Getting Started
24
24
 
@@ -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 8080 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
- def to_html
45
- "<html><body>Hello, world!</body></html>"
46
- end
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,19 +54,44 @@ 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
- 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
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
69
- callbacks. Give them a try!
69
+ {Webmachine::Resource::Callbacks}. Give them a try!
70
+
71
+ ### Configurator
72
+
73
+ There's a configurator that allows you to set the ip address and port
74
+ bindings as well as a different webserver adapter. You can also add
75
+ your routes in a block. Both of these call return the `Webmachine`
76
+ module, so you could chain them if you like.
77
+
78
+ ```ruby
79
+ require 'webmachine'
80
+ require 'my_resource'
81
+
82
+ Webmachine.routes do
83
+ add ['*'], MyResource
84
+ end
85
+
86
+ Webmachine.configure do |config|
87
+ config.ip = '127.0.0.1'
88
+ config.port = 3000
89
+ config.adapter = :Mongrel
90
+ end
91
+
92
+ # Start the server.
93
+ Webmachine.run
94
+ ```
70
95
 
71
96
  ## Features
72
97
 
@@ -75,14 +100,14 @@ callbacks. Give them a try!
75
100
  * Most callbacks can interrupt the decision flow by returning an
76
101
  integer response code. You generally only want to do this when new
77
102
  information comes to light, requiring a modification of the response.
78
- * Supports WEBrick and Mongrel (1.2pre+). Other host servers are being
79
- investigated.
80
- * Streaming/chunked response bodies are permitted as Enumerables or Procs.
103
+ * Supports WEBrick and Mongrel (1.2pre+), and a Rack shim. Other host
104
+ servers are being investigated.
105
+ * Streaming/chunked response bodies are permitted as Enumerables,
106
+ Procs, or Fibers!
81
107
  * Unlike the Erlang original, it does real Language negotiation.
82
108
 
83
109
  ## Problems/TODOs
84
110
 
85
- * Support streamed responses as Fibers.
86
111
  * Command-line tools, and general polish.
87
112
  * Tracing is exposed as an Array of decisions visited on the response
88
113
  object. You should be able to turn this off and on, and visualize
@@ -90,6 +115,33 @@ callbacks. Give them a try!
90
115
 
91
116
  ## Changelog
92
117
 
118
+ ### 0.3.0 November 9, 2011
119
+
120
+ 0.3.0 introduces some new features, refactorings, and now has 100%
121
+ documentation coverage! Among the new features are minimal Rack
122
+ compatibility, streaming responses via Fibers and a friendlier route
123
+ definition syntax. Added Jamis Buck as a committer. Thank you for your
124
+ contributions!
125
+
126
+ * Chunked bodies are now wrapped in a way that works on webservers
127
+ that don't automatically produce them.
128
+ * HTTP Basic Authentication is easy to add to resources, just include
129
+ `Webmachine::Resource::Authentication`.
130
+ * Routes are a little less painful to add, you can now specify them
131
+ with `Webmachine.routes` which will be evaled into the `Dispatcher`.
132
+ * The new default port is 8080.
133
+ * Rack is minimally supported as a host server. _Don't put middleware
134
+ above Webmachine!_
135
+ * Fibers can be used as streamed response bodies.
136
+ * `Dispatcher#add_route` will now return the added `Route` instance.
137
+ * The header-conversion code for CGI-style servers has been extracted
138
+ into `Webmachine::Headers`.
139
+ * `Route#path_spec` is now public so that applications can inspect
140
+ existing routes, perhaps for URL generation.
141
+ * `Request#query` now uses `CGI.unescape` so '+' characters are
142
+ correctly parsed.
143
+ * YARD documentation has 100% coverage.
144
+
93
145
  ### 0.2.0 September 11, 2011
94
146
 
95
147
  0.2.0 includes an adapter for Mongrel and a central place for
data/Rakefile CHANGED
@@ -1,6 +1,16 @@
1
1
  require 'rubygems'
2
2
  require 'rubygems/package_task'
3
3
 
4
+ begin
5
+ require 'yard'
6
+ require 'yard/rake/yardoc_task'
7
+ YARD::Rake::YardocTask.new do |doc|
8
+ doc.files = Dir["lib/**/*.rb"] + ['README.md']
9
+ doc.options = ["-m", "markdown"]
10
+ end
11
+ rescue LoadError
12
+ end
13
+
4
14
  def gemspec
5
15
  $webmachine_gemspec ||= Gem::Specification.load("webmachine.gemspec")
6
16
  end
@@ -4,6 +4,7 @@ require 'webmachine/headers'
4
4
  require 'webmachine/request'
5
5
  require 'webmachine/response'
6
6
  require 'webmachine/dispatcher'
7
+ require 'webmachine/chunked_body'
7
8
 
8
9
  module Webmachine
9
10
  module Adapters
@@ -26,9 +27,11 @@ module Webmachine
26
27
  config.join
27
28
  end
28
29
 
30
+ # A Mongrel handler for Webmachine
29
31
  class Handler < ::Mongrel::HttpHandler
32
+ # Processes an individual request from Mongrel through Webmachine.
30
33
  def process(wreq, wres)
31
- header = http_headers(wreq.params, Webmachine::Headers.new)
34
+ header = Webmachine::Headers.from_cgi(wreq.params)
32
35
 
33
36
  request = Webmachine::Request.new(wreq.params["REQUEST_METHOD"],
34
37
  URI.parse(wreq.params["REQUEST_URI"]),
@@ -55,29 +58,22 @@ module Webmachine
55
58
  wres.write response.body
56
59
  wres.socket.flush
57
60
  when Enumerable
58
- response.body.each { |part|
61
+ Webmachine::ChunkedBody.new(response.body).each { |part|
59
62
  wres.write part
60
63
  wres.socket.flush
61
64
  }
62
65
  else
63
66
  if response.body.respond_to?(:call)
64
- wres.write part
65
- wres.socket.flush
67
+ Webmachine::ChunkedBody.new(Array(response.body.call)).each { |part|
68
+ wres.write part
69
+ wres.socket.flush
70
+ }
66
71
  end
67
72
  end
68
73
  ensure
69
74
  response.body.close if response.body.respond_to? :close
70
75
  end
71
76
  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
77
  end
82
78
  end
83
79
  end
@@ -0,0 +1,50 @@
1
+ require 'rack/request'
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
+ # A minimal "shim" adapter to allow Webmachine to interface with Rack. The
11
+ # intention here is to allow Webmachine to run under Rack-compatible
12
+ # web-servers, like unicorn and pow, and is not intended to allow Webmachine
13
+ # to be "plugged in" to an existing Rack app as middleware.
14
+ #
15
+ # To use this adapter, create a config.ru file and populate it like so:
16
+ #
17
+ # require 'webmachine/adapters/rack'
18
+ #
19
+ # # put your own Webmachine resources in another file:
20
+ # require 'my/resources'
21
+ #
22
+ # run Webmachine::Adapters::Rack.new
23
+ #
24
+ # Servers like pow and unicorn will read config.ru by default and it should
25
+ # all "just work".
26
+ class Rack
27
+ # Handles a Rack-based request.
28
+ # @param [Hash] env the Rack environment
29
+ def call(env)
30
+ headers = Webmachine::Headers.from_cgi(env)
31
+
32
+ rack_req = ::Rack::Request.new env
33
+ request = Webmachine::Request.new(rack_req.request_method,
34
+ URI.parse(rack_req.url),
35
+ headers,
36
+ rack_req.body)
37
+
38
+ response = Webmachine::Response.new
39
+ Webmachine::Dispatcher.dispatch request, response
40
+
41
+ response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")
42
+
43
+ body = response.body.respond_to?(:call) ? response.body.call : response.body
44
+ body = body.is_a?(String) ? [ body ] : body
45
+
46
+ [response.code.to_i, response.headers, body || []]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -21,7 +21,9 @@ module Webmachine
21
21
  Thread.new { server.start }.join
22
22
  end
23
23
 
24
+ # WEBRick::HTTPServer that is run by the WEBrick adapter.
24
25
  class Server < ::WEBrick::HTTPServer
26
+ # Handles a request
25
27
  def service(wreq, wres)
26
28
  header = Webmachine::Headers.new
27
29
  wreq.each {|k,v| header[k] = v }
@@ -0,0 +1,43 @@
1
+ module Webmachine
2
+ # {ChunkedBody} is used to wrap an {Enumerable} object (like an enumerable
3
+ # {Response#body}) so it yields proper chunks for chunked transfer encoding.
4
+ #
5
+ # case response.body
6
+ # when String
7
+ # socket.write(response.body)
8
+ # when Enumerable
9
+ # Webmachine::ChunkedBody.new(response.body).each do |chunk|
10
+ # socket.write(chunk)
11
+ # end
12
+ # end
13
+ #
14
+ # This is needed for Ruby webservers which don't do the chunking themselves.
15
+ class ChunkedBody
16
+ # Delimiter for chunked encoding
17
+ CRLF = "\r\n"
18
+
19
+ # Final chunk in any chunked-encoding response
20
+ FINAL_CHUNK = "0#{CRLF}#{CRLF}"
21
+
22
+ # Creates a new {ChunkedBody} from the given {Enumerable}.
23
+ # @param [Enumerable] body the enumerable response body
24
+ # @return [ChunkedBody] the wrapped response body
25
+ def initialize(body)
26
+ @body = body
27
+ end
28
+
29
+ # Calls the given block once for each chunk, passing that chunk as a
30
+ # parameter.
31
+ # Returns an {Enumerator} if no block is given.
32
+ def each
33
+ return self.to_enum unless block_given?
34
+
35
+ @body.each do |chunk|
36
+ size = chunk.bytesize
37
+ next if size == 0
38
+ yield([size.to_s(16), CRLF, chunk, CRLF].join)
39
+ end
40
+ yield(FINAL_CHUNK)
41
+ end
42
+ end
43
+ end
@@ -87,6 +87,7 @@ module Webmachine
87
87
  # equals the tag, or if it exactly equals a prefix of the
88
88
  # tag such that the first tag character following the prefix
89
89
  # is "-".
90
+ # @api private
90
91
  def language_match(range, tag)
91
92
  range.downcase == tag.downcase || tag =~ /^#{Regexp.escape(range)}\-/i
92
93
  end
@@ -215,7 +216,7 @@ module Webmachine
215
216
  end
216
217
  end
217
218
 
218
- # Like a {PriorityList}, but for {MediaTypes}, since they have
219
+ # Like a {PriorityList}, but for {MediaType}s, since they have
219
220
  # parameters in addition to q.
220
221
  # @private
221
222
  class MediaTypeList < PriorityList
@@ -6,8 +6,9 @@ require 'webmachine/translation'
6
6
  module Webmachine
7
7
  module Decision
8
8
  # This module encapsulates all of the decisions in Webmachine's
9
- # flow-chart. These invoke {Resource} {Callbacks} to determine the
10
- # appropriate response code, headers, and body for the response.
9
+ # flow-chart. These invoke {Webmachine::Resource::Callbacks} methods to
10
+ # determine the appropriate response code, headers, and body for
11
+ # the response.
11
12
  #
12
13
  # This module is included into {FSM}, which drives the processing
13
14
  # of the chart.
@@ -390,6 +391,7 @@ module Webmachine
390
391
  decision_test(resource.delete_resource, true, :m20b, 500)
391
392
  end
392
393
 
394
+ # Did the DELETE complete?
393
395
  def m20b
394
396
  decision_test(resource.delete_completed?, true, :o20, 202)
395
397
  end
@@ -5,6 +5,7 @@ module Webmachine
5
5
  module Decision
6
6
  # Methods that assist the Decision {Flow}.
7
7
  module Helpers
8
+ # Pattern for quoted headers
8
9
  QUOTED = /^"(.*)"$/
9
10
 
10
11
  # Determines if the response has a body/entity set.
@@ -28,6 +29,8 @@ module Webmachine
28
29
  response.body = case body
29
30
  when String # 1.8 treats Strings as Enumerable
30
31
  resource.send(encoder, resource.send(charsetter, body))
32
+ when Fiber
33
+ FiberEncoder.new(resource, encoder, charsetter, body)
31
34
  when Enumerable
32
35
  EnumerableEncoder.new(resource, encoder, charsetter, body)
33
36
  else
@@ -84,6 +87,7 @@ module Webmachine
84
87
  end
85
88
  end
86
89
 
90
+ # Adds caching-related headers to the response.
87
91
  def add_caching_headers
88
92
  if etag = resource.generate_etag
89
93
  response.headers['ETag'] = ensure_quoted_header(etag)