patron 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e380f44f007f28da3773814cfb58b14feee67d9
4
- data.tar.gz: 799cc7f47f3897ea44cf8f843e1608219444f354
3
+ metadata.gz: 03bffb3257b8f683269cebfec72cc4ea4fd86075
4
+ data.tar.gz: 2ece06c37c56204f850e138bdf3c481856953dd2
5
5
  SHA512:
6
- metadata.gz: 8236a3a412232c0bb956476f7031d13ebdfb570a9e583dcf9c064ce2ac7dd83c61a17022c92f0d223baf80e37749d25d1ece6212b39d2ade81c9caf2e552e08a
7
- data.tar.gz: 9b7540a564c74bc40116ce2accab42d0bdf48abd428828bdf3d12a3e77a250b781e99f92d63b569b7993be7de4bc4ac9b423d9181b1db4280689cebbe1315377
6
+ metadata.gz: 7f4a926542616faefd9a2a431d802d8941f99f5eb8c6c592c1ddb1119b30a4000743be8778f62148db4678300265f0283b6b1b927bfa2a8b8dacffb49adc3d2f
7
+ data.tar.gz: 8f8b71f17282c674149ba146ac32c3e7fb4f954ce44cfa162405b713fe5add7648b1c3d261afb6a5bc58317dbb333049afda93cce6dc8c9023a9256478c0bd4e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ### 0.11.0
2
+
3
+ * Added `Session#progress_callback` which accepts a callable object, which can be used to report session progress during request
4
+ execution.
5
+ * Fixed parsing of response headers when multiple responses are involved (redirect chains and HTTP proxies)
6
+
1
7
  ### 0.10.0
2
8
 
3
9
  * Added `Session#low_speed_time` and `Session#low_speed_limit`. When used, they will force libCURL to raise
@@ -40,7 +40,12 @@ struct patron_curl_state {
40
40
  membuffer header_buffer;
41
41
  membuffer body_buffer;
42
42
  size_t download_byte_limit;
43
+ VALUE user_progress_blk;
43
44
  int interrupt;
45
+ size_t dltotal;
46
+ size_t dlnow;
47
+ size_t ultotal;
48
+ size_t ulnow;
44
49
  };
45
50
 
46
51
 
@@ -69,16 +74,45 @@ static size_t file_write_handler(void* stream, size_t size, size_t nmemb, FILE*
69
74
  }
70
75
  }
71
76
 
77
+ static int call_user_rb_progress_blk(void* vd_curl_state) {
78
+ struct patron_curl_state* state = (struct patron_curl_state*)vd_curl_state;
79
+ // Invoke the block with the array
80
+ VALUE blk_result = rb_funcall(state->user_progress_blk,
81
+ rb_intern("call"), 4,
82
+ LONG2NUM(state->dltotal),
83
+ LONG2NUM(state->dlnow),
84
+ LONG2NUM(state->ultotal),
85
+ LONG2NUM(state->ulnow));
86
+ return 0;
87
+ }
88
+
89
+
72
90
  /* A non-zero return value from the progress handler will terminate the current
73
91
  * request. We use this fact in order to interrupt any request when either the
74
92
  * user calls the "interrupt" method on the session or when the Ruby interpreter
75
93
  * is attempting to exit.
76
94
  */
