goshrine_bot 0.1.1 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,288 +0,0 @@
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
-
27
-
28
- module GoshrineBot
29
-
30
- # === Usage
31
- #
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
- # Chunked transfer encoding.
54
- # Convenience methods for requests. get, post, url, etc.
55
- # SSL.
56
- # Handle status codes like 304, 100, etc.
57
- # Refactor this code so that protocol errors all get handled one way (an exception?),
58
- # instead of sprinkling set_deferred_status :failed calls everywhere.
59
- class HttpClient < EventMachine::Connection
60
- include EventMachine::Deferrable
61
-
62
- MaxPostContentLength = 20 * 1024 * 1024
63
-
64
- # === Arg list
65
- # :host => 'ip/dns', :port => fixnum, :verb => 'GET', :request => 'path',
66
- # :basic_auth => {:username => '', :password => ''}, :content => 'content',
67
- # :contenttype => 'text/plain', :query_string => '', :host_header => '',
68
- # :cookie => ''
69
- def self.request( args = {} )
70
- args[:port] ||= 80
71
- EventMachine.connect( args[:host], args[:port], self ) {|c|
72
- # According to the docs, we will get here AFTER post_init is called.
73
- c.instance_eval {@args = args}
74
- }
75
- end
76
-
77
- def post_init
78
- @start_time = Time.now
79
- @data = ""
80
- @read_state = :base
81
- end
82
-
83
- # We send the request when we get a connection.
84
- # AND, we set an instance variable to indicate we passed through here.
85
- # That allows #unbind to know whether there was a successful connection.
86
- # NB: This naive technique won't work when we have to support multiple
87
- # requests on a single connection.
88
- def connection_completed
89
- @connected = true
90
- send_request @args
91
- end
92
-
93
- def send_request args
94
- args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
95
- args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
96
-
97
- verb = args[:verb].to_s.upcase
98
- unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
99
- set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
100
- return # NOTE THE EARLY RETURN, we're not sending any data.
101
- end
102
-
103
- request = args[:request] || "/"
104
- unless request[0,1] == "/"
105
- request = "/" + request
106
- end
107
-
108
- qs = args[:query_string] || ""
109
- if qs.length > 0 and qs[0,1] != '?'
110
- qs = "?" + qs
111
- end
112
-
113
- version = args[:version] || "1.1"
114
-
115
- # Allow an override for the host header if it's not the connect-string.
116
- host = args[:host_header] || args[:host] || "_"
117
- # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
118
- port = args[:port]
119
-
120
- # POST items.
121
- postcontenttype = args[:contenttype] || "application/octet-stream"
122
- postcontent = args[:content] || ""
123
- raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
124
-
125
- # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
126
- # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
127
- req = [
128
- "#{verb} #{request}#{qs} HTTP/#{version}",
129
- "Host: #{host}:#{port}",
130
- "User-agent: Ruby EventMachine",
131
- ]
132
-
133
- if verb == "POST" || verb == "PUT"
134
- req << "Content-type: #{postcontenttype}"
135
- req << "Content-length: #{postcontent.length}"
136
- end
137
-
138
- # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
139
- # Eventually we will want to deal intelligently with arrays and hashes.
140
- if args[:cookie]
141
- req << "Cookie: #{args[:cookie]}"
142
- end
143
-
144
- # Allow custom HTTP headers, e.g. SOAPAction
145
- args[:custom_headers].each do |k,v|
146
- req << "#{k}: #{v}"
147
- end
148
-
149
- # Basic-auth stanza contributed by Matt Murphy.
150
- if args[:basic_auth]
151
- basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'')
152
- req << "Authorization: Basic #{basic_auth_string}"
153
- end
154
-
155
- req << ""
156
- reqstring = req.map {|l| "#{l}\r\n"}.join
157
- send_data reqstring
158
-
159
- if verb == "POST" || verb == "PUT"
160
- send_data postcontent
161
- end
162
- end
163
-
164
-
165
- def receive_data data
166
- while data and data.length > 0
167
- case @read_state
168
- when :base
169
- # Perform any per-request initialization here and don't consume any data.
170
- @data = ""
171
- @headers = []
172
- @content_length = nil # not zero
173
- @content = ""
174
- @status = nil
175
- @read_state = :header
176
- @connection_close = nil
177
- when :header
178
- ary = data.split( /\r?\n/m, 2 )
179
- if ary.length == 2
180
- data = ary.last
181
- if ary.first == ""
182
- if (@content_length and @content_length > 0) || @chunked || @connection_close
183
- @read_state = :content
184
- else
185
- dispatch_response
186
- @read_state = :base
187
- end
188
- else
189
- @headers << ary.first
190
- if @headers.length == 1
191
- parse_response_line
192
- elsif ary.first =~ /\Acontent-length:\s*/i
193
- # Only take the FIRST content-length header that appears,
194
- # which we can distinguish because @content_length is nil.
195
- # TODO, it's actually a fatal error if there is more than one
196
- # content-length header, because the caller is presumptively
197
- # a bad guy. (There is an exploit that depends on multiple
198
- # content-length headers.)
199
- @content_length ||= $'.to_i
200
- elsif ary.first =~ /\Aconnection:\s*close/i
201
- @connection_close = true
202
- elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i
203
- @chunked = true
204
- end
205
- end
206
- else
207
- @data << data
208
- data = ""
209
- end
210
- when :content
211
- if @chunked && @chunk_length
212
- bytes_needed = @chunk_length - @chunk_read
213
- new_data = data[0, bytes_needed]
214
- @chunk_read += new_data.length
215
- @content += new_data
216
- data = data[bytes_needed..-1] || ""
217
- if @chunk_length == @chunk_read && data[0,2] == "\r\n"
218
- @chunk_length = nil
219
- data = data[2..-1]
220
- end
221
- elsif @chunked
222
- if (m = data.match(/\A(\S*)\r\n/m))
223
- data = data[m[0].length..-1]
224
- @chunk_length = m[1].to_i(16)
225
- @chunk_read = 0
226
- if @chunk_length == 0
227
- dispatch_response
228
- @read_state = :base
229
- end
230
- end
231
- elsif @content_length
232
- # If there was no content-length header, we have to wait until the connection
233
- # closes. Everything we get until that point is content.
234
- # TODO: Must impose a content-size limit, and also must implement chunking.
235
- # Also, must support either temporary files for large content, or calling
236
- # a content-consumer block supplied by the user.
237
- bytes_needed = @content_length - @content.length
238
- @content += data[0, bytes_needed]
239
- data = data[bytes_needed..-1] || ""
240
- if @content_length == @content.length
241
- dispatch_response
242
- @read_state = :base
243
- end
244
- else
245
- @content << data
246
- data = ""
247
- end
248
- end
249
- end
250
- end
251
-
252
-
253
- # We get called here when we have received an HTTP response line.
254
- # It's an opportunity to throw an exception or trigger other exceptional
255
- # handling.
256
- def parse_response_line
257
- if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
258
- @status = $1.to_i
259
- else
260
- set_deferred_status :failed, {
261
- :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
262
- }
263
- close_connection
264
- end
265
- end
266
- private :parse_response_line
267
-
268
- def dispatch_response
269
- @read_state = :base
270
- set_deferred_status :succeeded, {
271
- :content => @content,
272
- :headers => @headers,
273
- :status => @status
274
- }
275
- # TODO, we close the connection for now, but this is wrong for persistent clients.
276
- close_connection
277
- end
278
-
279
- def unbind
280
- if !@connected
281
- set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
282
- elsif (@read_state == :content and @content_length == nil)
283
- dispatch_response
284
- end
285
- end
286
- end
287
-
288
- end