Welcome to the "trac"-ing site of soap4r!
[soap4r] [httpclient] [openpgp4u] [pkcs1] [logger] [csv] [vtr]

root/branches/1_5/lib/soap/streamHandler.rb

Revision 1885, 7.8 kB (checked in by nahi, 1 year ago)
  • use httpclient instead of http-access2. (works with http-access/2.0.9 as same as with httpclient/2.1.0)
  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 # SOAP4R - Stream handler.
2 # Copyright (C) 2000-2007  NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
4 # This program is copyrighted free software by NAKAMURA, Hiroshi.  You can
5 # redistribute it and/or modify it under the same terms of Ruby's license;
6 # either the dual license version in 2003, or any later version.
7
8
9 require 'soap/soap'
10 require 'soap/httpconfigloader'
11 require 'soap/filter/filterchain'
12 begin
13   require 'stringio'
14   require 'zlib'
15 rescue LoadError
16   warn("Loading stringio or zlib failed.  No gzipped response support.") if $DEBUG
17 end
18
19
20 module SOAP
21
22
23 class StreamHandler
24   RUBY_VERSION_STRING = "ruby #{ RUBY_VERSION } (#{ RUBY_RELEASE_DATE }) [#{ RUBY_PLATFORM }]"
25
26   attr_reader :filterchain
27
28   class ConnectionData
29     attr_accessor :send_string
30     attr_accessor :send_contenttype
31     attr_accessor :receive_string
32     attr_accessor :receive_contenttype
33     attr_accessor :is_fault
34     attr_accessor :is_nocontent
35     attr_accessor :soapaction
36
37     def initialize(send_string = nil)
38       @send_string = send_string
39       @send_contenttype = nil
40       @receive_string = nil
41       @receive_contenttype = nil
42       @is_fault = false
43       @is_nocontent = false
44       @soapaction = nil
45     end
46   end
47
48   def initialize
49     @filterchain = Filter::FilterChain.new
50   end
51
52   def self.parse_media_type(str)
53     if /^#{ MediaType }(?:\s*;\s*charset=([^"]+|"[^"]+"))?$/i !~ str
54       return nil
55     end
56     charset = $1
57     charset.gsub!(/"/, '') if charset
58     charset || 'us-ascii'
59   end
60
61   def self.create_media_type(charset)
62     "#{ MediaType }; charset=#{ charset }"
63   end
64
65   def send(url, conn_data, soapaction = nil, charset = nil)
66     # send a ConnectionData to specified url.
67     # return value is a ConnectionData with receive_* property filled.
68     # You can fill values of given conn_data and return it.
69   end
70
71   def reset(url = nil)
72     # for initializing connection status if needed.
73     # return value is not expected.
74   end
75
76   def set_wiredump_file_base(wiredump_file_base)
77     # for logging.  return value is not expected.
78     # Override it when you want.
79     raise NotImplementedError
80   end
81
82   def test_loopback_response
83     # for loopback testing.  see HTTPStreamHandler for more detail.
84     # return value is an Array of loopback responses.
85     # Override it when you want.
86     raise NotImplementedError
87   end
88 end
89
90
91 class HTTPStreamHandler < StreamHandler
92   include SOAP
93
94   begin
95     require 'httpclient'
96     Client = HTTPClient
97     RETRYABLE = true
98   rescue LoadError
99     begin
100       require 'http-access2'
101       if HTTPAccess2::VERSION < "2.0"
102         raise LoadError.new("http-access/2.0 or later is required.")
103       end
104       Client = HTTPAccess2::Client
105       RETRYABLE = true
106     rescue LoadError
107       warn("Loading http-access2 failed.  Net/http is used.") if $DEBUG
108       require 'soap/netHttpClient'
109       Client = SOAP::NetHttpClient
110       RETRYABLE = false
111     end
112   end
113
114   class HttpPostRequestFilter
115     def initialize(filterchain)
116       @filterchain = filterchain
117     end
118
119     def filter_request(req)
120       @filterchain.each do |filter|
121         filter.on_http_outbound(req)
122       end
123     end
124
125     def filter_response(req, res)
126       @filterchain.each do |filter|
127         filter.on_http_inbound(req, res)
128       end
129     end
130   end
131
132 public
133  
134   attr_reader :client
135   attr_accessor :wiredump_file_base
136  
137   MAX_RETRY_COUNT = 10          # [times]
138
139   def self.create(options)
140     new(options)
141   end
142
143   def initialize(options)
144     super()
145     @client = Client.new(nil, "SOAP4R/#{ Version }")
146     if @client.respond_to?(:request_filter)
147       @client.request_filter << HttpPostRequestFilter.new(@filterchain)
148     end
149     @wiredump_file_base = nil
150     @charset = @wiredump_dev = nil
151     @options = options
152     set_options
153     @client.debug_dev = @wiredump_dev
154     @cookie_store = nil
155     @accept_encoding_gzip = false
156   end
157
158   def test_loopback_response
159     @client.test_loopback_response
160   end
161
162   def accept_encoding_gzip=(allow)
163     @accept_encoding_gzip = allow
164   end
165
166   def inspect
167     "#<#{self.class}>"
168   end
169
170   def send(url, conn_data, soapaction = nil, charset = @charset)
171     conn_data.soapaction ||= soapaction # for backward conpatibility
172     conn_data = send_post(url, conn_data, charset)
173     @client.save_cookie_store if @cookie_store
174     conn_data
175   end
176
177   def reset(url = nil)
178     if url.nil?
179       @client.reset_all
180     else
181       @client.reset(url)
182     end
183     @client.save_cookie_store if @cookie_store
184   end
185
186 private
187
188   def set_options
189     @options["http"] ||= ::SOAP::Property.new
190     HTTPConfigLoader.set_options(@client, @options["http"])
191     @charset = @options["http.charset"] || XSD::Charset.xml_encoding_label
192     @options.add_hook("http.charset") do |key, value|
193       @charset = value
194     end
195     @wiredump_dev = @options["http.wiredump_dev"]
196     @options.add_hook("http.wiredump_dev") do |key, value|
197       @wiredump_dev = value
198       @client.debug_dev = @wiredump_dev
199     end
200     set_cookie_store_file(@options["http.cookie_store_file"])
201     @options.add_hook("http.cookie_store_file") do |key, value|
202       set_cookie_store_file(value)
203     end
204     ssl_config = @options["http.ssl_config"]
205     basic_auth = @options["http.basic_auth"]
206     auth = @options["http.auth"]
207     @options["http"].lock(true)
208     ssl_config.unlock
209     basic_auth.unlock
210     auth.unlock
211   end
212
213   def set_cookie_store_file(value)
214     value = nil if value and value.empty?
215     @cookie_store = value
216     @client.set_cookie_store(@cookie_store) if @cookie_store
217   end
218
219   def send_post(url, conn_data, charset)
220     conn_data.send_contenttype ||= StreamHandler.create_media_type(charset)
221
222     if @wiredump_file_base
223       filename = @wiredump_file_base + '_request.xml'
224       f = File.open(filename, "w")
225       f << conn_data.send_string
226       f.close
227     end
228
229     extheader = {}
230     extheader['Content-Type'] = conn_data.send_contenttype
231     extheader['SOAPAction'] = "\"#{ conn_data.soapaction }\""
232     extheader['Accept-Encoding'] = 'gzip' if send_accept_encoding_gzip?
233     send_string = conn_data.send_string
234     @wiredump_dev << "Wire dump:\n\n" if @wiredump_dev
235     begin
236       retry_count = 0
237       while true
238         res = @client.post(url, send_string, extheader)
239         if RETRYABLE and HTTP::Status.redirect?(res.status)
240           retry_count += 1
241           if retry_count >= MAX_RETRY_COUNT
242             raise HTTPStreamError.new("redirect count exceeded")
243           end
244           url = res.header["location"][0]
245           puts "redirected to #{url}" if $DEBUG
246         else
247           break
248         end
249       end
250     rescue
251       @client.reset(url)
252       raise
253     end
254     @wiredump_dev << "\n\n" if @wiredump_dev
255     receive_string = res.content
256     if @wiredump_file_base
257       filename = @wiredump_file_base + '_response.xml'
258       f = File.open(filename, "w")
259       f << receive_string
260       f.close
261     end
262     case res.status
263     when 405
264       raise PostUnavailableError.new("#{ res.status }: #{ res.reason }")
265     when 200, 202, 500
266       # Nothing to do.  202 is for oneway service.
267     else
268       raise HTTPStreamError.new("#{ res.status }: #{ res.reason }")
269     end
270
271     # decode gzipped content, if we know it's there from the headers
272     if res.respond_to?(:header) and !res.header['content-encoding'].empty? and
273         res.header['content-encoding'][0].downcase == 'gzip'
274       receive_string = decode_gzip(receive_string)
275     # otherwise check for the gzip header
276     elsif @accept_encoding_gzip && receive_string[0..1] == "\x1f\x8b"
277       receive_string = decode_gzip(receive_string)
278     end
279     conn_data.receive_string = receive_string
280     conn_data.receive_contenttype = res.contenttype
281     conn_data
282   end
283
284   def send_accept_encoding_gzip?
285     @accept_encoding_gzip and defined?(::Zlib)
286   end
287
288   def decode_gzip(instring)
289     unless send_accept_encoding_gzip?
290       raise HTTPStreamError.new("Gzipped response content.")
291     end
292     begin
293       gz = Zlib::GzipReader.new(StringIO.new(instring))
294       gz.read
295     ensure
296       gz.close
297     end
298   end
299 end
300
301
302 end
Note: See TracBrowser for help on using the browser.