mongrel2 0.26.0 → 0.27.0
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.
- data.tar.gz.sig +0 -0
- data/ChangeLog +36 -1
- data/History.rdoc +6 -0
- data/Manifest.txt +1 -0
- data/README.rdoc +52 -21
- data/bin/m2sh.rb +55 -1
- data/data/mongrel2/config.rb.in +71 -0
- data/examples/.env +1 -0
- data/examples/Procfile +1 -1
- data/examples/config.rb +1 -1
- data/examples/ws-echo.rb +12 -0
- data/lib/mongrel2.rb +2 -2
- data/lib/mongrel2/config.rb +3 -8
- data/lib/mongrel2/connection.rb +1 -1
- data/lib/mongrel2/constants.rb +12 -0
- data/lib/mongrel2/control.rb +1 -1
- data/lib/mongrel2/handler.rb +21 -8
- data/lib/mongrel2/httpresponse.rb +6 -5
- data/lib/mongrel2/testing.rb +52 -8
- data/lib/mongrel2/websocket.rb +101 -5
- data/spec/lib/constants.rb +48 -0
- data/spec/lib/helpers.rb +50 -4
- data/spec/mongrel2/handler_spec.rb +36 -0
- data/spec/mongrel2/httpresponse_spec.rb +4 -3
- data/spec/mongrel2/websocket_spec.rb +64 -0
- metadata +3 -2
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/ChangeLog
CHANGED
@@ -1,5 +1,40 @@
|
|
1
|
+
2012-06-30 Michael Granger <ged@FaerieMUD.org>
|
2
|
+
|
3
|
+
* Manifest.txt, README.rdoc, bin/m2sh.rb, data/mongrel2/config.rb.in,
|
4
|
+
lib/mongrel2/config.rb, lib/mongrel2/constants.rb:
|
5
|
+
Adding 'bootstrap' and 'quickstart' commands to m2sh.rb.
|
6
|
+
|
7
|
+
Also adding documentation for them to the README, and moved the
|
8
|
+
DATA_DIR constants into mongrel2/constants.
|
9
|
+
[2f2b9b4bc86f] [tip]
|
10
|
+
|
11
|
+
2012-06-27 Michael Granger <ged@FaerieMUD.org>
|
12
|
+
|
13
|
+
* examples/.env, examples/Procfile, examples/config.rb, examples/ws-
|
14
|
+
echo.rb, lib/mongrel2/handler.rb, lib/mongrel2/httpresponse.rb,
|
15
|
+
lib/mongrel2/testing.rb, lib/mongrel2/websocket.rb,
|
16
|
+
spec/lib/constants.rb, spec/lib/helpers.rb,
|
17
|
+
spec/mongrel2/handler_spec.rb, spec/mongrel2/httpresponse_spec.rb,
|
18
|
+
spec/mongrel2/websocket_spec.rb:
|
19
|
+
Updating websocket support for recent Mongrel2 changes
|
20
|
+
[c17bbae10985]
|
21
|
+
|
1
22
|
2012-06-26 Michael Granger <ged@FaerieMUD.org>
|
2
23
|
|
24
|
+
* .hgtags:
|
25
|
+
Added tag v0.26.0 for changeset 7fb25e6e09bc
|
26
|
+
[1cf11a8e5402]
|
27
|
+
|
28
|
+
* .hgsigs:
|
29
|
+
Added signature for changeset c2eac469ca66
|
30
|
+
[7fb25e6e09bc] [v0.26.0]
|
31
|
+
|
32
|
+
* History.rdoc, lib/mongrel2.rb, spec/lib/helpers.rb,
|
33
|
+
spec/mongrel2/handler_spec.rb, spec/mongrel2/request_spec.rb:
|
34
|
+
Fix the specs broken by the async upload changes, bump minor
|
35
|
+
version, update history.
|
36
|
+
[c2eac469ca66]
|
37
|
+
|
3
38
|
* lib/mongrel2/config/handler.rb, lib/mongrel2/config/host.rb,
|
4
39
|
lib/mongrel2/config/route.rb, lib/mongrel2/config/server.rb,
|
5
40
|
lib/mongrel2/connection.rb, lib/mongrel2/handler.rb,
|
@@ -7,7 +42,7 @@
|
|
7
42
|
spec/mongrel2/handler_spec.rb, spec/mongrel2/httprequest_spec.rb,
|
8
43
|
spec/mongrel2/request_spec.rb:
|
9
44
|
Fix the async upload body path
|
10
|
-
[782174dcba2e]
|
45
|
+
[782174dcba2e]
|
11
46
|
|
12
47
|
2012-06-21 Michael Granger <ged@FaerieMUD.org>
|
13
48
|
|
data/History.rdoc
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
== v0.27.0 [2012-07-02] Michael Granger <ged@FaerieMUD.org>
|
2
|
+
|
3
|
+
- Adds support for websocket handshake in 'develop' branch.
|
4
|
+
- Adds 'bootstrap' and 'quickstart' commands to m2sh.rb.
|
5
|
+
|
6
|
+
|
1
7
|
== v0.26.0 [2012-06-26] Michael Granger <ged@FaerieMUD.org>
|
2
8
|
|
3
9
|
- Fix the derived path to the async upload body
|
data/Manifest.txt
CHANGED
data/README.rdoc
CHANGED
@@ -17,29 +17,36 @@ databases in pure Ruby, a Control port interface object, and handler classes
|
|
17
17
|
for creating applications or higher-level frameworks.
|
18
18
|
|
19
19
|
|
20
|
-
== Installation
|
20
|
+
== Installation and Setup
|
21
21
|
|
22
|
-
|
22
|
+
Install mongrel2:
|
23
23
|
|
24
|
+
$ {brew,port,portmaster,apt-get install,etc} mongrel2
|
24
25
|
|
25
|
-
|
26
|
-
classes, but it will also fall back to using the sqlite3 library instead:
|
26
|
+
Install the mongrel2 gem:
|
27
27
|
|
28
|
-
|
29
|
-
$ rspec -rsqlite3 -cfp spec
|
30
|
-
>>> Using SQLite3 1.3.4 for DB access.
|
31
|
-
.....[...]
|
28
|
+
$ gem install mongrel2
|
32
29
|
|
33
|
-
|
34
|
-
102 examples, 0 failures
|
30
|
+
Dump a config database generation script into the current working directory:
|
35
31
|
|
36
|
-
|
37
|
-
$ rspec -cfp spec
|
38
|
-
>>> Using Amalgalite 1.1.2 for DB access.
|
39
|
-
.....[...]
|
32
|
+
$ m2sh.rb bootstrap
|
40
33
|
|
41
|
-
|
42
|
-
|
34
|
+
Edit the generated file:
|
35
|
+
|
36
|
+
$ $EDITOR config.rb
|
37
|
+
|
38
|
+
Create a config database from the Ruby config:
|
39
|
+
|
40
|
+
$ m2sh.rb load config.rb
|
41
|
+
|
42
|
+
Start the server:
|
43
|
+
|
44
|
+
$ m2sh.rb start
|
45
|
+
|
46
|
+
Or combine <tt>bootstrap</tt>, <tt>load</tt>, and <tt>start</tt> all into one
|
47
|
+
command:
|
48
|
+
|
49
|
+
$ m2sh.rb quickstart
|
43
50
|
|
44
51
|
|
45
52
|
== Usage
|
@@ -54,6 +61,26 @@ for adding the Ruby configuration DSL to your namespace, and the top-level
|
|
54
61
|
Mongrel2::Config class, which manages the database connection, installs the
|
55
62
|
schema, etc.
|
56
63
|
|
64
|
+
The ORM classes use Jeremy Hinegardner's 'amalgalite' library, but it will
|
65
|
+
also fall back to using the sqlite3 library instead:
|
66
|
+
|
67
|
+
# Loading the sqlite3 library explicitly
|
68
|
+
$ rspec -rsqlite3 -cfp spec
|
69
|
+
>>> Using SQLite3 1.3.4 for DB access.
|
70
|
+
.....[...]
|
71
|
+
|
72
|
+
Finished in 5.53 seconds
|
73
|
+
102 examples, 0 failures
|
74
|
+
|
75
|
+
# No -rsqlite3 means amalgalite loads first.
|
76
|
+
$ rspec -cfp spec
|
77
|
+
>>> Using Amalgalite 1.1.2 for DB access.
|
78
|
+
.....[...]
|
79
|
+
|
80
|
+
Finished in 3.67 seconds
|
81
|
+
102 examples, 0 failures
|
82
|
+
|
83
|
+
|
57
84
|
* Mongrel2::Config
|
58
85
|
* Mongrel2::Config::DSL
|
59
86
|
* Mongrel2::Config::Server
|
@@ -82,6 +109,7 @@ Mongrel2 sends:
|
|
82
109
|
* Mongrel2::HTTPRequest
|
83
110
|
* Mongrel2::JSONRequest
|
84
111
|
* Mongrel2::XMLRequest
|
112
|
+
* Mongrel2::WebSocket::ClientHandshake
|
85
113
|
* Mongrel2::WebSocket::Frame
|
86
114
|
|
87
115
|
These are all {overridable}[rdoc-ref:Mongrel2::Request.register_request_type]
|
@@ -94,17 +122,20 @@ handlers.
|
|
94
122
|
=== The Control Class
|
95
123
|
|
96
124
|
The Mongrel2::Control class is an object interface to {the Mongrel2 control
|
97
|
-
port}[http://mongrel2.org/static/
|
125
|
+
port}[http://mongrel2.org/static/book-finalch4.html#x6-390003.8]. It can be
|
98
126
|
used to stop and restart the server, check its status, etc.
|
99
127
|
|
100
128
|
|
101
129
|
=== Other Classes
|
102
130
|
|
103
|
-
There are a few other classes and modules
|
131
|
+
There are a few other classes and modules worth checking out, too:
|
104
132
|
|
105
|
-
|
106
|
-
|
107
|
-
|
133
|
+
Mongrel2::Table::
|
134
|
+
A hash-like data structure for headers, etc.
|
135
|
+
Mongrel2::Constants::
|
136
|
+
A collection of convenience constants for Mongrel2 handlers.
|
137
|
+
Mongrel2::RequestFactory::
|
138
|
+
A factory for generating fixtured requests of various types for testing.
|
108
139
|
|
109
140
|
|
110
141
|
== Contributing
|
data/bin/m2sh.rb
CHANGED
@@ -186,7 +186,7 @@ class Mongrel2::M2SHCommand
|
|
186
186
|
text ''
|
187
187
|
|
188
188
|
text 'Global Options'
|
189
|
-
opt :config, "Specify the
|
189
|
+
opt :config, "Specify the config database to use.",
|
190
190
|
:default => DEFAULT_CONFIG_URI
|
191
191
|
opt :sudo, "Use 'sudo' to run the mongrel2 server."
|
192
192
|
opt :port, "Reset the server port to <i> before starting it.",
|
@@ -588,6 +588,43 @@ class Mongrel2::M2SHCommand
|
|
588
588
|
usage :running, "[server]"
|
589
589
|
|
590
590
|
|
591
|
+
### The 'bootstrap' command.
|
592
|
+
def bootstrap_command( *args )
|
593
|
+
scriptname = args.shift || DEFAULT_CONFIG_SCRIPT
|
594
|
+
template = Mongrel2::DATA_DIR + 'config.rb.in'
|
595
|
+
data = template.read
|
596
|
+
|
597
|
+
data.gsub!( /%% PWD %%/, Dir.pwd )
|
598
|
+
|
599
|
+
header "Writing a config-generation script to %s" % [ scriptname ]
|
600
|
+
File.open( scriptname, File::WRONLY|File::EXCL|File::CREAT, 0755, encoding: 'utf-8' ) do |fh|
|
601
|
+
fh.print( data )
|
602
|
+
end
|
603
|
+
message "Done."
|
604
|
+
end
|
605
|
+
help :bootstrap, "Generate a basic config-generation script."
|
606
|
+
usage :boostrap, "[scriptname]"
|
607
|
+
|
608
|
+
|
609
|
+
### The 'quickstart' command.
|
610
|
+
def quickstart_command( *args )
|
611
|
+
configfile = 'config.rb'
|
612
|
+
|
613
|
+
header "Quickstart!"
|
614
|
+
self.bootstrap_command( configfile )
|
615
|
+
edit( configfile )
|
616
|
+
self.load_command( configfile )
|
617
|
+
|
618
|
+
message '---'
|
619
|
+
header "Point a browser at: "
|
620
|
+
message '---'
|
621
|
+
|
622
|
+
self.start_command()
|
623
|
+
end
|
624
|
+
help :quickstart, "Set up a basic mongrel2 server and run it."
|
625
|
+
usage :quickstart
|
626
|
+
|
627
|
+
|
591
628
|
### The 'version' command
|
592
629
|
def version_command( *args )
|
593
630
|
message( "<%= color 'Version:', :header %> " + Mongrel2.version_string(true) )
|
@@ -599,6 +636,10 @@ class Mongrel2::M2SHCommand
|
|
599
636
|
# Utility methods
|
600
637
|
#
|
601
638
|
|
639
|
+
#######
|
640
|
+
private
|
641
|
+
#######
|
642
|
+
|
602
643
|
### Output normal output
|
603
644
|
def message( *parts )
|
604
645
|
self.prompt.say( parts.map(&:to_s).join($/) )
|
@@ -676,6 +717,19 @@ class Mongrel2::M2SHCommand
|
|
676
717
|
return server
|
677
718
|
end
|
678
719
|
|
720
|
+
|
721
|
+
### Invoke the user's editor on the given +filename+ and return the exit code
|
722
|
+
### from doing so.
|
723
|
+
def edit( filename )
|
724
|
+
editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
|
725
|
+
system editor, filename.to_s
|
726
|
+
unless $?.success? || editor =~ /vim/i
|
727
|
+
raise "Editor exited with an error status (%d)" % [ $?.exitstatus ]
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
|
732
|
+
|
679
733
|
end # class Mongrel2::M2SHCommand
|
680
734
|
|
681
735
|
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
# This is a Ruby script that will *generate* the SQLite database
|
8
|
+
# that Mongrel2 uses for its configuration. You can just as easily
|
9
|
+
# use Mongrel2's 'm2sh' and the Pythonish config syntax described
|
10
|
+
# in the manual if you prefer that.
|
11
|
+
#
|
12
|
+
# See the "Mongrel2 Config DSL" section of the API docs, and the "How A
|
13
|
+
# Config Is Structured" section of the manual for details
|
14
|
+
# on specific items:
|
15
|
+
#
|
16
|
+
# Mongrel2 Config DSL::
|
17
|
+
# http://deveiate.org/code/mongrel2/DSL_rdoc.html
|
18
|
+
#
|
19
|
+
# How A Config Is Structured::
|
20
|
+
# http://mongrel2.org/static/book-finalch4.html#x6-260003.4
|
21
|
+
#
|
22
|
+
# You can load this via the 'm2sh.rb' tool that comes with the 'mongrel2'
|
23
|
+
# gem:
|
24
|
+
#
|
25
|
+
# m2sh.rb -c config.sqlite load config.rb
|
26
|
+
|
27
|
+
# Establish some directories
|
28
|
+
base_dir = Pathname( '%% PWD %%' )
|
29
|
+
upload_dir = Pathname( Dir.tmpdir ) + 'm2spool'
|
30
|
+
|
31
|
+
# Main Mongrel2 server config
|
32
|
+
main = server 'main' do
|
33
|
+
|
34
|
+
name 'Main'
|
35
|
+
default_host 'localhost'
|
36
|
+
chroot base_dir
|
37
|
+
|
38
|
+
# All of these values are relative to the 'chroot' value if Mongrel2
|
39
|
+
# is started as root. If it's not, they're relative to the directory
|
40
|
+
# it's started in.
|
41
|
+
access_log '/logs/access.log'
|
42
|
+
error_log '/logs/error.log'
|
43
|
+
pid_file '/var/run/mongrel2.pid'
|
44
|
+
|
45
|
+
# This the address and port the server will listen on. You can
|
46
|
+
# use '0.0.0.0' as the bind_addr to listen on all interfaces/IPs.
|
47
|
+
bind_addr '127.0.0.1'
|
48
|
+
port 8113
|
49
|
+
|
50
|
+
host 'localhost' do
|
51
|
+
|
52
|
+
# Serve static content out of a 'public' subdirectory
|
53
|
+
route '/', directory( "public/", 'index.html', 'text/html' )
|
54
|
+
|
55
|
+
# Dynamic content is served via handler routes
|
56
|
+
route '/hello', handler( 'tcp://127.0.0.1:9999', 'helloworld' )
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
setting 'limits.content_length', 512 * 1024
|
63
|
+
setting 'control_port', 'ipc://var/run/mongrel2.sock'
|
64
|
+
setting 'upload.temp_store', upload_dir + 'mongrel2.upload.XXXXXX'
|
65
|
+
|
66
|
+
# Make relative directories so that starting as a regular user works
|
67
|
+
(base_dir + "./#{main.access_log}").dirname.mkpath
|
68
|
+
(base_dir + "./#{main.error_log}").dirname.mkpath
|
69
|
+
(base_dir + "./#{main.pid_file}").dirname.mkpath
|
70
|
+
mkdir_p( upload_dir )
|
71
|
+
|
data/examples/.env
CHANGED
data/examples/Procfile
CHANGED
data/examples/config.rb
CHANGED
data/examples/ws-echo.rb
CHANGED
@@ -86,6 +86,18 @@ class WebSocketEchoServer < Mongrel2::Handler
|
|
86
86
|
end
|
87
87
|
|
88
88
|
|
89
|
+
# Handle the initial handshake. Assumes no sub-protocols or protocol version
|
90
|
+
# checks are necessary.
|
91
|
+
def handle_websocket_handshake( handshake )
|
92
|
+
self.log.info "Handshake from %s" % [ handshake.remote_ip ]
|
93
|
+
|
94
|
+
response = handshake.response( handshake.protocols.first )
|
95
|
+
@connections[ [handshake.sender_id, handshake.conn_id] ] = Time.now
|
96
|
+
|
97
|
+
return response
|
98
|
+
end
|
99
|
+
|
100
|
+
|
89
101
|
# This is the main handler for WebSocket requests. Each frame comes in as a
|
90
102
|
# Mongrel::WebSocket::Frame object, and then is dispatched according to what
|
91
103
|
# opcode it has.
|
data/lib/mongrel2.rb
CHANGED
@@ -20,10 +20,10 @@ module Mongrel2
|
|
20
20
|
abort "\n\n>>> Mongrel2 requires Ruby 1.9.2 or later. <<<\n\n" if RUBY_VERSION < '1.9.2'
|
21
21
|
|
22
22
|
# Library version constant
|
23
|
-
VERSION = '0.
|
23
|
+
VERSION = '0.27.0'
|
24
24
|
|
25
25
|
# Version-control revision constant
|
26
|
-
REVISION = %q$Revision:
|
26
|
+
REVISION = %q$Revision: 6de3cbe2409c $
|
27
27
|
|
28
28
|
|
29
29
|
require 'mongrel2/constants'
|
data/lib/mongrel2/config.rb
CHANGED
@@ -28,6 +28,7 @@ end
|
|
28
28
|
|
29
29
|
require 'mongrel2' unless defined?( Mongrel2 )
|
30
30
|
require 'mongrel2/table'
|
31
|
+
require 'mongrel2/constants'
|
31
32
|
|
32
33
|
module Mongrel2
|
33
34
|
|
@@ -42,10 +43,11 @@ module Mongrel2
|
|
42
43
|
# plugins.
|
43
44
|
#
|
44
45
|
# == References
|
45
|
-
# * http://mongrel2.org/static/
|
46
|
+
# * http://mongrel2.org/static/book-finalch4.html#x6-260003.4
|
46
47
|
#
|
47
48
|
class Config < Sequel::Model
|
48
49
|
extend Loggability
|
50
|
+
include Mongrel2::Constants
|
49
51
|
|
50
52
|
# Loggability API -- set up logging under the 'mongrel2' log host
|
51
53
|
log_to :mongrel2
|
@@ -63,13 +65,6 @@ module Mongrel2
|
|
63
65
|
}
|
64
66
|
DEFAULTS = CONFIG_DEFAULTS
|
65
67
|
|
66
|
-
# The Pathname of the data directory
|
67
|
-
DATA_DIR = if Gem.datadir( 'mongrel2' )
|
68
|
-
Pathname( Gem.datadir('mongrel2') )
|
69
|
-
else
|
70
|
-
Pathname( __FILE__ ).dirname.parent.parent + 'data/mongrel2'
|
71
|
-
end
|
72
|
-
|
73
68
|
# The Pathname of the SQL file used to create the config database
|
74
69
|
CONFIG_SQL = DATA_DIR + 'config.sql'
|
75
70
|
|
data/lib/mongrel2/connection.rb
CHANGED
@@ -15,7 +15,7 @@ require 'mongrel2' unless defined?( Mongrel2 )
|
|
15
15
|
# then encodes and sends Mongrel2::Response objects back to the server.
|
16
16
|
#
|
17
17
|
# == References
|
18
|
-
# * http://mongrel2.org/static/
|
18
|
+
# * http://mongrel2.org/static/book-finalch6.html#x8-710005.3
|
19
19
|
class Mongrel2::Connection
|
20
20
|
extend Loggability
|
21
21
|
|
data/lib/mongrel2/constants.rb
CHANGED
@@ -8,9 +8,21 @@ require 'mongrel2' unless defined?( Mongrel2 )
|
|
8
8
|
# A collection of constants that are shared across the library
|
9
9
|
module Mongrel2::Constants
|
10
10
|
|
11
|
+
# The Pathname of the data directory
|
12
|
+
DATA_DIR = if ENV['MONGREL2_DATADIR']
|
13
|
+
Pathname( ENV['MONGREL2_DATADIR'] )
|
14
|
+
elsif Gem.datadir( 'mongrel2' )
|
15
|
+
Pathname( Gem.datadir('mongrel2') )
|
16
|
+
else
|
17
|
+
Pathname( __FILE__ ).dirname.parent.parent + 'data/mongrel2'
|
18
|
+
end
|
19
|
+
|
11
20
|
# The path to the default Sqlite configuration database
|
12
21
|
DEFAULT_CONFIG_URI = 'config.sqlite'
|
13
22
|
|
23
|
+
# The default name of the config-generation script
|
24
|
+
DEFAULT_CONFIG_SCRIPT = 'config.rb'
|
25
|
+
|
14
26
|
# The default URI of the control socket
|
15
27
|
DEFAULT_CONTROL_SOCKET = 'ipc://run/control'
|
16
28
|
|
data/lib/mongrel2/control.rb
CHANGED
@@ -11,7 +11,7 @@ require 'mongrel2' unless defined?( Mongrel2 )
|
|
11
11
|
# An interface to the Mongrel2 control port.
|
12
12
|
#
|
13
13
|
# == References
|
14
|
-
# (http://mongrel2.org/static/
|
14
|
+
# (http://mongrel2.org/static/book-finalch4.html#x6-390003.8)
|
15
15
|
class Mongrel2::Control
|
16
16
|
extend Loggability
|
17
17
|
|
data/lib/mongrel2/handler.rb
CHANGED
@@ -207,19 +207,18 @@ class Mongrel2::Handler
|
|
207
207
|
return self.handle_async_upload_start( request )
|
208
208
|
|
209
209
|
else
|
210
|
+
self.log.debug "%s request." % [ request.headers['METHOD'] ]
|
210
211
|
case request
|
212
|
+
when Mongrel2::WebSocket::ClientHandshake
|
213
|
+
return self.handle_websocket_handshake( request )
|
214
|
+
when Mongrel2::WebSocket::Frame
|
215
|
+
return self.handle_websocket( request )
|
211
216
|
when Mongrel2::HTTPRequest
|
212
|
-
self.log.debug "HTTP request."
|
213
217
|
return self.handle( request )
|
214
218
|
when Mongrel2::JSONRequest
|
215
|
-
self.log.debug "JSON message request."
|
216
219
|
return self.handle_json( request )
|
217
220
|
when Mongrel2::XMLRequest
|
218
|
-
self.log.debug "XML message request."
|
219
221
|
return self.handle_xml( request )
|
220
|
-
when Mongrel2::WebSocket::Frame
|
221
|
-
self.log.debug "WEBSOCKET message request."
|
222
|
-
return self.handle_websocket( request )
|
223
222
|
else
|
224
223
|
self.log.error "Unhandled request type %s (%p)" %
|
225
224
|
[ request.headers['METHOD'], request.class ]
|
@@ -279,10 +278,24 @@ class Mongrel2::Handler
|
|
279
278
|
### Handle a WebSocket frame in +request+. If not overridden, WebSocket connections are
|
280
279
|
### closed with a policy error status.
|
281
280
|
def handle_websocket( request )
|
282
|
-
self.log.warn "Unhandled WEBSOCKET
|
281
|
+
self.log.warn "Unhandled WEBSOCKET frame (%p)" % [ request.headers.path ]
|
283
282
|
res = request.response
|
284
283
|
res.make_close_frame( WebSocket::CLOSE_POLICY_VIOLATION )
|
285
|
-
|
284
|
+
self.conn.reply( res)
|
285
|
+
|
286
|
+
self.conn.reply_close( request )
|
287
|
+
|
288
|
+
return nil
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
### Handle a WebSocket handshake HTTP +request+. If not overridden, this method drops
|
293
|
+
### the connection.
|
294
|
+
def handle_websocket_handshake( handshake )
|
295
|
+
self.log.warn "Unhandled WEBSOCKET_HANDSHAKE request (%p)" % [ request.headers.path ]
|
296
|
+
self.conn.reply_close( request )
|
297
|
+
|
298
|
+
return nil
|
286
299
|
end
|
287
300
|
|
288
301
|
|
@@ -169,12 +169,13 @@ class Mongrel2::HTTPResponse < Mongrel2::Response
|
|
169
169
|
headers = self.headers.dup
|
170
170
|
|
171
171
|
headers[:date] ||= Time.now.httpdate
|
172
|
-
headers[:content_length] ||= self.get_content_length
|
173
172
|
|
174
173
|
if self.bodiless?
|
174
|
+
headers.delete( :content_length )
|
175
175
|
headers.delete( :content_type )
|
176
176
|
else
|
177
|
-
headers[:
|
177
|
+
headers[:content_length] ||= self.get_content_length
|
178
|
+
headers[:content_type] ||= DEFAULT_CONTENT_TYPE.dup
|
178
179
|
end
|
179
180
|
|
180
181
|
return headers
|
@@ -184,11 +185,11 @@ class Mongrel2::HTTPResponse < Mongrel2::Response
|
|
184
185
|
### Get the length of the body IO. If the IO's offset is somewhere other than
|
185
186
|
### the beginning or end, the size of the remainder is used.
|
186
187
|
def get_content_length
|
187
|
-
if self.
|
188
|
-
|
189
|
-
elsif self.body.pos.nonzero? && !self.body.eof?
|
188
|
+
if self.body.pos.nonzero? && !self.body.eof?
|
189
|
+
self.log.info "Calculating content length based on an offset of %d" % [ self.body.pos ]
|
190
190
|
return self.body.size - self.body.pos
|
191
191
|
else
|
192
|
+
self.log.debug "Calculating body size via %p" % [ self.body.method(:size) ]
|
192
193
|
return self.body.size
|
193
194
|
end
|
194
195
|
end
|
data/lib/mongrel2/testing.rb
CHANGED
@@ -12,13 +12,38 @@ require 'mongrel2/request'
|
|
12
12
|
|
13
13
|
module Mongrel2
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
# A collection of helper functions that are generally useful
|
16
|
+
# for testing Mongrel2::Handlers.
|
17
17
|
module SpecHelpers
|
18
18
|
end # module SpecHelpers
|
19
19
|
|
20
20
|
|
21
|
-
|
21
|
+
# A factory for generating Mongrel2::Request objects for testing.
|
22
|
+
#
|
23
|
+
# Usage:
|
24
|
+
#
|
25
|
+
# require 'mongrel2/testing'
|
26
|
+
#
|
27
|
+
# describe "MyHandler" do
|
28
|
+
# before( :all ) do
|
29
|
+
# @factory = Mongrel2::RequestFactory.
|
30
|
+
# new( sender_id: 'my-handler',
|
31
|
+
# route: '/api/v1',
|
32
|
+
# headers: {accept: 'application/json'} )
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# before( :each ) do
|
36
|
+
# @app = MyHandler.new( 'my-handler', 'tcp://0.0.0.0:5556',
|
37
|
+
# 'tcp://0.0.0.0:5555' )
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# it "handles a JSON request for GET /" do
|
41
|
+
# request = @factory.get( '/api/v1' )
|
42
|
+
# response = @app.dispatch_request( request )
|
43
|
+
# #...
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
22
47
|
class RequestFactory
|
23
48
|
extend Loggability
|
24
49
|
|
@@ -218,14 +243,11 @@ module Mongrel2
|
|
218
243
|
end # RequestFactory
|
219
244
|
|
220
245
|
|
221
|
-
|
246
|
+
# A factory for generating WebSocket request objects for testing.
|
247
|
+
#
|
222
248
|
class WebSocketFrameFactory < Mongrel2::RequestFactory
|
223
|
-
extend Loggability
|
224
249
|
include Mongrel2::Constants
|
225
250
|
|
226
|
-
# Loggability API -- set up logging under the 'mongrel2' log host
|
227
|
-
log_to :mongrel2
|
228
|
-
|
229
251
|
# The default host
|
230
252
|
DEFAULT_TESTING_HOST = 'localhost'
|
231
253
|
DEFAULT_TESTING_PORT = '8113'
|
@@ -261,6 +283,8 @@ module Mongrel2
|
|
261
283
|
:headers => DEFAULT_TESTING_HEADERS,
|
262
284
|
}
|
263
285
|
|
286
|
+
DEFAULT_HANDSHAKE_BODY = 'GR7M5bFPiY2GvVc5a7CIMErQ18Q='
|
287
|
+
|
264
288
|
# Freeze all testing constants
|
265
289
|
constants.each do |cname|
|
266
290
|
const_get(cname).freeze
|
@@ -285,6 +309,26 @@ module Mongrel2
|
|
285
309
|
public
|
286
310
|
######
|
287
311
|
|
312
|
+
### Create an initial websocket handshake request and return it.
|
313
|
+
def handshake( uri, *subprotocols )
|
314
|
+
raise "Request doesn't route through %p" % [ self.route ] unless
|
315
|
+
uri.start_with?( self.route )
|
316
|
+
|
317
|
+
headers = if subprotocols.last.is_a?( Hash ) then subprotocols.pop else {} end
|
318
|
+
headers = self.make_merged_headers( uri, 0, headers )
|
319
|
+
headers.delete( :flags )
|
320
|
+
|
321
|
+
unless subprotocols.empty?
|
322
|
+
protos = subprotocols.map( &:to_s ).join( ', ' )
|
323
|
+
headers.sec_websocket_protocol = protos
|
324
|
+
end
|
325
|
+
|
326
|
+
rclass = Mongrel2::Request.subclass_for_method( :WEBSOCKET_HANDSHAKE )
|
327
|
+
|
328
|
+
return rclass.new( self.sender_id, self.conn_id.to_s, self.route, headers, DEFAULT_HANDSHAKE_BODY.dup )
|
329
|
+
end
|
330
|
+
|
331
|
+
|
288
332
|
### Create a new request with the specified +uri+, +data+, and +flags+.
|
289
333
|
def create( uri, data, *flags )
|
290
334
|
raise "Request doesn't route through %p" % [ self.route ] unless
|
data/lib/mongrel2/websocket.rb
CHANGED
@@ -9,6 +9,11 @@ require 'mongrel2/constants'
|
|
9
9
|
#
|
10
10
|
# class WebSocketEchoServer
|
11
11
|
#
|
12
|
+
# def handle_websocket_handshake( handshake )
|
13
|
+
# # :TODO: Sub-protocol/protocol version checks?
|
14
|
+
# return handshake.response
|
15
|
+
# end
|
16
|
+
#
|
12
17
|
# def handle_websocket( frame )
|
13
18
|
#
|
14
19
|
# # Close connections that send invalid frames
|
@@ -191,6 +196,93 @@ module Mongrel2::WebSocket
|
|
191
196
|
# Exception raised when a frame is malformed, doesn't parse, or is otherwise invalid.
|
192
197
|
class FrameError < Mongrel2::WebSocket::Error; end
|
193
198
|
|
199
|
+
# Exception raised when a handshake is created with an unrequested sub-protocol.
|
200
|
+
class HandshakeError < Mongrel2::WebSocket::Error; end
|
201
|
+
|
202
|
+
|
203
|
+
# The client (request) handshake for a WebSocket opening handshake.
|
204
|
+
class ClientHandshake < Mongrel2::HTTPRequest
|
205
|
+
include Mongrel2::WebSocket::Constants
|
206
|
+
|
207
|
+
# Set this class as the one that will handle WEBSOCKET_HANDSHAKE requests
|
208
|
+
register_request_type( self, :WEBSOCKET_HANDSHAKE )
|
209
|
+
|
210
|
+
|
211
|
+
### Override the type of response returned by this request type. Since
|
212
|
+
### websocket handshakes are symmetrical, responses are just new
|
213
|
+
### Mongrel2::WebSocket::Handshakes with the same Mongrel2 sender
|
214
|
+
### and connection IDs.
|
215
|
+
def self::response_class
|
216
|
+
return Mongrel2::WebSocket::ServerHandshake
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
######
|
221
|
+
public
|
222
|
+
######
|
223
|
+
|
224
|
+
### The list of protocols in the handshake's Sec-WebSocket-Protocol header
|
225
|
+
### as an Array of Strings.
|
226
|
+
def protocols
|
227
|
+
return ( self.headers.sec_websocket_protocol || '' ).split( /\s*,\s*/ )
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
### Create a Mongrel2::WebSocket::Handshake that will respond to the same server/connection as
|
232
|
+
### the receiver.
|
233
|
+
def response( protocol=nil )
|
234
|
+
@response = super() unless @response
|
235
|
+
if protocol
|
236
|
+
raise Mongrel2::WebSocket::HandshakeError,
|
237
|
+
"attempt to create a %s handshake which isn't supported by the client." %
|
238
|
+
[ protocol ] unless self.protocols.include?( protocol.to_s )
|
239
|
+
@response.protocols = protocol
|
240
|
+
end
|
241
|
+
|
242
|
+
return @response
|
243
|
+
end
|
244
|
+
|
245
|
+
end # class ClientHandshake
|
246
|
+
|
247
|
+
|
248
|
+
# The server (response) handshake for a WebSocket opening handshake.
|
249
|
+
class ServerHandshake < Mongrel2::HTTPResponse
|
250
|
+
include Mongrel2::WebSocket::Constants
|
251
|
+
|
252
|
+
### Create a server handshake frame from the given client +handshake+.
|
253
|
+
def self::from_request( handshake )
|
254
|
+
self.log.debug "Creating the server handshake for client handshake %p" % [ handshake ]
|
255
|
+
response = super
|
256
|
+
response.body.truncate( 0 )
|
257
|
+
|
258
|
+
# Mongrel2 puts the negotiated key in the body of the request
|
259
|
+
response.headers.sec_websocket_accept = handshake.body.read
|
260
|
+
|
261
|
+
# Set up the other typical server handshake values
|
262
|
+
response.status = HTTP::SWITCHING_PROTOCOLS
|
263
|
+
response.header.upgrade = 'websocket'
|
264
|
+
response.header.connection = 'Upgrade'
|
265
|
+
|
266
|
+
return response
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
### The list of protocols in the handshake's Sec-WebSocket-Protocol header
|
271
|
+
### as an Array of Strings.
|
272
|
+
def protocols
|
273
|
+
return ( self.headers.sec_websocket_protocol || '' ).split( /\s*,\s*/ )
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
### Set the list of protocols in the handshake's Sec-WebSocket-Protocol header.
|
278
|
+
def protocols=( new_protocols )
|
279
|
+
value = Array( new_protocols ).join( ', ' )
|
280
|
+
self.headers.sec_websocket_protocol = value
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
end # class ServerHandshake
|
285
|
+
|
194
286
|
|
195
287
|
# WebSocket frame class; this is used for both requests and responses in
|
196
288
|
# WebSocket services.
|
@@ -207,7 +299,8 @@ module Mongrel2::WebSocket
|
|
207
299
|
|
208
300
|
### Override the type of response returned by this request type. Since
|
209
301
|
### WebSocket connections are symmetrical, responses are just new
|
210
|
-
###
|
302
|
+
### Mongrel2::WebSocket::Frames with the same Mongrel2 sender and
|
303
|
+
### connection IDs.
|
211
304
|
def self::response_class
|
212
305
|
return self
|
213
306
|
end
|
@@ -448,10 +541,13 @@ module Mongrel2::WebSocket
|
|
448
541
|
end
|
449
542
|
|
450
543
|
|
451
|
-
### Create a
|
452
|
-
###
|
453
|
-
###
|
454
|
-
###
|
544
|
+
### Create a frame in response to the receiving Frame (i.e., with the same
|
545
|
+
### Mongrel2 connection ID and sender). This automatically sets up the correct
|
546
|
+
### status, Sec-WebSocket-Accept:, Connection, and Upgrade: headers based on the
|
547
|
+
### receiver. If +protocol+ is non-nil, and it matches one of the
|
548
|
+
### values listed in 'Sec-WebSocket-Protocol', it will be set as the
|
549
|
+
### Handshake's Sec-WebSocket-Protocol header. If it is non-nil, but doesn't
|
550
|
+
### match one of the request's values, a Mongrel2::WebSocket::Error is raised.
|
455
551
|
def response( *flags )
|
456
552
|
unless @response
|
457
553
|
@response = super()
|
data/spec/lib/constants.rb
CHANGED
@@ -109,6 +109,54 @@ module Mongrel2::TestConstants # :nodoc:all
|
|
109
109
|
}
|
110
110
|
|
111
111
|
|
112
|
+
#
|
113
|
+
# WebSocket frame constants
|
114
|
+
#
|
115
|
+
|
116
|
+
TEST_WEBSOCKET_PATH = '/ws'
|
117
|
+
|
118
|
+
TEST_WEBSOCKET_HEADERS = {
|
119
|
+
'connection' => 'Upgrade',
|
120
|
+
'FLAGS' => '0x8A',
|
121
|
+
'host' => 'host.example.com:80',
|
122
|
+
'METHOD' => 'WEBSOCKET',
|
123
|
+
'origin' => 'http://host.example.com:80',
|
124
|
+
'PATH' => TEST_WEBSOCKET_PATH,
|
125
|
+
'PATTERN' => TEST_WEBSOCKET_PATH,
|
126
|
+
'sec-websocket-extensions' => 'x-webkit-deflate-frame',
|
127
|
+
'sec-websocket-key' => 'SQvDVdT2SbgTg6P/lSZo7Q==',
|
128
|
+
'sec-websocket-protocol' => 'echo',
|
129
|
+
'sec-websocket-version' => '13',
|
130
|
+
'upgrade' => 'websocket',
|
131
|
+
'URI' => TEST_WEBSOCKET_PATH,
|
132
|
+
'VERSION' => 'HTTP/1.1',
|
133
|
+
'x-forwarded-for' => '127.0.0.2',
|
134
|
+
}
|
135
|
+
TEST_WEBSOCKET_HANDSHAKE_HEADERS = {
|
136
|
+
'connection' => 'Upgrade',
|
137
|
+
'host' => 'host.example.com:80',
|
138
|
+
'METHOD' => 'WEBSOCKET_HANDSHAKE',
|
139
|
+
'origin' => 'http://host.example.com:80',
|
140
|
+
'PATH' => TEST_WEBSOCKET_PATH,
|
141
|
+
'PATTERN' => TEST_WEBSOCKET_PATH,
|
142
|
+
'sec-websocket-extensions' => 'x-webkit-deflate-frame',
|
143
|
+
'sec-websocket-key' => 'SQvDVdT2SbgTg6P/lSZo7Q==',
|
144
|
+
'sec-websocket-protocol' => 'echo',
|
145
|
+
'sec-websocket-version' => '13',
|
146
|
+
'upgrade' => 'websocket',
|
147
|
+
'URI' => TEST_WEBSOCKET_PATH,
|
148
|
+
'VERSION' => 'HTTP/1.1',
|
149
|
+
'x-forwarded-for' => '127.0.0.2',
|
150
|
+
}
|
151
|
+
TEST_WEBSOCKET_BODY = 'GR7M5bFPiY2GvVc5a7CIMErQ18Q='
|
152
|
+
TEST_WEBSOCKET_REQUEST_OPTS = {
|
153
|
+
:uuid => TEST_UUID,
|
154
|
+
:id => TEST_ID,
|
155
|
+
:path => TEST_WEBSOCKET_PATH,
|
156
|
+
:body => '',
|
157
|
+
}
|
158
|
+
|
159
|
+
|
112
160
|
#
|
113
161
|
# HTTP constants
|
114
162
|
#
|
data/spec/lib/helpers.rb
CHANGED
@@ -114,13 +114,14 @@ module Mongrel2::SpecHelpers
|
|
114
114
|
bodystring = TNetstring.dump( opts[:body] || '' )
|
115
115
|
|
116
116
|
# UUID ID PATH SIZE:HEADERS,SIZE:BODY,
|
117
|
-
|
117
|
+
data = "%s %d %s %s%s" % [
|
118
118
|
opts[:uuid],
|
119
119
|
opts[:id],
|
120
120
|
opts[:path],
|
121
121
|
headerstring,
|
122
122
|
bodystring,
|
123
123
|
]
|
124
|
+
return data.encode( 'binary' )
|
124
125
|
end
|
125
126
|
|
126
127
|
|
@@ -134,13 +135,14 @@ module Mongrel2::SpecHelpers
|
|
134
135
|
bodystring = TNetstring.dump( opts[:body] || '' )
|
135
136
|
|
136
137
|
# UUID ID PATH SIZE:HEADERS,SIZE:BODY,
|
137
|
-
|
138
|
+
data = "%s %d %s %s%s" % [
|
138
139
|
opts[:uuid],
|
139
140
|
opts[:id],
|
140
141
|
opts[:path],
|
141
142
|
headerstring,
|
142
143
|
bodystring,
|
143
144
|
]
|
145
|
+
return data.encode( 'binary' )
|
144
146
|
end
|
145
147
|
|
146
148
|
|
@@ -156,13 +158,14 @@ module Mongrel2::SpecHelpers
|
|
156
158
|
bodystring = TNetstring.dump( Yajl::Encoder.encode(opts[:body] || []) )
|
157
159
|
|
158
160
|
# UUID ID PATH SIZE:HEADERS,SIZE:BODY,
|
159
|
-
|
161
|
+
data = "%s %d %s %s%s" % [
|
160
162
|
opts[:uuid],
|
161
163
|
opts[:id],
|
162
164
|
opts[:path],
|
163
165
|
headerstring,
|
164
166
|
bodystring,
|
165
167
|
]
|
168
|
+
return data.encode( 'binary' )
|
166
169
|
end
|
167
170
|
|
168
171
|
|
@@ -178,13 +181,56 @@ module Mongrel2::SpecHelpers
|
|
178
181
|
bodystring = TNetstring.dump( opts[:body] || "#{TEST_XML_PATH} />" )
|
179
182
|
|
180
183
|
# UUID ID PATH SIZE:HEADERS,SIZE:BODY,
|
181
|
-
|
184
|
+
data = "%s %d %s %s%s" % [
|
182
185
|
opts[:uuid],
|
183
186
|
opts[:id],
|
184
187
|
opts[:path],
|
185
188
|
headerstring,
|
186
189
|
bodystring,
|
187
190
|
]
|
191
|
+
return data.encode( 'binary' )
|
192
|
+
end
|
193
|
+
|
194
|
+
### Make a Mongrel2 handshake request for a WebSocket route.
|
195
|
+
def make_websocket_handshake( opts={} )
|
196
|
+
opts = TEST_WEBSOCKET_REQUEST_OPTS.merge( opts )
|
197
|
+
headers = normalize_headers( opts, TEST_WEBSOCKET_HANDSHAKE_HEADERS )
|
198
|
+
|
199
|
+
Mongrel2.log.debug "WebSocket start handshake, headers = %p, opts = %p" % [ headers, opts ]
|
200
|
+
|
201
|
+
headerstring = TNetstring.dump( Yajl::Encoder.encode(headers) )
|
202
|
+
bodystring = TNetstring.dump( opts[:body] || TEST_WEBSOCKET_BODY )
|
203
|
+
|
204
|
+
# UUID ID PATH SIZE:HEADERS,SIZE:BODY,
|
205
|
+
data = "%s %d %s %s%s" % [
|
206
|
+
opts[:uuid],
|
207
|
+
opts[:id],
|
208
|
+
opts[:path],
|
209
|
+
headerstring,
|
210
|
+
bodystring,
|
211
|
+
]
|
212
|
+
return data.encode( 'binary' )
|
213
|
+
end
|
214
|
+
|
215
|
+
### Make a Mongrel2 frame for a WebSocket route.
|
216
|
+
def make_websocket_frame( opts={} )
|
217
|
+
opts = TEST_WEBSOCKET_REQUEST_OPTS.merge( opts )
|
218
|
+
headers = normalize_headers( opts, TEST_WEBSOCKET_HEADERS )
|
219
|
+
|
220
|
+
Mongrel2.log.debug "WebSocket frame, headers = %p, opts = %p" % [ headers, opts ]
|
221
|
+
|
222
|
+
headerstring = TNetstring.dump( Yajl::Encoder.encode(headers) )
|
223
|
+
bodystring = TNetstring.dump( opts[:body] )
|
224
|
+
|
225
|
+
# UUID ID PATH SIZE:HEADERS,SIZE:BODY,
|
226
|
+
data = "%s %d %s %s%s" % [
|
227
|
+
opts[:uuid],
|
228
|
+
opts[:id],
|
229
|
+
opts[:path],
|
230
|
+
headerstring,
|
231
|
+
bodystring,
|
232
|
+
]
|
233
|
+
return data.encode( 'binary' )
|
188
234
|
end
|
189
235
|
|
190
236
|
end
|
@@ -209,6 +209,42 @@ describe Mongrel2::Handler do
|
|
209
209
|
response.should be_a( Mongrel2::Response )
|
210
210
|
end
|
211
211
|
|
212
|
+
it "dispatches WebSocket opening handshakes to the #handle_websocket_handshake method" do
|
213
|
+
ws_handler = Class.new( OneShotHandler ) do
|
214
|
+
def handle_websocket_handshake( handshake )
|
215
|
+
return handshake.response
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
req = make_websocket_handshake()
|
220
|
+
@request_sock.should_receive( :recv ).and_return( req )
|
221
|
+
|
222
|
+
res = ws_handler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
|
223
|
+
|
224
|
+
res.transactions.should have( 1 ).member
|
225
|
+
request, response = res.transactions.first
|
226
|
+
request.should be_a( Mongrel2::WebSocket::ClientHandshake )
|
227
|
+
response.should be_a( Mongrel2::WebSocket::ServerHandshake )
|
228
|
+
end
|
229
|
+
|
230
|
+
it "dispatches WebSocket protocol frames to the #handle_websocket method" do
|
231
|
+
ws_handler = Class.new( OneShotHandler ) do
|
232
|
+
def handle_websocket( frame )
|
233
|
+
return frame.response
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
req = make_websocket_frame()
|
238
|
+
@request_sock.should_receive( :recv ).and_return( req )
|
239
|
+
|
240
|
+
res = ws_handler.new( TEST_UUID, TEST_SEND_SPEC, TEST_RECV_SPEC ).run
|
241
|
+
|
242
|
+
res.transactions.should have( 1 ).member
|
243
|
+
request, response = res.transactions.first
|
244
|
+
request.should be_a( Mongrel2::WebSocket::Frame )
|
245
|
+
response.should be_a( Mongrel2::WebSocket::Frame )
|
246
|
+
end
|
247
|
+
|
212
248
|
it "continues when a ZMQ::Error is received but the connection remains open" do
|
213
249
|
req = make_request()
|
214
250
|
|
@@ -55,7 +55,7 @@ describe Mongrel2::HTTPResponse do
|
|
55
55
|
it "returns an empty response if its status is set to NO_CONTENT" do
|
56
56
|
@response.puts "The response body"
|
57
57
|
@response.status = HTTP::NO_CONTENT
|
58
|
-
@response.header_data.
|
58
|
+
@response.header_data.should_not =~ /Content-length/i
|
59
59
|
@response.header_data.should_not =~ /Content-type/i
|
60
60
|
@response.to_s.should_not =~ /The response body/i
|
61
61
|
end
|
@@ -69,9 +69,10 @@ describe Mongrel2::HTTPResponse do
|
|
69
69
|
end
|
70
70
|
|
71
71
|
it "re-calculates the automatically-added headers when re-rendered" do
|
72
|
-
@response.header_data.should =~ /Content-length: 0/i
|
72
|
+
@response.header_data.should =~ /Content-length: 0\b/i
|
73
|
+
@response.status = HTTP::OK
|
73
74
|
@response << "More data!"
|
74
|
-
@response.header_data.should =~ /Content-length: 10/i
|
75
|
+
@response.header_data.should =~ /Content-length: 10\b/i
|
75
76
|
end
|
76
77
|
|
77
78
|
it "doesn't have a body" do
|
@@ -53,6 +53,69 @@ describe Mongrel2::WebSocket do
|
|
53
53
|
BINARY_DATA.force_encoding( Encoding::ASCII_8BIT )
|
54
54
|
|
55
55
|
|
56
|
+
describe 'ClientHandshake' do
|
57
|
+
|
58
|
+
it "is the registered request type for WEBSOCKET_HANDSHAKE requests" do
|
59
|
+
Mongrel2::Request.request_types[:WEBSOCKET_HANDSHAKE].should == Mongrel2::WebSocket::ClientHandshake
|
60
|
+
end
|
61
|
+
|
62
|
+
it "knows what subprotocols were requested" do
|
63
|
+
handshake = @factory.handshake( '/websock', 'echo', 'superecho' )
|
64
|
+
handshake.protocols.should == ['echo', 'superecho']
|
65
|
+
end
|
66
|
+
|
67
|
+
it "doesn't error if no subprotocols were requested" do
|
68
|
+
handshake = @factory.handshake( '/websock' )
|
69
|
+
handshake.protocols.should == []
|
70
|
+
end
|
71
|
+
|
72
|
+
it "can create a response WebSocket::ServerHandshake for itself" do
|
73
|
+
handshake = @factory.handshake( '/websock' )
|
74
|
+
result = handshake.response
|
75
|
+
handshake.body.rewind
|
76
|
+
|
77
|
+
result.should be_a( Mongrel2::WebSocket::ServerHandshake )
|
78
|
+
result.sender_id.should == handshake.sender_id
|
79
|
+
result.conn_id.should == handshake.conn_id
|
80
|
+
result.header.sec_websocket_accept.should == handshake.body.string
|
81
|
+
result.status.should == HTTP::SWITCHING_PROTOCOLS
|
82
|
+
result.header.connection.should =~ /upgrade/i
|
83
|
+
result.header.upgrade.should =~ /websocket/i
|
84
|
+
|
85
|
+
result.body.rewind
|
86
|
+
result.body.read.should == ''
|
87
|
+
end
|
88
|
+
|
89
|
+
it "can create a response WebSocket::ServerHandshake with a valid sub-protocol for itself" do
|
90
|
+
handshake = @factory.handshake( '/websock', 'echo', 'superecho' )
|
91
|
+
result = handshake.response( :superecho )
|
92
|
+
handshake.body.rewind
|
93
|
+
|
94
|
+
result.should be_a( Mongrel2::WebSocket::ServerHandshake )
|
95
|
+
result.sender_id.should == handshake.sender_id
|
96
|
+
result.conn_id.should == handshake.conn_id
|
97
|
+
result.header.sec_websocket_accept.should == handshake.body.string
|
98
|
+
result.status.should == HTTP::SWITCHING_PROTOCOLS
|
99
|
+
result.header.connection.should =~ /upgrade/i
|
100
|
+
result.header.upgrade.should =~ /websocket/i
|
101
|
+
result.protocols.should == ['superecho']
|
102
|
+
|
103
|
+
result.body.rewind
|
104
|
+
result.body.read.should == ''
|
105
|
+
end
|
106
|
+
|
107
|
+
it "raises an exception if the specified protocol is not one of the client's advertised ones" do
|
108
|
+
handshake = @factory.handshake( '/websock', 'echo', 'superecho' )
|
109
|
+
|
110
|
+
expect {
|
111
|
+
handshake.response( :map_updates )
|
112
|
+
}.to raise_error( Mongrel2::WebSocket::HandshakeError, /map_updates/i )
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
|
56
119
|
describe 'Frame' do
|
57
120
|
|
58
121
|
it "is the registered request type for WEBSOCKET requests" do
|
@@ -310,5 +373,6 @@ describe Mongrel2::WebSocket do
|
|
310
373
|
|
311
374
|
end
|
312
375
|
|
376
|
+
|
313
377
|
end
|
314
378
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongrel2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.27.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -36,7 +36,7 @@ cert_chain:
|
|
36
36
|
YUhDS0xaZFNLai9SSHVUT3QrZ2JsUmV4OEZBaDhOZUEKY21saFhlNDZwWk5K
|
37
37
|
Z1dLYnhaYWg4NWpJang5NWhSOHZPSStOQU01aUg5a09xSzEzRHJ4YWNUS1Bo
|
38
38
|
cWo1UGp3RgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
|
39
|
-
date: 2012-
|
39
|
+
date: 2012-07-02 00:00:00.000000000 Z
|
40
40
|
dependencies:
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: nokogiri
|
@@ -328,6 +328,7 @@ files:
|
|
328
328
|
- Rakefile
|
329
329
|
- bin/m2sh.rb
|
330
330
|
- data/mongrel2/bootstrap.html
|
331
|
+
- data/mongrel2/config.rb.in
|
331
332
|
- data/mongrel2/config.sql
|
332
333
|
- data/mongrel2/css/master.css
|
333
334
|
- data/mongrel2/js/websock-test.js
|
metadata.gz.sig
CHANGED
Binary file
|