dispatcher 0.0.1

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.
File without changes
@@ -0,0 +1,33 @@
1
+ = Dispatcher
2
+
3
+ The +Dispatcher+ module defines a simple and consistent interface between Ruby and most webserver
4
+ configurations. This library provides a very restrictive set of features, and as such is generally
5
+ not meant to be directly used by web-application authors, but instead targets implementors of
6
+ frameworks and web-libraries.
7
+
8
+ == Basic Usage
9
+
10
+ The following is a very basic example of relaying a "Hello World" type response back to the
11
+ webserver. Notice that we rely on the +autodetection+ facilities of +Dispatcher+ here, by not
12
+ defining or configuring which webserver interface the script is to utilize.
13
+
14
+ require 'dispatcher'
15
+
16
+ Dispatcher.dispatch do |request|
17
+ header 'Status', '200 OK'
18
+ header 'Content-Type', 'text/plain'
19
+
20
+ print 'Hello World'
21
+ end
22
+
23
+ However, in some instances, we may wish to use a webserver that cannot be necessairly
24
+ +autodetected+, such as a standalone server like +Mongrel+.
25
+
26
+ require 'dispatcher/mongrel'
27
+
28
+ Dispatcher.dispatch(:port => 8081) do |request|
29
+ header 'Status', '200 OK'
30
+ header 'Content-Type', 'text/plain'
31
+
32
+ print 'Hello World'
33
+ end
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,31 @@
1
+ require 'dispatcher/cgi'
2
+
3
+ Dispatcher.dispatch do |request|
4
+ header 'Status', '200 NOT OK'
5
+ header 'Content-Type', 'text/plain'
6
+
7
+ print "#{request.uri}\n\n"
8
+
9
+ print request.inspect.gsub(' @', "\n@").gsub('={', "={\n ").gsub('",', "\",\n ").gsub('"}', "\"\n}")
10
+ =begin
11
+ print %{<?xml version="1.0" encoding="UTF-8"?>
12
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
13
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
14
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
15
+ <head>
16
+ <title>Roar</title>
17
+ </head>
18
+ <body>}
19
+ print "<h2>#{Dispatcher.autodetect}</h2>"
20
+ print "<h3>Working Directory</h3>\n"
21
+ print Dir.getwd
22
+ print "<h3>Environment Variables</h3>\n"
23
+ print "<table style=\"font-size: 0.75em;\"><tbody>\n"
24
+ ENV.each do |key, value|
25
+ print "<tr><td style=\"text-align:right;\"><b>#{key}</b>:</td><td>#{value}</td></tr>\n"
26
+ end
27
+ print "</tbody></table>\n"
28
+ print %{ </body>
29
+ </html>}
30
+ =end
31
+ end
@@ -0,0 +1,306 @@
1
+ module Dispatcher
2
+ CR = 13.chr
3
+ LF = 10.chr
4
+ CRLF = CR + LF
5
+
6
+ # Dispatches the specified responder block, given the passed parameters.
7
+ #
8
+ # If a subclass of Dispatcher is passed, it will be used as the dispatcher interface. Otherwise,
9
+ # the last-defined interface will be used. Failing that, the functian will appempt to autodetect
10
+ # an appropriate interface.
11
+ #
12
+ # Additionally, any number of named parameters (:param => value) may be passed, and will be
13
+ # considered configuration values for the given dispatcher interface.
14
+ def self::dispatch(*params, &responder)
15
+ dispatcher = self.default_dispatcher
16
+ config = Hash.new
17
+
18
+ params.each do |param|
19
+ dispatcher = param if param.kind_of? CoreDispatcher
20
+ config = param if param.kind_of? Hash
21
+ end
22
+
23
+ # Autodetect if don't have a valid dispatcher
24
+ dispatcher = self.autodetect if not dispatcher
25
+
26
+ # create our Dispatcher, and start listening for requests
27
+ dispatcher = dispatcher.new(responder, config)
28
+ dispatcher.listen
29
+ end
30
+
31
+ # Attempts to autodetects an appropriate dispatcher interface depending on the current
32
+ # configuration. If an appropriate interface is found, it will be loaded, and the appropriate
33
+ # Dispatcher subclass will be returned. (CoreDispatcher by default)
34
+ def self.autodetect
35
+ # Check for CGI
36
+ if ENV.has_key? 'GATEWAY_INTERFACE' and ENV['GATEWAY_INTERFACE'][0,4].upcase == 'CGI/'
37
+ require 'dispatcher/cgi'
38
+ return CGIDispatcher
39
+
40
+ # Check for CLI
41
+ elsif ENV.has_key? 'PROMPT'
42
+ require 'dispatcher/cli'
43
+ return CLIDispatcher
44
+
45
+ # By default, we turn to the CoreDispatcher
46
+ else
47
+ return CoreDispatcher
48
+ end
49
+ end
50
+
51
+ # A core Dispatcher interface.
52
+ #
53
+ # This is intended as a 'virtual' class that all other dispatcher interfaces inherit from.
54
+ class CoreDispatcher
55
+ # Creates a new
56
+ def initialize(responder, *config)
57
+ @responder = responder
58
+ @config = config
59
+ end
60
+
61
+ # Start listening for incoming requests.
62
+ def listen
63
+ err = 'You are attempting to use the default Dispatcher interface. '
64
+ err += 'Most likely the library was unable to autodetect an appropriate interface. '
65
+ err += 'Try specifying one manually via a require \'request/<interface>\'.'
66
+
67
+ raise TypeError, err, caller
68
+ end
69
+
70
+ # Super-simple handler.
71
+ #
72
+ # Just outputs any STDOUT content. Buffering should be done at the Responder level.
73
+ def handle(request)
74
+ response = Response.new(@responder)
75
+ response.render(request)
76
+ end
77
+
78
+ # Set the default_dispatcher to the last class that inherits CoreDispatcher.
79
+ def self.inherited(subclass)
80
+ Dispatcher.default_dispatcher = subclass
81
+ end
82
+ end
83
+
84
+ # A HTTP request.
85
+ class Request
86
+ # Any HTTP headers passed along with the request
87
+ attr_accessor :headers
88
+
89
+ # The body of the request, if given.
90
+ attr_accessor :body
91
+
92
+ # A full set of CGI environment values
93
+ attr_accessor :values
94
+
95
+ # Every environment variable passed from the server
96
+ attr_accessor :env
97
+
98
+ # Generates a new request.
99
+ #
100
+ # Any values passed will be set to the appropriate attributes.
101
+ def initialize(*vals)
102
+ @headers = HTTPHeaderHash.new
103
+ @env = Hash.new
104
+ @values = Hash.new
105
+
106
+ vals.each do |key, value|
107
+ instance_variable_set(key, value)
108
+ end
109
+ end
110
+
111
+ # The current request method.
112
+ def method
113
+ @values['REQUEST_METHOD']
114
+ end
115
+
116
+ # The requested path
117
+ def path
118
+ @values['PATH_INFO']
119
+ end
120
+
121
+ # The fully qualified URI of the request
122
+ def uri
123
+ # protocol
124
+ result = 'http'
125
+ if @values['HTTPS'] == 'on'
126
+ result += 's'
127
+ end
128
+
129
+ # server/port
130
+ result += "://#{@values['HTTP_HOST']}"
131
+
132
+ # path
133
+ result += @values['PATH_INFO']
134
+
135
+ # query
136
+ if @values['QUERY_STRING'] != ''
137
+ result += "?#{@values['QUERY_STRING']}"
138
+ end
139
+
140
+ return result
141
+ end
142
+ alias_method :url, :uri
143
+ end
144
+
145
+ # A response object embodies a single HTTP response to the Dispatcher.
146
+ class Response
147
+ attr_reader :body, :headers
148
+ attr_accessor :body_start
149
+
150
+ # Defines a new response
151
+ #
152
+ # The responder is a Proc object, that when run, output the HTTP response, via $stdout and any
153
+ # helper functions defined in this class, such as header().
154
+ #
155
+ # If buffered is true, the responder method will be completely processed before any output is
156
+ # rendered or returned.
157
+ def initialize(responder, buffered = false)
158
+ @headers = HTTPHeaderHash.new
159
+ @body = ''
160
+
161
+ @body_start = false
162
+ @buffered = buffered
163
+
164
+ (class <<self; self; end).send(:define_method, :responder, responder)
165
+ end
166
+
167
+ # Sets a header for the response.
168
+ #
169
+ # If the response is currently unbuffered, the header will be rendered once $stdout is written
170
+ # to, otherwise it will be rendered at the end of buffering (or the end of the responder).
171
+ #
172
+ # Please note that if a header is sent after content is already rendered, an IOError will be
173
+ # thrown.
174
+ def header(field, value)
175
+ if @body_start
176
+ raise IOError, 'Attempt to output headers after the response body has started.', caller
177
+ end
178
+
179
+ @headers[field] = value
180
+ end
181
+
182
+ # Buffers a block's output to $stdout.
183
+ #
184
+ # The method will return the buffered output if passback is true.
185
+ def buffer(passback = false)
186
+ old_out = $stdout
187
+ $stdout = $stdout.clone
188
+ class <<$stdout
189
+ attr_accessor :buffer
190
+
191
+ def write(value)
192
+ @buffer << value
193
+ end
194
+ end
195
+ $stdout.buffer = ''
196
+
197
+ yield
198
+
199
+ result = $stdout.buffer
200
+ $stdout = old_out
201
+
202
+ if passback
203
+ return result
204
+
205
+ else
206
+ # if this is the first render of the body, we need to print out any headers
207
+ if not @body_start
208
+ @body_start = true
209
+
210
+ @headers.each do |field, value|
211
+ print field + ': ' + value + CRLF
212
+ end
213
+ print CRLF
214
+ end
215
+
216
+ print result
217
+ end
218
+ end
219
+
220
+ # Renders the request
221
+ #
222
+ # If the response was designated as a buffered response at creation, the headers and body may
223
+ # be retrieved from the object (no output will be rendered by this method).
224
+ #
225
+ # If the response is not buffered, the body and header attributes must be ignored.
226
+ def render(request)
227
+ if @buffered
228
+ @body = buffer(true) do
229
+ responder(request)
230
+ end
231
+ else
232
+ # intercept the first chunk of output so we can detect header usage
233
+ old_out = $stdout
234
+ $stdout = $stdout.clone
235
+ class <<$stdout
236
+ attr_accessor :response
237
+
238
+ def write(val)
239
+ if not @ignore and not @response.body_start
240
+ @response.body_start = true
241
+
242
+ @response.headers.each do |field, value|
243
+ print field + ': ' + value + CRLF
244
+ end
245
+
246
+ print CRLF
247
+ end
248
+
249
+ super(val)
250
+ end
251
+ end
252
+ $stdout.response = self
253
+
254
+ responder(request)
255
+ print ''
256
+
257
+ $stdout = old_out
258
+ end
259
+ end
260
+ end
261
+
262
+ # Defines a Hash of HTTP headers.
263
+ #
264
+ # Keys are case-insensitive, and converted to 'pretty' keys
265
+ # e.g. 'CoNtEnT-TYPE' would be 'Content-Type'
266
+ #
267
+ # This class <b>does not</b> parse for valid HTTP header fields, so you may recieve
268
+ # errors from your webserver if a malformed header is passed.
269
+ class HTTPHeaderHash < Hash
270
+ def initialize(*values)
271
+ values.each do |field, value|
272
+ self[field] = value
273
+ end
274
+ end
275
+
276
+ # Converts the given key string into a 'pretty' HTTP header field
277
+ def self.pretty_field(field)
278
+ chunks = field.gsub('_', '-').split('-')
279
+
280
+ chunks.collect! do |val|
281
+ val.capitalize
282
+ end
283
+
284
+ return chunks.join('-')
285
+ end
286
+
287
+ # Sets the key to value.
288
+ def []=(field, value)
289
+ super(HTTPHeaderHash.pretty_field(field.to_s), value.to_s)
290
+ end
291
+
292
+ # Returns the value stored for key.
293
+ def [](field)
294
+ super(HTTPHeaderHash.pretty_field(field.to_s))
295
+ end
296
+ end
297
+
298
+ # Typically, this returns the last-defined subclass of CoreDispatcher.
299
+ def self.default_dispatcher
300
+ @default_dispatcher
301
+ end
302
+ # This is automatically set when a class inherits from CoreDispatcher.
303
+ def self.default_dispatcher=(dispatcher)
304
+ @default_dispatcher = dispatcher
305
+ end
306
+ end
@@ -0,0 +1,74 @@
1
+ require 'dispatcher'
2
+
3
+
4
+ module Dispatcher
5
+ # CGI Dispatcher
6
+ #
7
+ # Manages dispatching to CGI interfaces.
8
+ class CGIDispatcher < CoreDispatcher
9
+ # Immediately processes the request for the values given in ENV.
10
+ def listen
11
+ # set up our request
12
+ request = generate_request
13
+
14
+ # and handle it
15
+ handle(request)
16
+ end
17
+
18
+ # Generates a reques object from the current values of ENV
19
+ def generate_request
20
+ request = Request.new
21
+
22
+ # Copy information out of the environment
23
+ ENV.each do |key, value|
24
+ request.env[key] = value
25
+
26
+ if key[0,5].upcase == 'HTTP_'
27
+ request.headers[key[5, key.length - 5]] = value
28
+ end
29
+ end
30
+
31
+ # Set up the CGI value Hash
32
+ request.values = build_cgi_environment(request.env)
33
+
34
+ return request
35
+ end
36
+
37
+ #:nodoc:
38
+ # List of environment values used in the CGI environment.
39
+ # We use Hash keys to avoid O(n^2) type behavior.
40
+ CGI_VARS = {
41
+ 'AUTH_TYPE' => true,
42
+ 'CONTENT_LENGTH' => true,
43
+ 'CONTENT_TYPE' => true,
44
+ 'GATEWAY_INTERFACE' => true,
45
+ 'HTTPS' => true, # non-spec
46
+ 'PATH_INFO' => true,
47
+ 'PATH_TRANSLATED' => true,
48
+ 'QUERY_STRING' => true,
49
+ 'REMOTE_ADDR' => true,
50
+ 'REMOTE_HOST' => true,
51
+ 'REMOTE_IDENT' => true,
52
+ 'REMOTE_USER' => true,
53
+ 'REQUEST_METHOD' => true,
54
+ 'SCRIPT_NAME' => true,
55
+ 'SERVER_NAME' => true,
56
+ 'SERVER_PORT' => true,
57
+ 'SERVER_PROTOCOL' => true,
58
+ 'SERVER_SOFTWARE' => true,
59
+ }
60
+
61
+ # Generates a hash of a CGI environment from values in the given hash.
62
+ def build_cgi_environment(env)
63
+ cgi_env = Hash.new
64
+
65
+ env.each do |key, value|
66
+ if key[0,5].upcase == 'HTTP_' or CGI_VARS[key.upcase]
67
+ cgi_env[key] = value
68
+ end
69
+ end
70
+
71
+ return cgi_env
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,76 @@
1
+ require 'dispatcher'
2
+ require 'getoptlong'
3
+ require 'uri'
4
+
5
+ module Dispatcher
6
+ # Command line Dispatcher interface.
7
+ #
8
+ # The CLI interface allows for easy command line debugging of Responders.
9
+ #
10
+ # Usage: script.rb [URI] [switches]
11
+ class CLIDispatcher < CoreDispatcher
12
+ OPTS = GetoptLong.new(
13
+ ['--help', '-h', GetoptLong::NO_ARGUMENT],
14
+ ['--clean', '-c', GetoptLong::NO_ARGUMENT]
15
+ )
16
+
17
+ # Sets up the request, and handles the response.
18
+ def listen
19
+ # read the options into a Hash
20
+ options = Hash.new
21
+ OPTS.quiet = true # shut it up
22
+
23
+ begin
24
+ OPTS.each do |opt, value|
25
+ options[opt] = value
26
+ end
27
+ # catch invalid option exceptions
28
+ rescue GetoptLong::InvalidOption => err
29
+ puts err.to_s + ' (-h will show valid options)'
30
+ end
31
+
32
+ # help?
33
+ if options.has_key? '--help'
34
+ display_help
35
+
36
+ # otherwise we're going to handle a request
37
+ else
38
+ # set up our request
39
+ request = Request.new
40
+
41
+ # and handle it
42
+ if options.has_key? '--clean'
43
+ handle_cleaned(request)
44
+ else
45
+ handle(request)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Displays a nicely formatted copy of the response
51
+ def handle_cleaned(request)
52
+ response = Response.new(@responder, true)
53
+ response.render(request)
54
+ body = response.body
55
+
56
+ # strip tags
57
+ body.gsub!(/<\/?[^>]*>/, "")
58
+
59
+ print body
60
+ end
61
+
62
+ # Displays the command line help and usage details
63
+ def display_help
64
+ print %{
65
+ Usage: #{File.basename($0)} [URI] [options]
66
+
67
+ Options:
68
+ -c/--clean Cleans up any HTML output for easy screen reading
69
+ -h/--help Help. You're looking at it
70
+
71
+ Further Information:
72
+ http://dispatcher.rubyforge.org
73
+ }
74
+ end
75
+ end
76
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: dispatcher
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2006-10-08 00:00:00 -05:00
8
+ summary: A lightweight HTTP dispatch interface between Ruby and most webserver configurations.
9
+ require_paths:
10
+ - lib
11
+ email: imacleod@gmail.com
12
+ homepage: http://dispatcher.rubyforge.org/
13
+ rubyforge_project: dispatcher
14
+ description: Dispatcher provides a simple and consistent interface between Ruby and most webserver configurations. It is typically used in conjunction with a request-building system, such as the Responder gem.
15
+ autorequire: dispatcher
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Ian MacLeod
31
+ files:
32
+ - lib/dispatch.rb
33
+ - lib/dispatcher.rb
34
+ - lib/dispatcher/cgi.rb
35
+ - lib/dispatcher/cli.rb
36
+ - SyntaxCheck/lib/dispatcher.rb
37
+ - SyntaxCheck/lib/dispatcher/cgi.rb
38
+ - SyntaxCheck/lib/dispatcher/cli.rb
39
+ - LICENSE.txt
40
+ - README.txt
41
+ - TODO.txt
42
+ test_files: []
43
+
44
+ rdoc_options:
45
+ - --title
46
+ - Dispatcher
47
+ - --main
48
+ - README.txt
49
+ extra_rdoc_files:
50
+ - LICENSE.txt
51
+ - README.txt
52
+ - TODO.txt
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ requirements: []
58
+
59
+ dependencies: []
60
+