em-http-request 0.2.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.

Potentially problematic release.


This version of em-http-request might be problematic. Click here for more details.

@@ -0,0 +1,53 @@
1
+ class Hash
2
+ # Stolen partially from Merb : http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html
3
+ # Convert this hash to a query string:
4
+ #
5
+ # { :name => "Bob",
6
+ # :address => {
7
+ # :street => '111 Ruby Ave.',
8
+ # :city => 'Ruby Central',
9
+ # :phones => ['111-111-1111', '222-222-2222']
10
+ # }
11
+ # }.to_params
12
+ # #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
13
+ #
14
+ def to_params
15
+ params = ''
16
+ stack = []
17
+
18
+ each do |k, v|
19
+ if v.is_a?(Hash)
20
+ stack << [k,v]
21
+ elsif v.is_a?(Array)
22
+ stack << [k,Hash.from_array(v)]
23
+ else
24
+ params << "#{k}=#{v}&"
25
+ end
26
+ end
27
+
28
+ stack.each do |parent, hash|
29
+ hash.each do |k, v|
30
+ if v.is_a?(Hash)
31
+ stack << ["#{parent}[#{k}]", v]
32
+ else
33
+ params << "#{parent}[#{k}]=#{v}&"
34
+ end
35
+ end
36
+ end
37
+
38
+ params.chop! # trailing &
39
+ params
40
+ end
41
+
42
+ ##
43
+ # Builds a hash from an array with keys as array indices.
44
+ def self.from_array(array = [])
45
+ h = Hash.new
46
+ array.size.times do |t|
47
+ h[t] = array[t]
48
+ end
49
+ h
50
+ end
51
+
52
+ end
53
+
@@ -0,0 +1,122 @@
1
+ require 'zlib'
2
+ require 'stringio'
3
+
4
+ ##
5
+ # Provides a unified callback interface to decompression libraries.
6
+ module EventMachine::HttpDecoders
7
+
8
+ class DecoderError < StandardError
9
+ end
10
+
11
+ class << self
12
+ def accepted_encodings
13
+ DECODERS.inject([]) { |r,d| r + d.encoding_names }
14
+ end
15
+
16
+ def decoder_for_encoding(encoding)
17
+ DECODERS.each { |d|
18
+ return d if d.encoding_names.include? encoding
19
+ }
20
+ nil
21
+ end
22
+ end
23
+
24
+ class Base
25
+ def self.encoding_names
26
+ name = to_s.split('::').last.downcase
27
+ [name]
28
+ end
29
+
30
+ ##
31
+ # chunk_callback:: [Block] To handle a decompressed chunk
32
+ def initialize(&chunk_callback)
33
+ @chunk_callback = chunk_callback
34
+ end
35
+
36
+ def <<(compressed)
37
+ return unless compressed && compressed.size > 0
38
+
39
+ decompressed = decompress(compressed)
40
+ receive_decompressed decompressed
41
+ end
42
+
43
+ def finalize!
44
+ decompressed = finalize
45
+ receive_decompressed decompressed
46
+ end
47
+
48
+ private
49
+
50
+ def receive_decompressed(decompressed)
51
+ if decompressed && decompressed.size > 0
52
+ @chunk_callback.call(decompressed)
53
+ end
54
+ end
55
+
56
+ protected
57
+
58
+ ##
59
+ # Must return a part of decompressed
60
+ def decompress(compressed)
61
+ nil
62
+ end
63
+
64
+ ##
65
+ # May return last part
66
+ def finalize
67
+ nil
68
+ end
69
+ end
70
+
71
+ class Deflate < Base
72
+ def decompress(compressed)
73
+ begin
74
+ @zstream ||= Zlib::Inflate.new(nil)
75
+ @zstream.inflate(compressed)
76
+ rescue Zlib::Error
77
+ raise DecoderError
78
+ end
79
+ end
80
+
81
+ def finalize
82
+ return nil unless @zstream
83
+
84
+ begin
85
+ r = @zstream.inflate(nil)
86
+ @zstream.close
87
+ r
88
+ rescue Zlib::Error
89
+ raise DecoderError
90
+ end
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Oneshot decompressor, due to lack of a streaming Gzip reader
96
+ # implementation. We may steal code from Zliby to improve this.
97
+ #
98
+ # For now, do not put `gzip' or `compressed' in your accept-encoding
99
+ # header if you expect much data through the :on_response interface.
100
+ class GZip < Base
101
+ def self.encoding_names
102
+ %w(gzip compressed)
103
+ end
104
+
105
+ def decompress(compressed)
106
+ @buf ||= ''
107
+ @buf += compressed
108
+ nil
109
+ end
110
+
111
+ def finalize
112
+ begin
113
+ Zlib::GzipReader.new(StringIO.new(@buf.to_s)).read
114
+ rescue Zlib::Error
115
+ raise DecoderError
116
+ end
117
+ end
118
+ end
119
+
120
+ DECODERS = [Deflate, GZip]
121
+
122
+ end
@@ -0,0 +1,51 @@
1
+ module EventMachine
2
+
3
+ # EventMachine based Multi request client, based on a streaming HTTPRequest class,
4
+ # which allows you to open multiple parallel connections and return only when all
5
+ # of them finish. (i.e. ideal for parallelizing workloads)
6
+ #
7
+ # == Example
8
+ #
9
+ # EventMachine.run {
10
+ #
11
+ # multi = EventMachine::MultiRequest.new
12
+ #
13
+ # # add multiple requests to the multi-handler
14
+ # multi.add(EventMachine::HttpRequest.new('http://www.google.com/').get)
15
+ # multi.add(EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
16
+ #
17
+ # multi.callback {
18
+ # p multi.responses[:succeeded]
19
+ # p multi.responses[:failed]
20
+ #
21
+ # EventMachine.stop
22
+ # }
23
+ # }
24
+ #
25
+
26
+ class MultiRequest
27
+ include EventMachine::Deferrable
28
+
29
+ attr_reader :requests, :responses
30
+
31
+ def initialize
32
+ @requests = []
33
+ @responses = {:succeeded => [], :failed => []}
34
+ end
35
+
36
+ def add(conn)
37
+ conn.callback { @responses[:succeeded].push(conn); check_progress }
38
+ conn.errback { @responses[:failed].push(conn); check_progress }
39
+
40
+ @requests.push(conn)
41
+ end
42
+
43
+ protected
44
+
45
+ # invoke callback if all requests have completed
46
+ def check_progress
47
+ succeed if (@responses[:succeeded].size + @responses[:failed].size) == @requests.size
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,81 @@
1
+ require 'base64'
2
+ require 'addressable/uri'
3
+
4
+ module EventMachine
5
+
6
+ # EventMachine based HTTP request class with support for streaming consumption
7
+ # of the response. Response is parsed with a Ragel-generated whitelist parser
8
+ # which supports chunked HTTP encoding.
9
+ #
10
+ # == Example
11
+ #
12
+ #
13
+ # EventMachine.run {
14
+ # http = EventMachine::HttpRequest.new('http://127.0.0.1/').get :query => {'keyname' => 'value'}
15
+ #
16
+ # http.callback {
17
+ # p http.response_header.status
18
+ # p http.response_header
19
+ # p http.response
20
+ #
21
+ # EventMachine.stop
22
+ # }
23
+ # }
24
+ #
25
+
26
+ class HttpRequest
27
+
28
+ def initialize(host, headers = {})
29
+ @headers = headers
30
+ @uri = host.kind_of?(Addressable::URI) ? host : Addressable::URI::parse(host)
31
+ end
32
+
33
+ # Send an HTTP request and consume the response. Supported options:
34
+ #
35
+ # head: {Key: Value}
36
+ # Specify an HTTP header, e.g. {'Connection': 'close'}
37
+ #
38
+ # query: {Key: Value}
39
+ # Specify query string parameters (auto-escaped)
40
+ #
41
+ # body: String
42
+ # Specify the request body (you must encode it for now)
43
+ #
44
+ # on_response: Proc
45
+ # Called for each response body chunk (you may assume HTTP 200
46
+ # OK then)
47
+ #
48
+
49
+ def get options = {}; send_request(:get, options); end
50
+ def head options = {}; send_request(:head, options); end
51
+ def post options = {}; send_request(:post, options); end
52
+
53
+ protected
54
+
55
+ def send_request(method, options)
56
+ raise ArgumentError, "invalid request path" unless /^\// === @uri.path
57
+
58
+ # default connect & inactivity timeouts
59
+ options[:timeout] = 5 if not options[:timeout]
60
+
61
+ # Make sure the port is set as Addressable::URI doesn't set the
62
+ # port if it isn't there.
63
+ @uri.port = @uri.port ? @uri.port : 80
64
+ method = method.to_s.upcase
65
+ begin
66
+ EventMachine.connect(@uri.host, @uri.port, EventMachine::HttpClient) { |c|
67
+ c.uri = @uri
68
+ c.method = method
69
+ c.options = options
70
+ c.comm_inactivity_timeout = options[:timeout]
71
+ c.pending_connect_timeout = options[:timeout]
72
+ }
73
+ rescue RuntimeError => e
74
+ raise e unless e.message == "no connection"
75
+ conn = EventMachine::HttpClient.new("")
76
+ conn.on_error("no connection", true)
77
+ conn
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'pp'
4
+
5
+ require 'lib/em-http'
@@ -0,0 +1,144 @@
1
+ # #--
2
+ # Includes portion originally Copyright (C)2008 Michael Fellinger
3
+ # license See file LICENSE for details
4
+ # #--
5
+
6
+ require 'rack'
7
+
8
+ module Stallion
9
+ class Mount
10
+ def initialize(name, *methods, &block)
11
+ @name, @methods, @block = name, methods, block
12
+ end
13
+
14
+ def ride
15
+ @block.call
16
+ end
17
+
18
+ def match?(request)
19
+ method = request['REQUEST_METHOD']
20
+ right_method = @methods.empty? or @methods.include?(method)
21
+ end
22
+ end
23
+
24
+ class Stable
25
+ attr_reader :request, :response
26
+
27
+ def initialize
28
+ @boxes = {}
29
+ end
30
+
31
+ def in(path, *methods, &block)
32
+ mount = Mount.new(path, *methods, &block)
33
+ @boxes[[path, methods]] = mount
34
+ mount
35
+ end
36
+
37
+ def call(request, response)
38
+ @request, @response = request, response
39
+ @boxes.each do |(path, methods), mount|
40
+ if mount.match?(request)
41
+ mount.ride
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ STABLES = {}
48
+
49
+ def self.saddle(name = nil)
50
+ STABLES[name] = stable = Stable.new
51
+ yield stable
52
+ end
53
+
54
+ def self.run(options = {})
55
+ options = {:Host => "127.0.0.1", :Port => 8080}.merge(options)
56
+ Rack::Handler::Mongrel.run(Rack::Lint.new(self), options)
57
+ end
58
+
59
+ def self.call(env)
60
+ request = Rack::Request.new(env)
61
+ response = Rack::Response.new
62
+
63
+ STABLES.each do |name, stable|
64
+ stable.call(request, response)
65
+ end
66
+
67
+ response.finish
68
+ end
69
+ end
70
+
71
+ Stallion.saddle :spec do |stable|
72
+ stable.in '/' do
73
+
74
+ if stable.request.path_info == '/fail'
75
+ stable.response.status = 404
76
+
77
+ elsif stable.request.query_string == 'q=test'
78
+ stable.response.write 'test'
79
+
80
+ elsif stable.request.path_info == '/echo_query'
81
+ stable.response.write stable.request.query_string
82
+
83
+ elsif stable.request.path_info == '/echo_content_length'
84
+ stable.response.write stable.request.content_length
85
+
86
+ elsif stable.request.head?
87
+ stable.response.status = 200
88
+
89
+ elsif stable.request.post?
90
+ stable.response.write stable.request.body.read
91
+
92
+ elsif stable.request.path_info == '/set_cookie'
93
+ stable.response["Set-Cookie"] = "id=1; expires=Tue, 09-Aug-2011 17:53:39 GMT; path=/;"
94
+ stable.response.write "cookie set"
95
+
96
+ elsif stable.request.path_info == '/echo_cookie'
97
+ stable.response.write stable.request.env["HTTP_COOKIE"]
98
+
99
+ elsif stable.request.path_info == '/timeout'
100
+ sleep(10)
101
+ stable.response.write 'timeout'
102
+
103
+ elsif stable.request.path_info == '/gzip'
104
+ io = StringIO.new
105
+ gzip = Zlib::GzipWriter.new(io)
106
+ gzip << "compressed"
107
+ gzip.close
108
+
109
+ stable.response.write io.string
110
+ stable.response["Content-Encoding"] = "gzip"
111
+
112
+ elsif stable.request.path_info == '/deflate'
113
+ stable.response.write Zlib::Deflate.deflate("compressed")
114
+ stable.response["Content-Encoding"] = "deflate"
115
+
116
+ elsif stable.request.env["HTTP_IF_NONE_MATCH"]
117
+ stable.response.status = 304
118
+
119
+ elsif stable.request.env["HTTP_AUTHORIZATION"]
120
+ auth = "Basic %s" % Base64.encode64(['user', 'pass'].join(':')).chomp
121
+
122
+ if auth == stable.request.env["HTTP_AUTHORIZATION"]
123
+ stable.response.status = 200
124
+ stable.response.write 'success'
125
+ else
126
+ stable.response.status = 401
127
+ end
128
+
129
+ elsif
130
+ stable.response.write 'Hello, World!'
131
+ end
132
+
133
+ end
134
+ end
135
+
136
+ Thread.new do
137
+ begin
138
+ Stallion.run :Host => '127.0.0.1', :Port => 8080
139
+ rescue Exception => e
140
+ print e
141
+ end
142
+ end
143
+
144
+ sleep(1)