eventmachine 1.2.0.dev.2-x64-mingw32
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +105 -0
- data/GNU +281 -0
- data/LICENSE +60 -0
- data/README.md +108 -0
- data/docs/DocumentationGuidesIndex.md +27 -0
- data/docs/GettingStarted.md +521 -0
- data/docs/old/ChangeLog +211 -0
- data/docs/old/DEFERRABLES +246 -0
- data/docs/old/EPOLL +141 -0
- data/docs/old/INSTALL +13 -0
- data/docs/old/KEYBOARD +42 -0
- data/docs/old/LEGAL +25 -0
- data/docs/old/LIGHTWEIGHT_CONCURRENCY +130 -0
- data/docs/old/PURE_RUBY +75 -0
- data/docs/old/RELEASE_NOTES +94 -0
- data/docs/old/SMTP +4 -0
- data/docs/old/SPAWNED_PROCESSES +148 -0
- data/docs/old/TODO +8 -0
- data/examples/guides/getting_started/01_eventmachine_echo_server.rb +18 -0
- data/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb +22 -0
- data/examples/guides/getting_started/03_simple_chat_server.rb +149 -0
- data/examples/guides/getting_started/04_simple_chat_server_step_one.rb +27 -0
- data/examples/guides/getting_started/05_simple_chat_server_step_two.rb +43 -0
- data/examples/guides/getting_started/06_simple_chat_server_step_three.rb +98 -0
- data/examples/guides/getting_started/07_simple_chat_server_step_four.rb +121 -0
- data/examples/guides/getting_started/08_simple_chat_server_step_five.rb +141 -0
- data/examples/old/ex_channel.rb +43 -0
- data/examples/old/ex_queue.rb +2 -0
- data/examples/old/ex_tick_loop_array.rb +15 -0
- data/examples/old/ex_tick_loop_counter.rb +32 -0
- data/examples/old/helper.rb +2 -0
- data/ext/binder.cpp +124 -0
- data/ext/binder.h +46 -0
- data/ext/cmain.cpp +988 -0
- data/ext/ed.cpp +2111 -0
- data/ext/ed.h +442 -0
- data/ext/em.cpp +2379 -0
- data/ext/em.h +308 -0
- data/ext/eventmachine.h +143 -0
- data/ext/extconf.rb +270 -0
- data/ext/fastfilereader/extconf.rb +110 -0
- data/ext/fastfilereader/mapper.cpp +216 -0
- data/ext/fastfilereader/mapper.h +59 -0
- data/ext/fastfilereader/rubymain.cpp +127 -0
- data/ext/kb.cpp +79 -0
- data/ext/page.cpp +107 -0
- data/ext/page.h +51 -0
- data/ext/pipe.cpp +354 -0
- data/ext/project.h +176 -0
- data/ext/rubymain.cpp +1504 -0
- data/ext/ssl.cpp +615 -0
- data/ext/ssl.h +103 -0
- data/java/.classpath +8 -0
- data/java/.project +17 -0
- data/java/src/com/rubyeventmachine/EmReactor.java +591 -0
- data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
- data/java/src/com/rubyeventmachine/EventableChannel.java +72 -0
- data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +201 -0
- data/java/src/com/rubyeventmachine/EventableSocketChannel.java +415 -0
- data/lib/2.0/fastfilereaderext.so +0 -0
- data/lib/2.0/rubyeventmachine.so +0 -0
- data/lib/2.1/fastfilereaderext.so +0 -0
- data/lib/2.1/rubyeventmachine.so +0 -0
- data/lib/2.2/fastfilereaderext.so +0 -0
- data/lib/2.2/rubyeventmachine.so +0 -0
- data/lib/2.3/fastfilereaderext.so +0 -0
- data/lib/2.3/rubyeventmachine.so +0 -0
- data/lib/em/buftok.rb +59 -0
- data/lib/em/callback.rb +58 -0
- data/lib/em/channel.rb +69 -0
- data/lib/em/completion.rb +304 -0
- data/lib/em/connection.rb +770 -0
- data/lib/em/deferrable.rb +210 -0
- data/lib/em/deferrable/pool.rb +2 -0
- data/lib/em/file_watch.rb +73 -0
- data/lib/em/future.rb +61 -0
- data/lib/em/iterator.rb +252 -0
- data/lib/em/messages.rb +66 -0
- data/lib/em/pool.rb +151 -0
- data/lib/em/process_watch.rb +45 -0
- data/lib/em/processes.rb +123 -0
- data/lib/em/protocols.rb +37 -0
- data/lib/em/protocols/header_and_content.rb +138 -0
- data/lib/em/protocols/httpclient.rb +299 -0
- data/lib/em/protocols/httpclient2.rb +600 -0
- data/lib/em/protocols/line_and_text.rb +125 -0
- data/lib/em/protocols/line_protocol.rb +29 -0
- data/lib/em/protocols/linetext2.rb +166 -0
- data/lib/em/protocols/memcache.rb +331 -0
- data/lib/em/protocols/object_protocol.rb +46 -0
- data/lib/em/protocols/postgres3.rb +246 -0
- data/lib/em/protocols/saslauth.rb +175 -0
- data/lib/em/protocols/smtpclient.rb +394 -0
- data/lib/em/protocols/smtpserver.rb +666 -0
- data/lib/em/protocols/socks4.rb +66 -0
- data/lib/em/protocols/stomp.rb +205 -0
- data/lib/em/protocols/tcptest.rb +54 -0
- data/lib/em/pure_ruby.rb +1022 -0
- data/lib/em/queue.rb +80 -0
- data/lib/em/resolver.rb +232 -0
- data/lib/em/spawnable.rb +84 -0
- data/lib/em/streamer.rb +118 -0
- data/lib/em/threaded_resource.rb +90 -0
- data/lib/em/tick_loop.rb +85 -0
- data/lib/em/timers.rb +61 -0
- data/lib/em/version.rb +3 -0
- data/lib/eventmachine.rb +1584 -0
- data/lib/fastfilereaderext.rb +2 -0
- data/lib/jeventmachine.rb +301 -0
- data/lib/rubyeventmachine.rb +2 -0
- data/rakelib/package.rake +120 -0
- data/rakelib/test.rake +8 -0
- data/tests/client.crt +31 -0
- data/tests/client.key +51 -0
- data/tests/dhparam.pem +13 -0
- data/tests/em_test_helper.rb +151 -0
- data/tests/test_attach.rb +151 -0
- data/tests/test_basic.rb +283 -0
- data/tests/test_channel.rb +75 -0
- data/tests/test_completion.rb +178 -0
- data/tests/test_connection_count.rb +54 -0
- data/tests/test_connection_write.rb +35 -0
- data/tests/test_defer.rb +35 -0
- data/tests/test_deferrable.rb +35 -0
- data/tests/test_epoll.rb +142 -0
- data/tests/test_error_handler.rb +38 -0
- data/tests/test_exc.rb +28 -0
- data/tests/test_file_watch.rb +66 -0
- data/tests/test_fork.rb +75 -0
- data/tests/test_futures.rb +170 -0
- data/tests/test_get_sock_opt.rb +37 -0
- data/tests/test_handler_check.rb +35 -0
- data/tests/test_hc.rb +155 -0
- data/tests/test_httpclient.rb +233 -0
- data/tests/test_httpclient2.rb +128 -0
- data/tests/test_idle_connection.rb +25 -0
- data/tests/test_inactivity_timeout.rb +54 -0
- data/tests/test_ipv4.rb +125 -0
- data/tests/test_ipv6.rb +131 -0
- data/tests/test_iterator.rb +115 -0
- data/tests/test_kb.rb +28 -0
- data/tests/test_line_protocol.rb +33 -0
- data/tests/test_ltp.rb +138 -0
- data/tests/test_ltp2.rb +308 -0
- data/tests/test_many_fds.rb +22 -0
- data/tests/test_next_tick.rb +104 -0
- data/tests/test_object_protocol.rb +36 -0
- data/tests/test_pause.rb +107 -0
- data/tests/test_pending_connect_timeout.rb +52 -0
- data/tests/test_pool.rb +196 -0
- data/tests/test_process_watch.rb +50 -0
- data/tests/test_processes.rb +128 -0
- data/tests/test_proxy_connection.rb +180 -0
- data/tests/test_pure.rb +88 -0
- data/tests/test_queue.rb +64 -0
- data/tests/test_resolver.rb +104 -0
- data/tests/test_running.rb +14 -0
- data/tests/test_sasl.rb +47 -0
- data/tests/test_send_file.rb +217 -0
- data/tests/test_servers.rb +33 -0
- data/tests/test_set_sock_opt.rb +39 -0
- data/tests/test_shutdown_hooks.rb +23 -0
- data/tests/test_smtpclient.rb +75 -0
- data/tests/test_smtpserver.rb +57 -0
- data/tests/test_spawn.rb +293 -0
- data/tests/test_ssl_args.rb +78 -0
- data/tests/test_ssl_dhparam.rb +83 -0
- data/tests/test_ssl_ecdh_curve.rb +79 -0
- data/tests/test_ssl_extensions.rb +49 -0
- data/tests/test_ssl_methods.rb +65 -0
- data/tests/test_ssl_protocols.rb +246 -0
- data/tests/test_ssl_verify.rb +126 -0
- data/tests/test_stomp.rb +37 -0
- data/tests/test_system.rb +46 -0
- data/tests/test_threaded_resource.rb +61 -0
- data/tests/test_tick_loop.rb +59 -0
- data/tests/test_timers.rb +123 -0
- data/tests/test_ud.rb +8 -0
- data/tests/test_unbind_reason.rb +52 -0
- metadata +381 -0
@@ -0,0 +1,299 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
4
|
+
# Homepage:: http://rubyeventmachine.com
|
5
|
+
# Date:: 16 July 2006
|
6
|
+
#
|
7
|
+
# See EventMachine and EventMachine::Connection for documentation and
|
8
|
+
# usage examples.
|
9
|
+
#
|
10
|
+
#----------------------------------------------------------------------------
|
11
|
+
#
|
12
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
13
|
+
# Gmail: blackhedd
|
14
|
+
#
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
19
|
+
#
|
20
|
+
# See the file COPYING for complete licensing information.
|
21
|
+
#
|
22
|
+
#---------------------------------------------------------------------------
|
23
|
+
#
|
24
|
+
#
|
25
|
+
|
26
|
+
module EventMachine
|
27
|
+
module Protocols
|
28
|
+
|
29
|
+
# <b>Note:</b> This class is deprecated and will be removed. Please use EM-HTTP-Request instead.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# EventMachine.run {
|
33
|
+
# http = EventMachine::Protocols::HttpClient.request(
|
34
|
+
# :host => server,
|
35
|
+
# :port => 80,
|
36
|
+
# :request => "/index.html",
|
37
|
+
# :query_string => "parm1=value1&parm2=value2"
|
38
|
+
# )
|
39
|
+
# http.callback {|response|
|
40
|
+
# puts response[:status]
|
41
|
+
# puts response[:headers]
|
42
|
+
# puts response[:content]
|
43
|
+
# }
|
44
|
+
# }
|
45
|
+
#--
|
46
|
+
# TODO:
|
47
|
+
# Add streaming so we can support enormous POSTs. Current max is 20meg.
|
48
|
+
# Timeout for connections that run too long or hang somewhere in the middle.
|
49
|
+
# Persistent connections (HTTP/1.1), may need a associated delegate object.
|
50
|
+
# DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
|
51
|
+
# DNS lookups are unbelievably slow.
|
52
|
+
# HEAD requests.
|
53
|
+
# Convenience methods for requests. get, post, url, etc.
|
54
|
+
# SSL.
|
55
|
+
# Handle status codes like 304, 100, etc.
|
56
|
+
# Refactor this code so that protocol errors all get handled one way (an exception?),
|
57
|
+
# instead of sprinkling set_deferred_status :failed calls everywhere.
|
58
|
+
class HttpClient < Connection
|
59
|
+
include EventMachine::Deferrable
|
60
|
+
|
61
|
+
MaxPostContentLength = 20 * 1024 * 1024
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
warn "HttpClient is deprecated and will be removed. EM-Http-Request should be used instead."
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param args [Hash] The request arguments
|
68
|
+
# @option args [String] :host The host IP/DNS name
|
69
|
+
# @option args [Integer] :port The port to connect too
|
70
|
+
# @option args [String] :verb The request type [GET | POST | DELETE | PUT]
|
71
|
+
# @option args [String] :request The request path
|
72
|
+
# @option args [Hash] :basic_auth The basic auth credentials (:username and :password)
|
73
|
+
# @option args [String] :content The request content
|
74
|
+
# @option args [String] :contenttype The content type (e.g. text/plain)
|
75
|
+
# @option args [String] :query_string The query string
|
76
|
+
# @option args [String] :host_header The host header to set
|
77
|
+
# @option args [String] :cookie Cookies to set
|
78
|
+
def self.request( args = {} )
|
79
|
+
args[:port] ||= 80
|
80
|
+
EventMachine.connect( args[:host], args[:port], self ) {|c|
|
81
|
+
# According to the docs, we will get here AFTER post_init is called.
|
82
|
+
c.instance_eval {@args = args}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def post_init
|
87
|
+
@start_time = Time.now
|
88
|
+
@data = ""
|
89
|
+
@read_state = :base
|
90
|
+
end
|
91
|
+
|
92
|
+
# We send the request when we get a connection.
|
93
|
+
# AND, we set an instance variable to indicate we passed through here.
|
94
|
+
# That allows #unbind to know whether there was a successful connection.
|
95
|
+
# NB: This naive technique won't work when we have to support multiple
|
96
|
+
# requests on a single connection.
|
97
|
+
def connection_completed
|
98
|
+
@connected = true
|
99
|
+
send_request @args
|
100
|
+
end
|
101
|
+
|
102
|
+
def send_request args
|
103
|
+
args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
|
104
|
+
args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
|
105
|
+
|
106
|
+
verb = args[:verb].to_s.upcase
|
107
|
+
unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
|
108
|
+
set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
|
109
|
+
return # NOTE THE EARLY RETURN, we're not sending any data.
|
110
|
+
end
|
111
|
+
|
112
|
+
request = args[:request] || "/"
|
113
|
+
unless request[0,1] == "/"
|
114
|
+
request = "/" + request
|
115
|
+
end
|
116
|
+
|
117
|
+
qs = args[:query_string] || ""
|
118
|
+
if qs.length > 0 and qs[0,1] != '?'
|
119
|
+
qs = "?" + qs
|
120
|
+
end
|
121
|
+
|
122
|
+
version = args[:version] || "1.1"
|
123
|
+
|
124
|
+
# Allow an override for the host header if it's not the connect-string.
|
125
|
+
host = args[:host_header] || args[:host] || "_"
|
126
|
+
# For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
|
127
|
+
port = args[:port].to_i != 80 ? ":#{args[:port]}" : ""
|
128
|
+
|
129
|
+
# POST items.
|
130
|
+
postcontenttype = args[:contenttype] || "application/octet-stream"
|
131
|
+
postcontent = args[:content] || ""
|
132
|
+
raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
|
133
|
+
|
134
|
+
# ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
|
135
|
+
# TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
|
136
|
+
req = [
|
137
|
+
"#{verb} #{request}#{qs} HTTP/#{version}",
|
138
|
+
"Host: #{host}#{port}",
|
139
|
+
"User-agent: Ruby EventMachine",
|
140
|
+
]
|
141
|
+
|
142
|
+
if verb == "POST" || verb == "PUT"
|
143
|
+
req << "Content-type: #{postcontenttype}"
|
144
|
+
req << "Content-length: #{postcontent.length}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
|
148
|
+
# Eventually we will want to deal intelligently with arrays and hashes.
|
149
|
+
if args[:cookie]
|
150
|
+
req << "Cookie: #{args[:cookie]}"
|
151
|
+
end
|
152
|
+
|
153
|
+
# Allow custom HTTP headers, e.g. SOAPAction
|
154
|
+
args[:custom_headers].each do |k,v|
|
155
|
+
req << "#{k}: #{v}"
|
156
|
+
end if args[:custom_headers]
|
157
|
+
|
158
|
+
# Basic-auth stanza contributed by Matt Murphy.
|
159
|
+
if args[:basic_auth]
|
160
|
+
basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'')
|
161
|
+
req << "Authorization: Basic #{basic_auth_string}"
|
162
|
+
end
|
163
|
+
|
164
|
+
req << ""
|
165
|
+
reqstring = req.map {|l| "#{l}\r\n"}.join
|
166
|
+
send_data reqstring
|
167
|
+
|
168
|
+
if verb == "POST" || verb == "PUT"
|
169
|
+
send_data postcontent
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
def receive_data data
|
175
|
+
while data and data.length > 0
|
176
|
+
case @read_state
|
177
|
+
when :base
|
178
|
+
# Perform any per-request initialization here and don't consume any data.
|
179
|
+
@data = ""
|
180
|
+
@headers = []
|
181
|
+
@content_length = nil # not zero
|
182
|
+
@content = ""
|
183
|
+
@status = nil
|
184
|
+
@chunked = false
|
185
|
+
@chunk_length = nil
|
186
|
+
@read_state = :header
|
187
|
+
@connection_close = nil
|
188
|
+
when :header
|
189
|
+
ary = data.split( /\r?\n/m, 2 )
|
190
|
+
if ary.length == 2
|
191
|
+
data = ary.last
|
192
|
+
if ary.first == ""
|
193
|
+
if (@content_length and @content_length > 0) || @chunked || @connection_close
|
194
|
+
@read_state = :content
|
195
|
+
else
|
196
|
+
dispatch_response
|
197
|
+
@read_state = :base
|
198
|
+
end
|
199
|
+
else
|
200
|
+
@headers << ary.first
|
201
|
+
if @headers.length == 1
|
202
|
+
parse_response_line
|
203
|
+
elsif ary.first =~ /\Acontent-length:\s*/i
|
204
|
+
# Only take the FIRST content-length header that appears,
|
205
|
+
# which we can distinguish because @content_length is nil.
|
206
|
+
# TODO, it's actually a fatal error if there is more than one
|
207
|
+
# content-length header, because the caller is presumptively
|
208
|
+
# a bad guy. (There is an exploit that depends on multiple
|
209
|
+
# content-length headers.)
|
210
|
+
@content_length ||= $'.to_i
|
211
|
+
elsif ary.first =~ /\Aconnection:\s*close/i
|
212
|
+
@connection_close = true
|
213
|
+
elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i
|
214
|
+
@chunked = true
|
215
|
+
end
|
216
|
+
end
|
217
|
+
else
|
218
|
+
@data << data
|
219
|
+
data = ""
|
220
|
+
end
|
221
|
+
when :content
|
222
|
+
if @chunked && @chunk_length
|
223
|
+
bytes_needed = @chunk_length - @chunk_read
|
224
|
+
new_data = data[0, bytes_needed]
|
225
|
+
@chunk_read += new_data.length
|
226
|
+
@content += new_data
|
227
|
+
data = data[bytes_needed..-1] || ""
|
228
|
+
if @chunk_length == @chunk_read && data[0,2] == "\r\n"
|
229
|
+
@chunk_length = nil
|
230
|
+
data = data[2..-1]
|
231
|
+
end
|
232
|
+
elsif @chunked
|
233
|
+
if (m = data.match(/\A(\S*)\r\n/m))
|
234
|
+
data = data[m[0].length..-1]
|
235
|
+
@chunk_length = m[1].to_i(16)
|
236
|
+
@chunk_read = 0
|
237
|
+
if @chunk_length == 0
|
238
|
+
dispatch_response
|
239
|
+
@read_state = :base
|
240
|
+
end
|
241
|
+
end
|
242
|
+
elsif @content_length
|
243
|
+
# If there was no content-length header, we have to wait until the connection
|
244
|
+
# closes. Everything we get until that point is content.
|
245
|
+
# TODO: Must impose a content-size limit, and also must implement chunking.
|
246
|
+
# Also, must support either temporary files for large content, or calling
|
247
|
+
# a content-consumer block supplied by the user.
|
248
|
+
bytes_needed = @content_length - @content.length
|
249
|
+
@content += data[0, bytes_needed]
|
250
|
+
data = data[bytes_needed..-1] || ""
|
251
|
+
if @content_length == @content.length
|
252
|
+
dispatch_response
|
253
|
+
@read_state = :base
|
254
|
+
end
|
255
|
+
else
|
256
|
+
@content << data
|
257
|
+
data = ""
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
# We get called here when we have received an HTTP response line.
|
265
|
+
# It's an opportunity to throw an exception or trigger other exceptional
|
266
|
+
# handling.
|
267
|
+
def parse_response_line
|
268
|
+
if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
|
269
|
+
@status = $1.to_i
|
270
|
+
else
|
271
|
+
set_deferred_status :failed, {
|
272
|
+
:status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
|
273
|
+
}
|
274
|
+
close_connection
|
275
|
+
end
|
276
|
+
end
|
277
|
+
private :parse_response_line
|
278
|
+
|
279
|
+
def dispatch_response
|
280
|
+
@read_state = :base
|
281
|
+
set_deferred_status :succeeded, {
|
282
|
+
:content => @content,
|
283
|
+
:headers => @headers,
|
284
|
+
:status => @status
|
285
|
+
}
|
286
|
+
# TODO, we close the connection for now, but this is wrong for persistent clients.
|
287
|
+
close_connection
|
288
|
+
end
|
289
|
+
|
290
|
+
def unbind
|
291
|
+
if !@connected
|
292
|
+
set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
|
293
|
+
elsif (@read_state == :content and @content_length == nil)
|
294
|
+
dispatch_response
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,600 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
4
|
+
# Homepage:: http://rubyeventmachine.com
|
5
|
+
# Date:: 16 July 2006
|
6
|
+
#
|
7
|
+
# See EventMachine and EventMachine::Connection for documentation and
|
8
|
+
# usage examples.
|
9
|
+
#
|
10
|
+
#----------------------------------------------------------------------------
|
11
|
+
#
|
12
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
13
|
+
# Gmail: blackhedd
|
14
|
+
#
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
19
|
+
#
|
20
|
+
# See the file COPYING for complete licensing information.
|
21
|
+
#
|
22
|
+
#---------------------------------------------------------------------------
|
23
|
+
#
|
24
|
+
#
|
25
|
+
|
26
|
+
module EventMachine
|
27
|
+
module Protocols
|
28
|
+
|
29
|
+
# <b>Note:</b> This class is deprecated and will be removed. Please use EM-HTTP-Request instead.
|
30
|
+
#
|
31
|
+
# === Usage
|
32
|
+
#
|
33
|
+
# EM.run{
|
34
|
+
# conn = EM::Protocols::HttpClient2.connect 'google.com', 80
|
35
|
+
#
|
36
|
+
# req = conn.get('/')
|
37
|
+
# req.callback{ |response|
|
38
|
+
# p(response.status)
|
39
|
+
# p(response.headers)
|
40
|
+
# p(response.content)
|
41
|
+
# }
|
42
|
+
# }
|
43
|
+
class HttpClient2 < Connection
|
44
|
+
include LineText2
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
warn "HttpClient2 is deprecated and will be removed. EM-Http-Request should be used instead."
|
48
|
+
|
49
|
+
@authorization = nil
|
50
|
+
@closed = nil
|
51
|
+
@requests = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
# @private
|
55
|
+
class Request
|
56
|
+
include Deferrable
|
57
|
+
|
58
|
+
attr_reader :version
|
59
|
+
attr_reader :status
|
60
|
+
attr_reader :header_lines
|
61
|
+
attr_reader :headers
|
62
|
+
attr_reader :content
|
63
|
+
attr_reader :internal_error
|
64
|
+
|
65
|
+
def initialize conn, args
|
66
|
+
@conn = conn
|
67
|
+
@args = args
|
68
|
+
@header_lines = []
|
69
|
+
@headers = {}
|
70
|
+
@blanks = 0
|
71
|
+
@chunk_trailer = nil
|
72
|
+
@chunking = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def send_request
|
76
|
+
az = @args[:authorization] and az = "Authorization: #{az}\r\n"
|
77
|
+
|
78
|
+
r = [
|
79
|
+
"#{@args[:verb]} #{@args[:uri]} HTTP/#{@args[:version] || "1.1"}\r\n",
|
80
|
+
"Host: #{@args[:host_header] || "_"}\r\n",
|
81
|
+
az || "",
|
82
|
+
"\r\n"
|
83
|
+
]
|
84
|
+
@conn.send_data r.join
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
#--
|
89
|
+
#
|
90
|
+
def receive_line ln
|
91
|
+
if @chunk_trailer
|
92
|
+
receive_chunk_trailer(ln)
|
93
|
+
elsif @chunking
|
94
|
+
receive_chunk_header(ln)
|
95
|
+
else
|
96
|
+
receive_header_line(ln)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#--
|
101
|
+
#
|
102
|
+
def receive_chunk_trailer ln
|
103
|
+
if ln.length == 0
|
104
|
+
@conn.pop_request
|
105
|
+
succeed(self)
|
106
|
+
else
|
107
|
+
p "Received chunk trailer line"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#--
|
112
|
+
# Allow up to ten blank lines before we get a real response line.
|
113
|
+
# Allow no more than 100 lines in the header.
|
114
|
+
#
|
115
|
+
def receive_header_line ln
|
116
|
+
if ln.length == 0
|
117
|
+
if @header_lines.length > 0
|
118
|
+
process_header
|
119
|
+
else
|
120
|
+
@blanks += 1
|
121
|
+
if @blanks > 10
|
122
|
+
@conn.close_connection
|
123
|
+
end
|
124
|
+
end
|
125
|
+
else
|
126
|
+
@header_lines << ln
|
127
|
+
if @header_lines.length > 100
|
128
|
+
@internal_error = :bad_header
|
129
|
+
@conn.close_connection
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
#--
|
135
|
+
# Cf RFC 2616 pgh 3.6.1 for the format of HTTP chunks.
|
136
|
+
#
|
137
|
+
def receive_chunk_header ln
|
138
|
+
if ln.length > 0
|
139
|
+
chunksize = ln.to_i(16)
|
140
|
+
if chunksize > 0
|
141
|
+
@conn.set_text_mode(ln.to_i(16))
|
142
|
+
else
|
143
|
+
@content = @content ? @content.join : ''
|
144
|
+
@chunk_trailer = true
|
145
|
+
end
|
146
|
+
else
|
147
|
+
# We correctly come here after each chunk gets read.
|
148
|
+
# p "Got A BLANK chunk line"
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
#--
|
155
|
+
# We get a single chunk. Append it to the incoming content and switch back to line mode.
|
156
|
+
#
|
157
|
+
def receive_chunked_text text
|
158
|
+
# p "RECEIVED #{text.length} CHUNK"
|
159
|
+
(@content ||= []) << text
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
#--
|
164
|
+
# TODO, inefficient how we're handling this. Part of it is done so as to
|
165
|
+
# make sure we don't have problems in detecting chunked-encoding, content-length,
|
166
|
+
# etc.
|
167
|
+
#
|
168
|
+
HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
|
169
|
+
ClenRE = /\AContent-length:\s*(\d+)/i
|
170
|
+
ChunkedRE = /\ATransfer-encoding:\s*chunked/i
|
171
|
+
ColonRE = /\:\s*/
|
172
|
+
|
173
|
+
def process_header
|
174
|
+
unless @header_lines.first =~ HttpResponseRE
|
175
|
+
@conn.close_connection
|
176
|
+
@internal_error = :bad_request
|
177
|
+
end
|
178
|
+
@version = $1.dup
|
179
|
+
@status = $2.dup.to_i
|
180
|
+
|
181
|
+
clen = nil
|
182
|
+
chunks = nil
|
183
|
+
@header_lines.each_with_index do |e,ix|
|
184
|
+
if ix > 0
|
185
|
+
hdr,val = e.split(ColonRE,2)
|
186
|
+
(@headers[hdr.downcase] ||= []) << val
|
187
|
+
end
|
188
|
+
|
189
|
+
if clen == nil and e =~ ClenRE
|
190
|
+
clen = $1.dup.to_i
|
191
|
+
end
|
192
|
+
if e =~ ChunkedRE
|
193
|
+
chunks = true
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
if clen
|
198
|
+
# If the content length is zero we should not call set_text_mode,
|
199
|
+
# because a value of zero will make it wait forever, hanging the
|
200
|
+
# connection. Just return success instead, with empty content.
|
201
|
+
if clen == 0 then
|
202
|
+
@content = ""
|
203
|
+
@conn.pop_request
|
204
|
+
succeed(self)
|
205
|
+
else
|
206
|
+
@conn.set_text_mode clen
|
207
|
+
end
|
208
|
+
elsif chunks
|
209
|
+
@chunking = true
|
210
|
+
else
|
211
|
+
# Chunked transfer, multipart, or end-of-connection.
|
212
|
+
# For end-of-connection, we need to go the unbind
|
213
|
+
# method and suppress its desire to fail us.
|
214
|
+
p "NO CLEN"
|
215
|
+
p @args[:uri]
|
216
|
+
p @header_lines
|
217
|
+
@internal_error = :unsupported_clen
|
218
|
+
@conn.close_connection
|
219
|
+
end
|
220
|
+
end
|
221
|
+
private :process_header
|
222
|
+
|
223
|
+
|
224
|
+
def receive_text text
|
225
|
+
@chunking ? receive_chunked_text(text) : receive_sized_text(text)
|
226
|
+
end
|
227
|
+
|
228
|
+
#--
|
229
|
+
# At the present time, we only handle contents that have a length
|
230
|
+
# specified by the content-length header.
|
231
|
+
#
|
232
|
+
def receive_sized_text text
|
233
|
+
@content = text
|
234
|
+
@conn.pop_request
|
235
|
+
succeed(self)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Make a connection to a remote HTTP server.
|
240
|
+
# Can take either a pair of arguments (which will be interpreted as
|
241
|
+
# a hostname/ip-address and a port), or a hash.
|
242
|
+
# If the arguments are a hash, then supported values include:
|
243
|
+
# :host => a hostname or ip-address
|
244
|
+
# :port => a port number
|
245
|
+
# :ssl => true to enable ssl
|
246
|
+
def self.connect *args
|
247
|
+
if args.length == 2
|
248
|
+
args = {:host=>args[0], :port=>args[1]}
|
249
|
+
else
|
250
|
+
args = args.first
|
251
|
+
end
|
252
|
+
|
253
|
+
h,prt,ssl = args[:host], Integer(args[:port]), (args[:tls] || args[:ssl])
|
254
|
+
conn = EM.connect( h, prt, self )
|
255
|
+
conn.start_tls if ssl
|
256
|
+
conn.set_default_host_header( h, prt, ssl )
|
257
|
+
conn
|
258
|
+
end
|
259
|
+
|
260
|
+
# Get a url
|
261
|
+
#
|
262
|
+
# req = conn.get(:uri => '/')
|
263
|
+
# req.callback{|response| puts response.content }
|
264
|
+
#
|
265
|
+
def get args
|
266
|
+
if args.is_a?(String)
|
267
|
+
args = {:uri=>args}
|
268
|
+
end
|
269
|
+
args[:verb] = "GET"
|
270
|
+
request args
|
271
|
+
end
|
272
|
+
|
273
|
+
# Post to a url
|
274
|
+
#
|
275
|
+
# req = conn.post('/data')
|
276
|
+
# req.callback{|response| puts response.content }
|
277
|
+
#--
|
278
|
+
# XXX there's no way to supply a POST body.. wtf?
|
279
|
+
def post args
|
280
|
+
if args.is_a?(String)
|
281
|
+
args = {:uri=>args}
|
282
|
+
end
|
283
|
+
args[:verb] = "POST"
|
284
|
+
request args
|
285
|
+
end
|
286
|
+
|
287
|
+
|
288
|
+
#--
|
289
|
+
# Compute and remember a string to be used as the host header in HTTP requests
|
290
|
+
# unless the user overrides it with an argument to #request.
|
291
|
+
#
|
292
|
+
# @private
|
293
|
+
def set_default_host_header host, port, ssl
|
294
|
+
if (ssl and port != 443) or (!ssl and port != 80)
|
295
|
+
@host_header = "#{host}:#{port}"
|
296
|
+
else
|
297
|
+
@host_header = host
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
# @private
|
303
|
+
def post_init
|
304
|
+
super
|
305
|
+
@connected = EM::DefaultDeferrable.new
|
306
|
+
end
|
307
|
+
|
308
|
+
# @private
|
309
|
+
def connection_completed
|
310
|
+
super
|
311
|
+
@connected.succeed
|
312
|
+
end
|
313
|
+
|
314
|
+
#--
|
315
|
+
# All pending requests, if any, must fail.
|
316
|
+
# We might come here without ever passing through connection_completed
|
317
|
+
# in case we can't connect to the server. We'll also get here when the
|
318
|
+
# connection closes (either because the server closes it, or we close it
|
319
|
+
# due to detecting an internal error or security violation).
|
320
|
+
# In either case, run down all pending requests, if any, and signal failure
|
321
|
+
# on them.
|
322
|
+
#
|
323
|
+
# Set and remember a flag (@closed) so we can immediately fail any
|
324
|
+
# subsequent requests.
|
325
|
+
#
|
326
|
+
# @private
|
327
|
+
def unbind
|
328
|
+
super
|
329
|
+
@closed = true
|
330
|
+
(@requests || []).each {|r| r.fail}
|
331
|
+
end
|
332
|
+
|
333
|
+
# @private
|
334
|
+
def request args
|
335
|
+
args[:host_header] = @host_header unless args.has_key?(:host_header)
|
336
|
+
args[:authorization] = @authorization unless args.has_key?(:authorization)
|
337
|
+
r = Request.new self, args
|
338
|
+
if @closed
|
339
|
+
r.fail
|
340
|
+
else
|
341
|
+
(@requests ||= []).unshift r
|
342
|
+
@connected.callback {r.send_request}
|
343
|
+
end
|
344
|
+
r
|
345
|
+
end
|
346
|
+
|
347
|
+
# @private
|
348
|
+
def receive_line ln
|
349
|
+
if req = @requests.last
|
350
|
+
req.receive_line ln
|
351
|
+
else
|
352
|
+
p "??????????"
|
353
|
+
p ln
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# @private
|
358
|
+
def receive_binary_data text
|
359
|
+
@requests.last.receive_text text
|
360
|
+
end
|
361
|
+
|
362
|
+
#--
|
363
|
+
# Called by a Request object when it completes.
|
364
|
+
#
|
365
|
+
# @private
|
366
|
+
def pop_request
|
367
|
+
@requests.pop
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
|
372
|
+
=begin
|
373
|
+
class HttpClient2x < Connection
|
374
|
+
include LineText2
|
375
|
+
|
376
|
+
# TODO: Make this behave appropriate in case a #connect fails.
|
377
|
+
# Currently, this produces no errors.
|
378
|
+
|
379
|
+
# Make a connection to a remote HTTP server.
|
380
|
+
# Can take either a pair of arguments (which will be interpreted as
|
381
|
+
# a hostname/ip-address and a port), or a hash.
|
382
|
+
# If the arguments are a hash, then supported values include:
|
383
|
+
# :host => a hostname or ip-address;
|
384
|
+
# :port => a port number
|
385
|
+
#--
|
386
|
+
# TODO, support optional encryption arguments like :ssl
|
387
|
+
def self.connect *args
|
388
|
+
if args.length == 2
|
389
|
+
args = {:host=>args[0], :port=>args[1]}
|
390
|
+
else
|
391
|
+
args = args.first
|
392
|
+
end
|
393
|
+
|
394
|
+
h,prt = args[:host],Integer(args[:port])
|
395
|
+
EM.connect( h, prt, self, h, prt )
|
396
|
+
end
|
397
|
+
|
398
|
+
|
399
|
+
#--
|
400
|
+
# Sugars a connection that makes a single request and then
|
401
|
+
# closes the connection. Matches the behavior and the arguments
|
402
|
+
# of the original implementation of class HttpClient.
|
403
|
+
#
|
404
|
+
# Intended primarily for back compatibility, but the idiom
|
405
|
+
# is probably useful so it's not deprecated.
|
406
|
+
# We return a Deferrable, as did the original implementation.
|
407
|
+
#
|
408
|
+
# Because we're improving the way we deal with errors and exceptions
|
409
|
+
# (specifically, HTTP response codes other than 2xx will trigger the
|
410
|
+
# errback rather than the callback), this may break some existing code.
|
411
|
+
#
|
412
|
+
def self.request args
|
413
|
+
c = connect args
|
414
|
+
end
|
415
|
+
|
416
|
+
#--
|
417
|
+
# Requests can be pipelined. When we get a request, add it to the
|
418
|
+
# front of a queue as an array. The last element of the @requests
|
419
|
+
# array is always the oldest request received. Each element of the
|
420
|
+
# @requests array is a two-element array consisting of a hash with
|
421
|
+
# the original caller's arguments, and an initially-empty Ostruct
|
422
|
+
# containing the data we retrieve from the server's response.
|
423
|
+
# Maintain the instance variable @current_response, which is the response
|
424
|
+
# of the oldest pending request. That's just to make other code a little
|
425
|
+
# easier. If the variable doesn't exist when we come here, we're
|
426
|
+
# obviously the first request being made on the connection.
|
427
|
+
#
|
428
|
+
# The reason for keeping this method private (and requiring use of the
|
429
|
+
# convenience methods #get, #post, #head, etc) is to avoid the small
|
430
|
+
# performance penalty of canonicalizing the verb.
|
431
|
+
#
|
432
|
+
def request args
|
433
|
+
d = EventMachine::DefaultDeferrable.new
|
434
|
+
|
435
|
+
if @closed
|
436
|
+
d.fail
|
437
|
+
return d
|
438
|
+
end
|
439
|
+
|
440
|
+
o = OpenStruct.new
|
441
|
+
o.deferrable = d
|
442
|
+
(@requests ||= []).unshift [args, o]
|
443
|
+
@current_response ||= @requests.last.last
|
444
|
+
@connected.callback {
|
445
|
+
az = args[:authorization] and az = "Authorization: #{az}\r\n"
|
446
|
+
|
447
|
+
r = [
|
448
|
+
"#{args[:verb]} #{args[:uri]} HTTP/#{args[:version] || "1.1"}\r\n",
|
449
|
+
"Host: #{args[:host_header] || @host_header}\r\n",
|
450
|
+
az || "",
|
451
|
+
"\r\n"
|
452
|
+
]
|
453
|
+
p r
|
454
|
+
send_data r.join
|
455
|
+
}
|
456
|
+
o.deferrable
|
457
|
+
end
|
458
|
+
private :request
|
459
|
+
|
460
|
+
def get args
|
461
|
+
if args.is_a?(String)
|
462
|
+
args = {:uri=>args}
|
463
|
+
end
|
464
|
+
args[:verb] = "GET"
|
465
|
+
request args
|
466
|
+
end
|
467
|
+
|
468
|
+
def initialize host, port
|
469
|
+
super
|
470
|
+
@host_header = "#{host}:#{port}"
|
471
|
+
end
|
472
|
+
def post_init
|
473
|
+
super
|
474
|
+
@connected = EM::DefaultDeferrable.new
|
475
|
+
end
|
476
|
+
|
477
|
+
|
478
|
+
def connection_completed
|
479
|
+
super
|
480
|
+
@connected.succeed
|
481
|
+
end
|
482
|
+
|
483
|
+
#--
|
484
|
+
# Make sure to throw away any leftover incoming data if we've
|
485
|
+
# been closed due to recognizing an error.
|
486
|
+
#
|
487
|
+
# Generate an internal error if we get an unreasonable number of
|
488
|
+
# header lines. It could be malicious.
|
489
|
+
#
|
490
|
+
def receive_line ln
|
491
|
+
p ln
|
492
|
+
return if @closed
|
493
|
+
|
494
|
+
if ln.length > 0
|
495
|
+
(@current_response.headers ||= []).push ln
|
496
|
+
abort_connection if @current_response.headers.length > 100
|
497
|
+
else
|
498
|
+
process_received_headers
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
#--
|
503
|
+
# We come here when we've seen all the headers for a particular request.
|
504
|
+
# What we do next depends on the response line (which should be the
|
505
|
+
# first line in the header set), and whether there is content to read.
|
506
|
+
# We may transition into a text-reading state to read content, or
|
507
|
+
# we may abort the connection, or we may go right back into parsing
|
508
|
+
# responses for the next response in the chain.
|
509
|
+
#
|
510
|
+
# We make an ASSUMPTION that the first line is an HTTP response.
|
511
|
+
# Anything else produces an error that aborts the connection.
|
512
|
+
# This may not be enough, because it may be that responses to pipelined
|
513
|
+
# requests will come with a blank-line delimiter.
|
514
|
+
#
|
515
|
+
# Any non-2xx response will be treated as a fatal error, and abort the
|
516
|
+
# connection. We will set up the status and other response parameters.
|
517
|
+
# TODO: we will want to properly support 1xx responses, which some versions
|
518
|
+
# of IIS copiously generate.
|
519
|
+
# TODO: We need to give the option of not aborting the connection with certain
|
520
|
+
# non-200 responses, in order to work with NTLM and other authentication
|
521
|
+
# schemes that work at the level of individual connections.
|
522
|
+
#
|
523
|
+
# Some error responses will get sugarings. For example, we'll return the
|
524
|
+
# Location header in the response in case of a 301/302 response.
|
525
|
+
#
|
526
|
+
# Possible dispositions here:
|
527
|
+
# 1) No content to read (either content-length is zero or it's a HEAD request);
|
528
|
+
# 2) Switch to text mode to read a specific number of bytes;
|
529
|
+
# 3) Read a chunked or multipart response;
|
530
|
+
# 4) Read till the server closes the connection.
|
531
|
+
#
|
532
|
+
# Our reponse to the client can be either to wait till all the content
|
533
|
+
# has been read and then to signal caller's deferrable, or else to signal
|
534
|
+
# it when we finish the processing the headers and then expect the caller
|
535
|
+
# to have given us a block to call as the content comes in. And of course
|
536
|
+
# the latter gets stickier with chunks and multiparts.
|
537
|
+
#
|
538
|
+
HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
|
539
|
+
ClenRE = /\AContent-length:\s*(\d+)/i
|
540
|
+
def process_received_headers
|
541
|
+
abort_connection unless @current_response.headers.first =~ HttpResponseRE
|
542
|
+
@current_response.version = $1.dup
|
543
|
+
st = $2.dup
|
544
|
+
@current_response.status = st.to_i
|
545
|
+
abort_connection unless st[0,1] == "2"
|
546
|
+
|
547
|
+
clen = nil
|
548
|
+
@current_response.headers.each do |e|
|
549
|
+
if clen == nil and e =~ ClenRE
|
550
|
+
clen = $1.dup.to_i
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
if clen
|
555
|
+
set_text_mode clen
|
556
|
+
end
|
557
|
+
end
|
558
|
+
private :process_received_headers
|
559
|
+
|
560
|
+
|
561
|
+
def receive_binary_data text
|
562
|
+
@current_response.content = text
|
563
|
+
@current_response.deferrable.succeed @current_response
|
564
|
+
@requests.pop
|
565
|
+
@current_response = (@requests.last || []).last
|
566
|
+
set_line_mode
|
567
|
+
end
|
568
|
+
|
569
|
+
|
570
|
+
|
571
|
+
# We've received either a server error or an internal error.
|
572
|
+
# Close the connection and abort any pending requests.
|
573
|
+
#--
|
574
|
+
# When should we call close_connection? It will cause #unbind
|
575
|
+
# to be fired. Should the user expect to see #unbind before
|
576
|
+
# we call #receive_http_error, or the other way around?
|
577
|
+
#
|
578
|
+
# Set instance variable @closed. That's used to inhibit further
|
579
|
+
# processing of any inbound data after an error has been recognized.
|
580
|
+
#
|
581
|
+
# We shouldn't have to worry about any leftover outbound data,
|
582
|
+
# because we call close_connection (not close_connection_after_writing).
|
583
|
+
# That ensures that any pipelined requests received after an error
|
584
|
+
# DO NOT get streamed out to the server on this connection.
|
585
|
+
# Very important. TODO, write a unit-test to establish that behavior.
|
586
|
+
#
|
587
|
+
def abort_connection
|
588
|
+
close_connection
|
589
|
+
@closed = true
|
590
|
+
@current_response.deferrable.fail( @current_response )
|
591
|
+
end
|
592
|
+
|
593
|
+
|
594
|
+
#------------------------
|
595
|
+
# Below here are user-overridable methods.
|
596
|
+
|
597
|
+
end
|
598
|
+
=end
|
599
|
+
end
|
600
|
+
end
|