eventmachine_httpserver 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +281 -0
- data/README +8 -0
- data/RELEASE_NOTES +3 -0
- data/Rakefile +85 -0
- data/ext/extconf.rb +128 -0
- data/ext/http.cpp +568 -0
- data/ext/http.h +117 -0
- data/ext/ptch +148 -0
- data/ext/rubyhttp.cpp +278 -0
- data/lib/evma_httpserver.rb +37 -0
- data/lib/evma_httpserver/response.rb +280 -0
- data/test/app.rb +235 -0
- data/test/response.rb +183 -0
- metadata +66 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
# $Id: evma_httpserver.rb 3897 2007-03-06 20:21:08Z francis $
|
2
|
+
#
|
3
|
+
# EventMachine HTTP Server
|
4
|
+
#
|
5
|
+
# Author:: blackhedd (gmail address: garbagecat10).
|
6
|
+
#
|
7
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
8
|
+
#
|
9
|
+
# This program is made available under the terms of the GPL version 2.
|
10
|
+
#
|
11
|
+
#----------------------------------------------------------------------------
|
12
|
+
#
|
13
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
14
|
+
#
|
15
|
+
# Gmail: garbagecat10
|
16
|
+
#
|
17
|
+
# This program is free software; you can redistribute it and/or modify
|
18
|
+
# it under the terms of the GNU General Public License as published by
|
19
|
+
# the Free Software Foundation; either version 2 of the License, or
|
20
|
+
# (at your option) any later version.
|
21
|
+
#
|
22
|
+
# This program is distributed in the hope that it will be useful,
|
23
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
24
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
25
|
+
# GNU General Public License for more details.
|
26
|
+
#
|
27
|
+
# You should have received a copy of the GNU General Public License
|
28
|
+
# along with this program; if not, write to the Free Software
|
29
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
30
|
+
#
|
31
|
+
#---------------------------------------------------------------------------
|
32
|
+
#
|
33
|
+
|
34
|
+
|
35
|
+
require 'eventmachine_httpserver'
|
36
|
+
require 'evma_httpserver/response'
|
37
|
+
|
@@ -0,0 +1,280 @@
|
|
1
|
+
# $Id: response.rb 3897 2007-03-06 20:21:08Z francis $
|
2
|
+
#
|
3
|
+
# EventMachine HTTP Server
|
4
|
+
# HTTP Response-support class
|
5
|
+
#
|
6
|
+
# Author:: blackhedd (gmail address: garbagecat10).
|
7
|
+
#
|
8
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
9
|
+
#
|
10
|
+
# This program is made available under the terms of the GPL version 2.
|
11
|
+
#
|
12
|
+
#----------------------------------------------------------------------------
|
13
|
+
#
|
14
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
15
|
+
#
|
16
|
+
# Gmail: garbagecat10
|
17
|
+
#
|
18
|
+
# This program is free software; you can redistribute it and/or modify
|
19
|
+
# it under the terms of the GNU General Public License as published by
|
20
|
+
# the Free Software Foundation; either version 2 of the License, or
|
21
|
+
# (at your option) any later version.
|
22
|
+
#
|
23
|
+
# This program is distributed in the hope that it will be useful,
|
24
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
25
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
26
|
+
# GNU General Public License for more details.
|
27
|
+
#
|
28
|
+
# You should have received a copy of the GNU General Public License
|
29
|
+
# along with this program; if not, write to the Free Software
|
30
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
31
|
+
#
|
32
|
+
#---------------------------------------------------------------------------
|
33
|
+
#
|
34
|
+
|
35
|
+
module EventMachine
|
36
|
+
|
37
|
+
class HttpResponse
|
38
|
+
attr_accessor :status, :content, :headers, :chunks, :multiparts
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@headers = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def keep_connection_open arg=true
|
45
|
+
@keep_connection_open = arg
|
46
|
+
end
|
47
|
+
|
48
|
+
# sugarings for headers
|
49
|
+
def content_type *mime
|
50
|
+
if mime.length > 0
|
51
|
+
@headers ["Content-type"] = mime.first.to_s
|
52
|
+
else
|
53
|
+
@headers ["Content-type"]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Sugaring for Set-cookie headers. These are a pain because there can easily and
|
58
|
+
# legitimately be more than one. So we use an ugly verb to signify that.
|
59
|
+
# #add_set_cookies does NOT disturb the set-cookie headers which may have been
|
60
|
+
# added on a prior call. #set_cookie clears them out first.
|
61
|
+
def add_set_cookie *ck
|
62
|
+
if ck.length > 0
|
63
|
+
h = (@headers ["Set-cookie"] ||= [])
|
64
|
+
ck.each {|c| h << c}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
def set_cookie *ck
|
68
|
+
h = (@headers ["Set-cookie"] ||= [])
|
69
|
+
if ck.length > 0
|
70
|
+
h.clear
|
71
|
+
add_set_cookie *ck
|
72
|
+
else
|
73
|
+
h
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# This is intended to send a complete HTTP response, including closing the connection
|
79
|
+
# if appropriate at the end of the transmission. Don't use this method to send partial
|
80
|
+
# or iterated responses. This method will send chunks and multiparts provided they
|
81
|
+
# are all available when we get here.
|
82
|
+
# Note that the default @status is 200 if the value doesn't exist.
|
83
|
+
def send_response
|
84
|
+
send_headers
|
85
|
+
send_body
|
86
|
+
send_trailer
|
87
|
+
close_connection_after_writing unless (@keep_connection_open and (@status || 200) == 200)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Send the headers out in alpha-sorted order. This will degrade performance to some
|
91
|
+
# degree, and is intended only to simplify the construction of unit tests.
|
92
|
+
#
|
93
|
+
def send_headers
|
94
|
+
raise "sent headers already" if @sent_headers
|
95
|
+
@sent_headers = true
|
96
|
+
|
97
|
+
fixup_headers
|
98
|
+
|
99
|
+
ary = []
|
100
|
+
ary << "HTTP/1.1 #{@status || 200} ...\r\n"
|
101
|
+
ary += generate_header_lines(@headers)
|
102
|
+
ary << "\r\n"
|
103
|
+
|
104
|
+
send_data ary.join
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def generate_header_lines in_hash
|
109
|
+
out_ary = []
|
110
|
+
in_hash.keys.sort.each {|k|
|
111
|
+
v = @headers[k]
|
112
|
+
if v.is_a?(Array)
|
113
|
+
v.each {|v1| out_ary << "#{k}: #{v1}\r\n" }
|
114
|
+
else
|
115
|
+
out_ary << "#{k}: #{v}\r\n"
|
116
|
+
end
|
117
|
+
}
|
118
|
+
out_ary
|
119
|
+
end
|
120
|
+
private :generate_header_lines
|
121
|
+
|
122
|
+
|
123
|
+
# Examine the content type and data and other things, and perform a final
|
124
|
+
# fixup of the header array. We expect this to be called just before sending
|
125
|
+
# headers to the remote peer.
|
126
|
+
# In the case of multiparts, we ASSUME we will get called before any content
|
127
|
+
# gets sent out, because the multipart boundary is created here.
|
128
|
+
#
|
129
|
+
def fixup_headers
|
130
|
+
if @content
|
131
|
+
@headers ["Content-length"] = @content.to_s.length
|
132
|
+
elsif @chunks
|
133
|
+
@headers ["Transfer-encoding"] = "chunked"
|
134
|
+
# Might be nice to ENSURE there is no content-length header,
|
135
|
+
# but how to detect all the possible permutations of upper/lower case?
|
136
|
+
elsif @multiparts
|
137
|
+
@multipart_boundary = self.class.concoct_multipart_boundary
|
138
|
+
@headers ["Content-type"] = "multipart/x-mixed-replace; boundary=\"#{@multipart_boundary}\""
|
139
|
+
else
|
140
|
+
@headers ["Content-length"] = 0
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# we send either content, chunks, or multiparts. Content can only be sent once.
|
145
|
+
# Chunks and multiparts can be sent any number of times.
|
146
|
+
# DO NOT close the connection or send any goodbye kisses. This method can
|
147
|
+
# be called multiple times to send out chunks or multiparts.
|
148
|
+
def send_body
|
149
|
+
if @content
|
150
|
+
send_content
|
151
|
+
elsif @chunks
|
152
|
+
send_chunks
|
153
|
+
elsif @multiparts
|
154
|
+
send_multiparts
|
155
|
+
else
|
156
|
+
@content = ""
|
157
|
+
send_content
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# send a trailer which depends on the type of body we're dealing with.
|
162
|
+
# The assumption is that we're about to end the transmission of this particular
|
163
|
+
# HTTP response. (A connection-close may or may not follow.)
|
164
|
+
#
|
165
|
+
def send_trailer
|
166
|
+
send_headers unless @sent_headers
|
167
|
+
if @content
|
168
|
+
# no-op
|
169
|
+
elsif @chunks
|
170
|
+
unless @last_chunk_sent
|
171
|
+
chunk ""
|
172
|
+
send_chunks
|
173
|
+
end
|
174
|
+
elsif @multiparts
|
175
|
+
# in the lingo of RFC 2046/5.1.1, we're sending an "epilog"
|
176
|
+
# consisting of a blank line. I really don't know how that is
|
177
|
+
# supposed to interact with the case where we leave the connection
|
178
|
+
# open after transmitting the multipart response.
|
179
|
+
send_data "\r\n--#{@multipart_boundary}--\r\n\r\n"
|
180
|
+
else
|
181
|
+
# no-op
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def send_content
|
186
|
+
raise "sent content already" if @sent_content
|
187
|
+
@sent_content = true
|
188
|
+
send_data((@content || "").to_s)
|
189
|
+
end
|
190
|
+
|
191
|
+
# add a chunk to go to the output.
|
192
|
+
# Will cause the headers to pick up "content-transfer-encoding"
|
193
|
+
# Add the chunk to a list. Calling #send_chunks will send out the
|
194
|
+
# available chunks and clear the chunk list WITHOUT closing the connection,
|
195
|
+
# so it can be called any number of times.
|
196
|
+
# TODO!!! Per RFC2616, we may not send chunks to an HTTP/1.0 client.
|
197
|
+
# Raise an exception here if our user tries to do so.
|
198
|
+
# Chunked transfer coding is defined in RFC2616 pgh 3.6.1.
|
199
|
+
# The argument can be a string or a hash. The latter allows for
|
200
|
+
# sending chunks with extensions (someday).
|
201
|
+
#
|
202
|
+
def chunk text
|
203
|
+
@chunks ||= []
|
204
|
+
@chunks << text
|
205
|
+
end
|
206
|
+
|
207
|
+
# send the contents of the chunk list and clear it out.
|
208
|
+
# ASSUMES that headers have been sent.
|
209
|
+
# Does NOT close the connection.
|
210
|
+
# Can be called multiple times.
|
211
|
+
# According to RFC2616, phg 3.6.1, the last chunk will be zero length.
|
212
|
+
# But some caller could accidentally set a zero-length chunk in the middle
|
213
|
+
# of the stream. If that should happen, raise an exception.
|
214
|
+
# The reason for supporting chunks that are hashes instead of just strings
|
215
|
+
# is to enable someday supporting chunk-extension codes (cf the RFC).
|
216
|
+
# TODO!!! We're not supporting the final entity-header that may be
|
217
|
+
# transmitted after the last (zero-length) chunk.
|
218
|
+
#
|
219
|
+
def send_chunks
|
220
|
+
send_headers unless @sent_headers
|
221
|
+
while chunk = @chunks.shift
|
222
|
+
raise "last chunk already sent" if @last_chunk_sent
|
223
|
+
text = chunk.is_a?(Hash) ? chunk[:text] : chunk.to_s
|
224
|
+
send_data "#{format("%x", text.length).upcase}\r\n#{text}\r\n"
|
225
|
+
@last_chunk_sent = true if text.length == 0
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# To add a multipart to the outgoing response, specify the headers and the
|
230
|
+
# body. If only a string is given, it's treated as the body (in this case,
|
231
|
+
# the header is assumed to be empty).
|
232
|
+
#
|
233
|
+
def multipart arg
|
234
|
+
vals = if arg.is_a?(String)
|
235
|
+
{:body => arg, :headers => {}}
|
236
|
+
else
|
237
|
+
arg
|
238
|
+
end
|
239
|
+
|
240
|
+
@multiparts ||= []
|
241
|
+
@multiparts << vals
|
242
|
+
end
|
243
|
+
|
244
|
+
# Multipart syntax is defined in RFC 2046, pgh 5.1.1 et seq.
|
245
|
+
# The CRLF which introduces the boundary line of each part (content entity)
|
246
|
+
# is defined as being part of the boundary, not of the preceding part.
|
247
|
+
# So we don't need to mess with interpreting the last bytes of a part
|
248
|
+
# to ensure they are CRLF-terminated.
|
249
|
+
#
|
250
|
+
def send_multiparts
|
251
|
+
send_headers unless @sent_headers
|
252
|
+
while part = @multiparts.shift
|
253
|
+
send_data "\r\n--#{@multipart_boundary}\r\n"
|
254
|
+
send_data( generate_header_lines( part[:headers] || {} ).join)
|
255
|
+
send_data "\r\n"
|
256
|
+
send_data part[:body].to_s
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
#
|
262
|
+
def self.concoct_multipart_boundary
|
263
|
+
@multipart_index ||= 0
|
264
|
+
@multipart_index += 1
|
265
|
+
if @multipart_index >= 1000
|
266
|
+
@multipart_index = 0
|
267
|
+
@multipart_guid = nil
|
268
|
+
end
|
269
|
+
@multipart_guid ||= `uuidgen -r`.chomp.gsub(/\-/,"")
|
270
|
+
"#{@multipart_guid}#{@multipart_index}"
|
271
|
+
end
|
272
|
+
|
273
|
+
def send_redirect location
|
274
|
+
@status = 302 # TODO, make 301 available by parameter
|
275
|
+
@headers["Location"] = location
|
276
|
+
send_response
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
data/test/app.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
# $Id: app.rb 3893 2007-03-06 20:12:09Z francis $
|
2
|
+
#
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
7
|
+
|
8
|
+
require 'eventmachine'
|
9
|
+
require 'evma_httpserver'
|
10
|
+
|
11
|
+
|
12
|
+
#--------------------------------------
|
13
|
+
|
14
|
+
module EventMachine
|
15
|
+
module HttpServer
|
16
|
+
def process_http_request
|
17
|
+
send_data generate_response()
|
18
|
+
close_connection_after_writing
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#--------------------------------------
|
24
|
+
|
25
|
+
|
26
|
+
class TestApp < Test::Unit::TestCase
|
27
|
+
|
28
|
+
TestHost = "127.0.0.1"
|
29
|
+
TestPort = 8911
|
30
|
+
|
31
|
+
TestResponse_1 = %Q(HTTP/1.0 200 ...
|
32
|
+
Content-length: 4
|
33
|
+
Content-type: text/plain
|
34
|
+
Connection: close
|
35
|
+
|
36
|
+
1234)
|
37
|
+
|
38
|
+
def setup
|
39
|
+
Thread.abort_on_exception = true
|
40
|
+
end
|
41
|
+
|
42
|
+
def teardown
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
def test_simple_get
|
48
|
+
received_response = nil
|
49
|
+
|
50
|
+
EventMachine::HttpServer.module_eval {
|
51
|
+
def generate_response
|
52
|
+
TestResponse_1
|
53
|
+
end
|
54
|
+
}
|
55
|
+
|
56
|
+
|
57
|
+
EventMachine.run {
|
58
|
+
EventMachine.start_server TestHost, TestPort, EventMachine::HttpServer
|
59
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
60
|
+
|
61
|
+
EventMachine.defer proc {
|
62
|
+
tcp = TCPSocket.new TestHost, TestPort
|
63
|
+
tcp.write "GET / HTTP/1.0\r\n\r\n"
|
64
|
+
received_response = tcp.read
|
65
|
+
}, proc {
|
66
|
+
EventMachine.stop
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
assert_equal( TestResponse_1, received_response )
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
# This frowsy-looking protocol handler allows the test harness to make some
|
77
|
+
# its local variables visible, so we can set them here and they can be asserted later.
|
78
|
+
class MyTestServer < EventMachine::Connection
|
79
|
+
include EventMachine::HttpServer
|
80
|
+
def initialize *args
|
81
|
+
super
|
82
|
+
end
|
83
|
+
def generate_response
|
84
|
+
@assertions.call
|
85
|
+
TestResponse_1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
def test_parameters
|
92
|
+
path_info = "/test.html"
|
93
|
+
query_string = "a=b&c=d"
|
94
|
+
cookie = "eat_me=I'm a cookie"
|
95
|
+
etag = "12345"
|
96
|
+
|
97
|
+
# collect all the stuff we want to assert outside the actual test,
|
98
|
+
# to ensure it gets asserted even if the test catches some exception.
|
99
|
+
received_response = nil
|
100
|
+
request_parms = {}
|
101
|
+
|
102
|
+
|
103
|
+
EventMachine.run {
|
104
|
+
EventMachine.start_server(TestHost, TestPort, MyTestServer) {|conn|
|
105
|
+
# In each accepted connection, set up a procedure that will copy
|
106
|
+
# the request parameters into a local variable visible here, so
|
107
|
+
# we can assert the values later.
|
108
|
+
conn.instance_eval {@assertions = proc {
|
109
|
+
%w( PATH_INFO
|
110
|
+
QUERY_STRING
|
111
|
+
HTTP_COOKIE
|
112
|
+
IF_NONE_MATCH
|
113
|
+
CONTENT_TYPE
|
114
|
+
REQUEST_METHOD
|
115
|
+
REQUEST_URI
|
116
|
+
).each {|parm|
|
117
|
+
# request_parms is bound to a local variable visible in this context.
|
118
|
+
request_parms[parm] = ENV[parm]
|
119
|
+
}
|
120
|
+
}}
|
121
|
+
}
|
122
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
123
|
+
|
124
|
+
EventMachine.defer proc {
|
125
|
+
tcp = TCPSocket.new TestHost, TestPort
|
126
|
+
tcp.write [
|
127
|
+
"GET #{path_info}?#{query_string} HTTP/1.1\r\n",
|
128
|
+
"Cookie: #{cookie}\r\n",
|
129
|
+
"If-none-match: #{etag}\r\n",
|
130
|
+
"\r\n"
|
131
|
+
].join
|
132
|
+
received_response = tcp.read
|
133
|
+
}, proc {
|
134
|
+
EventMachine.stop
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
assert_equal( TestResponse_1, received_response )
|
139
|
+
assert_equal( path_info, request_parms["PATH_INFO"] )
|
140
|
+
assert_equal( query_string, request_parms["QUERY_STRING"] )
|
141
|
+
assert_equal( cookie, request_parms["HTTP_COOKIE"] )
|
142
|
+
assert_equal( etag, request_parms["IF_NONE_MATCH"] )
|
143
|
+
assert_equal( nil, request_parms["CONTENT_TYPE"] )
|
144
|
+
assert_equal( "GET", request_parms["REQUEST_METHOD"] )
|
145
|
+
assert_equal( path_info, request_parms["REQUEST_URI"] )
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def test_headers
|
150
|
+
received_header_string = nil
|
151
|
+
received_header_ary = nil
|
152
|
+
|
153
|
+
EventMachine.run {
|
154
|
+
EventMachine.start_server(TestHost, TestPort, MyTestServer) {|conn|
|
155
|
+
# In each accepted connection, set up a procedure that will copy
|
156
|
+
# the request parameters into a local variable visible here, so
|
157
|
+
# we can assert the values later.
|
158
|
+
# The @http_headers is set automatically and can easily be parsed.
|
159
|
+
# It isn't automatically parsed into Ruby values because that is
|
160
|
+
# a costly operation, but we should provide an optional method that
|
161
|
+
# does the parsing so it doesn't need to be done by users.
|
162
|
+
conn.instance_eval {@assertions = proc {
|
163
|
+
received_header_string = @http_headers
|
164
|
+
received_header_ary = @http_headers.split(/\0/).map {|line| line.split(/:\s*/, 2) }
|
165
|
+
}}
|
166
|
+
}
|
167
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
168
|
+
|
169
|
+
EventMachine.defer proc {
|
170
|
+
tcp = TCPSocket.new TestHost, TestPort
|
171
|
+
tcp.write [
|
172
|
+
"GET / HTTP/1.1\r\n",
|
173
|
+
"aaa: 111\r\n",
|
174
|
+
"bbb: 222\r\n",
|
175
|
+
"ccc: 333\r\n",
|
176
|
+
"ddd: 444\r\n",
|
177
|
+
"\r\n"
|
178
|
+
].join
|
179
|
+
received_response = tcp.read
|
180
|
+
}, proc {
|
181
|
+
EventMachine.stop
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
assert_equal( "aaa: 111\0bbb: 222\0ccc: 333\0ddd: 444\0\0", received_header_string )
|
186
|
+
assert_equal( [["aaa","111"], ["bbb","222"], ["ccc","333"], ["ddd","444"]], received_header_ary )
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
|
193
|
+
def test_post
|
194
|
+
received_header_string = nil
|
195
|
+
post_content = "1234567890"
|
196
|
+
content_type = "text/plain"
|
197
|
+
received_post_content = ""
|
198
|
+
received_content_type = ""
|
199
|
+
|
200
|
+
EventMachine.run {
|
201
|
+
EventMachine.start_server(TestHost, TestPort, MyTestServer) {|conn|
|
202
|
+
# In each accepted connection, set up a procedure that will copy
|
203
|
+
# the request parameters into a local variable visible here, so
|
204
|
+
# we can assert the values later.
|
205
|
+
# The @http_post_content variable is set automatically.
|
206
|
+
conn.instance_eval {@assertions = proc {
|
207
|
+
received_post_content = @http_post_content
|
208
|
+
received_content_type = ENV["CONTENT_TYPE"]
|
209
|
+
}}
|
210
|
+
}
|
211
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
212
|
+
|
213
|
+
EventMachine.defer proc {
|
214
|
+
tcp = TCPSocket.new TestHost, TestPort
|
215
|
+
tcp.write [
|
216
|
+
"POST / HTTP/1.1\r\n",
|
217
|
+
"Content-type: #{content_type}\r\n",
|
218
|
+
"Content-length: #{post_content.length}\r\n",
|
219
|
+
"\r\n",
|
220
|
+
post_content
|
221
|
+
].join
|
222
|
+
received_response = tcp.read
|
223
|
+
}, proc {
|
224
|
+
EventMachine.stop
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|
228
|
+
assert_equal( received_post_content, post_content )
|
229
|
+
assert_equal( received_content_type, content_type )
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
|