http 5.0.0 → 5.0.4

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
  SHA256:
3
- metadata.gz: cc5c5ebaa25630442cf182d6cc9925d112dccbfeb4030f07951cfaad72f35bde
4
- data.tar.gz: bdb8d0148f8fd0cdb3634ab743db153a998bfa8f8be28295bb8d95bae5833d68
3
+ metadata.gz: 92001458b2f36332dfd042ab8750595408d2be8e941ed52be9237e11c071fef7
4
+ data.tar.gz: de00f9d0a8f59f3af1605f479e503269741c751b397d62d618c8866e58929140
5
5
  SHA512:
6
- metadata.gz: d74104f964a90c0d0689df9f5af8082fcd5d4902dfa46bf9bdc22ed1a2a86d48211c29fd42b20c037afee958f89ef1f5e7d442285083f169852ccf31bd522818
7
- data.tar.gz: 6bac3882bc504513b6a05eb7facf5823b4f46ea55a737933b75305a336b23a97a21914f77378f8a67d7ab262a3b16ec46c592f224036491254f1bb0363e3d294
6
+ metadata.gz: 63249cd08800a312ffba0ed4b9a508e3890b3088058956e5433c71a60957bfe9985070225855b926d78f5a64c3b587a122c52137788b54e10da65076b39f541f
7
+ data.tar.gz: 89605690a8dd8ac3bf23447de8a75f90bc37a77d8ff0e85d84b0748daf2cf06b69011d8e19cc4b22067e479decdfc9d8ca46f7fc0495ec90c25a433e10c1eac8
@@ -2,9 +2,9 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ master ]
5
+ branches: [ main ]
6
6
  pull_request:
7
- branches: [ master ]
7
+ branches: [ main ]
8
8
 
9
9
  env:
10
10
  BUNDLE_WITHOUT: "development"
