em-http-server 0.1.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/.gitignore +18 -0
- data/Gemfile +15 -0
- data/LICENSE +22 -0
- data/README.md +56 -0
- data/Rakefile +11 -0
- data/em-http-server.gemspec +17 -0
- data/lib/em-http-server.rb +2 -0
- data/lib/em-http-server/response.rb +314 -0
- data/lib/em-http-server/server.rb +94 -0
- data/test/test_app.rb +270 -0
- data/test/test_delegated.rb +187 -0
- data/test/test_response.rb +242 -0
- metadata +71 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in em-http-server.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'eventmachine'
|
7
|
+
|
8
|
+
# Add dependencies to develop your gem here.
|
9
|
+
# Include everything needed to run rake, tests, features, etc.
|
10
|
+
group :development do
|
11
|
+
gem "bundler", "> 1.0.0"
|
12
|
+
gem 'rake'
|
13
|
+
gem 'test-unit'
|
14
|
+
gem 'simplecov'
|
15
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 alor
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Em::Http::Server
|
2
|
+
|
3
|
+
Simple http server to be used with Eventmachine.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'em-http-server'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install em-http-server
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
require 'eventmachine'
|
22
|
+
require 'em-http-server'
|
23
|
+
|
24
|
+
class HTTPHandler < EM::Http::Server
|
25
|
+
|
26
|
+
def process_http_request
|
27
|
+
puts @http_request_method
|
28
|
+
puts @http_request_uri
|
29
|
+
puts @http_query_string
|
30
|
+
puts @http_protocol
|
31
|
+
puts @http_content
|
32
|
+
puts @http[:cookie]
|
33
|
+
puts @http[:content_type]
|
34
|
+
# you have all the http headers in this hash
|
35
|
+
puts @http.inspect
|
36
|
+
|
37
|
+
response = EM::DelegatedHttpResponse.new(self)
|
38
|
+
response.status = 200
|
39
|
+
response.content_type 'text/html'
|
40
|
+
response.content = 'It works'
|
41
|
+
response.send_response
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
EM::run do
|
47
|
+
EM::start_server("0.0.0.0", 80, HTTPHandler)
|
48
|
+
end
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
1. Fork it
|
53
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
54
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
55
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
56
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |gem|
|
3
|
+
gem.authors = ["alor"]
|
4
|
+
gem.email = ["alberto.ornaghi@gmail.com"]
|
5
|
+
gem.description = %q{Simple http server for eventmachine}
|
6
|
+
gem.summary = %q{Simple http server for eventmachine with the same interface as evma_httpserver}
|
7
|
+
gem.homepage = ""
|
8
|
+
|
9
|
+
gem.files = `git ls-files`.split($\)
|
10
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
11
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
12
|
+
gem.name = "em-http-server"
|
13
|
+
gem.require_paths = ["lib"]
|
14
|
+
gem.version = "0.1.0"
|
15
|
+
|
16
|
+
gem.add_runtime_dependency "eventmachine"
|
17
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
# EventMachine HTTP Server
|
2
|
+
# HTTP Response-support class
|
3
|
+
#
|
4
|
+
# Author:: blackhedd (gmail address: garbagecat10).
|
5
|
+
#
|
6
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
7
|
+
#
|
8
|
+
# This program is made available under the terms of the GPL version 2.
|
9
|
+
#
|
10
|
+
#----------------------------------------------------------------------------
|
11
|
+
#
|
12
|
+
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
|
13
|
+
#
|
14
|
+
# Gmail: garbagecat10
|
15
|
+
#
|
16
|
+
# This program is free software; you can redistribute it and/or modify
|
17
|
+
# it under the terms of the GNU General Public License as published by
|
18
|
+
# the Free Software Foundation; either version 2 of the License, or
|
19
|
+
# (at your option) any later version.
|
20
|
+
#
|
21
|
+
# This program is distributed in the hope that it will be useful,
|
22
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
23
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
24
|
+
# GNU General Public License for more details.
|
25
|
+
#
|
26
|
+
# You should have received a copy of the GNU General Public License
|
27
|
+
# along with this program; if not, write to the Free Software
|
28
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
29
|
+
#
|
30
|
+
#---------------------------------------------------------------------------
|
31
|
+
#
|
32
|
+
|
33
|
+
module EventMachine
|
34
|
+
|
35
|
+
# This class provides a wide variety of features for generating and
|
36
|
+
# dispatching HTTP responses. It allows you to conveniently generate
|
37
|
+
# headers and content (including chunks and multiparts), and dispatch
|
38
|
+
# responses (including deferred or partially-complete responses).
|
39
|
+
#
|
40
|
+
# Although HttpResponse is coded as a class, it's not complete as it
|
41
|
+
# stands. It assumes that it has certain of the behaviors of
|
42
|
+
# EventMachine::Connection. You must add these behaviors, either by
|
43
|
+
# subclassing HttpResponse, or using the alternate version of this
|
44
|
+
# class, DelegatedHttpResponse. See the test cases for current information
|
45
|
+
# on which behaviors you have to add.
|
46
|
+
#
|
47
|
+
# TODO, someday it would be nice to provide a version of this functionality
|
48
|
+
# that is coded as a Module, so it can simply be mixed into an instance of
|
49
|
+
# EventMachine::Connection.
|
50
|
+
#
|
51
|
+
class HttpResponse
|
52
|
+
attr_accessor :status, :status_string, :content, :headers, :chunks, :multiparts
|
53
|
+
|
54
|
+
def initialize
|
55
|
+
@headers = {}
|
56
|
+
end
|
57
|
+
|
58
|
+
def keep_connection_open arg=true
|
59
|
+
@keep_connection_open = arg
|
60
|
+
end
|
61
|
+
|
62
|
+
# sugarings for headers
|
63
|
+
def content_type *mime
|
64
|
+
if mime.length > 0
|
65
|
+
@headers["Content-type"] = mime.first.to_s
|
66
|
+
else
|
67
|
+
@headers["Content-type"]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sugaring for Set-cookie headers. These are a pain because there can easily and
|
72
|
+
# legitimately be more than one. So we use an ugly verb to signify that.
|
73
|
+
# #add_set_cookies does NOT disturb the set-cookie headers which may have been
|
74
|
+
# added on a prior call. #set_cookie clears them out first.
|
75
|
+
def add_set_cookie *ck
|
76
|
+
if ck.length > 0
|
77
|
+
h = (@headers["Set-cookie"] ||= [])
|
78
|
+
ck.each {|c| h << c}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
def set_cookie *ck
|
82
|
+
h = (@headers["Set-cookie"] ||= [])
|
83
|
+
if ck.length > 0
|
84
|
+
h.clear
|
85
|
+
add_set_cookie *ck
|
86
|
+
else
|
87
|
+
h
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# This is intended to send a complete HTTP response, including closing the connection
|
93
|
+
# if appropriate at the end of the transmission. Don't use this method to send partial
|
94
|
+
# or iterated responses. This method will send chunks and multiparts provided they
|
95
|
+
# are all available when we get here.
|
96
|
+
# Note that the default @status is 200 if the value doesn't exist.
|
97
|
+
def send_response
|
98
|
+
send_headers
|
99
|
+
send_body
|
100
|
+
send_trailer
|
101
|
+
close_connection_after_writing unless (@keep_connection_open and (@status || 200) < 500)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Send the headers out in alpha-sorted order. This will degrade performance to some
|
105
|
+
# degree, and is intended only to simplify the construction of unit tests.
|
106
|
+
#
|
107
|
+
def send_headers
|
108
|
+
raise "sent headers already" if @sent_headers
|
109
|
+
@sent_headers = true
|
110
|
+
|
111
|
+
fixup_headers
|
112
|
+
|
113
|
+
ary = []
|
114
|
+
ary << "HTTP/1.1 #{@status || 200} #{@status_string || '...'}\r\n"
|
115
|
+
ary += generate_header_lines(@headers)
|
116
|
+
ary << "\r\n"
|
117
|
+
|
118
|
+
send_data ary.join
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def generate_header_lines in_hash
|
123
|
+
out_ary = []
|
124
|
+
in_hash.keys.sort.each {|k|
|
125
|
+
v = in_hash[k]
|
126
|
+
if v.is_a?(Array)
|
127
|
+
v.each {|v1| out_ary << "#{k}: #{v1}\r\n" }
|
128
|
+
else
|
129
|
+
out_ary << "#{k}: #{v}\r\n"
|
130
|
+
end
|
131
|
+
}
|
132
|
+
out_ary
|
133
|
+
end
|
134
|
+
private :generate_header_lines
|
135
|
+
|
136
|
+
|
137
|
+
# Examine the content type and data and other things, and perform a final
|
138
|
+
# fixup of the header array. We expect this to be called just before sending
|
139
|
+
# headers to the remote peer.
|
140
|
+
# In the case of multiparts, we ASSUME we will get called before any content
|
141
|
+
# gets sent out, because the multipart boundary is created here.
|
142
|
+
#
|
143
|
+
def fixup_headers
|
144
|
+
if @content
|
145
|
+
@headers["Content-length"] = @content.to_s.length
|
146
|
+
elsif @chunks
|
147
|
+
@headers["Transfer-encoding"] = "chunked"
|
148
|
+
# Might be nice to ENSURE there is no content-length header,
|
149
|
+
# but how to detect all the possible permutations of upper/lower case?
|
150
|
+
elsif @multiparts
|
151
|
+
@multipart_boundary = self.class.concoct_multipart_boundary
|
152
|
+
@headers["Content-type"] = "multipart/x-mixed-replace; boundary=\"#{@multipart_boundary}\""
|
153
|
+
else
|
154
|
+
@headers["Content-length"] = 0
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# we send either content, chunks, or multiparts. Content can only be sent once.
|
159
|
+
# Chunks and multiparts can be sent any number of times.
|
160
|
+
# DO NOT close the connection or send any goodbye kisses. This method can
|
161
|
+
# be called multiple times to send out chunks or multiparts.
|
162
|
+
def send_body
|
163
|
+
if @content
|
164
|
+
send_content
|
165
|
+
elsif @chunks
|
166
|
+
send_chunks
|
167
|
+
elsif @multiparts
|
168
|
+
send_multiparts
|
169
|
+
else
|
170
|
+
@content = ""
|
171
|
+
send_content
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# send a trailer which depends on the type of body we're dealing with.
|
176
|
+
# The assumption is that we're about to end the transmission of this particular
|
177
|
+
# HTTP response. (A connection-close may or may not follow.)
|
178
|
+
#
|
179
|
+
def send_trailer
|
180
|
+
send_headers unless @sent_headers
|
181
|
+
if @content
|
182
|
+
# no-op
|
183
|
+
elsif @chunks
|
184
|
+
unless @last_chunk_sent
|
185
|
+
chunk ""
|
186
|
+
send_chunks
|
187
|
+
end
|
188
|
+
elsif @multiparts
|
189
|
+
# in the lingo of RFC 2046/5.1.1, we're sending an "epilog"
|
190
|
+
# consisting of a blank line. I really don't know how that is
|
191
|
+
# supposed to interact with the case where we leave the connection
|
192
|
+
# open after transmitting the multipart response.
|
193
|
+
send_data "\r\n--#{@multipart_boundary}--\r\n\r\n"
|
194
|
+
else
|
195
|
+
# no-op
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def send_content
|
200
|
+
raise "sent content already" if @sent_content
|
201
|
+
@sent_content = true
|
202
|
+
send_data((@content || "").to_s)
|
203
|
+
end
|
204
|
+
|
205
|
+
# add a chunk to go to the output.
|
206
|
+
# Will cause the headers to pick up "content-transfer-encoding"
|
207
|
+
# Add the chunk to a list. Calling #send_chunks will send out the
|
208
|
+
# available chunks and clear the chunk list WITHOUT closing the connection,
|
209
|
+
# so it can be called any number of times.
|
210
|
+
# TODO!!! Per RFC2616, we may not send chunks to an HTTP/1.0 client.
|
211
|
+
# Raise an exception here if our user tries to do so.
|
212
|
+
# Chunked transfer coding is defined in RFC2616 pgh 3.6.1.
|
213
|
+
# The argument can be a string or a hash. The latter allows for
|
214
|
+
# sending chunks with extensions (someday).
|
215
|
+
#
|
216
|
+
def chunk text
|
217
|
+
@chunks ||= []
|
218
|
+
@chunks << text
|
219
|
+
end
|
220
|
+
|
221
|
+
# send the contents of the chunk list and clear it out.
|
222
|
+
# ASSUMES that headers have been sent.
|
223
|
+
# Does NOT close the connection.
|
224
|
+
# Can be called multiple times.
|
225
|
+
# According to RFC2616, phg 3.6.1, the last chunk will be zero length.
|
226
|
+
# But some caller could accidentally set a zero-length chunk in the middle
|
227
|
+
# of the stream. If that should happen, raise an exception.
|
228
|
+
# The reason for supporting chunks that are hashes instead of just strings
|
229
|
+
# is to enable someday supporting chunk-extension codes (cf the RFC).
|
230
|
+
# TODO!!! We're not supporting the final entity-header that may be
|
231
|
+
# transmitted after the last (zero-length) chunk.
|
232
|
+
#
|
233
|
+
def send_chunks
|
234
|
+
send_headers unless @sent_headers
|
235
|
+
while chunk = @chunks.shift
|
236
|
+
raise "last chunk already sent" if @last_chunk_sent
|
237
|
+
text = chunk.is_a?(Hash) ? chunk[:text] : chunk.to_s
|
238
|
+
send_data "#{format("%x", text.length).upcase}\r\n#{text}\r\n"
|
239
|
+
@last_chunk_sent = true if text.length == 0
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# To add a multipart to the outgoing response, specify the headers and the
|
244
|
+
# body. If only a string is given, it's treated as the body (in this case,
|
245
|
+
# the header is assumed to be empty).
|
246
|
+
#
|
247
|
+
def multipart arg
|
248
|
+
vals = if arg.is_a?(String)
|
249
|
+
{:body => arg, :headers => {}}
|
250
|
+
else
|
251
|
+
arg
|
252
|
+
end
|
253
|
+
|
254
|
+
@multiparts ||= []
|
255
|
+
@multiparts << vals
|
256
|
+
end
|
257
|
+
|
258
|
+
# Multipart syntax is defined in RFC 2046, pgh 5.1.1 et seq.
|
259
|
+
# The CRLF which introduces the boundary line of each part (content entity)
|
260
|
+
# is defined as being part of the boundary, not of the preceding part.
|
261
|
+
# So we don't need to mess with interpreting the last bytes of a part
|
262
|
+
# to ensure they are CRLF-terminated.
|
263
|
+
#
|
264
|
+
def send_multiparts
|
265
|
+
send_headers unless @sent_headers
|
266
|
+
while part = @multiparts.shift
|
267
|
+
send_data "\r\n--#{@multipart_boundary}\r\n"
|
268
|
+
send_data( generate_header_lines( part[:headers] || {} ).join)
|
269
|
+
send_data "\r\n"
|
270
|
+
send_data part[:body].to_s
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# TODO, this is going to be way too slow. Cache up the uuidgens.
|
275
|
+
#
|
276
|
+
def self.concoct_multipart_boundary
|
277
|
+
@multipart_index ||= 0
|
278
|
+
@multipart_index += 1
|
279
|
+
if @multipart_index >= 1000
|
280
|
+
@multipart_index = 0
|
281
|
+
@multipart_guid = nil
|
282
|
+
end
|
283
|
+
@multipart_guid ||= `uuidgen -r`.chomp.gsub(/\-/,"")
|
284
|
+
"#{@multipart_guid}#{@multipart_index}"
|
285
|
+
end
|
286
|
+
|
287
|
+
def send_redirect location
|
288
|
+
@status = 302 # TODO, make 301 available by parameter
|
289
|
+
@status_string = "Moved Temporarily"
|
290
|
+
@headers["Location"] = location
|
291
|
+
send_response
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
#----------------------------------------------------------------------------
|
297
|
+
|
298
|
+
require 'forwardable'
|
299
|
+
|
300
|
+
module EventMachine
|
301
|
+
class DelegatedHttpResponse < HttpResponse
|
302
|
+
extend Forwardable
|
303
|
+
def_delegators :@delegate,
|
304
|
+
:send_data,
|
305
|
+
:close_connection,
|
306
|
+
:close_connection_after_writing
|
307
|
+
|
308
|
+
def initialize dele
|
309
|
+
super()
|
310
|
+
@delegate = dele
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
module EventMachine
|
5
|
+
module Http
|
6
|
+
class Server < EM::P::HeaderAndContentProtocol
|
7
|
+
|
8
|
+
# everything starts from here.
|
9
|
+
# Protocol::HeaderAndContentProtocol does the dirty job for us
|
10
|
+
# it will pass headers and content, we just need to parse the headers
|
11
|
+
# the fill the right variables
|
12
|
+
def receive_request(headers, content)
|
13
|
+
|
14
|
+
# save the whole headers array, verbatim
|
15
|
+
@http_headers = headers
|
16
|
+
|
17
|
+
# parse the headers into an hash to be able to access them like:
|
18
|
+
# @http[:host]
|
19
|
+
# @http[:content_type]
|
20
|
+
@http = headers_2_hash headers
|
21
|
+
|
22
|
+
# parse the HTTP request
|
23
|
+
parse_first_header headers.first
|
24
|
+
|
25
|
+
# save the binary content
|
26
|
+
@http_content = content
|
27
|
+
|
28
|
+
# invoke the method in the user-provided instance
|
29
|
+
if respond_to?(:process_http_request)
|
30
|
+
process_http_request
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# parse the first HTTP header line
|
35
|
+
# get the http METHOD, URI and PROTOCOL
|
36
|
+
def parse_first_header(line)
|
37
|
+
|
38
|
+
# split the line into: METHOD URI PROTOCOL
|
39
|
+
# eg: GET / HTTP1/1
|
40
|
+
parsed = line.split(' ')
|
41
|
+
|
42
|
+
# a correct request has three parts
|
43
|
+
send_error(400, "Bad request") unless parsed.size == 3
|
44
|
+
|
45
|
+
@http_request_method, uri, @http_protocol = parsed
|
46
|
+
|
47
|
+
# uri must begin with a /
|
48
|
+
send_error(400, "Bad request") unless uri.start_with?('/')
|
49
|
+
|
50
|
+
# optional query string
|
51
|
+
@http_request_uri, @http_query_string = uri.split('?')
|
52
|
+
end
|
53
|
+
|
54
|
+
# send back to the client an HTTP error
|
55
|
+
def send_error(code, desc)
|
56
|
+
string = "HTTP1/1 #{code} #{desc}\r\n"
|
57
|
+
string << "Connection: close\r\n"
|
58
|
+
string << "Content-type: text/plain\r\n"
|
59
|
+
string << "\r\n"
|
60
|
+
string << "Detected error: HTTP code #{code}"
|
61
|
+
send_data string
|
62
|
+
close_connection_after_writing
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
if __FILE__ == $0
|
71
|
+
|
72
|
+
class HTTPHandler < EM::Http::Server
|
73
|
+
|
74
|
+
def process_http_request
|
75
|
+
puts @http_request_method
|
76
|
+
puts @http_request_uri
|
77
|
+
puts @http_query_string
|
78
|
+
puts @http_protocol
|
79
|
+
puts @http[:cookie]
|
80
|
+
puts @http[:content_type]
|
81
|
+
puts @http_content
|
82
|
+
puts @http.inspect
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
port = 8080
|
88
|
+
# all the events are handled here
|
89
|
+
EM::run do
|
90
|
+
EM::start_server("0.0.0.0", port, HTTPHandler)
|
91
|
+
puts "Listening on port #{port}..."
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/test/test_app.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'em-http-server'
|
3
|
+
|
4
|
+
begin
|
5
|
+
once = false
|
6
|
+
require 'eventmachine'
|
7
|
+
rescue LoadError => e
|
8
|
+
raise e if once
|
9
|
+
once = true
|
10
|
+
require 'rubygems'
|
11
|
+
retry
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
#--------------------------------------
|
17
|
+
|
18
|
+
module EventMachine
|
19
|
+
class HttpServer < EM::Http::Server
|
20
|
+
def process_http_request
|
21
|
+
send_data generate_response()
|
22
|
+
close_connection_after_writing
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#--------------------------------------
|
28
|
+
|
29
|
+
require 'socket'
|
30
|
+
|
31
|
+
class TestApp < Test::Unit::TestCase
|
32
|
+
|
33
|
+
TestHost = "127.0.0.1"
|
34
|
+
TestPort = 8911
|
35
|
+
|
36
|
+
TestResponse_1 = <<EORESP
|
37
|
+
HTTP/1.0 200 ...
|
38
|
+
Content-length: 4
|
39
|
+
Content-type: text/plain
|
40
|
+
Connection: close
|
41
|
+
|
42
|
+
1234
|
43
|
+
EORESP
|
44
|
+
|
45
|
+
Thread.abort_on_exception = true
|
46
|
+
|
47
|
+
def test_simple_get
|
48
|
+
received_response = nil
|
49
|
+
|
50
|
+
EventMachine::HttpServer.class_eval do
|
51
|
+
def generate_response
|
52
|
+
TestResponse_1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
EventMachine.run do
|
58
|
+
EventMachine.start_server TestHost, TestPort, EventMachine::HttpServer
|
59
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
60
|
+
|
61
|
+
cb = proc do
|
62
|
+
tcp = TCPSocket.new TestHost, TestPort
|
63
|
+
tcp.write "GET / HTTP/1.0\r\n\r\n"
|
64
|
+
received_response = tcp.read
|
65
|
+
end
|
66
|
+
eb = proc { EventMachine.stop }
|
67
|
+
EventMachine.defer cb, eb
|
68
|
+
end
|
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::HttpServer
|
79
|
+
|
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_http = {}
|
101
|
+
request_uri = ""
|
102
|
+
request_query = ""
|
103
|
+
request_method = ""
|
104
|
+
|
105
|
+
|
106
|
+
EventMachine.run do
|
107
|
+
EventMachine.start_server(TestHost, TestPort, MyTestServer) do |conn|
|
108
|
+
# In each accepted connection, set up a procedure that will copy
|
109
|
+
# the request parameters into a local variable visible here, so
|
110
|
+
# we can assert the values later.
|
111
|
+
conn.instance_eval do
|
112
|
+
@assertions = proc {
|
113
|
+
request_method = @http_request_method
|
114
|
+
request_uri = @http_request_uri
|
115
|
+
request_query = @http_query_string
|
116
|
+
request_http = @http
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
121
|
+
|
122
|
+
cb = proc do
|
123
|
+
tcp = TCPSocket.new TestHost, TestPort
|
124
|
+
data = [
|
125
|
+
"GET #{path_info}?#{query_string} HTTP/1.1\r\n",
|
126
|
+
"Cookie: #{cookie}\r\n",
|
127
|
+
"If-none-match: #{etag}\r\n",
|
128
|
+
"\r\n"
|
129
|
+
].join
|
130
|
+
tcp.write(data)
|
131
|
+
received_response = tcp.read
|
132
|
+
end
|
133
|
+
eb = proc { EventMachine.stop }
|
134
|
+
EventMachine.defer cb, eb
|
135
|
+
end
|
136
|
+
|
137
|
+
assert_equal( TestResponse_1, received_response )
|
138
|
+
assert_equal( path_info, request_uri )
|
139
|
+
assert_equal( query_string, request_query )
|
140
|
+
assert_equal( cookie, request_http[:cookie] )
|
141
|
+
assert_equal( etag, request_http[:if_none_match] )
|
142
|
+
assert_equal( nil, request_http[:content_type] )
|
143
|
+
assert_equal( "GET", request_method )
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def test_headers
|
148
|
+
received_header_string = nil
|
149
|
+
received_header_ary = nil
|
150
|
+
|
151
|
+
EventMachine.run do
|
152
|
+
EventMachine.start_server(TestHost, TestPort, MyTestServer) do |conn|
|
153
|
+
# In each accepted connection, set up a procedure that will copy
|
154
|
+
# the request parameters into a local variable visible here, so
|
155
|
+
# we can assert the values later.
|
156
|
+
# The @http_headers is set automatically and can easily be parsed.
|
157
|
+
# It isn't automatically parsed into Ruby values because that is
|
158
|
+
# a costly operation, but we should provide an optional method that
|
159
|
+
# does the parsing so it doesn't need to be done by users.
|
160
|
+
conn.instance_eval do
|
161
|
+
@assertions = proc do
|
162
|
+
received_header_string = @http_headers
|
163
|
+
received_header_ary = @http.map {|line| line }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
cb = proc do
|
169
|
+
tcp = TCPSocket.new TestHost, TestPort
|
170
|
+
data = [
|
171
|
+
"GET / HTTP/1.1\r\n",
|
172
|
+
"aaa: 111\r\n",
|
173
|
+
"bbb: 222\r\n",
|
174
|
+
"ccc: 333\r\n",
|
175
|
+
"ddd: 444\r\n",
|
176
|
+
"\r\n"
|
177
|
+
].join
|
178
|
+
tcp.write data
|
179
|
+
received_response = tcp.read
|
180
|
+
end
|
181
|
+
eb = proc { EventMachine.stop }
|
182
|
+
EventMachine.defer cb, eb
|
183
|
+
|
184
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
185
|
+
end
|
186
|
+
|
187
|
+
assert_equal( ["GET / HTTP/1.1", "aaa: 111", "bbb: 222", "ccc: 333", "ddd: 444"], received_header_string )
|
188
|
+
assert_equal( [[:aaa,"111"], [:bbb,"222"], [:ccc,"333"], [:ddd,"444"]], received_header_ary )
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
def test_post
|
196
|
+
received_header_string = nil
|
197
|
+
post_content = "1234567890"
|
198
|
+
content_type = "text/plain"
|
199
|
+
received_post_content = ""
|
200
|
+
received_content_type = ""
|
201
|
+
|
202
|
+
EventMachine.run do
|
203
|
+
EventMachine.start_server(TestHost, TestPort, MyTestServer) do |conn|
|
204
|
+
# In each accepted connection, set up a procedure that will copy
|
205
|
+
# the request parameters into a local variable visible here, so
|
206
|
+
# we can assert the values later.
|
207
|
+
# The @http_post_content variable is set automatically.
|
208
|
+
conn.instance_eval do
|
209
|
+
@assertions = proc do
|
210
|
+
received_post_content = @http_content
|
211
|
+
received_content_type = @http[:content_type]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
216
|
+
|
217
|
+
cb = proc do
|
218
|
+
tcp = TCPSocket.new TestHost, TestPort
|
219
|
+
data = [
|
220
|
+
"POST / HTTP/1.1\r\n",
|
221
|
+
"Content-type: #{content_type}\r\n",
|
222
|
+
"Content-length: #{post_content.length}\r\n",
|
223
|
+
"\r\n",
|
224
|
+
post_content
|
225
|
+
].join
|
226
|
+
tcp.write(data)
|
227
|
+
received_response = tcp.read
|
228
|
+
end
|
229
|
+
eb = proc do
|
230
|
+
EventMachine.stop
|
231
|
+
end
|
232
|
+
EventMachine.defer cb, eb
|
233
|
+
end
|
234
|
+
|
235
|
+
assert_equal( post_content, received_post_content)
|
236
|
+
assert_equal( content_type, received_content_type)
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
def test_invalid
|
241
|
+
received_response = nil
|
242
|
+
|
243
|
+
EventMachine.run do
|
244
|
+
EventMachine.start_server(TestHost, TestPort, MyTestServer) do |conn|
|
245
|
+
conn.instance_eval do
|
246
|
+
@assertions = proc do
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
cb = proc do
|
252
|
+
tcp = TCPSocket.new TestHost, TestPort
|
253
|
+
data = [
|
254
|
+
"GET foo HTTP/1.1\r\n",
|
255
|
+
"\r\n"
|
256
|
+
].join
|
257
|
+
tcp.write data
|
258
|
+
received_response = tcp.read
|
259
|
+
end
|
260
|
+
eb = proc { EventMachine.stop }
|
261
|
+
EventMachine.defer cb, eb
|
262
|
+
|
263
|
+
EventMachine.add_timer(1) {raise "timed out"} # make sure the test completes
|
264
|
+
end
|
265
|
+
|
266
|
+
assert_equal( "HTTP1/1 400 Bad request\r\nConnection: close\r\nContent-type: text/plain\r\n\r\nDetected error: HTTP code 400", received_response )
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'em-http-server'
|
3
|
+
|
4
|
+
begin
|
5
|
+
once = false
|
6
|
+
require 'eventmachine'
|
7
|
+
rescue LoadError => e
|
8
|
+
raise e if once
|
9
|
+
once = true
|
10
|
+
require 'rubygems'
|
11
|
+
retry
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
#--------------------------------------
|
16
|
+
|
17
|
+
|
18
|
+
class TestDelegatedHttpResponse < Test::Unit::TestCase
|
19
|
+
|
20
|
+
# This is a delegate class that (trivially) implements the
|
21
|
+
# several classes needed to work with HttpResponse.
|
22
|
+
#
|
23
|
+
class D
|
24
|
+
attr_reader :output_data
|
25
|
+
attr_reader :closed_after_writing
|
26
|
+
|
27
|
+
def send_data data
|
28
|
+
@output_data ||= ""
|
29
|
+
@output_data << data
|
30
|
+
end
|
31
|
+
def close_connection_after_writing
|
32
|
+
@closed_after_writing = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def setup
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def teardown
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def test_properties
|
45
|
+
a = EM::DelegatedHttpResponse.new( D.new )
|
46
|
+
a.status = 200
|
47
|
+
a.content = "Some content"
|
48
|
+
a.headers["Content-type"] = "text/xml"
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_header_sugarings
|
52
|
+
a = EM::DelegatedHttpResponse.new( D.new )
|
53
|
+
a.content_type "text/xml"
|
54
|
+
a.set_cookie "a=b"
|
55
|
+
a.headers["X-bayshore"] = "aaaa"
|
56
|
+
|
57
|
+
assert_equal({
|
58
|
+
"Content-type" => "text/xml",
|
59
|
+
"Set-cookie" => ["a=b"],
|
60
|
+
"X-bayshore" => "aaaa"
|
61
|
+
}, a.headers)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_send_response
|
65
|
+
d = D.new
|
66
|
+
a = EM::DelegatedHttpResponse.new( d )
|
67
|
+
a.status = 200
|
68
|
+
a.send_response
|
69
|
+
assert_equal([
|
70
|
+
"HTTP/1.1 200 ...\r\n",
|
71
|
+
"Content-length: 0\r\n",
|
72
|
+
"\r\n"
|
73
|
+
].join, d.output_data)
|
74
|
+
assert_equal( true, d.closed_after_writing )
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_send_response_1
|
78
|
+
d = D.new
|
79
|
+
a = EM::DelegatedHttpResponse.new( d )
|
80
|
+
a.status = 200
|
81
|
+
a.content_type "text/plain"
|
82
|
+
a.content = "ABC"
|
83
|
+
a.send_response
|
84
|
+
assert_equal([
|
85
|
+
"HTTP/1.1 200 ...\r\n",
|
86
|
+
"Content-length: 3\r\n",
|
87
|
+
"Content-type: text/plain\r\n",
|
88
|
+
"\r\n",
|
89
|
+
"ABC"
|
90
|
+
].join, d.output_data)
|
91
|
+
assert( d.closed_after_writing )
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_send_response_no_close
|
95
|
+
d = D.new
|
96
|
+
a = EM::DelegatedHttpResponse.new( d )
|
97
|
+
a.status = 200
|
98
|
+
a.content_type "text/plain"
|
99
|
+
a.content = "ABC"
|
100
|
+
a.keep_connection_open
|
101
|
+
a.send_response
|
102
|
+
assert_equal([
|
103
|
+
"HTTP/1.1 200 ...\r\n",
|
104
|
+
"Content-length: 3\r\n",
|
105
|
+
"Content-type: text/plain\r\n",
|
106
|
+
"\r\n",
|
107
|
+
"ABC"
|
108
|
+
].join, d.output_data)
|
109
|
+
assert( ! d.closed_after_writing )
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_send_response_multiple_times
|
113
|
+
a = EM::DelegatedHttpResponse.new( D.new )
|
114
|
+
a.status = 200
|
115
|
+
a.send_response
|
116
|
+
assert_raise( RuntimeError ) {
|
117
|
+
a.send_response
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_send_headers
|
122
|
+
d = D.new
|
123
|
+
a = EM::DelegatedHttpResponse.new( d )
|
124
|
+
a.status = 200
|
125
|
+
a.send_headers
|
126
|
+
assert_equal([
|
127
|
+
"HTTP/1.1 200 ...\r\n",
|
128
|
+
"Content-length: 0\r\n",
|
129
|
+
"\r\n"
|
130
|
+
].join, d.output_data)
|
131
|
+
assert( ! d.closed_after_writing )
|
132
|
+
assert_raise( RuntimeError ) {
|
133
|
+
a.send_headers
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_send_chunks
|
138
|
+
d = D.new
|
139
|
+
a = EM::DelegatedHttpResponse.new( d )
|
140
|
+
a.chunk "ABC"
|
141
|
+
a.chunk "DEF"
|
142
|
+
a.chunk "GHI"
|
143
|
+
a.keep_connection_open
|
144
|
+
a.send_response
|
145
|
+
assert_equal([
|
146
|
+
"HTTP/1.1 200 ...\r\n",
|
147
|
+
"Transfer-encoding: chunked\r\n",
|
148
|
+
"\r\n",
|
149
|
+
"3\r\n",
|
150
|
+
"ABC\r\n",
|
151
|
+
"3\r\n",
|
152
|
+
"DEF\r\n",
|
153
|
+
"3\r\n",
|
154
|
+
"GHI\r\n",
|
155
|
+
"0\r\n",
|
156
|
+
"\r\n"
|
157
|
+
].join, d.output_data)
|
158
|
+
assert( !d.closed_after_writing )
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_send_chunks_with_close
|
162
|
+
d = D.new
|
163
|
+
a = EM::DelegatedHttpResponse.new( d )
|
164
|
+
a.chunk "ABC"
|
165
|
+
a.chunk "DEF"
|
166
|
+
a.chunk "GHI"
|
167
|
+
a.send_response
|
168
|
+
assert_equal([
|
169
|
+
"HTTP/1.1 200 ...\r\n",
|
170
|
+
"Transfer-encoding: chunked\r\n",
|
171
|
+
"\r\n",
|
172
|
+
"3\r\n",
|
173
|
+
"ABC\r\n",
|
174
|
+
"3\r\n",
|
175
|
+
"DEF\r\n",
|
176
|
+
"3\r\n",
|
177
|
+
"GHI\r\n",
|
178
|
+
"0\r\n",
|
179
|
+
"\r\n"
|
180
|
+
].join, d.output_data)
|
181
|
+
assert( d.closed_after_writing )
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'em-http-server'
|
3
|
+
|
4
|
+
begin
|
5
|
+
once = false
|
6
|
+
require 'eventmachine'
|
7
|
+
rescue LoadError => e
|
8
|
+
raise e if once
|
9
|
+
once = true
|
10
|
+
require 'rubygems'
|
11
|
+
retry
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
#--------------------------------------
|
16
|
+
|
17
|
+
module EventMachine
|
18
|
+
|
19
|
+
# This is a test harness wired into the HttpResponse class so we
|
20
|
+
# can test it without requiring any actual network communication.
|
21
|
+
#
|
22
|
+
class HttpResponse
|
23
|
+
attr_reader :output_data
|
24
|
+
attr_reader :closed_after_writing
|
25
|
+
|
26
|
+
def send_data data
|
27
|
+
@output_data ||= ""
|
28
|
+
@output_data << data
|
29
|
+
end
|
30
|
+
def close_connection_after_writing
|
31
|
+
@closed_after_writing = true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#--------------------------------------
|
37
|
+
|
38
|
+
|
39
|
+
class TestHttpResponse < Test::Unit::TestCase
|
40
|
+
|
41
|
+
def test_properties
|
42
|
+
a = EventMachine::HttpResponse.new
|
43
|
+
a.status = 200
|
44
|
+
a.content = "Some content"
|
45
|
+
a.headers["Content-type"] = "text/xml"
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_header_sugarings
|
49
|
+
a = EventMachine::HttpResponse.new
|
50
|
+
a.content_type "text/xml"
|
51
|
+
a.set_cookie "a=b"
|
52
|
+
a.headers["X-bayshore"] = "aaaa"
|
53
|
+
|
54
|
+
assert_equal({
|
55
|
+
"Content-type" => "text/xml",
|
56
|
+
"Set-cookie" => ["a=b"],
|
57
|
+
"X-bayshore" => "aaaa"
|
58
|
+
}, a.headers)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_send_response
|
62
|
+
a = EventMachine::HttpResponse.new
|
63
|
+
a.status = 200
|
64
|
+
a.send_response
|
65
|
+
assert_equal([
|
66
|
+
"HTTP/1.1 200 ...\r\n",
|
67
|
+
"Content-length: 0\r\n",
|
68
|
+
"\r\n"
|
69
|
+
].join, a.output_data)
|
70
|
+
assert_equal( true, a.closed_after_writing )
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_send_response_with_status
|
74
|
+
a = EventMachine::HttpResponse.new
|
75
|
+
a.status = 200
|
76
|
+
a.status_string = "OK-TEST"
|
77
|
+
a.send_response
|
78
|
+
assert_equal([
|
79
|
+
"HTTP/1.1 200 OK-TEST\r\n",
|
80
|
+
"Content-length: 0\r\n",
|
81
|
+
"\r\n"
|
82
|
+
].join, a.output_data)
|
83
|
+
assert_equal( true, a.closed_after_writing )
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_send_response_1
|
87
|
+
a = EventMachine::HttpResponse.new
|
88
|
+
a.status = 200
|
89
|
+
a.content_type "text/plain"
|
90
|
+
a.content = "ABC"
|
91
|
+
a.send_response
|
92
|
+
assert_equal([
|
93
|
+
"HTTP/1.1 200 ...\r\n",
|
94
|
+
"Content-length: 3\r\n",
|
95
|
+
"Content-type: text/plain\r\n",
|
96
|
+
"\r\n",
|
97
|
+
"ABC"
|
98
|
+
].join, a.output_data)
|
99
|
+
assert( a.closed_after_writing )
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_send_response_no_close
|
103
|
+
a = EventMachine::HttpResponse.new
|
104
|
+
a.status = 200
|
105
|
+
a.content_type "text/plain"
|
106
|
+
a.content = "ABC"
|
107
|
+
a.keep_connection_open
|
108
|
+
a.send_response
|
109
|
+
assert_equal([
|
110
|
+
"HTTP/1.1 200 ...\r\n",
|
111
|
+
"Content-length: 3\r\n",
|
112
|
+
"Content-type: text/plain\r\n",
|
113
|
+
"\r\n",
|
114
|
+
"ABC"
|
115
|
+
].join, a.output_data)
|
116
|
+
assert( ! a.closed_after_writing )
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_send_response_no_close_with_a_404_response
|
120
|
+
a = EventMachine::HttpResponse.new
|
121
|
+
a.status = 404
|
122
|
+
a.content_type "text/plain"
|
123
|
+
a.content = "ABC"
|
124
|
+
a.keep_connection_open
|
125
|
+
a.send_response
|
126
|
+
assert_equal([
|
127
|
+
"HTTP/1.1 404 ...\r\n",
|
128
|
+
"Content-length: 3\r\n",
|
129
|
+
"Content-type: text/plain\r\n",
|
130
|
+
"\r\n",
|
131
|
+
"ABC"
|
132
|
+
].join, a.output_data)
|
133
|
+
assert( ! a.closed_after_writing )
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_send_response_no_close_with_a_201_response
|
137
|
+
a = EventMachine::HttpResponse.new
|
138
|
+
a.status = 201
|
139
|
+
a.content_type "text/plain"
|
140
|
+
a.content = "ABC"
|
141
|
+
a.keep_connection_open
|
142
|
+
a.send_response
|
143
|
+
assert_equal([
|
144
|
+
"HTTP/1.1 201 ...\r\n",
|
145
|
+
"Content-length: 3\r\n",
|
146
|
+
"Content-type: text/plain\r\n",
|
147
|
+
"\r\n",
|
148
|
+
"ABC"
|
149
|
+
].join, a.output_data)
|
150
|
+
assert( ! a.closed_after_writing )
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_send_response_no_close_with_a_500_response
|
154
|
+
a = EventMachine::HttpResponse.new
|
155
|
+
a.status = 500
|
156
|
+
a.content_type "text/plain"
|
157
|
+
a.content = "ABC"
|
158
|
+
a.keep_connection_open
|
159
|
+
a.send_response
|
160
|
+
assert_equal([
|
161
|
+
"HTTP/1.1 500 ...\r\n",
|
162
|
+
"Content-length: 3\r\n",
|
163
|
+
"Content-type: text/plain\r\n",
|
164
|
+
"\r\n",
|
165
|
+
"ABC"
|
166
|
+
].join, a.output_data)
|
167
|
+
assert( a.closed_after_writing )
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_send_response_multiple_times
|
171
|
+
a = EventMachine::HttpResponse.new
|
172
|
+
a.status = 200
|
173
|
+
a.send_response
|
174
|
+
assert_raise( RuntimeError ) {
|
175
|
+
a.send_response
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_send_headers
|
180
|
+
a = EventMachine::HttpResponse.new
|
181
|
+
a.status = 200
|
182
|
+
a.send_headers
|
183
|
+
assert_equal([
|
184
|
+
"HTTP/1.1 200 ...\r\n",
|
185
|
+
"Content-length: 0\r\n",
|
186
|
+
"\r\n"
|
187
|
+
].join, a.output_data)
|
188
|
+
assert( ! a.closed_after_writing )
|
189
|
+
assert_raise( RuntimeError ) {
|
190
|
+
a.send_headers
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_send_chunks
|
195
|
+
a = EventMachine::HttpResponse.new
|
196
|
+
a.chunk "ABC"
|
197
|
+
a.chunk "DEF"
|
198
|
+
a.chunk "GHI"
|
199
|
+
a.keep_connection_open
|
200
|
+
a.send_response
|
201
|
+
assert_equal([
|
202
|
+
"HTTP/1.1 200 ...\r\n",
|
203
|
+
"Transfer-encoding: chunked\r\n",
|
204
|
+
"\r\n",
|
205
|
+
"3\r\n",
|
206
|
+
"ABC\r\n",
|
207
|
+
"3\r\n",
|
208
|
+
"DEF\r\n",
|
209
|
+
"3\r\n",
|
210
|
+
"GHI\r\n",
|
211
|
+
"0\r\n",
|
212
|
+
"\r\n"
|
213
|
+
].join, a.output_data)
|
214
|
+
assert( !a.closed_after_writing )
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_send_chunks_with_close
|
218
|
+
a = EventMachine::HttpResponse.new
|
219
|
+
a.chunk "ABC"
|
220
|
+
a.chunk "DEF"
|
221
|
+
a.chunk "GHI"
|
222
|
+
a.send_response
|
223
|
+
assert_equal([
|
224
|
+
"HTTP/1.1 200 ...\r\n",
|
225
|
+
"Transfer-encoding: chunked\r\n",
|
226
|
+
"\r\n",
|
227
|
+
"3\r\n",
|
228
|
+
"ABC\r\n",
|
229
|
+
"3\r\n",
|
230
|
+
"DEF\r\n",
|
231
|
+
"3\r\n",
|
232
|
+
"GHI\r\n",
|
233
|
+
"0\r\n",
|
234
|
+
"\r\n"
|
235
|
+
].join, a.output_data)
|
236
|
+
assert( a.closed_after_writing )
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-http-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- alor
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: &70366152260060 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70366152260060
|
25
|
+
description: Simple http server for eventmachine
|
26
|
+
email:
|
27
|
+
- alberto.ornaghi@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- em-http-server.gemspec
|
38
|
+
- lib/em-http-server.rb
|
39
|
+
- lib/em-http-server/response.rb
|
40
|
+
- lib/em-http-server/server.rb
|
41
|
+
- test/test_app.rb
|
42
|
+
- test/test_delegated.rb
|
43
|
+
- test/test_response.rb
|
44
|
+
homepage: ''
|
45
|
+
licenses: []
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.8.15
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Simple http server for eventmachine with the same interface as evma_httpserver
|
68
|
+
test_files:
|
69
|
+
- test/test_app.rb
|
70
|
+
- test/test_delegated.rb
|
71
|
+
- test/test_response.rb
|