dwaite-em-http-request 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,119 @@
1
+ require 'zlib'
2
+
3
+ ##
4
+ # Provides a unified callback interface to decompression libraries.
5
+ module EventMachine::HttpDecoders
6
+
7
+ class DecoderError < StandardError
8
+ end
9
+
10
+ class << self
11
+ def accepted_encodings
12
+ DECODERS.inject([]) { |r,d| r + d.encoding_names }
13
+ end
14
+
15
+ def decoder_for_encoding(encoding)
16
+ DECODERS.each { |d|
17
+ return d if d.encoding_names.include? encoding
18
+ }
19
+ nil
20
+ end
21
+ end
22
+
23
+ class Base
24
+ def self.encoding_names
25
+ name = to_s.split('::').last.downcase
26
+ [name]
27
+ end
28
+
29
+ ##
30
+ # chunk_callback:: [Block] To handle a decompressed chunk
31
+ def initialize(&chunk_callback)
32
+ @chunk_callback = chunk_callback
33
+ end
34
+
35
+ def <<(compressed)
36
+ return unless compressed && compressed.size > 0
37
+
38
+ decompressed = decompress(compressed)
39
+ receive_decompressed decompressed
40
+ end
41
+
42
+ def finalize!
43
+ decompressed = finalize
44
+ receive_decompressed decompressed
45
+ end
46
+
47
+ private
48
+
49
+ def receive_decompressed(decompressed)
50
+ if decompressed && decompressed.size > 0
51
+ @chunk_callback.call(decompressed)
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ ##
58
+ # Must return a part of decompressed
59
+ def decompress(compressed)
60
+ nil
61
+ end
62
+
63
+ ##
64
+ # May return last part
65
+ def finalize
66
+ nil
67
+ end
68
+ end
69
+
70
+ class Deflate < Base
71
+ def decompress(compressed)
72
+ begin
73
+ @zstream ||= Zlib::Inflate.new(nil)
74
+ @zstream.inflate(compressed)
75
+ rescue Zlib::Error
76
+ raise DecoderError
77
+ end
78
+ end
79
+
80
+ def finalize
81
+ return nil unless @zstream
82
+
83
+ begin
84
+ r = @zstream.inflate(nil)
85
+ @zstream.close
86
+ r
87
+ rescue Zlib::Error
88
+ raise DecoderError
89
+ end
90
+ end
91
+ end
92
+
93
+ ##
94
+ # Oneshot decompressor, due to lack of a streaming Gzip reader
95
+ # implementation. We may steal code from Zliby to improve this.
96
+ #
97
+ # For now, do not put `gzip' or `compressed' in your accept-encoding
98
+ # header if you expect much data through the :on_response interface.
99
+ class GZip < Base
100
+ def self.encoding_names
101
+ %w(gzip compressed)
102
+ end
103
+
104
+ def decompress(compressed)
105
+ @buf ||= ''
106
+ @buf += compressed
107
+ nil
108
+ end
109
+
110
+ def finalize
111
+ Zlib::GzipReader.new(StringIO.new(@buf)).read
112
+ end
113
+ end
114
+
115
+ DECODERS = [Deflate, GZip]
116
+
117
+ end
118
+
119
+
@@ -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,72 @@
1
+ require 'uri'
2
+ require 'base64'
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?(URI) ? host : 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
+ def send_request(method, options)
54
+ raise ArgumentError, "invalid request path" unless /^\// === @uri.path
55
+
56
+ method = method.to_s.upcase
57
+ begin
58
+ EventMachine.connect(@uri.host, @uri.port, EventMachine::HttpClient) { |c|
59
+ c.uri = @uri
60
+ c.method = method
61
+ c.options = options
62
+ c.comm_inactivity_timeout = options[:timeout] || 5
63
+ }
64
+ rescue RuntimeError => e
65
+ raise e unless e.message == "no connection"
66
+ conn = EventMachine::HttpClient.new("")
67
+ conn.on_error("no connection", true)
68
+ conn
69
+ end
70
+ end
71
+ end
72
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'pp'
4
+
5
+ require 'lib/em-http'
data/test/stallion.rb ADDED
@@ -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)