http 5.0.0 → 5.0.4

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