mongrel2 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.
Files changed (70) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.gemtest +0 -0
  3. data/History.rdoc +4 -0
  4. data/Manifest.txt +66 -0
  5. data/README.rdoc +169 -0
  6. data/Rakefile +77 -0
  7. data/bin/m2sh.rb +600 -0
  8. data/data/mongrel2/bootstrap.html +25 -0
  9. data/data/mongrel2/config.sql +84 -0
  10. data/data/mongrel2/mimetypes.sql +855 -0
  11. data/examples/README.txt +6 -0
  12. data/examples/config.rb +54 -0
  13. data/examples/helloworld-handler.rb +31 -0
  14. data/examples/request-dumper.rb +45 -0
  15. data/examples/request-dumper.tmpl +71 -0
  16. data/examples/run +17 -0
  17. data/lib/mongrel2.rb +62 -0
  18. data/lib/mongrel2/config.rb +212 -0
  19. data/lib/mongrel2/config/directory.rb +78 -0
  20. data/lib/mongrel2/config/dsl.rb +206 -0
  21. data/lib/mongrel2/config/handler.rb +124 -0
  22. data/lib/mongrel2/config/host.rb +88 -0
  23. data/lib/mongrel2/config/log.rb +48 -0
  24. data/lib/mongrel2/config/mimetype.rb +15 -0
  25. data/lib/mongrel2/config/proxy.rb +15 -0
  26. data/lib/mongrel2/config/route.rb +51 -0
  27. data/lib/mongrel2/config/server.rb +58 -0
  28. data/lib/mongrel2/config/setting.rb +15 -0
  29. data/lib/mongrel2/config/statistic.rb +23 -0
  30. data/lib/mongrel2/connection.rb +212 -0
  31. data/lib/mongrel2/constants.rb +159 -0
  32. data/lib/mongrel2/control.rb +165 -0
  33. data/lib/mongrel2/exceptions.rb +59 -0
  34. data/lib/mongrel2/handler.rb +309 -0
  35. data/lib/mongrel2/httprequest.rb +51 -0
  36. data/lib/mongrel2/httpresponse.rb +187 -0
  37. data/lib/mongrel2/jsonrequest.rb +43 -0
  38. data/lib/mongrel2/logging.rb +241 -0
  39. data/lib/mongrel2/mixins.rb +143 -0
  40. data/lib/mongrel2/request.rb +148 -0
  41. data/lib/mongrel2/response.rb +74 -0
  42. data/lib/mongrel2/table.rb +216 -0
  43. data/lib/mongrel2/xmlrequest.rb +36 -0
  44. data/spec/lib/constants.rb +237 -0
  45. data/spec/lib/helpers.rb +246 -0
  46. data/spec/lib/matchers.rb +50 -0
  47. data/spec/mongrel2/config/directory_spec.rb +91 -0
  48. data/spec/mongrel2/config/dsl_spec.rb +218 -0
  49. data/spec/mongrel2/config/handler_spec.rb +118 -0
  50. data/spec/mongrel2/config/host_spec.rb +30 -0
  51. data/spec/mongrel2/config/log_spec.rb +95 -0
  52. data/spec/mongrel2/config/proxy_spec.rb +30 -0
  53. data/spec/mongrel2/config/route_spec.rb +83 -0
  54. data/spec/mongrel2/config/server_spec.rb +84 -0
  55. data/spec/mongrel2/config/setting_spec.rb +30 -0
  56. data/spec/mongrel2/config/statistic_spec.rb +30 -0
  57. data/spec/mongrel2/config_spec.rb +111 -0
  58. data/spec/mongrel2/connection_spec.rb +172 -0
  59. data/spec/mongrel2/constants_spec.rb +32 -0
  60. data/spec/mongrel2/control_spec.rb +192 -0
  61. data/spec/mongrel2/handler_spec.rb +261 -0
  62. data/spec/mongrel2/httpresponse_spec.rb +232 -0
  63. data/spec/mongrel2/logging_spec.rb +76 -0
  64. data/spec/mongrel2/mixins_spec.rb +62 -0
  65. data/spec/mongrel2/request_spec.rb +157 -0
  66. data/spec/mongrel2/response_spec.rb +81 -0
  67. data/spec/mongrel2/table_spec.rb +176 -0
  68. data/spec/mongrel2_spec.rb +34 -0
  69. metadata +294 -0
  70. metadata.gz.sig +0 -0
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mongrel2' unless defined?( Mongrel2 )
4
+ require 'mongrel2/config' unless defined?( Mongrel2::Config )
5
+
6
+ # Mongrel2 Directory (Dir) configuration class
7
+ class Mongrel2::Config::Directory < Mongrel2::Config( :directory )
8
+
9
+ ### As of Mongrel2/1.7.5:
10
+ # CREATE TABLE directory (id INTEGER PRIMARY KEY,
11
+ # base TEXT,
12
+ # index_file TEXT,
13
+ # default_ctype TEXT,
14
+ # cache_ttl INTEGER DEFAULT 0);
15
+
16
+ ### Sequel validation callback: add errors if the record is invalid.
17
+ def validate
18
+ self.validate_base
19
+ self.validate_index_file
20
+ self.validate_default_ctype
21
+ self.validate_cache_ttl
22
+ end
23
+
24
+
25
+ #########
26
+ protected
27
+ #########
28
+
29
+ ### Validate the 'base' directory which will be served.
30
+ def validate_base
31
+ if self.base
32
+ if self.base.start_with?( '/' )
33
+ errmsg = "[%p]: shouldn't start with '/'; that will fail when not in chroot." %
34
+ [ self.base ]
35
+ self.log.error( 'base ' + errmsg )
36
+ self.errors.add( :base, errmsg )
37
+ end
38
+
39
+ unless self.base.end_with?( '/' )
40
+ errmsg = "[%p]: must end with '/' or it won't work right." %
41
+ [ self.base ]
42
+ self.log.error( 'base ' + errmsg )
43
+ self.errors.add( :base, errmsg )
44
+ end
45
+ else
46
+ errmsg = "missing or nil"
47
+ self.log.error( 'base ' + errmsg )
48
+ self.errors.add( :base, errmsg )
49
+ end
50
+ end
51
+
52
+
53
+ ### Validate the 'index_file' attribute.
54
+ def validate_index_file
55
+ self.validates_presence( :index_file, :message => "must not be nil" )
56
+ end
57
+
58
+
59
+ ### Validate the index file attribute.
60
+ def validate_default_ctype
61
+ self.validates_presence( :default_ctype, :message => "must not be nil" )
62
+ end
63
+
64
+
65
+ ### Validate the cache TTL if one is set.
66
+ def validate_cache_ttl
67
+ if self.cache_ttl && Integer( self.cache_ttl ) < 0
68
+ errmsg = "[%p]: not a positive Integer" % [ self.cache_ttl ]
69
+ self.log.error( 'cache_ttl' + errmsg )
70
+ self.errors.add( :cache_ttl, errmsg )
71
+ end
72
+ end
73
+
74
+
75
+
76
+
77
+ end # class Mongrel2::Config::Directory
78
+
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mongrel2' unless defined?( Mongrel2 )
4
+ require 'mongrel2/config' unless defined?( Mongrel2::Config )
5
+
6
+ # Mongrel2 configuration DSL mixin
7
+ #
8
+ # == Example
9
+ #
10
+ # This is the mongrel2.org config re-expressed in the Ruby DSL:
11
+ #
12
+ # # the server to run them all
13
+ # server '2f62bd5-9e59-49cd-993c-3b6013c28f05' do
14
+ #
15
+ # access_log "/logs/access.log"
16
+ # error_log "/logs/error.log"
17
+ # chroot "./"
18
+ # pid_file "/run/mongrel2.pid"
19
+ # default_host "mongrel2.org"
20
+ # name "main"
21
+ # port 6767
22
+ #
23
+ # # your main host
24
+ # host "mongrel2.org" do
25
+ #
26
+ # # a sample of doing some handlers
27
+ # route '@chat', handler(
28
+ # 'tcp://127.0.0.1:9999',
29
+ # '54c6755b-9628-40a4-9a2d-cc82a816345e',
30
+ # 'tcp://127.0.0.1:9998'
31
+ # )
32
+ #
33
+ # route '/handlertest', handler(
34
+ # 'tcp://127.0.0.1:9997',
35
+ # '34f9ceee-cd52-4b7f-b197-88bf2f0ec378',
36
+ # 'tcp://127.0.0.1:9996'
37
+ # )
38
+ #
39
+ # # a sample proxy route
40
+ # web_app_proxy = proxy( '127.0.0.1', 8080 )
41
+ #
42
+ # route '/chat/', web_app_proxy
43
+ # route '/', web_app_proxy
44
+ #
45
+ # # here's a sample directory
46
+ # test_directory = directory(
47
+ # 'tests/',
48
+ # :index_file => 'index.html',
49
+ # :default_ctype => 'text/plain'
50
+ # )
51
+ #
52
+ # route '/tests/', test_directory
53
+ # route '/testsmulti/(.*.json)', test_directory
54
+ #
55
+ # chat_demo_dir = directory(
56
+ # 'examples/chat/static/',
57
+ # :index_file => 'index.html',
58
+ # :default_ctype => 'text/plain'
59
+ # )
60
+ #
61
+ # route '/chatdemo/', chat_demo_dir
62
+ # route '/static/', chat_demo_dir
63
+ #
64
+ # route '/mp3stream', handler(
65
+ # 'tcp://127.0.0.1:9995',
66
+ # '53f9f1d1-1116-4751-b6ff-4fbe3e43d142',
67
+ # 'tcp://127.0.0.1:9994'
68
+ # )
69
+ # end
70
+ #
71
+ # end
72
+ #
73
+ # settings(
74
+ # "zeromq.threads" => 1,
75
+ # "upload.temp_store" => "/home/zedshaw/projects/mongrel2/tmp/upload.XXXXXX",
76
+ # "upload.temp_store_mode" => "0666"
77
+ # )
78
+ #
79
+ module Mongrel2::Config::DSL
80
+
81
+
82
+ # A decorator object that provides the DSL-ish interface to the various Config
83
+ # objects. It derives its interface on the fly from columns of the class it's
84
+ # created with and a DSLMethods mixin if the target class defines one.
85
+ class Adapter
86
+ include Mongrel2::Loggable
87
+
88
+ ### Adapt the specified +target+ to use a declarative interface.
89
+ def initialize( targetclass, opts={} )
90
+ self.log.debug "Wrapping a %p" % [ targetclass ]
91
+ @targetclass = targetclass
92
+ @target = @targetclass.new( opts )
93
+ self.decorate_with_column_declaratives( @target )
94
+ self.decorate_with_custom_declaratives( @targetclass )
95
+ end
96
+
97
+
98
+ ######
99
+ public
100
+ ######
101
+
102
+ # The decorated object
103
+ attr_reader :target
104
+
105
+
106
+ ### Backport the singleton_class method if there isn't one.
107
+ unless instance_methods.include?( :singleton_class )
108
+ def singleton_class
109
+ class << self; self; end
110
+ end
111
+ end
112
+
113
+ ### Add a declarative singleton method for the columns of the +adapted_object+.
114
+ def decorate_with_column_declaratives( adapted_object )
115
+ columns = adapted_object.columns
116
+ self.log.debug " decorating for columns: %s" % [ columns.map( &:to_s ).sort.join(', ') ]
117
+
118
+ columns.each do |colname|
119
+
120
+ # Create a method that will act as a writer if called with an
121
+ # argument, and a reader if not.
122
+ method_body = Proc.new do |*args|
123
+ if args.empty?
124
+ self.target.send( colname )
125
+ else
126
+ self.target.send( "#{colname}=", *args )
127
+ end
128
+ end
129
+
130
+ # Install the method
131
+ self.singleton_class.send( :define_method, colname, &method_body )
132
+ end
133
+ end
134
+
135
+
136
+ ### Mix in methods defined by the "DSLMethods" mixin defined by the class
137
+ ### of the object being adapted.
138
+ def decorate_with_custom_declaratives( objectclass )
139
+ return unless objectclass.const_defined?( :DSLMethods )
140
+ self.singleton_class.send( :include, objectclass.const_get(:DSLMethods) )
141
+ end
142
+
143
+
144
+ end # class Adapter
145
+
146
+
147
+ ### Create a Mongrel2::Config::Server with the specified +uuid+, evaluate
148
+ ### the block (if given) within its context, and return it.
149
+ def server( uuid, &block )
150
+ Mongrel2::Config.init_database
151
+
152
+ Mongrel2.log.debug "Server [%s] (block: %p)" % [ uuid, block ]
153
+ adapter = Adapter.new( Mongrel2::Config::Server, :uuid => uuid )
154
+ adapter.instance_eval( &block ) if block
155
+ return adapter.target
156
+ end
157
+
158
+
159
+ ### Set the value of one of the 'Tweakable Expert Settings'
160
+ def setting( key, val )
161
+ Mongrel2::Config.init_database
162
+ return Mongrel2::Config::Setting.create( :key => key, :value => val )
163
+ end
164
+
165
+
166
+ ### Set some 'Tweakable Expert Settings' en masse
167
+ def settings( hash )
168
+ result = []
169
+
170
+ Mongrel2::Config.db.transaction do
171
+ hash.each do |key, val|
172
+ result << setting( key, val )
173
+ end
174
+ end
175
+
176
+ return result
177
+ end
178
+
179
+
180
+ ### Set up a mimetype mapping between files with the given +extension+ and +mimetype+.
181
+ def mimetype( extension, mimetype )
182
+ Mongrel2::Config.init_database
183
+
184
+ type = Mongrel2::Config::Mimetype.find_or_create( :extension => extension )
185
+ type.mimetype = mimetype
186
+ type.save
187
+
188
+ return type
189
+ end
190
+
191
+
192
+ ### Set some mimetypes en masse.
193
+ def mimetypes( hash )
194
+ result = []
195
+
196
+ Mongrel2::Config.db.transaction do
197
+ hash.each do |ext, mimetype|
198
+ result << mimetype( ext, mimetype )
199
+ end
200
+ end
201
+
202
+ return result
203
+ end
204
+
205
+ end # module Mongrel2::Config::DSL
206
+
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mongrel2' unless defined?( Mongrel2 )
4
+ require 'mongrel2/config' unless defined?( Mongrel2::Config )
5
+
6
+ # Mongrel2 Handler configuration class
7
+ class Mongrel2::Config::Handler < Mongrel2::Config( :handler )
8
+
9
+ ### As of Mongrel2/1.7.5:
10
+ # CREATE TABLE handler (id INTEGER PRIMARY KEY,
11
+ # send_spec TEXT,
12
+ # send_ident TEXT,
13
+ # recv_spec TEXT,
14
+ # recv_ident TEXT,
15
+ # raw_payload INTEGER DEFAULT 0,
16
+ # protocol TEXT DEFAULT 'json');
17
+
18
+
19
+ # The list of 0mq transports Mongrel2 can use; "You need to use the
20
+ # ZeroMQ syntax for configuring them, but this means with one
21
+ # configuration format you can use handlers that are using UDP, TCP,
22
+ # Unix, or PGM transports." Note that I'm assuming by 'udp' Zed means
23
+ # 'epgm', as I can't find any udp 0mq transport.
24
+ VALID_SPEC_SCHEMES = %w[epgm tcp ipc pgm]
25
+
26
+ # The list of valid protocols
27
+ #--
28
+ # handler.h: typedef enum { HANDLER_PROTO_JSON, HANDLER_PROTO_TNET } handler_protocol_t;
29
+ VALID_PROTOCOLS = %w[json tnetstring]
30
+
31
+
32
+ ##
33
+ # :method: by_send_ident( uuid )
34
+ #
35
+ # Look up a Handler by its send_ident, which should be a +uuid+ or similar String.
36
+ def_dataset_method( :by_send_ident ) do |ident|
37
+ filter( :send_ident => ident )
38
+ end
39
+
40
+
41
+ ### Validate the object prior to saving it.
42
+ def validate
43
+ self.validate_idents
44
+ self.validate_specs
45
+ self.validate_protocol
46
+ end
47
+
48
+
49
+ #########
50
+ protected
51
+ #########
52
+
53
+ ### Turn nil recv_ident values into the empty string before validating.
54
+ def before_validation
55
+ self.recv_ident ||= ''
56
+ end
57
+
58
+
59
+ ### Validate the send_ident and recv_ident
60
+ ### --
61
+ ### :FIXME: I'm not sure if this is actually necessary, but it seems like
62
+ ### the ident should at least be UUID-like like the server ident.
63
+ def validate_idents
64
+ unless self.send_ident =~ /^\w[\w\-]+$/
65
+ errmsg = "[%p]: invalid sender identity (should be UUID-like)" % [ self.send_ident ]
66
+ self.log.error( 'send_ident: ' + errmsg )
67
+ self.errors.add( :send_ident, errmsg )
68
+ end
69
+
70
+ unless self.recv_ident == '' || self.send_ident =~ /^\w[\w\-]+$/
71
+ errmsg = "[%p]: invalid receiver identity (should be empty string or UUID-like)" %
72
+ [ self.recv_ident ]
73
+ self.log.error( 'send_ident: ' + errmsg )
74
+ self.errors.add( :send_ident, errmsg )
75
+ end
76
+ end
77
+
78
+
79
+ ### Validate the send_spec and recv_spec.
80
+ def validate_specs
81
+ if err = check_0mq_spec( self.send_spec )
82
+ errmsg = "[%p]: %s" % [ self.send_spec, err ]
83
+ self.log.error( 'send_spec: ' + errmsg )
84
+ self.errors.add( :recv_spec, errmsg )
85
+ end
86
+
87
+ if err = check_0mq_spec( self.recv_spec )
88
+ errmsg = "[%p]: %s" % [ self.recv_spec, err ]
89
+ self.log.error( 'recv_spec: ' + errmsg )
90
+ self.errors.add( :recv_spec, errmsg )
91
+ end
92
+ end
93
+
94
+
95
+ ### Validate the handler's protocol.
96
+ def validate_protocol
97
+ return unless self.protocol # nil == default
98
+ unless VALID_PROTOCOLS.include?( self.protocol )
99
+ errmsg = "[%p]: invalid" % [ self.protocol ]
100
+ self.log.error( 'protocol: ' + errmsg )
101
+ self.errors.add( :protocol, errmsg )
102
+ end
103
+ end
104
+
105
+
106
+
107
+ #######
108
+ private
109
+ #######
110
+
111
+ ### Returns +true+ if +url+ is a valid 0mq transport specification.
112
+ def check_0mq_spec( url )
113
+ return "must not be nil" unless url
114
+
115
+ u = URI( url )
116
+ return "invalid 0mq transport #{u.scheme}" unless VALID_SPEC_SCHEMES.include?( u.scheme )
117
+
118
+ return nil
119
+ rescue URI::InvalidURIError
120
+ return 'not a URI; should be something like "tcp://127.0.0.1:9998"'
121
+ end
122
+
123
+ end # class Mongrel2::Config::Handler
124
+
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mongrel2' unless defined?( Mongrel2 )
4
+ require 'mongrel2/config' unless defined?( Mongrel2::Config )
5
+
6
+ # Mongrel2 Host configuration class
7
+ class Mongrel2::Config::Host < Mongrel2::Config( :host )
8
+
9
+ ### As of Mongrel2/1.7.5:
10
+ # CREATE TABLE host (id INTEGER PRIMARY KEY,
11
+ # server_id INTEGER,
12
+ # maintenance BOOLEAN DEFAULT 0,
13
+ # name TEXT,
14
+ # matching TEXT);
15
+
16
+ one_to_many :routes
17
+ many_to_one :server
18
+
19
+ ### DSL methods for the Server context besides those automatically-generated from its
20
+ ### columns.
21
+ module DSLMethods
22
+
23
+ ### Add a Mongrel2::Config::Route to the Host object.
24
+ def route( path, target, opts={} )
25
+ self.target.save
26
+ Mongrel2.log.debug "Route %s -> %p [%p]" % [ path, target, opts ]
27
+
28
+ args = { :path => path, :target => target }
29
+ args.merge!( opts )
30
+ route = Mongrel2::Config::Route.new( args )
31
+
32
+ self.target.add_route( route )
33
+ end
34
+
35
+
36
+ # These are route _arguments_, so they need to be declared in Host's scope rather
37
+ # than Route's.
38
+
39
+ ### Create a new Mongrel2::Config::Directory object for the specified +base+ and
40
+ ### return it.
41
+ def directory( base, index_file, default_ctype='text/plain', opts={} )
42
+ opts.merge!( :base => base, :index_file => index_file, :default_ctype => default_ctype )
43
+ return Mongrel2::Config::Directory.create( opts )
44
+ end
45
+
46
+
47
+ ### Create a new Mongrel2::Config::Proxy object for the specified +addr+ and
48
+ ### +port+ and return it.
49
+ def proxy( addr, port=80 )
50
+ return Mongrel2::Config::Proxy.create( :addr => addr, :port => port )
51
+ end
52
+
53
+
54
+ ### Create a new Mongrel2::Config::Handler object with the specified +send_spec+,
55
+ ### +send_ident+, +recv_spec+, +recv_ident+, and +options+ and return it.
56
+ def handler( send_spec, send_ident, recv_spec=nil, recv_ident='', options={} )
57
+ # Shift the opts hash over if the other optional args were omitted
58
+ if recv_spec.is_a?( Hash )
59
+ options = recv_spec
60
+ recv_spec = nil
61
+ elsif recv_ident.is_a?( Hash )
62
+ options = recv_ident
63
+ recv_ident = ''
64
+ end
65
+
66
+ # Default to one port below the request spec
67
+ unless recv_spec
68
+ port = send_spec[ /:(\d+)$/, 1 ] or
69
+ "Can't guess default port for a send_spec without one (%p)" % [ send_spec ]
70
+ recv_spec = URI( send_spec )
71
+ recv_spec.port = port.to_i - 1
72
+ end
73
+
74
+ options.merge!(
75
+ :send_spec => send_spec.to_s,
76
+ :send_ident => send_ident,
77
+ :recv_spec => recv_spec.to_s,
78
+ :recv_ident => recv_ident
79
+ )
80
+
81
+ Mongrel2.log.debug "Creating handler with options: %p" % [ options ]
82
+ return Mongrel2::Config::Handler.create( options )
83
+ end
84
+
85
+ end # module DSLMethods
86
+
87
+ end # class Mongrel2::Config::Host
88
+