mongrel2 0.0.1

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