Project

General

Profile

Bug #11526 ยป trowe-net-http-idempotent-retry-fix-with-test-fixes.diff

tdg5 (Danny Guinther), 09/15/2015 08:00 PM

View differences:

lib/net/http.rb
1403 1403
      if proxy_user()
1404 1404
        req.proxy_basic_auth proxy_user(), proxy_pass() unless use_ssl?
1405 1405
      end
1406
      req.retry_networking_errors = false if block_given?
1406 1407
      req.set_body_internal body
1407 1408
      res = transport_request(req, &block)
1408 1409
      if sspi_auth?(res)
......
1452 1453
             # avoid a dependency on OpenSSL
1453 1454
             defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : IOError,
1454 1455
             Timeout::Error => exception
1455
        if count == 0 && IDEMPOTENT_METHODS_.include?(req.method)
1456
        if count == 0 && req.retry_networking_errors
1456 1457
          count += 1
1458
          req.body_stream.rewind if req.body_stream
1457 1459
          @socket.close if @socket and not @socket.closed?
1458 1460
          D "Conn close because of error #{exception}, and retry"
1459 1461
          retry
lib/net/http/generic_request.rb
9 9

  
10 10
  def initialize(m, reqbody, resbody, uri_or_path, initheader = nil)
11 11
    @method = m
12
    @retry_networking_errors = Net::HTTP::IDEMPOTENT_METHODS_.include?(m)
12 13
    @request_has_body = reqbody
13 14
    @response_has_body = resbody
14 15

  
......
58 59
  # themselves.
59 60
  attr_reader :decode_content
60 61

  
62
  # When true, this request may be retried if a networking error is
63
  # encountered. Set to false to disable retries. Defaults to
64
  # true for idempotent HTTP methods.
65
  attr_accessor :retry_networking_errors
66

  
61 67
  def inspect
62 68
    "\#<#{self.class} #{@method}>"
63 69
  end
......
97 103
  attr_reader :body_stream
98 104

  
99 105
  def body_stream=(input)
106
    @retry_networking_errors = input.respond_to?(:rewind)
100 107
    @body = nil
101 108
    @body_stream = input
102 109
    @body_data = nil
test/net/http/test_http.rb
881 881
  end
882 882

  
883 883
  def test_keep_alive_get_auto_retry
884
    start {|http|
885
      res = http.get('/')
886
      http.keep_alive_timeout = 5
887
      assert_kind_of Net::HTTPResponse, res
888
      assert_kind_of String, res.body
889
      sleep 1.5
890
      res = http.get('/')
891
      assert_kind_of Net::HTTPResponse, res
892
      assert_kind_of String, res.body
893
    }
884
    http = new
885
    res = http.get('/')
886
    http.keep_alive_timeout = 5
887
    assert_kind_of Net::HTTPResponse, res
888
    assert_kind_of String, res.body
889
    sleep 1.5
890
    res = http.get('/')
891
    assert_kind_of Net::HTTPResponse, res
892
    assert_kind_of String, res.body
894 893
  end
895 894

  
896 895
  def test_keep_alive_server_close
test/net/http/test_http_retries.rb
1
# coding: US-ASCII
2
require 'test/unit'
3
require 'net/http'
4
require 'stringio'
5
require 'socket'
6

  
7
class TestNetHTTPIdempotentRetries < Test::Unit::TestCase
8

  
9
  class SimpleHttpServer
10

  
11
    def initialize
12
      @address = '127.0.0.1'
13
      @port = 3000
14
      @handlers = []
15
    end
16

  
17
    attr_reader :address, :port
18

  
19
    # The given proc should accept the follow block arguments:
20
    #
21
    # * `http_method` - String
22
    # * `request_uri` - String
23
    # * `request_headers` - Hash<String,String>
24
    # * `socket` - Socket
25
    #
26
    # The block is expected to read the request body and to write
27
    # the full response.
28
    def handle(&handler)
29
      @handlers << Proc.new
30
    end
31

  
32
    def start
33
      @server = TCPServer.new(@address, @port)
34
      @thread = Thread.new do
35
        loop do
36
          begin
37
            socket = @server.accept
38
            method, uri, _ = socket.gets.split(/\s+/)
39
            @handlers.shift.call(method, uri, headers(socket), socket)
