kerryb-right_aws 1.7.3

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,660 @@
1
+ #
2
+ # Copyright (c) 2007-2008 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ # Test
25
+ module RightAws
26
+ require 'md5'
27
+
28
+ class AwsUtils #:nodoc:
29
+ @@digest = OpenSSL::Digest::Digest.new("sha1")
30
+ def self.sign(aws_secret_access_key, auth_string)
31
+ Base64.encode64(OpenSSL::HMAC.digest(@@digest, aws_secret_access_key, auth_string)).strip
32
+ end
33
+
34
+ # From Amazon's SQS Dev Guide, a brief description of how to escape:
35
+ # "URL encode the computed signature and other query parameters as specified in
36
+ # RFC1738, section 2.2. In addition, because the + character is interpreted as a blank space
37
+ # by Sun Java classes that perform URL decoding, make sure to encode the + character
38
+ # although it is not required by RFC1738."
39
+ # Avoid using CGI::escape to escape URIs.
40
+ # CGI::escape will escape characters in the protocol, host, and port
41
+ # sections of the URI. Only target chars in the query
42
+ # string should be escaped.
43
+ def self.URLencode(raw)
44
+ e = URI.escape(raw)
45
+ e.gsub(/\+/, "%2b")
46
+ end
47
+
48
+ end
49
+
50
+ class AwsBenchmarkingBlock #:nodoc:
51
+ attr_accessor :xml, :service
52
+ def initialize
53
+ # Benchmark::Tms instance for service (Ec2, S3, or SQS) access benchmarking.
54
+ @service = Benchmark::Tms.new()
55
+ # Benchmark::Tms instance for XML parsing benchmarking.
56
+ @xml = Benchmark::Tms.new()
57
+ end
58
+ end
59
+
60
+ class AwsNoChange < RuntimeError
61
+ end
62
+
63
+ class RightAwsBase
64
+
65
+ # Amazon HTTP Error handling
66
+
67
+ # Text, if found in an error message returned by AWS, indicates that this may be a transient
68
+ # error. Transient errors are automatically retried with exponential back-off.
69
+ AMAZON_PROBLEMS = [ 'internal service error',
70
+ 'is currently unavailable',
71
+ 'no response from',
72
+ 'Please try again',
73
+ 'InternalError',
74
+ 'ServiceUnavailable', #from SQS docs
75
+ 'Unavailable',
76
+ 'This application is not currently available',
77
+ 'InsufficientInstanceCapacity'
78
+ ]
79
+ @@amazon_problems = AMAZON_PROBLEMS
80
+ # Returns a list of Amazon service responses which are known to be transient problems.
81
+ # We have to re-request if we get any of them, because the problem will probably disappear.
82
+ # By default this method returns the same value as the AMAZON_PROBLEMS const.
83
+ def self.amazon_problems
84
+ @@amazon_problems
85
+ end
86
+
87
+ # Sets the list of Amazon side problems. Use in conjunction with the
88
+ # getter to append problems.
89
+ def self.amazon_problems=(problems_list)
90
+ @@amazon_problems = problems_list
91
+ end
92
+
93
+ end
94
+
95
+ module RightAwsBaseInterface
96
+ DEFAULT_SIGNATURE_VERSION = '1'
97
+
98
+ @@caching = false
99
+ def self.caching
100
+ @@caching
101
+ end
102
+ def self.caching=(caching)
103
+ @@caching = caching
104
+ end
105
+
106
+ # Current aws_access_key_id
107
+ attr_reader :aws_access_key_id
108
+ # Last HTTP request object
109
+ attr_reader :last_request
110
+ # Last HTTP response object
111
+ attr_reader :last_response
112
+ # Last AWS errors list (used by AWSErrorHandler)
113
+ attr_accessor :last_errors
114
+ # Last AWS request id (used by AWSErrorHandler)
115
+ attr_accessor :last_request_id
116
+ # Logger object
117
+ attr_accessor :logger
118
+ # Initial params hash
119
+ attr_accessor :params
120
+ # RightHttpConnection instance
121
+ attr_reader :connection
122
+ # Cache
123
+ attr_reader :cache
124
+ # Signature version (all services except s3)
125
+ attr_reader :signature_version
126
+
127
+ def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc:
128
+ @params = params
129
+ raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
130
+ if aws_access_key_id.blank? || aws_secret_access_key.blank?
131
+ @aws_access_key_id = aws_access_key_id
132
+ @aws_secret_access_key = aws_secret_access_key
133
+ @params[:server] ||= service_info[:default_host]
134
+ @params[:port] ||= service_info[:default_port]
135
+ @params[:service] ||= service_info[:default_service]
136
+ @params[:protocol] ||= service_info[:default_protocol]
137
+ @params[:multi_thread] ||= defined?(AWS_DAEMON)
138
+ @logger = @params[:logger]
139
+ @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
140
+ @logger = Logger.new(STDOUT) if !@logger
141
+ @logger.info "New #{self.class.name} using #{@params[:multi_thread] ? 'multi' : 'single'}-threaded mode"
142
+ @error_handler = nil
143
+ @cache = {}
144
+ @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
145
+ end
146
+
147
+ # Returns +true+ if the describe_xxx responses are being cached
148
+ def caching?
149
+ @params.key?(:cache) ? @params[:cache] : @@caching
150
+ end
151
+
152
+ # Check if the aws function response hits the cache or not.
153
+ # If the cache hits:
154
+ # - raises an +AwsNoChange+ exception if +do_raise+ == +:raise+.
155
+ # - returnes parsed response from the cache if it exists or +true+ otherwise.
156
+ # If the cache miss or the caching is off then returns +false+.
157
+ def cache_hits?(function, response, do_raise=:raise)
158
+ result = false
159
+ if caching?
160
+ function = function.to_sym
161
+ response_md5 = MD5.md5(response).to_s
162
+ # well, the response is new, reset cache data
163
+ unless @cache[function] && @cache[function][:response_md5] == response_md5
164
+ update_cache(function, {:response_md5 => response_md5,
165
+ :timestamp => Time.now,
166
+ :hits => 0,
167
+ :parsed => nil})
168
+ else
169
+ # aha, cache hits, update the data and throw an exception if needed
170
+ @cache[function][:hits] += 1
171
+ if do_raise == :raise
172
+ raise(AwsNoChange, "Cache hit: #{function} response has not changed since "+
173
+ "#{@cache[function][:timestamp].strftime('%Y-%m-%d %H:%M:%S')}, "+
174
+ "hits: #{@cache[function][:hits]}.")
175
+ else
176
+ result = @cache[function][:parsed] || true
177
+ end
178
+ end
179
+ end
180
+ result
181
+ end
182
+
183
+ def update_cache(function, hash)
184
+ (@cache[function.to_sym] ||= {}).merge!(hash) if caching?
185
+ end
186
+
187
+ def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
188
+ raise if $!.is_a?(AwsNoChange)
189
+ AwsError::on_aws_exception(self, options)
190
+ end
191
+
192
+ # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
193
+ def multi_thread
194
+ @params[:multi_thread]
195
+ end
196
+
197
+ def request_info_impl(connection, benchblock, request, parser, &block) #:nodoc:
198
+ @connection = connection
199
+ @last_request = request[:request]
200
+ @last_response = nil
201
+ response=nil
202
+ blockexception = nil
203
+
204
+ if(block != nil)
205
+ # TRB 9/17/07 Careful - because we are passing in blocks, we get a situation where
206
+ # an exception may get thrown in the block body (which is high-level
207
+ # code either here or in the application) but gets caught in the
208
+ # low-level code of HttpConnection. The solution is not to let any
209
+ # exception escape the block that we pass to HttpConnection::request.
210
+ # Exceptions can originate from code directly in the block, or from user
211
+ # code called in the other block which is passed to response.read_body.
212
+ benchblock.service.add! do
213
+ responsehdr = @connection.request(request) do |response|
214
+ #########
215
+ begin
216
+ @last_response = response
217
+ if response.is_a?(Net::HTTPSuccess)
218
+ @error_handler = nil
219
+ response.read_body(&block)
220
+ else
221
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
222
+ check_result = @error_handler.check(request)
223
+ if check_result
224
+ @error_handler = nil
225
+ return check_result
226
+ end
227
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
228
+ end
229
+ rescue Exception => e
230
+ blockexception = e
231
+ end
232
+ end
233
+ #########
234
+
235
+ #OK, now we are out of the block passed to the lower level
236
+ if(blockexception)
237
+ raise blockexception
238
+ end
239
+ benchblock.xml.add! do
240
+ parser.parse(responsehdr)
241
+ end
242
+ return parser.result
243
+ end
244
+ else
245
+ benchblock.service.add!{ response = @connection.request(request) }
246
+ # check response for errors...
247
+ @last_response = response
248
+ if response.is_a?(Net::HTTPSuccess)
249
+ @error_handler = nil
250
+ benchblock.xml.add! { parser.parse(response) }
251
+ return parser.result
252
+ else
253
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
254
+ check_result = @error_handler.check(request)
255
+ if check_result
256
+ @error_handler = nil
257
+ return check_result
258
+ end
259
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
260
+ end
261
+ end
262
+ rescue
263
+ @error_handler = nil
264
+ raise
265
+ end
266
+
267
+ end
268
+
269
+
270
+ # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's
271
+ # web services raise this type of error.
272
+ # Attribute inherited by RuntimeError:
273
+ # message - the text of the error, generally as returned by AWS in its XML response.
274
+ class AwsError < RuntimeError
275
+
276
+ # either an array of errors where each item is itself an array of [code, message]),
277
+ # or an error string if the error was raised manually, as in <tt>AwsError.new('err_text')</tt>
278
+ attr_reader :errors
279
+
280
+ # Request id (if exists)
281
+ attr_reader :request_id
282
+
283
+ # Response HTTP error code
284
+ attr_reader :http_code
285
+
286
+ def initialize(errors=nil, http_code=nil, request_id=nil)
287
+ @errors = errors
288
+ @request_id = request_id
289
+ @http_code = http_code
290
+ super(@errors.is_a?(Array) ? @errors.map{|code, msg| "#{code}: #{msg}"}.join("; ") : @errors.to_s)
291
+ end
292
+
293
+ # Does any of the error messages include the regexp +pattern+?
294
+ # Used to determine whether to retry request.
295
+ def include?(pattern)
296
+ if @errors.is_a?(Array)
297
+ @errors.each{ |code, msg| return true if code =~ pattern }
298
+ else
299
+ return true if @errors_str =~ pattern
300
+ end
301
+ false
302
+ end
303
+
304
+ # Generic handler for AwsErrors. +aws+ is the RightAws::S3, RightAws::EC2, or RightAws::SQS
305
+ # object that caused the exception (it must provide last_request and last_response). Supported
306
+ # boolean options are:
307
+ # * <tt>:log</tt> print a message into the log using aws.logger to access the Logger
308
+ # * <tt>:puts</tt> do a "puts" of the error
309
+ # * <tt>:raise</tt> re-raise the error after logging
310
+ def self.on_aws_exception(aws, options={:raise=>true, :log=>true})
311
+ # Only log & notify if not user error
312
+ if !options[:raise] || system_error?($!)
313
+ error_text = "#{$!.inspect}\n#{$@}.join('\n')}"
314
+ puts error_text if options[:puts]
315
+ # Log the error
316
+ if options[:log]
317
+ request = aws.last_request ? aws.last_request.path : '-none-'
318
+ response = aws.last_response ? "#{aws.last_response.code} -- #{aws.last_response.message} -- #{aws.last_response.body}" : '-none-'
319
+ aws.logger.error error_text
320
+ aws.logger.error "Request was: #{request}"
321
+ aws.logger.error "Response was: #{response}"
322
+ end
323
+ end
324
+ raise if options[:raise] # re-raise an exception
325
+ return nil
326
+ end
327
+
328
+ # True if e is an AWS system error, i.e. something that is for sure not the caller's fault.
329
+ # Used to force logging.
330
+ def self.system_error?(e)
331
+ !e.is_a?(self) || e.message =~ /InternalError|InsufficientInstanceCapacity|Unavailable/
332
+ end
333
+
334
+ end
335
+
336
+
337
+ class AWSErrorHandler
338
+ # 0-100 (%)
339
+ DEFAULT_CLOSE_ON_4XX_PROBABILITY = 10
340
+
341
+ @@reiteration_start_delay = 0.2
342
+ def self.reiteration_start_delay
343
+ @@reiteration_start_delay
344
+ end
345
+ def self.reiteration_start_delay=(reiteration_start_delay)
346
+ @@reiteration_start_delay = reiteration_start_delay
347
+ end
348
+
349
+ @@reiteration_time = 5
350
+ def self.reiteration_time
351
+ @@reiteration_time
352
+ end
353
+ def self.reiteration_time=(reiteration_time)
354
+ @@reiteration_time = reiteration_time
355
+ end
356
+
357
+ @@close_on_error = true
358
+ def self.close_on_error
359
+ @@close_on_error
360
+ end
361
+ def self.close_on_error=(close_on_error)
362
+ @@close_on_error = close_on_error
363
+ end
364
+
365
+ @@close_on_4xx_probability = DEFAULT_CLOSE_ON_4XX_PROBABILITY
366
+ def self.close_on_4xx_probability
367
+ @@close_on_4xx_probability
368
+ end
369
+ def self.close_on_4xx_probability=(close_on_4xx_probability)
370
+ @@close_on_4xx_probability = close_on_4xx_probability
371
+ end
372
+
373
+ # params:
374
+ # :reiteration_time
375
+ # :errors_list
376
+ # :close_on_error = true | false
377
+ # :close_on_4xx_probability = 1-100
378
+ def initialize(aws, parser, params={}) #:nodoc:
379
+ @aws = aws # Link to RightEc2 | RightSqs | RightS3 instance
380
+ @parser = parser # parser to parse Amazon response
381
+ @started_at = Time.now
382
+ @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
383
+ @errors_list = params[:errors_list] || []
384
+ @reiteration_delay = @@reiteration_start_delay
385
+ @retries = 0
386
+ # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
387
+ @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
388
+ @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
389
+ end
390
+
391
+ # Returns false if
392
+ def check(request) #:nodoc:
393
+ result = false
394
+ error_found = false
395
+ redirect_detected= false
396
+ error_match = nil
397
+ last_errors_text = ''
398
+ response = @aws.last_response
399
+ # log error
400
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
401
+ # is this a redirect?
402
+ # yes!
403
+ if response.is_a?(Net::HTTPRedirection)
404
+ redirect_detected = true
405
+ else
406
+ # no, it's an error ...
407
+ @aws.logger.warn("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####")
408
+ @aws.logger.warn("##### #{@aws.class.name} request: #{request_text_data} ####")
409
+ end
410
+ # Check response body: if it is an Amazon XML document or not:
411
+ if redirect_detected || (response.body && response.body[/<\?xml/]) # ... it is a xml document
412
+ @aws.class.bench_xml.add! do
413
+ error_parser = RightErrorResponseParser.new
414
+ error_parser.parse(response)
415
+ @aws.last_errors = error_parser.errors
416
+ @aws.last_request_id = error_parser.requestID
417
+ last_errors_text = @aws.last_errors.flatten.join("\n")
418
+ # on redirect :
419
+ if redirect_detected
420
+ location = response['location']
421
+ # ... log information and ...
422
+ @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
423
+ @aws.logger.info("##### New location: #{location} #####")
424
+ # ... fix the connection data
425
+ request[:server] = URI.parse(location).host
426
+ request[:protocol] = URI.parse(location).scheme
427
+ request[:port] = URI.parse(location).port
428
+ end
429
+ end
430
+ else # ... it is not a xml document(probably just a html page?)
431
+ @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]]
432
+ @aws.last_request_id = '-undefined-'
433
+ last_errors_text = response.message
434
+ end
435
+ # now - check the error
436
+ unless redirect_detected
437
+ @errors_list.each do |error_to_find|
438
+ if last_errors_text[/#{error_to_find}/i]
439
+ error_found = true
440
+ error_match = error_to_find
441
+ @aws.logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
442
+ break
443
+ end
444
+ end
445
+ end
446
+ # check the time has gone from the first error come
447
+ if redirect_detected || error_found
448
+ # Close the connection to the server and recreate a new one.
449
+ # It may have a chance that one server is a semi-down and reconnection
450
+ # will help us to connect to the other server
451
+ if !redirect_detected && @close_on_error
452
+ @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
453
+ end
454
+
455
+ if (Time.now < @stop_at)
456
+ @retries += 1
457
+ unless redirect_detected
458
+ @aws.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
459
+ sleep @reiteration_delay
460
+ @reiteration_delay *= 2
461
+
462
+ # Always make sure that the fp is set to point to the beginning(?)
463
+ # of the File/IO. TODO: it assumes that offset is 0, which is bad.
464
+ if(request[:request].body_stream && request[:request].body_stream.respond_to?(:pos))
465
+ begin
466
+ request[:request].body_stream.pos = 0
467
+ rescue Exception => e
468
+ @logger.warn("Retry may fail due to unable to reset the file pointer" +
469
+ " -- #{self.class.name} : #{e.inspect}")
470
+ end
471
+ end
472
+ else
473
+ @aws.logger.info("##### Retry ##{@retries} is being performed due to a redirect. ####")
474
+ end
475
+ result = @aws.request_info(request, @parser)
476
+ else
477
+ @aws.logger.warn("##### Ooops, time is over... ####")
478
+ end
479
+ # aha, this is unhandled error:
480
+ elsif @close_on_error
481
+ # Is this a 5xx error ?
482
+ if @aws.last_response.code.to_s[/^5\d\d$/]
483
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
484
+ # Is this a 4xx error ?
485
+ elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
486
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
487
+ "probability: #{@close_on_4xx_probability}%"
488
+ end
489
+ end
490
+ result
491
+ end
492
+
493
+ end
494
+
495
+
496
+ #-----------------------------------------------------------------
497
+
498
+ class RightSaxParserCallback #:nodoc:
499
+ def self.include_callback
500
+ include XML::SaxParser::Callbacks
501
+ end
502
+ def initialize(right_aws_parser)
503
+ @right_aws_parser = right_aws_parser
504
+ end
505
+ def on_start_element(name, attr_hash)
506
+ @right_aws_parser.tag_start(name, attr_hash)
507
+ end
508
+ def on_characters(chars)
509
+ @right_aws_parser.text(chars)
510
+ end
511
+ def on_end_element(name)
512
+ @right_aws_parser.tag_end(name)
513
+ end
514
+ def on_start_document; end
515
+ def on_comment(msg); end
516
+ def on_processing_instruction(target, data); end
517
+ def on_cdata_block(cdata); end
518
+ def on_end_document; end
519
+ end
520
+
521
+ class RightAWSParser #:nodoc:
522
+ # default parsing library
523
+ DEFAULT_XML_LIBRARY = 'rexml'
524
+ # a list of supported parsers
525
+ @@supported_xml_libs = [DEFAULT_XML_LIBRARY, 'libxml']
526
+
527
+ @@xml_lib = DEFAULT_XML_LIBRARY # xml library name: 'rexml' | 'libxml'
528
+ def self.xml_lib
529
+ @@xml_lib
530
+ end
531
+ def self.xml_lib=(new_lib_name)
532
+ @@xml_lib = new_lib_name
533
+ end
534
+
535
+ attr_accessor :result
536
+ attr_reader :xmlpath
537
+ attr_accessor :xml_lib
538
+
539
+ def initialize(params={})
540
+ @xmlpath = ''
541
+ @result = false
542
+ @text = ''
543
+ @xml_lib = params[:xml_lib] || @@xml_lib
544
+ @logger = params[:logger]
545
+ reset
546
+ end
547
+ def tag_start(name, attributes)
548
+ @text = ''
549
+ tagstart(name, attributes)
550
+ @xmlpath += @xmlpath.empty? ? name : "/#{name}"
551
+ end
552
+ def tag_end(name)
553
+ @xmlpath[/^(.*?)\/?#{name}$/]
554
+ @xmlpath = $1
555
+ tagend(name)
556
+ end
557
+ def text(text)
558
+ @text += text
559
+ tagtext(text)
560
+ end
561
+ # Parser method.
562
+ # Params:
563
+ # xml_text - xml message text(String) or Net:HTTPxxx instance (response)
564
+ # params[:xml_lib] - library name: 'rexml' | 'libxml'
565
+ def parse(xml_text, params={})
566
+ # Get response body
567
+ xml_text = xml_text.body unless xml_text.is_a?(String)
568
+ @xml_lib = params[:xml_lib] || @xml_lib
569
+ # check that we had no problems with this library otherwise use default
570
+ @xml_lib = DEFAULT_XML_LIBRARY unless @@supported_xml_libs.include?(@xml_lib)
571
+ # load xml library
572
+ if @xml_lib=='libxml' && !defined?(XML::SaxParser)
573
+ begin
574
+ require 'xml/libxml'
575
+ # is it new ? - Setup SaxParserCallback
576
+ if XML::Parser::VERSION >= '0.5.1.0'
577
+ RightSaxParserCallback.include_callback
578
+ end
579
+ rescue LoadError => e
580
+ @@supported_xml_libs.delete(@xml_lib)
581
+ @xml_lib = DEFAULT_XML_LIBRARY
582
+ if @logger
583
+ @logger.error e.inspect
584
+ @logger.error e.backtrace
585
+ @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
586
+ end
587
+ end
588
+ end
589
+ # Parse the xml text
590
+ case @xml_lib
591
+ when 'libxml'
592
+ xml = XML::SaxParser.new
593
+ xml.string = xml_text
594
+ # check libxml-ruby version
595
+ if XML::Parser::VERSION >= '0.5.1.0'
596
+ xml.callbacks = RightSaxParserCallback.new(self)
597
+ else
598
+ xml.on_start_element{|name, attr_hash| self.tag_start(name, attr_hash)}
599
+ xml.on_characters{ |text| self.text(text)}
600
+ xml.on_end_element{ |name| self.tag_end(name)}
601
+ end
602
+ xml.parse
603
+ else
604
+ REXML::Document.parse_stream(xml_text, self)
605
+ end
606
+ end
607
+ # Parser must have a lots of methods
608
+ # (see /usr/lib/ruby/1.8/rexml/parsers/streamparser.rb)
609
+ # We dont need most of them in RightAWSParser and method_missing helps us
610
+ # to skip their definition
611
+ def method_missing(method, *params)
612
+ # if the method is one of known - just skip it ...
613
+ return if [:comment, :attlistdecl, :notationdecl, :elementdecl,
614
+ :entitydecl, :cdata, :xmldecl, :attlistdecl, :instruction,
615
+ :doctype].include?(method)
616
+ # ... else - call super to raise an exception
617
+ super(method, params)
618
+ end
619
+ # the functions to be overriden by children (if nessesery)
620
+ def reset ; end
621
+ def tagstart(name, attributes); end
622
+ def tagend(name) ; end
623
+ def tagtext(text) ; end
624
+ end
625
+
626
+ #-----------------------------------------------------------------
627
+ # PARSERS: Errors
628
+ #-----------------------------------------------------------------
629
+
630
+ #<Error>
631
+ # <Code>TemporaryRedirect</Code>
632
+ # <Message>Please re-send this request to the specified temporary endpoint. Continue to use the original request endpoint for future requests.</Message>
633
+ # <RequestId>FD8D5026D1C5ABA3</RequestId>
634
+ # <Endpoint>bucket-for-k.s3-external-3.amazonaws.com</Endpoint>
635
+ # <HostId>ItJy8xPFPli1fq/JR3DzQd3iDvFCRqi1LTRmunEdM1Uf6ZtW2r2kfGPWhRE1vtaU</HostId>
636
+ # <Bucket>bucket-for-k</Bucket>
637
+ #</Error>
638
+
639
+ class RightErrorResponseParser < RightAWSParser #:nodoc:
640
+ attr_accessor :errors # array of hashes: error/message
641
+ attr_accessor :requestID
642
+ # attr_accessor :endpoint, :host_id, :bucket
643
+ def tagend(name)
644
+ case name
645
+ when 'RequestID' ; @requestID = @text
646
+ when 'Code' ; @code = @text
647
+ when 'Message' ; @message = @text
648
+ # when 'Endpoint' ; @endpoint = @text
649
+ # when 'HostId' ; @host_id = @text
650
+ # when 'Bucket' ; @bucket = @text
651
+ when 'Error' ; @errors << [ @code, @message ]
652
+ end
653
+ end
654
+ def reset
655
+ @errors = []
656
+ end
657
+ end
658
+
659
+ end
660
+