dispatcher 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+