patron 0.10.0 → 0.11.0

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.
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