data/CHANGES.md CHANGED
@@ -1,3 +1,65 @@
1
+ ## 5.0.4 (2021-10-07)
2
+
3
+ * [#698](https://github.com/httprb/http/pull/698)
4
+ Fix `HTTP::Timeout::Global#connect_ssl`.
5
+ ([@tarcieri])
6
+
7
+ ## 5.0.3 (2021-10-06)
8
+
9
+ * [#695](https://github.com/httprb/http/pull/695)
10
+ Revert DNS resolving feature.
11
+ ([@PhilCoggins])
12
+
13
+ * [#694](https://github.com/httprb/http/pull/694)
14
+ Fix cookies extraction.
15
+ ([@flosacca])
16
+
17
+ ## 5.0.2 (2021-09-10)
18
+
19
+ * [#686](https://github.com/httprb/http/pull/686)
20
+ Correctly reset the parser.
21
+ ([@bryanp])
22
+
23
+ * [#684](https://github.com/httprb/http/pull/684)
24
+ Don't set Content-Length for GET, HEAD, DELETE, or CONNECT requests without a BODY.
25
+ ([@jyn514])
26
+
27
+ * [#679](https://github.com/httprb/http/pull/679)
28
+ Use features on redirected requests.
29
+ ([@nomis])
30
+
31
+ * [#678](https://github.com/schwern)
32
+ Restore `HTTP::Response` `:uri` option for backwards compatibility.
33
+ ([@schwern])
34
+
35
+ * [#676](https://github.com/httprb/http/pull/676)
36
+ Update addressable because of CVE-2021-32740.
37
+ ([@matheussilvasantos])
38
+
39
+ * [#653](https://github.com/httprb/http/pull/653)
40
+ Avoid force encodings on frozen strings.
41
+ ([@bvicenzo])
42
+
43
+ * [#638](https://github.com/httprb/http/pull/638)
44
+ DNS failover handling.
45
+ ([@midnight-wonderer])
46
+
47
+
48
+ ## 5.0.1 (2021-06-26)
49
+
50
+ * [#670](https://github.com/httprb/http/pull/670)
51
+ Revert `Response#parse` behavior introduced in [#540].
52
+ ([@DannyBen])
53
+
54
+ * [#669](https://github.com/httprb/http/pull/669)
55
+ Prevent bodies from being resubmitted when following unsafe redirects.
56
+ ([@odinhb])
57
+
58
+ * [#664](https://github.com/httprb/http/pull/664)
59
+ Bump llhttp-ffi to 0.3.0.
60
+ ([@bryanp])
61
+
62
+
1
63
  ## 5.0.0 (2021-05-12)
2
64
 
3
65
  * [#656](https://github.com/httprb/http/pull/656)
@@ -53,6 +115,12 @@
53
115
  Preserve header names casing.
54
116
  ([@joshuaflanagan])
55
117
 
118
+ * [#540](https://github.com/httprb/http/pull/540)
119
+ [#538](https://github.com/httprb/http/issues/538)
120
+ **BREAKING CHANGE**
121
+ Require explicit MIME type for Response#parse
122
+ ([@ixti])
123
+
56
124
  * [#532](https://github.com/httprb/http/pull/532)
57
125
  Fix pipes support in request bodies.
58
126
  ([@ixti])
@@ -79,6 +147,9 @@
79
147
  Drop Ruby 2.3.x support.
80
148
  ([@ixti])
81
149
 
150
+ * [3ed0c31](https://github.com/httprb/http/commit/3ed0c318eab6a8c390654cda17bf6df9e963c7d6)
151
+ Drop Ruby 2.4.x support.
152
+
82
153
 
83
154
  ## 4.4.0 (2020-03-25)
84
155
 
@@ -887,3 +958,13 @@ end
887
958
  [@semenyukdmitry]: https://github.com/semenyukdmitry
888
959
  [@bryanp]: https://github.com/bryanp
889
960
  [@meanphil]: https://github.com/meanphil
961
+ [@odinhb]: https://github.com/odinhb
962
+ [@DannyBen]: https://github.com/DannyBen
963
+ [@jyn514]: https://github.com/jyn514
964
+ [@bvicenzo]: https://github.com/bvicenzo
965
+ [@nomis]: https://github.com/nomis
966
+ [@midnight-wonderer]: https://github.com/midnight-wonderer
967
+ [@schwern]: https://github.com/schwern
968
+ [@matheussilvasantos]: https://github.com/matheussilvasantos
969
+ [@PhilCoggins]: https://github.com/PhilCoggins
970
+ [@flosacca]: https://github.com/flosacca
data/Gemfile CHANGED
@@ -27,7 +27,7 @@ group :test do
27
27
 
28
28
  gem "backports"
29
29
 
30
- gem "rubocop"
30
+ gem "rubocop", "~> 1.21"
31
31
  gem "rubocop-performance"
32
32
  gem "rubocop-rake"
33
33
  gem "rubocop-rspec"
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2016 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
1
+ Copyright (c) 2011-2021 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,16 +1,12 @@
1
- # ![http.rb](https://raw.github.com/httprb/http.rb/master/logo.png)
1
+ # ![http.rb](https://raw.github.com/httprb/http.rb/main/logo.png)
2
2
 
3
- [![Gem Version](https://img.shields.io/gem/v/http?logo=ruby)](https://rubygems.org/gems/http)
4
- [![Build Status](https://github.com/httprb/http/workflows/CI/badge.svg)](https://github.com/httprb/http/actions?query=workflow:CI)
5
- [![Code Climate](https://codeclimate.com/github/httprb/http.svg?branch=master)](https://codeclimate.com/github/httprb/http)
6
- [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/httprb/http/blob/master/LICENSE.txt)
3
+ [![Gem Version][gem-image]][gem-link]
4
+ [![MIT licensed][license-image]][license-link]
5
+ [![Build Status][build-image]][build-link]
6
+ [![Code Climate][codeclimate-image]][codeclimate-link]
7
7
 
8
8
  [Documentation]
9
9
 
10
- _NOTE: This is the 5.x **development** branch. For the 4.x **stable** branch, please see:_
11
-
12
- https://github.com/httprb/http/tree/4-x-stable
13
-
14
10
  ## About
15
11
 
16
12
  HTTP (The Gem! a.k.a. http.rb) is an easy-to-use client library for making requests
@@ -21,63 +17,16 @@ Under the hood, http.rb uses the [llhttp] parser, a fast HTTP parsing native ext
21
17
  This library isn't just yet another wrapper around `Net::HTTP`. It implements the HTTP
22
18
  protocol natively and outsources the parsing to native extensions.
23
19
 
24
- [requests]: http://docs.python-requests.org/en/latest/
25
- [llhttp]: https://llhttp.org/
26
-
27
-
28
- ## Another Ruby HTTP library? Why should I care?
29
-
30
- There are a lot of HTTP libraries to choose from in the Ruby ecosystem.
31
- So why would you choose this one?
20
+ ### Why http.rb?
32
21
 
33
- Top three reasons:
34
-
35
- 1. **Clean API**: http.rb offers an easy-to-use API that should be a
22
+ - **Clean API**: http.rb offers an easy-to-use API that should be a
36
23
  breath of fresh air after using something like Net::HTTP.
37
24
 
38
- 2. **Maturity**: http.rb is one of the most mature Ruby HTTP clients, supporting
25
+ - **Maturity**: http.rb is one of the most mature Ruby HTTP clients, supporting
39
26
  features like persistent connections and fine-grained timeouts.
40
27
 
41
- 3. **Performance**: using native parsers and a clean, lightweight implementation,
42
- http.rb achieves the best performance of any Ruby HTTP library which
43
- implements the HTTP protocol in Ruby instead of C:
44
-
45
- | HTTP client | Time | Implementation |
46
- |--------------------------|--------|-----------------------|
47
- | curb (persistent) | 2.519 | libcurl wrapper |
48
- | em-http-request | 2.731 | EM + http_parser.rb |
49
- | Typhoeus | 2.851 | libcurl wrapper |
50
- | StreamlyFFI (persistent) | 2.853 | libcurl wrapper |
51
- | http.rb (persistent) | 2.970 | Ruby + http_parser.rb |
52
- | http.rb | 3.588 | Ruby + http_parser.rb |
53
- | HTTParty | 3.931 | Net::HTTP wrapper |
54
- | Net::HTTP | 3.959 | Pure Ruby |
55
- | Net::HTTP (persistent) | 4.043 | Pure Ruby |
56
- | open-uri | 4.479 | Net::HTTP wrapper |
57
- | Excon (persistent) | 4.618 | Pure Ruby |
58
- | Excon | 4.701 | Pure Ruby |
59
- | RestClient | 26.838 | Net::HTTP wrapper |
60
-
61
- Benchmarks performed using excon's benchmarking tool
62
-
63
- DISCLAIMER: Most benchmarks you find in READMEs are crap,
64
- including this one. These are out-of-date. If you care about
65
- performance, benchmark for yourself for your own use cases!
66
-
67
- ## Help and Discussion
68
-
69
- If you need help or just want to talk about the http.rb,
70
- visit the http.rb Google Group:
71
-
72
- https://groups.google.com/forum/#!forum/httprb
73
-
74
- You can join by email by sending a message to:
75
-
76
- [httprb+subscribe@googlegroups.com](mailto:httprb+subscribe@googlegroups.com)
77
-
78
- If you believe you've found a bug, please report it at:
79
-
80
- https://github.com/httprb/http/issues
28
+ - **Performance**: using native parsers and a clean, lightweight implementation,
29
+ http.rb achieves high performance while implementing HTTP in Ruby instead of C.
81
30
 
82
31
 
83
32
  ## Installation
@@ -112,10 +61,9 @@ for more detailed documentation and usage notes.
112
61
 
113
62
  The following API documentation is also available:
114
63
 
115
- * [YARD API documentation](https://www.rubydoc.info/github/httprb/http)
116
- * [Chainable module (all chainable methods)](https://www.rubydoc.info/github/httprb/http/HTTP/Chainable)
64
+ - [YARD API documentation](https://www.rubydoc.info/github/httprb/http)
65
+ - [Chainable module (all chainable methods)](https://www.rubydoc.info/github/httprb/http/HTTP/Chainable)
117
66
 
118
- [documentation]: https://github.com/httprb/http/wiki
119
67
 
120
68
  ### Basic Usage
121
69
 
@@ -142,7 +90,7 @@ We can also obtain an `HTTP::Response::Body` object for this response:
142
90
  ```
143
91
 
144
92
  The response body can be streamed with `HTTP::Response::Body#readpartial`.
145
- In practice, you'll want to bind the HTTP::Response::Body to a local variable
93
+ In practice, you'll want to bind the `HTTP::Response::Body` to a local variable
146
94
  and call `#readpartial` on it repeatedly until it returns `nil`:
147
95
 
148
96
  ```ruby
@@ -159,13 +107,13 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
159
107
 
160
108
  ## Supported Ruby Versions
161
109
 
162
- This library aims to support and is [tested against][travis] the following Ruby
163
- versions:
110
+ This library aims to support and is [tested against][build-link]
111
+ the following Ruby versions:
164
112
 
165
- * Ruby 2.6
166
- * Ruby 2.7
167
- * Ruby 3.0
168
- * JRuby 9.2
113
+ - Ruby 2.6
114
+ - Ruby 2.7
115
+ - Ruby 3.0
116
+ - JRuby 9.2
169
117
 
170
118
  If something doesn't work on one of these versions, it's a bug.
171
119
 
@@ -180,20 +128,36 @@ patches in a timely fashion. If critical issues for a particular implementation
180
128
  exist at the time of a major release, support for that Ruby version may be
181
129
  dropped.
182
130
 
183
- [travis]: http://travis-ci.org/httprb/http
184
-
185
131
 
186
132
  ## Contributing to http.rb
187
133
 
188
- * Fork http.rb on GitHub
189
- * Make your changes
190
- * Ensure all tests pass (`bundle exec rake`)
191
- * Send a pull request
192
- * If we like them we'll merge them
193
- * If we've accepted a patch, feel free to ask for commit access!
134
+ - Fork http.rb on GitHub
135
+ - Make your changes
136
+ - Ensure all tests pass (`bundle exec rake`)
137
+ - Send a pull request
138
+ - If we like them we'll merge them
139
+ - If we've accepted a patch, feel free to ask for commit access!
194
140
 
195
141
 
196
142
  ## Copyright
197
143
 
198
- Copyright (c) 2011-2021 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
144
+ Copyright © 2011-2021 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
199
145
  See LICENSE.txt for further details.
146
+
147
+
148
+ [//]: # (badges)
149
+
150
+ [gem-image]: https://img.shields.io/gem/v/http?logo=ruby
151
+ [gem-link]: https://rubygems.org/gems/http
152
+ [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
153
+ [license-link]: https://github.com/httprb/http/blob/main/LICENSE.txt
154
+ [build-image]: https://github.com/httprb/http/workflows/CI/badge.svg
155
+ [build-link]: https://github.com/httprb/http/actions/workflows/ci.yml
156
+ [codeclimate-image]: https://codeclimate.com/github/httprb/http.svg?branch=main
157
+ [codeclimate-link]: https://codeclimate.com/github/httprb/http
158
+
159
+ [//]: # (links)
160
+
161
+ [documentation]: https://github.com/httprb/http/wiki
162
+ [requests]: http://docs.python-requests.org/en/latest/
163
+ [llhttp]: https://llhttp.org/
data/http.gemspec CHANGED
@@ -27,10 +27,10 @@ Gem::Specification.new do |gem|
27
27
 
28
28
  gem.required_ruby_version = ">= 2.5"
29
29
 
30
- gem.add_runtime_dependency "addressable", "~> 2.3"
30
+ gem.add_runtime_dependency "addressable", "~> 2.8"
31
31
  gem.add_runtime_dependency "http-cookie", "~> 1.0"
32
32
  gem.add_runtime_dependency "http-form_data", "~> 2.2"
33
- gem.add_runtime_dependency "llhttp-ffi", "~> 0.0.1"
33
+ gem.add_runtime_dependency "llhttp-ffi", "~> 0.4.0"
34
34
 
35
35
  gem.add_development_dependency "bundler", "~> 2.0"
36
36
 
data/lib/http/client.rb CHANGED
@@ -32,7 +32,7 @@ module HTTP
32
32
  return res unless opts.follow
33
33
 
34
34
  Redirector.new(opts.follow).perform(req, res) do |request|
35
- perform(request, opts)
35
+ perform(wrap_request(request, opts), opts)
36
36
  end
37
37
  end
38
38
 
@@ -52,9 +52,7 @@ module HTTP
52
52
  :body => body
53
53
  )
54
54
 
55
- opts.features.inject(req) do |request, (_name, feature)|
56
- feature.wrap_request(request)
57
- end
55
+ wrap_request(req, opts)
58
56
  end
59
57
 
60
58
  # @!method persistent?
@@ -104,6 +102,12 @@ module HTTP
104
102
 
105
103
  private
106
104
 
105
+ def wrap_request(req, opts)
106
+ opts.features.inject(req) do |request, (_name, feature)|
107
+ feature.wrap_request(request)
108
+ end
109
+ end
110
+
107
111
  def build_response(req, options)
108
112
  Response.new(
109
113
  :status => @connection.status_code,
@@ -21,8 +21,6 @@ module HTTP
21
21
  :request => response.request
22
22
  }
23
23
 
24
- options[:uri] = response.uri if response.uri
25
-
26
24
  Response.new(options)
27
25
  end
28
26
 
@@ -47,7 +47,11 @@ module HTTP
47
47
  # Adds the headers to the header array for the given request body we are working
48
48
  # with
49
49
  def add_body_type_headers
50
- return if @headers[Headers::CONTENT_LENGTH] || chunked?
50
+ return if @headers[Headers::CONTENT_LENGTH] || chunked? || (
51
+ @body.source.nil? && %w[GET HEAD DELETE CONNECT].any? do |method|
52
+ @request_header[0].start_with?("#{method} ")
53
+ end
54
+ )
51
55
 
52
56
  @request_header << "#{Headers::CONTENT_LENGTH}: #{@body.size}"
53
57
  end
@@ -57,7 +61,7 @@ module HTTP
57
61
  def join_headers
58
62
  # join the headers array with crlfs, stick two on the end because
59
63
  # that ends the request header
60
- @request_header.join(CRLF) + CRLF * 2
64
+ @request_header.join(CRLF) + (CRLF * 2)
61
65
  end
62
66
 
63
67
  # Writes HTTP request data into the socket.
data/lib/http/request.rb CHANGED
@@ -104,12 +104,26 @@ module HTTP
104
104
  headers = self.headers.dup
105
105
  headers.delete(Headers::HOST)
106
106
 
107
+ new_body = body.source
108
+ if verb == :get
109
+ # request bodies should not always be resubmitted when following a redirect
110
+ # some servers will close the connection after receiving the request headers
111
+ # which may cause Errno::ECONNRESET: Connection reset by peer
112
+ # see https://github.com/httprb/http/issues/649
113
+ # new_body = Request::Body.new(nil)
114
+ new_body = nil
115
+ # the CONTENT_TYPE header causes problems if set on a get request w/ an empty body
116
+ # the server might assume that there should be content if it is set to multipart
117
+ # rack raises EmptyContentError if this happens
118
+ headers.delete(Headers::CONTENT_TYPE)
119
+ end
120
+
107
121
  self.class.new(
108
122
  :verb => verb,
109
123
  :uri => @uri.join(uri),
110
124
  :headers => headers,
111
125
  :proxy => proxy,
112
- :body => body.source,
126
+ :body => new_body,
113
127
  :version => version,
114
128
  :uri_normalizer => uri_normalizer
115
129
  )
@@ -27,7 +27,9 @@ module HTTP
27
27
  # (see HTTP::Client#readpartial)
28
28
  def readpartial(*args)
29
29
  stream!
30
- @stream.readpartial(*args)&.force_encoding(@encoding)
30
+ chunk = @stream.readpartial(*args)
31
+
32
+ String.new(chunk, :encoding => @encoding) if chunk
31
33
  end
32
34
 
33
35
  # Iterate over the body, allowing it to be enumerable
@@ -45,11 +47,11 @@ module HTTP
45
47
 
46
48
  begin
47
49
  @streaming = false
48
- @contents = String.new("").force_encoding(@encoding)
50
+ @contents = String.new("", :encoding => @encoding)
49
51
 
50
52
  while (chunk = @stream.readpartial)
51
- @contents << chunk.force_encoding(@encoding)
52
- chunk.clear # deallocate string
53
+ @contents << String.new(chunk, :encoding => @encoding)
54
+ chunk = nil # deallocate string
53
55
  end
54
56
  rescue
55
57
  @contents = nil
@@ -15,7 +15,7 @@ module HTTP
15
15
  end
16
16
 
17
17
  def reset
18
- @parser.finish
18
+ @parser.reset
19
19
  @handler.reset
20
20
  @header_finished = false
21
21
  @message_finished = false
data/lib/http/response.rb CHANGED
@@ -40,10 +40,11 @@ module HTTP
40
40
  # @option opts [HTTP::Connection] :connection
41
41
  # @option opts [String] :encoding Encoding to use when reading body
42
42
  # @option opts [String] :body
43
- # @option opts [HTTP::Request] request
43
+ # @option opts [HTTP::Request] request The request this is in response to.
44
+ # @option opts [String] :uri (DEPRECATED) used to populate a missing request
44
45
  def initialize(opts)
45
46
  @version = opts.fetch(:version)
46
- @request = opts.fetch(:request)
47
+ @request = init_request(opts)
47
48
  @status = HTTP::Response::Status.new(opts.fetch(:status))
48
49
  @headers = HTTP::Headers.coerce(opts[:headers] || {})
49
50
  @proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
@@ -137,8 +138,8 @@ module HTTP
137
138
  def_delegator :content_type, :charset
138
139
 
139
140
  def cookies
140
- @cookies ||= headers.each_with_object CookieJar.new do |(k, v), jar|
141
- jar.parse(v, uri) if k == Headers::SET_COOKIE
141
+ @cookies ||= headers.get(Headers::SET_COOKIE).each_with_object CookieJar.new do |v, jar|
142
+ jar.parse(v, uri)
142
143
  end
143
144
  end
144
145
 
@@ -156,13 +157,30 @@ module HTTP
156
157
  # @param type [#to_s] Parse as given MIME type.
157
158
  # @raise (see MimeType.[])
158
159
  # @return [Object]
159
- def parse(type)
160
- MimeType[type].decode to_s
160
+ def parse(type = nil)
161
+ MimeType[type || mime_type].decode to_s
161
162
  end
162
163
 
163
164
  # Inspect a response
164
165
  def inspect
165
166
  "#<#{self.class}/#{@version} #{code} #{reason} #{headers.to_h.inspect}>"
166
167
  end
168
+
169
+ private
170
+
171
+ # Initialize an HTTP::Request from options.
172
+ #
173
+ # @return [HTTP::Request]
174
+ def init_request(opts)
175
+ raise ArgumentError, ":uri is for backwards compatibilty and conflicts with :request" \
176
+ if opts[:request] && opts[:uri]
177
+
178
+ # For backwards compatibilty
179
+ if opts[:uri]
180
+ HTTP::Request.new(:uri => opts[:uri], :verb => :get)
181
+ else
182
+ opts.fetch(:request)
183
+ end
184
+ end
167
185
  end
168
186
  end
@@ -35,12 +35,10 @@ module HTTP
35
35
  begin
36
36
  @socket.connect_nonblock
37
37
  rescue IO::WaitReadable
38
- IO.select([@socket], nil, nil, @time_left)
39
- log_time
38
+ wait_readable_or_timeout
40
39
  retry
41
40
  rescue IO::WaitWritable
42
- IO.select(nil, [@socket], nil, @time_left)
43
- log_time
41
+ wait_writable_or_timeout
44
42
  retry
45
43
  end
46
44
  end
data/lib/http/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP
4
- VERSION = "5.0.0"
4
+ VERSION = "5.0.4"
5
5
  end
@@ -4,6 +4,7 @@
4
4
  require "support/http_handling_shared"
5
5
  require "support/dummy_server"
6
6
  require "support/ssl_helper"
7
+ require "logger"
7
8
 
8
9
  RSpec.describe HTTP::Client do
9
10
  run_server(:dummy) { DummyServer.new }
@@ -115,6 +116,39 @@ RSpec.describe HTTP::Client do
115
116
  end
116
117
  end
117
118
 
119
+ describe "following redirects with logging" do
120
+ let(:logger) do
121
+ logger = Logger.new(logdev)
122
+ logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) }
123
+ logger.level = Logger::INFO
124
+ logger
125
+ end
126
+
127
+ let(:logdev) { StringIO.new }
128
+
129
+ it "logs all requests" do
130
+ client = StubbedClient.new(:follow => true, :features => { :logging => { :logger => logger } }).stub(
131
+ "http://example.com/" => redirect_response("/1"),
132
+ "http://example.com/1" => redirect_response("/2"),
133
+ "http://example.com/2" => redirect_response("/3"),
134
+ "http://example.com/3" => simple_response("OK")
135
+ )
136
+
137
+ expect { client.get("http://example.com/") }.not_to raise_error
138
+
139
+ expect(logdev.string).to eq <<~OUTPUT
140
+ ** INFO **
141
+ > GET http://example.com/
142
+ ** INFO **
143
+ > GET http://example.com/1
144
+ ** INFO **
145
+ > GET http://example.com/2
146
+ ** INFO **
147
+ > GET http://example.com/3
148
+ OUTPUT
149
+ end
150
+ end
151
+
118
152
  describe "parsing params" do
119
153
  let(:client) { HTTP::Client.new }
120
154
  before { allow(client).to receive :perform }
@@ -74,7 +74,6 @@ RSpec.describe HTTP::Features::AutoInflate do
74
74
  :status => 200,
75
75
  :headers => {:content_encoding => "gzip"},
76
76
  :connection => connection,
77
- :uri => "https://example.com",
78
77
  :request => HTTP::Request.new(:verb => :get, :uri => "https://example.com")
79
78
  )
80
79
  end
@@ -46,7 +46,6 @@ RSpec.describe HTTP::Features::Instrumentation do
46
46
  let(:response) do
47
47
  HTTP::Response.new(
48
48
  :version => "1.1",
49
- :uri => "https://example.com",
50
49
  :status => 200,
51
50
  :headers => {:content_type => "application/json"},
52
51
  :body => '{"success": true}',
@@ -42,7 +42,6 @@ RSpec.describe HTTP::Features::Logging do
42
42
  let(:response) do
43
43
  HTTP::Response.new(
44
44
  :version => "1.1",
45
- :uri => "https://example.com",
46
45
  :status => 200,
47
46
  :headers => {:content_type => "application/json"},
48
47
  :body => '{"success": true}',
@@ -396,5 +396,49 @@ RSpec.describe HTTP::Redirector do
396
396
  end
397
397
  end
398
398
  end
399
+
400
+ describe "changing verbs during redirects" do
401
+ let(:options) { {:strict => false} }
402
+ let(:post_body) { HTTP::Request::Body.new("i might be way longer in real life") }
403
+ let(:cookie) { "dont eat my cookies" }
404
+
405
+ def a_dangerous_request(verb)
406
+ HTTP::Request.new(
407
+ :verb => verb, :uri => "http://example.com",
408
+ :body => post_body, :headers => {
409
+ "Content-Type" => "meme",
410
+ "Cookie" => cookie
411
+ }
412
+ )
413
+ end
414
+
415
+ def empty_body
416
+ HTTP::Request::Body.new(nil)
417
+ end
418
+
419
+ it "follows without body/content type if it has to change verb" do
420
+ req = a_dangerous_request(:post)
421
+ res = redirect_response 302, "http://example.com/1"
422
+
423
+ redirector.perform(req, res) do |prev_req, _|
424
+ expect(prev_req.body).to eq(empty_body)
425
+ expect(prev_req.headers["Cookie"]).to eq(cookie)
426
+ expect(prev_req.headers["Content-Type"]).to eq(nil)
427
+ simple_response 200
428
+ end
429
+ end
430
+
431
+ it "leaves body/content-type intact if it does not have to change verb" do
432
+ req = a_dangerous_request(:post)
433
+ res = redirect_response 307, "http://example.com/1"
434
+
435
+ redirector.perform(req, res) do |prev_req, _|
436
+ expect(prev_req.body).to eq(post_body)
437
+ expect(prev_req.headers["Cookie"]).to eq(cookie)
438
+ expect(prev_req.headers["Content-Type"]).to eq("meme")
439
+ simple_response 200
440
+ end
441
+ end
442
+ end
399
443
  end
400
444
  end
@@ -118,10 +118,10 @@ RSpec.describe HTTP::Request::Body do
118
118
  end
119
119
 
120
120
  context "when body is a non-Enumerable IO" do
121
- let(:body) { FakeIO.new("a" * 16 * 1024 + "b" * 10 * 1024) }
121
+ let(:body) { FakeIO.new(("a" * 16 * 1024) + ("b" * 10 * 1024)) }
122
122
 
123
123
  it "yields chunks of content" do
124
- expect(chunks.inject("", :+)).to eq "a" * 16 * 1024 + "b" * 10 * 1024
124
+ expect(chunks.inject("", :+)).to eq ("a" * 16 * 1024) + ("b" * 10 * 1024)
125
125
  end
126
126
  end
127
127
 
@@ -148,7 +148,7 @@ RSpec.describe HTTP::Request::Body do
148
148
  end
149
149
 
150
150
  context "when body is an Enumerable IO" do
151
- let(:data) { "a" * 16 * 1024 + "b" * 10 * 1024 }
151
+ let(:data) { ("a" * 16 * 1024) + ("b" * 10 * 1024) }
152
152
  let(:body) { StringIO.new data }
153
153
 
154
154
  it "yields chunks of content" do
@@ -47,9 +47,20 @@ RSpec.describe HTTP::Request::Writer do
47
47
  end
48
48
  end
49
49
 
50
- context "when body is empty" do
50
+ context "when body is not set" do
51
51
  let(:body) { HTTP::Request::Body.new(nil) }
52
52
 
53
+ it "doesn't write anything to the socket and doesn't set Content-Length" do
54
+ writer.stream
55
+ expect(io.string).to eq [
56
+ "#{headerstart}\r\n\r\n"
57
+ ].join
58
+ end
59
+ end
60
+
61
+ context "when body is empty" do
62
+ let(:body) { HTTP::Request::Body.new("") }
63
+
53
64
  it "doesn't write anything to the socket and sets Content-Length" do
54
65
  writer.stream
55
66
  expect(io.string).to eq [
@@ -2,7 +2,7 @@
2
2
 
3
3
  RSpec.describe HTTP::Response::Body do
4
4
  let(:connection) { double(:sequence_id => 0) }
5
- let(:chunks) { [String.new("Hello, "), String.new("World!")] }
5
+ let(:chunks) { ["Hello, ", "World!"] }
6
6
 
7
7
  before do
8
8
  allow(connection).to receive(:readpartial) { chunks.shift }
@@ -16,7 +16,7 @@ RSpec.describe HTTP::Response::Body do
16
16
  end
17
17
 
18
18
  context "when body empty" do
19
- let(:chunks) { [String.new("")] }
19
+ let(:chunks) { [""] }
20
20
 
21
21
  it "returns responds to empty? with true" do
22
22
  expect(subject).to be_empty
@@ -45,12 +45,12 @@ RSpec.describe HTTP::Response::Body do
45
45
  it "returns content in specified encoding" do
46
46
  body = described_class.new(connection)
47
47
  expect(connection).to receive(:readpartial).
48
- and_return(String.new("content").force_encoding(Encoding::UTF_8))
48
+ and_return(String.new("content", :encoding => Encoding::UTF_8))
49
49
  expect(body.readpartial.encoding).to eq Encoding::BINARY
50
50
 
51
51
  body = described_class.new(connection, :encoding => Encoding::UTF_8)
52
52
  expect(connection).to receive(:readpartial).
53
- and_return(String.new("content").force_encoding(Encoding::BINARY))
53
+ and_return(String.new("content", :encoding => Encoding::BINARY))
54
54
  expect(body.readpartial.encoding).to eq Encoding::UTF_8
55
55
  end
56
56
  end
@@ -59,7 +59,7 @@ RSpec.describe HTTP::Response::Body do
59
59
  let(:chunks) do
60
60
  body = Zlib::Deflate.deflate("Hi, HTTP here ☺")
61
61
  len = body.length
62
- [String.new(body[0, len / 2]), String.new(body[(len / 2)..-1])]
62
+ [body[0, len / 2], body[(len / 2)..-1]]
63
63
  end
64
64
  subject(:body) do
65
65
  inflater = HTTP::Response::Inflater.new(connection)
@@ -46,9 +46,9 @@ RSpec.describe HTTP::Response::Parser do
46
46
  context "when got 100 Continue response" do
47
47
  let :raw_response do
48
48
  "HTTP/1.1 100 Continue\r\n\r\n" \
49
- "HTTP/1.1 200 OK\r\n" \
50
- "Content-Length: 12\r\n\r\n" \
51
- "Hello World!"
49
+ "HTTP/1.1 200 OK\r\n" \
50
+ "Content-Length: 12\r\n\r\n" \
51
+ "Hello World!"
52
52
  end
53
53
 
54
54
  context "when response is feeded in one part" do
@@ -4,6 +4,7 @@ RSpec.describe HTTP::Response do
4
4
  let(:body) { "Hello world!" }
5
5
  let(:uri) { "http://example.com/" }
6
6
  let(:headers) { {} }
7
+ let(:request) { HTTP::Request.new(:verb => :get, :uri => uri) }
7
8
 
8
9
  subject(:response) do
9
10
  HTTP::Response.new(
@@ -11,8 +12,7 @@ RSpec.describe HTTP::Response do
11
12
  :version => "1.1",
12
13
  :headers => headers,
13
14
  :body => body,
14
- :uri => uri,
15
- :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
15
+ :request => request
16
16
  )
17
17
  end
18
18
 
@@ -87,19 +87,32 @@ RSpec.describe HTTP::Response do
87
87
  end
88
88
 
89
89
  describe "#parse" do
90
- let(:headers) { {"Content-Type" => "application/json"} }
90
+ let(:headers) { {"Content-Type" => content_type} }
91
91
  let(:body) { '{"foo":"bar"}' }
92
92
 
93
- it "fails if MIME type decoder is not found" do
94
- expect { response.parse "text/html" }.to raise_error(HTTP::Error)
93
+ context "with known content type" do
94
+ let(:content_type) { "application/json" }
95
+ it "returns parsed body" do
96
+ expect(response.parse).to eq "foo" => "bar"
97
+ end
95
98
  end
96
99
 
97
- it "uses decoder found by given MIME type" do
98
- expect(response.parse("application/json")).to eq("foo" => "bar")
100
+ context "with unknown content type" do
101
+ let(:content_type) { "application/deadbeef" }
102
+ it "raises HTTP::Error" do
103
+ expect { response.parse }.to raise_error HTTP::Error
104
+ end
99
105
  end
100
106
 
101
- it "uses decoder found by given MIME type alias" do
102
- expect(response.parse(:json)).to eq("foo" => "bar")
107
+ context "with explicitly given mime type" do
108
+ let(:content_type) { "application/deadbeef" }
109
+ it "ignores mime_type of response" do
110
+ expect(response.parse("application/json")).to eq "foo" => "bar"
111
+ end
112
+
113
+ it "supports mime type aliases" do
114
+ expect(response.parse(:json)).to eq "foo" => "bar"
115
+ end
103
116
  end
104
117
  end
105
118
 
@@ -154,7 +167,7 @@ RSpec.describe HTTP::Response do
154
167
  :version => "1.1",
155
168
  :status => 200,
156
169
  :connection => connection,
157
- :request => HTTP::Request.new(:verb => :get, :uri => "http://example.com")
170
+ :request => request
158
171
  )
159
172
  end
160
173
 
@@ -171,4 +184,43 @@ RSpec.describe HTTP::Response do
171
184
  end
172
185
  it { is_expected.not_to be_chunked }
173
186
  end
187
+
188
+ describe "backwards compatibilty with :uri" do
189
+ context "with no :verb" do
190
+ subject(:response) do
191
+ HTTP::Response.new(
192
+ :status => 200,
193
+ :version => "1.1",
194
+ :headers => headers,
195
+ :body => body,
196
+ :uri => uri
197
+ )
198
+ end
199
+
200
+ it "defaults the uri to :uri" do
201
+ expect(response.request.uri.to_s).to eq uri
202
+ end
203
+
204
+ it "defaults to the verb to :get" do
205
+ expect(response.request.verb).to eq :get
206
+ end
207
+ end
208
+
209
+ context "with both a :request and :uri" do
210
+ subject(:response) do
211
+ HTTP::Response.new(
212
+ :status => 200,
213
+ :version => "1.1",
214
+ :headers => headers,
215
+ :body => body,
216
+ :uri => uri,
217
+ :request => request
218
+ )
219
+ end
220
+
221
+ it "raises ArgumentError" do
222
+ expect { response }.to raise_error(ArgumentError)
223
+ end
224
+ end
225
+ end
174
226
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2021-05-13 00:00:00.000000000 Z
14
+ date: 2021-10-07 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: addressable
@@ -19,14 +19,14 @@ dependencies:
19
19
  requirements:
20
20
  - - "~>"
21
21
  - !ruby/object:Gem::Version
22
- version: '2.3'
22
+ version: '2.8'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '2.3'
29
+ version: '2.8'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: http-cookie
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -61,14 +61,14 @@ dependencies:
61
61
  requirements:
62
62
  - - "~>"
63
63
  - !ruby/object:Gem::Version
64
- version: 0.0.1
64
+ version: 0.4.0
65
65
  type: :runtime
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
68
68
  requirements:
69
69
  - - "~>"
70
70
  - !ruby/object:Gem::Version
71
- version: 0.0.1
71
+ version: 0.4.0
72
72
  - !ruby/object:Gem::Dependency
73
73
  name: bundler
74
74
  requirement: !ruby/object:Gem::Requirement
@@ -191,7 +191,7 @@ metadata:
191
191
  source_code_uri: https://github.com/httprb/http
192
192
  wiki_uri: https://github.com/httprb/http/wiki
193
193
  bug_tracker_uri: https://github.com/httprb/http/issues
194
- changelog_uri: https://github.com/httprb/http/blob/v5.0.0/CHANGES.md
194
+ changelog_uri: https://github.com/httprb/http/blob/v5.0.4/CHANGES.md
195
195
  post_install_message:
196
196
  rdoc_options: []
197
197
  require_paths: