pgericson-handsoap 1.1.7

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.
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'handsoap/http/request'
3
+ require 'handsoap/http/response'
4
+ require 'handsoap/http/drivers'
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'handsoap/http/drivers/abstract_driver'
3
+ require 'handsoap/http/drivers/curb_driver'
4
+ require 'handsoap/http/drivers/event_machine_driver'
5
+ require 'handsoap/http/drivers/http_client_driver'
6
+ require 'handsoap/http/drivers/net_http_driver'
7
+ require 'handsoap/http/drivers/mock_driver'
8
+
9
+ module Handsoap
10
+ module Http
11
+ @@drivers = {
12
+ :curb => Drivers::CurbDriver,
13
+ :em => Drivers::EventMachineDriver,
14
+ :event_machine => Drivers::EventMachineDriver,
15
+ :httpclient => Drivers::HttpClientDriver,
16
+ :http_client => Drivers::HttpClientDriver,
17
+ :net_http => Drivers::NetHttpDriver,
18
+ }
19
+
20
+ def self.drivers
21
+ @@drivers
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,170 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Handsoap
4
+ module Http
5
+ module Drivers
6
+ class AbstractDriver
7
+ def self.load!
8
+ end
9
+
10
+ def initialize
11
+ self.class.load!
12
+ end
13
+
14
+ # Parses a raw http response into a +Response+ or +Part+ object.
15
+ def parse_http_part(headers, body, status = nil, content_type = nil)
16
+ if headers.kind_of? String
17
+ headers = parse_headers(headers)
18
+ end
19
+ headers = headers.inject({}) {|collect,item| collect[item[0].downcase] = item[1]; collect }
20
+ if content_type.nil? && headers['content-type']
21
+ content_type = headers['content-type'].first
22
+ end
23
+ boundary = parse_multipart_boundary(content_type)
24
+ parts = if boundary
25
+ parse_multipart(boundary, body).map {|raw_part| parse_http_part(raw_part[:head], raw_part[:body]) }
26
+ end
27
+ if status.nil?
28
+ Handsoap::Http::Part.new(headers, body, parts)
29
+ else
30
+ Handsoap::Http::Response.new(status, headers, body, parts)
31
+ end
32
+ end
33
+
34
+ # Content-Type header string -> mime-boundary | nil
35
+ def parse_multipart_boundary(content_type)
36
+ if %r|\Amultipart.*boundary=\"?([^\";,]+)\"?|n.match(content_type)
37
+ $1.dup
38
+ end
39
+ end
40
+
41
+ # Parses a multipart http-response body into parts.
42
+ # +boundary+ is a string of the boundary token.
43
+ # +content_io+ is either a string or an IO. If it's an IO, then content_length must be specified.
44
+ # +content_length+ (optional) is an integer, specifying the length of +content_io+
45
+ #
46
+ # This code is lifted from cgi.rb
47
+ #
48
+ def parse_multipart(boundary, content_io, content_length = nil)
49
+ if content_io.kind_of? String
50
+ content_length = content_io.length
51
+ content_io = StringIO.new(content_io, 'r')
52
+ elsif !(content_io.kind_of? IO) || content_length.nil?
53
+ raise "Second argument must be String or IO with content_length"
54
+ end
55
+
56
+ boundary = "--" + boundary
57
+ quoted_boundary = Regexp.quote(boundary, "n")
58
+ buf = ""
59
+ bufsize = 10 * 1024
60
+ boundary_end = ""
61
+
62
+ # start multipart/form-data
63
+ content_io.binmode if defined? content_io.binmode
64
+ boundary_size = boundary.size + "\r\n".size
65
+ content_length -= boundary_size
66
+ status = content_io.read(boundary_size)
67
+ if nil == status
68
+ raise EOFError, "no content body"
69
+ elsif boundary + "\r\n" != status
70
+ raise EOFError, "bad content body"
71
+ end
72
+
73
+ parts = []
74
+
75
+ loop do
76
+ head = nil
77
+ if 10240 < content_length
78
+ require "tempfile"
79
+ body = Tempfile.new("Handsoap")
80
+ else
81
+ begin
82
+ require "stringio"
83
+ body = StringIO.new
84
+ rescue LoadError
85
+ require "tempfile"
86
+ body = Tempfile.new("Handsoap")
87
+ end
88
+ end
89
+ body.binmode if defined? body.binmode
90
+
91
+ until head and /#{quoted_boundary}(?:\r\n|--)/n.match(buf)
92
+
93
+ if (not head) and /\r\n\r\n/n.match(buf)
94
+ buf = buf.sub(/\A((?:.|\n)*?\r\n)\r\n/n) do
95
+ head = $1.dup
96
+ ""
97
+ end
98
+ next
99
+ end
100
+
101
+ if head and ( ("\r\n" + boundary + "\r\n").size < buf.size )
102
+ body.print buf[0 ... (buf.size - ("\r\n" + boundary + "\r\n").size)]
103
+ buf[0 ... (buf.size - ("\r\n" + boundary + "\r\n").size)] = ""
104
+ end
105
+
106
+ c = if bufsize < content_length
107
+ content_io.read(bufsize)
108
+ else
109
+ content_io.read(content_length)
110
+ end
111
+ if c.nil? || c.empty?
112
+ raise EOFError, "bad content body"
113
+ end
114
+ buf.concat(c)
115
+ content_length -= c.size
116
+ end
117
+
118
+ buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
119
+ body.print $1
120
+ if "--" == $2
121
+ content_length = -1
122
+ end
123
+ boundary_end = $2.dup
124
+ ""
125
+ end
126
+
127
+ body.rewind
128
+ parts << {:head => head, :body => body.read(body.size)}
129
+
130
+ break if buf.size == 0
131
+ break if content_length == -1
132
+ end
133
+ raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/
134
+ parts
135
+ end
136
+
137
+ # lifted from webrick/httputils.rb
138
+ def parse_headers(raw)
139
+ header = Hash.new([].freeze)
140
+ field = nil
141
+ tmp = raw.gsub(/^(\r\n)+|(\r\n)+$/, '')
142
+ (tmp.respond_to?(:lines) ? tmp.lines : tmp).each {|line|
143
+ case line
144
+ when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
145
+ field, value = $1, $2
146
+ field.downcase!
147
+ header[field] = [] unless header.has_key?(field)
148
+ header[field] << value
149
+ when /^\s+(.*?)\s*\z/om
150
+ value = $1
151
+ unless field
152
+ raise "bad header '#{line.inspect}'."
153
+ end
154
+ header[field][-1] << " " << value
155
+ else
156
+ raise "bad header '#{line.inspect}'."
157
+ end
158
+ }
159
+ header.each {|key, values|
160
+ values.each {|value|
161
+ value.strip!
162
+ value.gsub!(/\s+/, " ")
163
+ }
164
+ }
165
+ header
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'handsoap/http/drivers/abstract_driver'
3
+
4
+ module Handsoap
5
+ module Http
6
+ module Drivers
7
+ class CurbDriver < AbstractDriver
8
+ attr_accessor :enable_cookies
9
+
10
+ def initialize
11
+ @enable_cookies = false
12
+ end
13
+
14
+ def self.load!
15
+ require 'curb'
16
+ end
17
+
18
+ def get_curl(url)
19
+ if @curl
20
+ @curl.url = url
21
+ else
22
+ @curl = ::Curl::Easy.new(url)
23
+ @curl.timeout = Handsoap.timeout
24
+ @curl.enable_cookies = @enable_cookies
25
+
26
+ if Handsoap.follow_redirects?
27
+ @curl.follow_location = true
28
+ @curl.max_redirects = Handsoap.max_redirects
29
+ end
30
+ end
31
+ @curl
32
+ end
33
+
34
+ private :get_curl
35
+
36
+ def send_http_request(request)
37
+ http_client = get_curl(request.url)
38
+ # Set credentials. The driver will negotiate the actual scheme
39
+ if request.username && request.password
40
+ http_client.userpwd = [request.username, ":", request.password].join
41
+ end
42
+ # pack headers
43
+ headers = request.headers.inject([]) do |arr, (k,v)|
44
+ arr + v.map {|x| "#{k}: #{x}" }
45
+ end
46
+ http_client.headers = headers
47
+ # I don't think put/delete is actually supported ..
48
+ case request.http_method
49
+ when :get
50
+ http_client.http_get
51
+ when :post
52
+ http_client.http_post(request.body)
53
+ when :put
54
+ http_client.http_put(request.body)
55
+ when :delete
56
+ http_client.http_delete
57
+ else
58
+ raise "Unsupported request method #{request.http_method}"
59
+ end
60
+ parse_http_part(http_client.header_str.gsub(/^HTTP.*\r\n/, ""), http_client.body_str, http_client.response_code, http_client.content_type)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Handsoap
4
+ module Http
5
+ module Drivers
6
+ class EventMachineDriver < AbstractDriver
7
+ def self.load!
8
+ require 'eventmachine'
9
+ require 'em-http'
10
+ end
11
+
12
+ def send_http_request_async(request)
13
+ emr = EventMachine::HttpRequest.new(request.url)
14
+
15
+ if request.username && request.password
16
+ # TODO: Verify that this is actually supported?
17
+ request.headers['authorization'] = [request.username, request.password]
18
+ end
19
+
20
+ case request.http_method
21
+ when :get
22
+ emdef = emr.get(:head => request.headers)
23
+ when :post
24
+ emdef = emr.post(:head => request.headers, :body => request.body)
25
+ when :put
26
+ emdef = emr.put(:head => request.headers, :body => request.body)
27
+ when :delete
28
+ emdef = emr.delete
29
+ else
30
+ raise "Unsupported request method #{request.http_method}"
31
+ end
32
+
33
+ deferred = Handsoap::Deferred.new
34
+ emdef.callback do
35
+ http_response = parse_http_part(emdef.response_header, emdef.response, emdef.response_header.status)
36
+ deferred.trigger_callback http_response
37
+ end
38
+ emdef.errback do
39
+ deferred.trigger_errback emdef
40
+ end
41
+ deferred
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'handsoap/http/drivers/abstract_driver'
3
+
4
+ module Handsoap
5
+ module Http
6
+ module Drivers
7
+ class HttpClientDriver < AbstractDriver
8
+ def self.load!
9
+ require 'httpclient'
10
+ end
11
+
12
+ def send_http_request(request)
13
+ http_client = HTTPClient.new
14
+ # Set credentials. The driver will negotiate the actual scheme
15
+ if request.username && request.password
16
+ domain = request.url.match(/^(http(s?):\/\/[^\/]+\/)/)[1]
17
+ http_client.set_auth(domain, request.username, request.password)
18
+ end
19
+ http_client.ssl_config.set_trust_ca(request.trust_ca_file) if request.trust_ca_file
20
+ http_client.ssl_config.set_client_cert_file(request.client_cert_file,request.client_cert_key_file) if request.client_cert_file and request.client_cert_key_file
21
+ # pack headers
22
+ headers = request.headers.inject([]) do |arr, (k,v)|
23
+ arr + v.map {|x| [k,x] }
24
+ end
25
+ response = http_client.request(request.http_method, request.url, nil, request.body, headers)
26
+ response_headers = response.header.all.inject({}) do |h, (k, v)|
27
+ k.downcase!
28
+ if h[k].nil?
29
+ h[k] = [v]
30
+ else
31
+ h[k] << v
32
+ end
33
+ h
34
+ end
35
+ parse_http_part(response_headers, response.content, response.status, response.contenttype)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'handsoap/http/drivers/abstract_driver'
3
+
4
+ module Handsoap
5
+ module Http
6
+ module Drivers
7
+ # A mock driver for your testing needs.
8
+ #
9
+ # To use it, create a new instance and assign to +Handsoap::Http.drivers+. Then configure +Handsoap::Service+ to use it:
10
+ #
11
+ # Handsoap::Http.drivers[:mock] = Handsoap::Http::Drivers::MockDriver.new :status => 200, :headers => headers, :content => body
12
+ # Handsoap.http_driver = :mock
13
+ #
14
+ # Remember that headers should use \r\n, rather than \n.
15
+ class MockDriver < AbstractDriver
16
+ attr_accessor :mock, :last_request, :is_loaded
17
+
18
+ def initialize(mock)
19
+ @mock = mock
20
+ @is_loaded = false
21
+ end
22
+
23
+ def load!
24
+ is_loaded = true
25
+ end
26
+
27
+ def new
28
+ self
29
+ end
30
+
31
+ def send_http_request(request)
32
+ @last_request = request
33
+ (mock.kind_of? Hash) ?
34
+ parse_http_part(mock[:headers], mock[:content], mock[:status], mock[:content_type]) : mock
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,75 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Handsoap
4
+ module Http
5
+ module Drivers
6
+ class NetHttpDriver < AbstractDriver
7
+ def self.load!
8
+ require 'net/http'
9
+ require 'uri'
10
+ end
11
+
12
+ def send_http_request(request)
13
+ url = request.url
14
+ unless url.kind_of? ::URI::Generic
15
+ url = ::URI.parse(url)
16
+ end
17
+ ::URI::Generic.send(:public, :path_query) # hackety hack
18
+ path = url.path_query
19
+ http_request = case request.http_method
20
+ when :get
21
+ Net::HTTP::Get.new(path)
22
+ when :post
23
+ Net::HTTP::Post.new(path)
24
+ when :put
25
+ Net::HTTP::Put.new(path)
26
+ when :delete
27
+ Net::HTTP::Delete.new(path)
28
+ else
29
+ raise "Unsupported request method #{request.http_method}"
30
+ end
31
+
32
+ http_client = Net::HTTP.new(url.host, url.port)
33
+
34
+ #http_client.read_timeout = 120
35
+ http_client.read_timeout = Handsoap.timeout
36
+
37
+ http_client.use_ssl = true if url.scheme == 'https'
38
+
39
+ if request.username && request.password
40
+ # TODO: http://codesnippets.joyent.com/posts/show/1075
41
+ http_request.basic_auth request.username, request.password
42
+ end
43
+ request.headers.each do |k, values|
44
+ values.each do |v|
45
+ http_request.add_field(k, v)
46
+ end
47
+ end
48
+ http_request.body = request.body
49
+ # require 'stringio'
50
+ # debug_output = StringIO.new
51
+ # http_client.set_debug_output debug_output
52
+ http_response = http_client.start do |client|
53
+ client.request(http_request)
54
+ end
55
+ # puts debug_output.string
56
+ # hacky-wacky
57
+ def http_response.get_headers
58
+ @header.inject({}) do |h, (k, v)|
59
+ h[k.downcase] = v
60
+ h
61
+ end
62
+ end
63
+ # net/http only supports basic auth. We raise a warning if the server requires something else.
64
+ if http_response.code == 401 && http_response.get_headers['www-authenticate']
65
+ auth_type = http_response.get_headers['www-authenticate'].chomp.match(/\w+/)[0].downcase
66
+ if auth_type != "basic"
67
+ raise "Authentication type #{auth_type} is unsupported by net/http"
68
+ end
69
+ end
70
+ parse_http_part(http_response.get_headers, http_response.body, http_response.code)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end