40
            socket.close unless socket.closed?
41
          rescue
42
            socket.close unless socket.closed?
43
            @server.close
44
            @server = TCPServer.new(@address, @port)
45
            retry
46
          end
47
        end
48
      end
49
    end
50

  
51
    def stop
52
      @thread.kill
53
      @server.close
54
    end
55

  
56
    private
57

  
58
    def headers(socket)
59
      headers = {}
60
      line = socket.gets
61
      until line == "\r\n"
62
        key, value = line.split(/:\s*/, 2)
63
        headers[key.downcase] = value
64
        line = socket.gets
65
      end
66
      headers
67
    end
68

  
69
  end
70

  
71
  def setup
72
    @server = SimpleHttpServer.new
73
  end
74

  
75
  def teardown
76
    @server.stop
77
  end
78

  
79
  def test_idempotent_retry_default
80
    @server.handle do |_, _, headers, socket|
81
      socket.close # close without responding
82
    end
83
    @server.handle do |_, _, headers, socket|
84
      socket.print("HTTP/1.1 200 OK\r\n")
85
      socket.print("\r\n")
86
    end
87
    @server.start
88

  
89
    req = Net::HTTP::Get.new('/')
90
    http = Net::HTTP.new(@server.address, @server.port)
91
    res = http.request(req)
92

  
93
    assert_equal('200', res.code)
94
  end
95

  
96
  def test_retry_can_be_disabled
97
    @server.handle do |_, _, headers, socket|
98
      socket.close # close without responding
99
    end
100
    @server.handle do |_, _, headers, socket|
101
      socket.print("HTTP/1.1 200 OK\r\n")
102
      socket.print("\r\n")
103
    end
104
    @server.start
105

  
106
    req = Net::HTTP::Get.new('/')
107
    req.retry_networking_errors = false
108

  
109
    http = Net::HTTP.new(@server.address, @server.port)
110
    assert_raises(EOFError) {
111
      http.request(req)
112
    }
113
  end
114

  
115
  def test_idempotent_retry_rewinds_put_body_stream
116
    @server.handle do |_, _, headers, socket|
117
      # intentionally read some of the data, not all
118
      socket.read(headers['content-length'].to_i / 2)
119
      socket.close
120
    end
121
    @server.handle do |_, _, headers, socket|
122
      body = socket.read(headers['content-length'].to_i)
123
      socket.print("HTTP/1.1 200 OK\r\n")
124
      socket.print("Content-Length: #{body.size}\n")
125
      socket.print("\r\n")
126
      socket.print(body)
127
    end
128
    @server.start
129

  
130
    body = StringIO.new('io-body')
131
    req = Net::HTTP::Put.new('/', 'Content-Length' => body.size.to_s )
132
    req.body_stream = body
133

  
134
    http = Net::HTTP.new(@server.address, @server.port)
135
    http.read_timeout = 0.5
136

  
137
    assert_nothing_raised {
138
      http.request(req)
139
    }
140
  end
141

  
142
  def test_idempotent_retry_disabled_with_request_block
143
    one_meg = 1024 * 1024
144
    @server.handle do |_, _, headers, socket|
145
      socket.print("HTTP/1.1 200 OK\r\n")
146
      socket.print("Content-Length: #{one_meg}\n")
147
      socket.print("\r\n")
148
      socket.print('.' * (one_meg / 2))
149
      raise 'error' # forcefully close the connection
150
    end
151
    @server.handle do |_, _, headers, socket|
152
      socket.print("HTTP/1.1 200 OK\r\n")
153
      socket.print("Content-Length: #{one_meg}\n")
154
      socket.print("\r\n")
155
      socket.print('.' * one_meg)
156
    end
157
    @server.start
158

  
159
    http = Net::HTTP.new(@server.address, @server.port)
160
    http.read_timeout = 0.5
161

  
162
    yield_count = 0
163
    byte_count = 0
164

  
165
    http.request(Net::HTTP::Get.new('/')) do |resp|
166
      yield_count += 1
167
      resp.read_body do |bytes|
168
        byte_count += bytes.bytesize
169
      end
170
    end
171

  
172
    assert_equal(one_meg / 2, byte_count)
173
    assert_equal(1, yield_count)
174
  end
175
end