77
- static int session_progress_handler(void *clientp, size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) {
95
+ static int session_progress_handler(void* clientp, size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) {
78
96
  struct patron_curl_state* state = (struct patron_curl_state*) clientp;
79
- UNUSED_ARGUMENT(dlnow);
80
- UNUSED_ARGUMENT(ultotal);
81
- UNUSED_ARGUMENT(ulnow);
97
+ state->dltotal = dltotal;
98
+ state->dlnow = dlnow;
99
+ state->ultotal = ultotal;
100
+ state->ulnow = ulnow;
101
+
102
+ // If a progress proc has been set, re-acquire the GIL and call it using
103
+ // `call_user_rb_progress_blk`. TODO: use the retval of that proc
104
+ // to permit premature abort
105
+ if(RTEST(state->user_progress_blk)) {
106
+ // Even though it is not documented, rb_thread_call_with_gvl is available even when
107
+ // rb_thread_call_without_gvl is not. See https://bugs.ruby-lang.org/issues/5543#note-4
108
+ // > rb_thread_call_with_gvl() is globally-visible (but not in headers)
109
+ // > for 1.9.3: https://bugs.ruby-lang.org/issues/4328
110
+ #if (defined(HAVE_TBR) || defined(HAVE_TCWOGVL)) && defined(USE_TBR)
111
+ rb_thread_call_with_gvl((void *(*)(void *)) call_user_rb_progress_blk, (void*)state);
112
+ #else
113
+ call_user_rb_progress_blk((void*)state);
114
+ #endif
115
+ }
82
116
 
83
117
  // Set the interrupt if the download byte limit has been reached
84
118
  if(state->download_byte_limit != 0 && (dltotal > state->download_byte_limit)) {
@@ -410,6 +444,7 @@ static void set_options_from_request(VALUE self, VALUE request) {
410
444
  VALUE action_name = rb_funcall(request, rb_intern("action"), 0);
411
445
  VALUE a_c_encoding = rb_funcall(request, rb_intern("automatic_content_encoding"), 0);
412
446
  VALUE download_byte_limit = rb_funcall(request, rb_intern("download_byte_limit"), 0);
447
+ VALUE maybe_progress_proc = rb_funcall(request, rb_intern("progress_callback"), 0);
413
448
 
414
449
  if (RTEST(download_byte_limit)) {
415
450
  state->download_byte_limit = FIX2INT(download_byte_limit);
@@ -417,6 +452,12 @@ static void set_options_from_request(VALUE self, VALUE request) {
417
452
  state->download_byte_limit = 0;
418
453
  }
419
454
 
455
+ if (rb_obj_is_proc(maybe_progress_proc)) {
456
+ state->user_progress_blk = maybe_progress_proc;
457
+ } else {
458
+ state->user_progress_blk = Qnil;
459
+ }
460
+
420
461
  headers = rb_funcall(request, rb_intern("headers"), 0);
421
462
  if (RTEST(headers)) {
422
463
  if (rb_type(headers) != T_HASH) {
@@ -718,6 +759,17 @@ static VALUE select_error(CURLcode code) {
718
759
  return error;
719
760
  }
720
761
 
762
+
763
+ /* Uses as the unblocking function when the thread running Patron gets
764
+ signaled. The important difference with session_interrupt is that we
765
+ are not allowed to touch any Ruby structures while outside the GIL,
766
+ but we _are_ permitted to touch our internal curl state struct
767
+ */
768
+ void session_ubf_abort(void* patron_state) {
769
+ struct patron_curl_state* state = (struct patron_curl_state*) patron_state;
770
+ state->interrupt = INTERRUPT_ABORT;
771
+ }
772
+
721
773
  /* Perform the actual HTTP request by calling libcurl. */
722
774
  static VALUE perform_request(VALUE self) {
723
775
  struct patron_curl_state *state = get_patron_curl_state(self);
@@ -745,17 +797,17 @@ static VALUE perform_request(VALUE self) {
745
797
  }
746
798
 
747
799
  #if (defined(HAVE_TBR) || defined(HAVE_TCWOGVL)) && defined(USE_TBR)
748
- #if defined(HAVE_TCWOGVL)
749
- ret = (CURLcode) rb_thread_call_without_gvl(
750
- (void *(*)(void *)) curl_easy_perform, curl,
751
- RUBY_UBF_IO, 0
752
- );
753
- #else
754
- ret = (CURLcode) rb_thread_blocking_region(
755
- (rb_blocking_function_t*) curl_easy_perform, curl,
756
- RUBY_UBF_IO, 0
757
- );
758
- #endif
800
+ #if defined(HAVE_TCWOGVL)
801
+ ret = (CURLcode) rb_thread_call_without_gvl(
802
+ (void *(*)(void *)) curl_easy_perform, curl,
803
+ session_ubf_abort, (void*)state
804
+ );
805
+ #else
806
+ ret = (CURLcode) rb_thread_blocking_region(
807
+ (rb_blocking_function_t*) curl_easy_perform, curl,
808
+ session_ubf_abort, (void*)state
809
+ );
810
+ #endif
759
811
  #else
760
812
  ret = curl_easy_perform(curl);
761
813
  #endif
@@ -0,0 +1,32 @@
1
+ module Patron::HeaderParser
2
+ CRLF = /#{Regexp.escape("\r\n")}/
3
+
4
+ # Returned for each response parsed out
5
+ class SingleResponseHeaders < Struct.new(:status_line, :headers)
6
+ end
7
+
8
+ # Parses a string with lines delimited with CRLF into
9
+ # an Array of SingleResponseHeaders objects. libCURL supplies
10
+ # us multiple responses in sequence, so if we encounter multiple redirect
11
+ # or operate through a proxy - that adds ConnectionEstablished status at
12
+ # the beginning of the response - we need to account for parsing
13
+ # multiple response headres and potentially preserving them.
14
+ #
15
+ # @param [String] the string of headers, with responses delimited by empty lines. All lines must end with CRLF
16
+ # @return Array<SingleResponseHeaders>
17
+ def self.parse(headers_from_multiple_responses_in_sequence)
18
+ s = StringScanner.new(headers_from_multiple_responses_in_sequence)
19
+ responses = []
20
+ until s.eos?
21
+ return unless scanned = s.scan_until(CRLF)
22
+ matched_line = scanned[0..-3]
23
+ if matched_line =~ /^HTTP\/\d\.\d \d+/
24
+ responses << SingleResponseHeaders.new(matched_line.strip, [])
25
+ elsif matched_line =~ /^[^:]+\:/
26
+ raise "Header should follow an HTTP status line" unless responses.any?
27
+ responses[-1].headers << matched_line
28
+ end # else it is the end of the headers for the request
29
+ end
30
+ responses
31
+ end
32
+ end
@@ -25,13 +25,13 @@ module Patron
25
25
  :ignore_content_length, :multipart, :action, :timeout, :connect_timeout,
26
26
  :max_redirects, :headers, :auth_type, :upload_data, :buffer_size, :cacert,
27
27
  :ssl_version, :http_version, :automatic_content_encoding, :force_ipv4, :download_byte_limit,
28
- :low_speed_time, :low_speed_limit
28
+ :low_speed_time, :low_speed_limit, :progress_callback
29
29
  ]
30
30
 
31
31
  WRITER_VARS = [
32
32
  :url, :username, :password, :file_name, :proxy, :proxy_type, :insecure,
33
33
  :ignore_content_length, :multipart, :cacert, :ssl_version, :http_version, :automatic_content_encoding, :force_ipv4, :download_byte_limit,
34
- :low_speed_time, :low_speed_limit
34
+ :low_speed_time, :low_speed_limit, :progress_callback
35
35
  ]
36
36
 
37
37
  attr_reader *READER_VARS
@@ -113,16 +113,14 @@ module Patron
113
113
  private
114
114
 
115
115
  # Called by the C code to parse and set the headers
116
- def parse_headers(header_data)
116
+ def parse_headers(header_data_for_multiple_responses)
117
117
  @headers = {}
118
118
 
119
- lines = header_data.split("\r\n")
120
-
121
- @status_line = lines.shift
122
-
123
- lines.each do |line|
124
- break if line.empty?
119
+ responses = Patron::HeaderParser.parse(header_data_for_multiple_responses)
120
+ last_response = responses[-1] # Only use the last response (for proxies and redirects)
125
121
 
122
+ @status_line = last_response.status_line
123
+ last_response.headers.each do |line|
126
124
  hdr, val = line.split(":", 2)
127
125
 
128
126
  val.strip! unless val.nil?
@@ -5,6 +5,7 @@ require 'patron/response_decoding'
5
5
  require 'patron/response'
6
6
  require 'patron/session_ext'
7
7
  require 'patron/util'
8
+ require 'patron/header_parser'
8
9
 
9
10
  module Patron
10
11
 
@@ -101,6 +102,11 @@ module Patron
101
102
 
102
103
  private :handle_request, :add_cookie_file, :set_debug_file
103
104
 
105
+ # @return [#call, nil] callable object that will be called with 4 arguments
106
+ # during request/response execution - `dltotal`, `dlnow`, `ultotal`, `ulnow`.
107
+ # All these arguments are in bytes.
108
+ attr_accessor :progress_callback
109
+
104
110
  # Create a new Session object for performing requests.
105
111
  #
106
112
  # @param args[Hash] options for the Session (same names as the writable attributes of the Session)
@@ -371,6 +377,7 @@ module Patron
371
377
  req.ignore_content_length = options.fetch :ignore_content_length, self.ignore_content_length
372
378
  req.buffer_size = options.fetch :buffer_size, self.buffer_size
373
379
  req.download_byte_limit = options.fetch :download_byte_limit, self.download_byte_limit
380
+ req.progress_callback = options.fetch :progress_callback, self.progress_callback
374
381
  req.multipart = options[:multipart]
375
382
  req.upload_data = options[:data]
376
383
  req.file_name = options[:file]
@@ -1,3 +1,3 @@
1
1
  module Patron
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Patron::HeaderParser do
4
+ it 'parses a standard header' do
5
+ simple_headers_path = File.dirname(__FILE__) + '/sample_response_headers/headers_wetransfer.txt'
6
+ responses = Patron::HeaderParser.parse(File.read(simple_headers_path))
7
+
8
+ expect(responses.length).to eq(1)
9
+ first_response = responses[0]
10
+ expect(first_response.status_line).to eq("HTTP/1.1 200 OK")
11
+ expect(first_response.headers).to be_kind_of(Array)
12
+ expect(first_response.headers[0]).to eq("Date: Mon, 29 Jan 2018 00:09:09 GMT")
13
+ expect(first_response.headers[1]).to eq("Content-Type: text/html; charset=utf-8")
14
+ expect(first_response.headers[2]).to eq("Transfer-Encoding: chunked")
15
+ expect(first_response.headers[3]).to eq("Connection: keep-alive")
16
+ end
17
+
18
+ it 'parses a sequence of responses resulting from a redirect' do
19
+ simple_headers_path = File.dirname(__FILE__) + '/sample_response_headers/headers_wetransfer_with_redirect.txt'
20
+ responses = Patron::HeaderParser.parse(File.read(simple_headers_path))
21
+
22
+ expect(responses.length).to eq(2)
23
+ first_response = responses[0]
24
+
25
+ expect(first_response.status_line).to eq("HTTP/1.1 301 Moved Permanently")
26
+ expect(first_response.headers).to be_kind_of(Array)
27
+ expect(first_response.headers[0]).to eq("Date: Mon, 29 Jan 2018 00:42:27 GMT")
28
+ expect(first_response.headers[2]).to eq("Connection: keep-alive")
29
+ expect(first_response.headers[3]).to eq("Location: https://wetransfer.com/")
30
+ expect(first_response.headers[4]).to be_nil
31
+
32
+ second_response = responses[1]
33
+ expect(second_response.status_line).to eq("HTTP/1.1 200 OK")
34
+ expect(second_response.headers).to be_kind_of(Array)
35
+ expect(second_response.headers[0]).to eq("Date: Mon, 29 Jan 2018 00:42:27 GMT")
36
+ expect(second_response.headers[1]).to eq("Content-Type: text/html; charset=utf-8")
37
+ expect(second_response.headers[2]).to eq("Transfer-Encoding: chunked")
38
+ end
39
+
40
+ it 'parses response headers that set cookies' do
41
+ simple_headers_path = File.dirname(__FILE__) + '/sample_response_headers/headers_with_set_cookie.txt'
42
+ responses = Patron::HeaderParser.parse(File.read(simple_headers_path))
43
+
44
+ expect(responses.length).to eq(1)
45
+ first_response = responses[0]
46
+
47
+ expect(first_response.status_line).to eq("HTTP/1.1 200 OK")
48
+ expect(first_response.headers).to be_kind_of(Array)
49
+ expect(first_response.headers[0]).to eq("Content-Type: text/plain")
50
+ expect(first_response.headers[1]).to start_with('Server:')
51
+ expect(first_response.headers[2]).to eq("Date: Mon, 29 Jan 2018 01:50:54 GMT")
52
+ expect(first_response.headers[3]).to eq("Content-Length: 3")
53
+ expect(first_response.headers[4]).to eq("Connection: Keep-Alive")
54
+ expect(first_response.headers[5]).to eq("Set-Cookie: a=1")
55
+ end
56
+
57
+ it 'parses response headers with an extra status line from a proxy' do
58
+ simple_headers_path = File.dirname(__FILE__) + '/sample_response_headers/headers_wetransfer_with_proxy_status.txt'
59
+ responses = Patron::HeaderParser.parse(File.read(simple_headers_path))
60
+
61
+ expect(responses.length).to eq(2)
62
+ first_response = responses[0]
63
+
64
+ expect(first_response.status_line).to eq("HTTP/1.1 200 Connection established")
65
+ expect(first_response.headers).to be_kind_of(Array)
66
+ expect(first_response.headers).to be_empty
67
+
68
+ second_response = responses[1]
69
+ expect(second_response.status_line).to eq("HTTP/1.1 200 OK")
70
+ expect(second_response.headers).to be_kind_of(Array)
71
+ expect(second_response.headers[0]).to eq("Date: Mon, 29 Jan 2018 00:09:09 GMT")
72
+ end
73
+ end
@@ -0,0 +1,18 @@
1
+ HTTP/1.1 200 OK
2
+ Date: Mon, 29 Jan 2018 00:09:09 GMT
3
+ Content-Type: text/html; charset=utf-8
4
+ Transfer-Encoding: chunked
5
+ Connection: keep-alive
6
+ X-Frame-Options: SAMEORIGIN
7
+ X-XSS-Protection: 1; mode=block
8
+ X-Content-Type-Options: nosniff
9
+ Cache-Control: no-cache, no-store, max-age=0, must-revalidate
10
+ Pragma: no-cache
11
+ Expires: Fri, 01 Jan 1990 00:00:00 GMT
12
+ Vary: Accept-Encoding, Origin
13
+ Set-Cookie: _wt_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTllOGNkZDg3N2Y2Mzc2M2E1NWY2NTQwMjYzNzljODJhBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMXk4YWtnZDVNYlRYUmJJWkFFaWkzVXFWM3IzbE1aU2xSbFl3SDQ3aHArbU09BjsARg%3D%3D--fe13900e81f693acb4a80d372d864c78704fa776; domain=wetransfer.com; path=/; secure; HttpOnly
14
+ X-Request-Id: 62b0d737-b0f8-400b-ac3a-0f84acf241af
15
+ X-Opaque: dev-1.wt-19171
16
+ X-Runtime: 0.116529
17
+ Strict-Transport-Security: max-age=15552000; includeSubDomains;
18
+
@@ -0,0 +1,20 @@
1
+ HTTP/1.1 200 Connection established
2
+
3
+ HTTP/1.1 200 OK
4
+ Date: Mon, 29 Jan 2018 00:09:09 GMT
5
+ Content-Type: text/html; charset=utf-8
6
+ Transfer-Encoding: chunked
7
+ Connection: keep-alive
8
+ X-Frame-Options: SAMEORIGIN
9
+ X-XSS-Protection: 1; mode=block
10
+ X-Content-Type-Options: nosniff
11
+ Cache-Control: no-cache, no-store, max-age=0, must-revalidate
12
+ Pragma: no-cache
13
+ Expires: Fri, 01 Jan 1990 00:00:00 GMT
14
+ Vary: Accept-Encoding, Origin
15
+ Set-Cookie: _wt_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTllOGNkZDg3N2Y2Mzc2M2E1NWY2NTQwMjYzNzljODJhBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMXk4YWtnZDVNYlRYUmJJWkFFaWkzVXFWM3IzbE1aU2xSbFl3SDQ3aHArbU09BjsARg%3D%3D--fe13900e81f693acb4a80d372d864c78704fa776; domain=wetransfer.com; path=/; secure; HttpOnly
16
+ X-Request-Id: 62b0d737-b0f8-400b-ac3a-0f84acf241af
17
+ X-Opaque: dev-1.wt-19171
18
+ X-Runtime: 0.116529
19
+ Strict-Transport-Security: max-age=15552000; includeSubDomains;
20
+
@@ -0,0 +1,24 @@
1
+ HTTP/1.1 301 Moved Permanently
2
+ Date: Mon, 29 Jan 2018 00:42:27 GMT
3
+ Content-Length: 0
4
+ Connection: keep-alive
5
+ Location: https://wetransfer.com/
6
+
7
+ HTTP/1.1 200 OK
8
+ Date: Mon, 29 Jan 2018 00:42:27 GMT
9
+ Content-Type: text/html; charset=utf-8
10
+ Transfer-Encoding: chunked
11
+ Connection: keep-alive
12
+ X-Frame-Options: SAMEORIGIN
13
+ X-XSS-Protection: 1; mode=block
14
+ X-Content-Type-Options: nosniff
15
+ Cache-Control: no-cache, no-store, max-age=0, must-revalidate
16
+ Pragma: no-cache
17
+ Expires: Fri, 01 Jan 1990 00:00:00 GMT
18
+ Vary: Accept-Encoding, Origin
19
+ Set-Cookie: _wt_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJWYyMGFmZGRlY2QyNDJkNTA3YTc2YmJmYzg2MWNhMGZlBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMUkvMUJqR0tCelZxQytKQU1kbDV4ZW5xNDNSaUlDNnVrODhDTEY4Q0dxbXM9BjsARg%3D%3D--b46297a54dd838e0c26b5559314b10deb1f59312; domain=wetransfer.com; path=/; secure; HttpOnly
20
+ X-Request-Id: 09e7a0ef-e9a1-4519-a580-8185e6d64c1c
21
+ X-Opaque: dev-1.wt-24185
22
+ X-Runtime: 0.102958
23
+ Strict-Transport-Security: max-age=15552000; includeSubDomains;
24
+
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 200 OK
2
+ Content-Type: text/plain
3
+ Server: WEBrick/1.3.1 (Ruby/2.4.1/2017-03-22) OpenSSL/1.0.2k
4
+ Date: Mon, 29 Jan 2018 01:50:54 GMT
5
+ Content-Length: 3
6
+ Connection: Keep-Alive
7
+ Set-Cookie: a=1
8
+ Set-Cookie: b=2
9
+
data/spec/session_spec.rb CHANGED
@@ -178,6 +178,59 @@ describe Patron::Session do
178
178
  expect {@session.get("/slow")}.to raise_error(Patron::TimeoutError)
179
179
  end
180
180
 
181
+ it "is able to terminate the thread that is running a slow request using Thread#kill (uses the custom unblock)" do
182
+ t = Thread.new do
183
+ session = Patron::Session.new
184
+ session.timeout = 40
185
+ session.base_url = "http://localhost:9001"
186
+ session.get("/slow")
187
+ end
188
+
189
+ # Our test server starts sending the body only after 20 seconds. We should be able to abort
190
+ # using a signal during that time.
191
+ started = Time.now.to_i
192
+ sleep 5 # Less than what it takes for the server to respond
193
+ t.kill # Kill the thread forcibly
194
+ t.join # wrap up the thread. If Patron is still busy there, this join call will still take 15s.
195
+
196
+ delta_s = Time.now.to_i - started
197
+ expect(delta_s).to be_within(2).of(5)
198
+ end
199
+
200
+ it "is able to terminate the thread that is running a slow request" do
201
+ t = Thread.new do
202
+ trap('SIGINT') do
203
+ exit # exit the thread
204
+ end
205
+ session = Patron::Session.new
206
+ session.timeout = 40
207
+ session.base_url = "http://localhost:9001"
208
+ session.get("/slow")
209
+ end
210
+
211
+ # Our test server starts sending the body only after 20 seconds. We should be able to abort
212
+ # using a signal during that time.
213
+ started = Time.now.to_i
214
+ sleep 5 # Less than what it takes for the server to respond
215
+ Process.kill("INT", Process.pid) # Signal ourselves...
216
+ t.join # wrap up the thread. If Patron is still busy there, this join call will still take 15s.
217
+ delta_s = Time.now.to_i - started
218
+ expect(delta_s).to be_within(2).of(5)
219
+ end
220
+
221
+ it "receives progress callbacks" do
222
+ session = Patron::Session.new
223
+ session.timeout = 40
224
+ session.base_url = "http://localhost:9001"
225
+ callback_args = []
226
+ session.progress_callback = Proc.new {|dltotal, dlnow, ultotal, ulnow|
227
+ callback_args << [dltotal, dlnow, ultotal, ulnow]
228
+ }
229
+ session.get("/slow")
230
+
231
+ expect(callback_args).not_to be_empty
232
+ end
233
+
181
234
  it "should follow redirects by default" do
182
235
  @session.max_redirects = 1
183
236
  response = @session.get("/redirect")
@@ -186,6 +239,12 @@ describe Patron::Session do
186
239
  expect(body.path).to be == "/test"
187
240
  end
188
241
 
242
+ it "should not keep the Location header from the redirecting response" do
243
+ @session.max_redirects = 1
244
+ response = @session.get("/redirect")
245
+ expect(response.headers['Location']).to be_nil
246
+ end
247
+
189
248
  it "should include redirect count in response" do
190
249
  @session.max_redirects = 1
191
250
  response = @session.get("/redirect")
@@ -66,8 +66,8 @@ class GzipServlet < HTTPServlet::AbstractServlet
66
66
 
67
67
  content_length = out.size
68
68
  # Content-Length gets set automatically by WEBrick, and if we do it manually
69
- # here then two headers will be set.
70
- # res.header['Content-Length'] = content_length
69
+ # here then two headers will be set. This is also against the content encoding
70
+ # description in the HTTP 1.1 RFC - but hey, Webrick!
71
71
  res.header['Content-Encoding'] = 'deflate'
72
72
  res.header['Vary'] = 'Accept-Encoding'
73
73
  res.body = out.string
@@ -82,9 +82,10 @@ end
82
82
 
83
83
  class SlowServlet < HTTPServlet::AbstractServlet
84
84
  def do_GET(req,res)
85
- sleep 3
86
85
  res.header['Content-Type'] = 'text/plain'
87
- res.body = 'beep'
86
+ res.body << 'x'
87
+ sleep 20
88
+ res.body << 'rest of body'
88
89
  end
89
90
  end
90
91
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phillip Toland
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-04 00:00:00.000000000 Z
11
+ date: 2018-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -120,6 +120,7 @@ files:
120
120
  - ext/patron/sglib.h
121
121
  - lib/patron.rb
122
122
  - lib/patron/error.rb
123
+ - lib/patron/header_parser.rb
123
124
  - lib/patron/proxy_type.rb
124
125
  - lib/patron/request.rb
125
126
  - lib/patron/response.rb
@@ -133,9 +134,14 @@ files:
133
134
  - script/test_server
134
135
  - spec/certs/cacert.pem
135
136
  - spec/certs/privkey.pem
137
+ - spec/header_parser_spec.rb
136
138
  - spec/patron_spec.rb
137
139
  - spec/request_spec.rb
138
140
  - spec/response_spec.rb
141
+ - spec/sample_response_headers/headers_wetransfer.txt
142
+ - spec/sample_response_headers/headers_wetransfer_with_proxy_status.txt
143
+ - spec/sample_response_headers/headers_wetransfer_with_redirect.txt
144
+ - spec/sample_response_headers/headers_with_set_cookie.txt
139
145
  - spec/session_spec.rb
140
146
  - spec/session_ssl_spec.rb
141
147
  - spec/spec_helper.rb