diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f41e027..18335d1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,13 +6,17 @@ on: pull_request: branches: [ master ] +env: + BUNDLE_WITHOUT: "development" + JRUBY_OPTS: "--dev --debug" + jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: - ruby: [ ruby-2.4, ruby-2.5, ruby-2.6, ruby-2.7, jruby-9.2.11 ] + ruby: [ ruby-2.4, ruby-2.5, ruby-2.6, ruby-2.7, ruby-3.0, jruby-9.2 ] os: [ ubuntu-latest ] steps: @@ -21,22 +25,9 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - - - uses: actions/cache@v1 - with: - path: vendor/bundle - key: bundle-use-ruby-${{ matrix.os }}-${{ matrix.ruby }}-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: bundle-use-ruby-${{ matrix.os }}-${{ matrix.ruby }}- - - - name: bundle install - run: | - bundle config set path "vendor/bundle" - bundle config set without "development" - bundle install --jobs 4 + bundler-cache: true - name: bundle exec rspec - env: - JRUBY_OPTS: --debug run: bundle exec rspec --format progress --force-colour - name: Prepare Coveralls test coverage report @@ -66,18 +57,9 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: 2.4 + bundler-cache: true - - uses: actions/cache@v1 - with: - path: vendor/bundle - key: bundle-use-ruby-lint-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: bundle-use-ruby-lint- - - - name: bundle install - run: | - bundle config set path "vendor/bundle" - bundle config set without "development" - bundle install --jobs 4 + - name: bundle exec rubocop + run: bundle exec rubocop --format progress --color - - run: bundle exec rubocop --color - run: bundle exec rake verify_measurements diff --git a/.rubocop.yml b/.rubocop.yml index a046bbc3..2dadbaed 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,127 +1,10 @@ -require: rubocop-performance +inherit_from: + - .rubocop_todo.yml + - .rubocop/layout.yml + - .rubocop/style.yml AllCops: - TargetRubyVersion: 2.3 + DefaultFormatter: fuubar DisplayCopNames: true - -## Layout ###################################################################### - -Layout/AlignHash: - EnforcedColonStyle: table - EnforcedHashRocketStyle: table - -Layout/DotPosition: - EnforcedStyle: trailing - -Layout/SpaceAroundOperators: - AllowForAlignment: true - -Layout/SpaceInsideHashLiteralBraces: - EnforcedStyle: no_space - -## Metrics ##################################################################### - -Metrics/AbcSize: - Enabled: false - -Metrics/BlockLength: - Exclude: - - "spec/**/*" - - "**/*.gemspec" - -Metrics/BlockNesting: - Max: 2 - -Metrics/ClassLength: - CountComments: false - Max: 125 - -# TODO: Lower to 6 -Metrics/CyclomaticComplexity: - Max: 8 - -Metrics/PerceivedComplexity: - Max: 8 - -# TODO: Lower to 80 -Metrics/LineLength: - AllowURI: true - Max: 143 - -# TODO: Lower to 15 -Metrics/MethodLength: - CountComments: false - Max: 25 - -Metrics/ModuleLength: - CountComments: false - Max: 120 - -Metrics/ParameterLists: - Max: 5 - CountKeywordArgs: true - -## Performance ################################################################# - -# XXX: requires ruby 2.4+ -Performance/RegexpMatch: - Enabled: false - -Performance/UnfreezeString: - Enabled: false - -## Style ####################################################################### - -Style/CollectionMethods: - PreferredMethods: - collect: 'map' - reduce: 'inject' - find: 'detect' - find_all: 'select' - -Style/Documentation: - Enabled: false - -Style/DoubleNegation: - Enabled: false - -Style/EachWithObject: - Enabled: false - -Style/Encoding: - Enabled: false - -Style/EmptyCaseCondition: - Enabled: false - -# XXX: Lots of times it suggests making code terrible to read. -Style/GuardClause: - Enabled: false - -Style/HashSyntax: - EnforcedStyle: hash_rockets - -Style/Lambda: - Enabled: false - -Style/OptionHash: - Enabled: true - -# XXX: requires ruby 2.3+ -Style/SafeNavigation: - Enabled: false - -Style/StringLiterals: - EnforcedStyle: double_quotes - -Style/TrivialAccessors: - Enabled: false - -Style/YodaCondition: - Enabled: false - -Style/FormatStringToken: - EnforcedStyle: unannotated - -Style/RescueStandardError: - EnforcedStyle: implicit + NewCops: enable + TargetRubyVersion: 2.4 diff --git a/.rubocop/layout.yml b/.rubocop/layout.yml new file mode 100644 index 00000000..f64f37a5 --- /dev/null +++ b/.rubocop/layout.yml @@ -0,0 +1,8 @@ +Layout/DotPosition: + Enabled: true + EnforcedStyle: leading + +Layout/HashAlignment: + Enabled: true + EnforcedColonStyle: table + EnforcedHashRocketStyle: table diff --git a/.rubocop/style.yml b/.rubocop/style.yml new file mode 100644 index 00000000..a3901789 --- /dev/null +++ b/.rubocop/style.yml @@ -0,0 +1,32 @@ +Style/Documentation: + Enabled: false + +Style/DocumentDynamicEvalDefinition: + Enabled: true + Exclude: + - 'spec/**/*.rb' + +Style/FormatStringToken: + Enabled: true + EnforcedStyle: unannotated + +Style/HashSyntax: + Enabled: true + EnforcedStyle: hash_rockets + +Style/OptionHash: + Enabled: true + +Style/RescueStandardError: + Enabled: true + EnforcedStyle: implicit + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +Style/WordArray: + Enabled: true + +Style/YodaCondition: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000..29a202f0 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,193 @@ +# This configuration was generated by +# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 42` +# on 2020-12-29 16:53:05 UTC using RuboCop version 1.7.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 51 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: leading, trailing +Layout/DotPosition: + Exclude: + - 'lib/http/options.rb' + - 'spec/lib/http/client_spec.rb' + - 'spec/lib/http/features/auto_deflate_spec.rb' + - 'spec/lib/http/headers_spec.rb' + - 'spec/lib/http/options/features_spec.rb' + - 'spec/lib/http/redirector_spec.rb' + - 'spec/lib/http/response/body_spec.rb' + - 'spec/lib/http_spec.rb' + - 'spec/support/http_handling_shared.rb' + +# Offense count: 174 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. +# SupportedStyles: space, no_space, compact +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideHashLiteralBraces: + Exclude: + - 'lib/http/chainable.rb' + - 'spec/lib/http/client_spec.rb' + - 'spec/lib/http/features/auto_inflate_spec.rb' + - 'spec/lib/http/features/instrumentation_spec.rb' + - 'spec/lib/http/features/logging_spec.rb' + - 'spec/lib/http/headers_spec.rb' + - 'spec/lib/http/options/features_spec.rb' + - 'spec/lib/http/options/merge_spec.rb' + - 'spec/lib/http/options/new_spec.rb' + - 'spec/lib/http/redirector_spec.rb' + - 'spec/lib/http/request_spec.rb' + - 'spec/lib/http/response_spec.rb' + - 'spec/lib/http_spec.rb' + - 'spec/support/dummy_server/servlet.rb' + - 'spec/support/http_handling_shared.rb' + - 'spec/support/ssl_helper.rb' + +# Offense count: 4 +Lint/MissingSuper: + Exclude: + - 'lib/http/features/auto_deflate.rb' + - 'lib/http/features/instrumentation.rb' + - 'lib/http/features/logging.rb' + - 'lib/http/features/normalize_uri.rb' + +# Offense count: 8 +# Configuration parameters: IgnoredMethods, CountRepeatedAttributes, Max. +Metrics/AbcSize: + Exclude: + - 'lib/http/chainable.rb' + - 'lib/http/client.rb' + - 'lib/http/connection.rb' + - 'lib/http/features/auto_deflate.rb' + - 'lib/http/redirector.rb' + - 'lib/http/request.rb' + - 'lib/http/response.rb' + +# Offense count: 66 +# Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods. +# IgnoredMethods: refine +Metrics/BlockLength: + Exclude: + - '**/*.gemspec' + - 'spec/lib/http/client_spec.rb' + - 'spec/lib/http/connection_spec.rb' + - 'spec/lib/http/content_type_spec.rb' + - 'spec/lib/http/features/auto_deflate_spec.rb' + - 'spec/lib/http/features/auto_inflate_spec.rb' + - 'spec/lib/http/features/instrumentation_spec.rb' + - 'spec/lib/http/features/logging_spec.rb' + - 'spec/lib/http/headers/mixin_spec.rb' + - 'spec/lib/http/headers_spec.rb' + - 'spec/lib/http/options/merge_spec.rb' + - 'spec/lib/http/redirector_spec.rb' + - 'spec/lib/http/request/body_spec.rb' + - 'spec/lib/http/request/writer_spec.rb' + - 'spec/lib/http/request_spec.rb' + - 'spec/lib/http/response/body_spec.rb' + - 'spec/lib/http/response/parser_spec.rb' + - 'spec/lib/http/response/status_spec.rb' + - 'spec/lib/http/response_spec.rb' + - 'spec/lib/http_spec.rb' + - 'spec/support/http_handling_shared.rb' + +# Offense count: 4 +# Configuration parameters: CountComments, Max, CountAsOne. +Metrics/ClassLength: + Exclude: + - 'lib/http/client.rb' + - 'lib/http/connection.rb' + - 'lib/http/headers.rb' + - 'lib/http/request.rb' + +# Offense count: 2 +# Configuration parameters: IgnoredMethods, Max. +Metrics/CyclomaticComplexity: + Exclude: + - 'lib/http/chainable.rb' + - 'lib/http/client.rb' + +# Offense count: 19 +# Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods. +Metrics/MethodLength: + Exclude: + - 'lib/http/chainable.rb' + - 'lib/http/client.rb' + - 'lib/http/connection.rb' + - 'lib/http/features/auto_deflate.rb' + - 'lib/http/features/auto_inflate.rb' + - 'lib/http/headers.rb' + - 'lib/http/options.rb' + - 'lib/http/redirector.rb' + - 'lib/http/request.rb' + - 'lib/http/response.rb' + - 'lib/http/response/body.rb' + - 'lib/http/timeout/global.rb' + +# Offense count: 1 +# Configuration parameters: CountComments, Max, CountAsOne. +Metrics/ModuleLength: + Exclude: + - 'lib/http/chainable.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: separated, grouped +Style/AccessorGrouping: + Exclude: + - 'lib/http/request.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +Style/EmptyCaseCondition: + Exclude: + - 'lib/http/client.rb' + - 'lib/http/headers.rb' + - 'lib/http/options.rb' + - 'lib/http/response/status.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +Style/Encoding: + Exclude: + - 'spec/lib/http/client_spec.rb' + - 'spec/lib/http/request/writer_spec.rb' + - 'spec/lib/http/request_spec.rb' + - 'spec/lib/http_spec.rb' + - 'spec/support/dummy_server/servlet.rb' + +# Offense count: 17 +# Configuration parameters: SuspiciousParamNames. +# SuspiciousParamNames: options, opts, args, params, parameters +Style/OptionHash: + Exclude: + - 'lib/http/chainable.rb' + - 'lib/http/client.rb' + - 'lib/http/feature.rb' + - 'lib/http/options.rb' + - 'lib/http/redirector.rb' + - 'lib/http/timeout/null.rb' + - 'spec/support/dummy_server.rb' + +# Offense count: 4 +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'lib/http/timeout/global.rb' + - 'lib/http/timeout/null.rb' + - 'lib/http/timeout/per_operation.rb' + - 'lib/http/uri.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Layout/LineLength: + Exclude: + - 'lib/http/chainable.rb' + - 'lib/http/connection.rb' + - 'spec/lib/http/options/proxy_spec.rb' diff --git a/Gemfile b/Gemfile index 3abd333b..747fdf9e 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,10 @@ ruby RUBY_VERSION gem "rake" +# Ruby 3.0 does not ship it anymore. +# TODO: We should probably refactor specs to avoid need for it. +gem "webrick" + group :development do gem "guard-rspec", :require => false gem "nokogiri", :require => false @@ -23,15 +27,17 @@ group :test do gem "backports" + gem "rubocop" + gem "rubocop-performance" + gem "rubocop-rake" + gem "rubocop-rspec" + gem "simplecov", :require => false gem "simplecov-lcov", :require => false gem "rspec", "~> 3.10" gem "rspec-its" - gem "rubocop", "= 0.68.1" - gem "rubocop-performance" - gem "yardstick" end diff --git a/README.md b/README.md index 99cd4fb2..0b504217 100644 --- a/README.md +++ b/README.md @@ -165,11 +165,12 @@ and call `#readpartial` on it repeatedly until it returns `nil`: This library aims to support and is [tested against][travis] the following Ruby versions: -* Ruby 2.4.x -* Ruby 2.5.x -* Ruby 2.6.x -* Ruby 2.7.x -* JRuby 9.2.x.x +* Ruby 2.4 +* Ruby 2.5 +* Ruby 2.6 +* Ruby 2.7 +* Ruby 3.0 +* JRuby 9.2 If something doesn't work on one of these versions, it's a bug. diff --git a/Rakefile b/Rakefile index ac1b2359..ca32a40d 100644 --- a/Rakefile +++ b/Rakefile @@ -35,7 +35,7 @@ task :generate_status_codes do end File.open("./lib/http/response/status/reasons.rb", "w") do |io| - io.puts <<-TPL.gsub(/^[ ]{6}/, "") + io.puts <<~TPL # AUTO-GENERATED FILE, DO NOT CHANGE IT MANUALLY require "delegate" diff --git a/http.gemspec b/http.gemspec index bfef1e8b..bd245ae1 100644 --- a/http.gemspec +++ b/http.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |gem| gem.require_paths = ["lib"] gem.version = HTTP::VERSION - gem.required_ruby_version = ">= 2.3" + gem.required_ruby_version = ">= 2.4" gem.add_runtime_dependency "addressable", "~> 2.3" gem.add_runtime_dependency "http-cookie", "~> 1.0" diff --git a/lib/http/chainable.rb b/lib/http/chainable.rb index 70a32bd4..de6affde 100644 --- a/lib/http/chainable.rb +++ b/lib/http/chainable.rb @@ -9,63 +9,63 @@ module Chainable # Request a get sans response body # @param uri # @option options [Hash] - def head(uri, options = {}) # rubocop:disable Style/OptionHash + def head(uri, options = {}) request :head, uri, options end # Get a resource # @param uri # @option options [Hash] - def get(uri, options = {}) # rubocop:disable Style/OptionHash + def get(uri, options = {}) request :get, uri, options end # Post to a resource # @param uri # @option options [Hash] - def post(uri, options = {}) # rubocop:disable Style/OptionHash + def post(uri, options = {}) request :post, uri, options end # Put to a resource # @param uri # @option options [Hash] - def put(uri, options = {}) # rubocop:disable Style/OptionHash + def put(uri, options = {}) request :put, uri, options end # Delete a resource # @param uri # @option options [Hash] - def delete(uri, options = {}) # rubocop:disable Style/OptionHash + def delete(uri, options = {}) request :delete, uri, options end # Echo the request back to the client # @param uri # @option options [Hash] - def trace(uri, options = {}) # rubocop:disable Style/OptionHash + def trace(uri, options = {}) request :trace, uri, options end # Return the methods supported on the given URI # @param uri # @option options [Hash] - def options(uri, options = {}) # rubocop:disable Style/OptionHash + def options(uri, options = {}) request :options, uri, options end # Convert to a transparent TCP/IP tunnel # @param uri # @option options [Hash] - def connect(uri, options = {}) # rubocop:disable Style/OptionHash + def connect(uri, options = {}) request :connect, uri, options end # Apply partial modifications to a resource # @param uri # @option options [Hash] - def patch(uri, options = {}) # rubocop:disable Style/OptionHash + def patch(uri, options = {}) request :patch, uri, options end @@ -148,7 +148,7 @@ def persistent(host, timeout: 5) yield p_client ensure - p_client.close if p_client + p_client&.close end # Make a request through an HTTP proxy @@ -173,7 +173,7 @@ def via(*proxy) # @param options # @return [HTTP::Client] # @see Redirector#initialize - def follow(options = {}) # rubocop:disable Style/OptionHash + def follow(options = {}) branch default_options.with_follow options end @@ -211,10 +211,11 @@ def auth(value) # @option opts [#to_s] :user # @option opts [#to_s] :pass def basic_auth(opts) - user = opts.fetch :user - pass = opts.fetch :pass + user = opts.fetch(:user) + pass = opts.fetch(:pass) + creds = "#{user}:#{pass}" - auth("Basic " + Base64.strict_encode64("#{user}:#{pass}")) + auth("Basic #{Base64.strict_encode64(creds)}") end # Get options for HTTP diff --git a/lib/http/client.rb b/lib/http/client.rb index 57319bae..dfa05b16 100644 --- a/lib/http/client.rb +++ b/lib/http/client.rb @@ -25,7 +25,7 @@ def initialize(default_options = {}) end # Make an HTTP request - def request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash + def request(verb, uri, opts = {}) opts = @default_options.merge(opts) req = build_request(verb, uri, opts) res = perform(req, opts) @@ -37,7 +37,7 @@ def request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash end # Prepare an HTTP request - def build_request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash + def build_request(verb, uri, opts = {}) opts = @default_options.merge(opts) uri = make_request_uri(uri, opts) headers = make_request_headers(opts) @@ -97,7 +97,7 @@ def perform(req, options) end def close - @connection.close if @connection + @connection&.close @connection = nil @state = :clean end @@ -120,15 +120,15 @@ def build_response(req, options) def verify_connection!(uri) if default_options.persistent? && uri.origin != default_options.persistent raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}" + end + # We re-create the connection object because we want to let prior requests # lazily load the body as long as possible, and this mimics prior functionality. - elsif @connection && (!@connection.keep_alive? || @connection.expired?) - close + return close if @connection && (!@connection.keep_alive? || @connection.expired?) + # If we get into a bad state (eg, Timeout.timeout ensure being killed) # close the connection to prevent potential for mixed responses. - elsif @state == :dirty - close - end + return close if @state == :dirty end # Merges query params if needed diff --git a/lib/http/connection.rb b/lib/http/connection.rb index 3e53c80f..2f48e317 100644 --- a/lib/http/connection.rb +++ b/lib/http/connection.rb @@ -68,8 +68,13 @@ def failed_proxy_connect? # @param [Request] req Request to send to the server # @return [nil] def send_request(req) - raise StateError, "Tried to send a request while one is pending already. Make sure you read off the body." if @pending_response - raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body." if @pending_request + if @pending_response + raise StateError, "Tried to send a request while one is pending already. Make sure you read off the body." + end + + if @pending_request + raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body." + end @pending_request = true diff --git a/lib/http/content_type.rb b/lib/http/content_type.rb index 3ae3d19c..aa40d3a3 100644 --- a/lib/http/content_type.rb +++ b/lib/http/content_type.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true module HTTP - ContentType = Struct.new(:mime_type, :charset) do + class ContentType MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze CHARSET_RE = /;\s*charset=([^;]+)/i.freeze + attr_accessor :mime_type, :charset + class << self # Parse string and return ContentType struct def parse(str) @@ -15,15 +17,18 @@ def parse(str) # :nodoc: def mime_type(str) - m = str.to_s[MIME_TYPE_RE, 1] - m && m.strip.downcase + str.to_s[MIME_TYPE_RE, 1]&.strip&.downcase end # :nodoc: def charset(str) - m = str.to_s[CHARSET_RE, 1] - m && m.strip.delete('"') + str.to_s[CHARSET_RE, 1]&.strip&.delete('"') end end + + def initialize(mime_type = nil, charset = nil) + @mime_type = mime_type + @charset = charset + end end end diff --git a/lib/http/feature.rb b/lib/http/feature.rb index adb60339..02e23187 100644 --- a/lib/http/feature.rb +++ b/lib/http/feature.rb @@ -2,7 +2,7 @@ module HTTP class Feature - def initialize(opts = {}) # rubocop:disable Style/OptionHash + def initialize(opts = {}) @opts = opts end diff --git a/lib/http/features/instrumentation.rb b/lib/http/features/instrumentation.rb index 7a67d486..2ba567a4 100644 --- a/lib/http/features/instrumentation.rb +++ b/lib/http/features/instrumentation.rb @@ -29,7 +29,7 @@ def initialize(instrumenter: NullInstrumenter.new, namespace: "http") def wrap_request(request) # Emit a separate "start" event, so a logger can print the request # being run without waiting for a response - instrumenter.instrument("start_#{name}", :request => request) {} + instrumenter.instrument("start_#{name}", :request => request) instrumenter.start(name, :request => request) request end diff --git a/lib/http/features/logging.rb b/lib/http/features/logging.rb index cef2f8dc..d65241c1 100644 --- a/lib/http/features/logging.rb +++ b/lib/http/features/logging.rb @@ -9,6 +9,20 @@ module Features # HTTP.use(logging: {logger: Logger.new(STDOUT)}).get("https://example.com/") # class Logging < Feature + HTTP::Options.register_feature(:logging, self) + + class NullLogger + %w[fatal error warn info debug].each do |level| + define_method(level.to_sym) do |*_args| + nil + end + + define_method(:"#{level}?") do + true + end + end + end + attr_reader :logger def initialize(logger: NullLogger.new) @@ -17,39 +31,23 @@ def initialize(logger: NullLogger.new) def wrap_request(request) logger.info { "> #{request.verb.to_s.upcase} #{request.uri}" } - logger.debug do - headers = request.headers.map { |name, value| "#{name}: #{value}" }.join("\n") - body = request.body.source + logger.debug { "#{stringify_headers(request.headers)}\n\n#{request.body.source}" } - headers + "\n\n" + body.to_s - end request end def wrap_response(response) logger.info { "< #{response.status}" } - logger.debug do - headers = response.headers.map { |name, value| "#{name}: #{value}" }.join("\n") - body = response.body.to_s + logger.debug { "#{stringify_headers(response.headers)}\n\n#{response.body}" } - headers + "\n\n" + body - end response end - class NullLogger - %w[fatal error warn info debug].each do |level| - define_method(level.to_sym) do |*_args| - nil - end + private - define_method(:"#{level}?") do - true - end - end + def stringify_headers(headers) + headers.map { |name, value| "#{name}: #{value}" }.join("\n") end - - HTTP::Options.register_feature(:logging, self) end end end diff --git a/lib/http/headers.rb b/lib/http/headers.rb index 4fb05079..aad91ea3 100644 --- a/lib/http/headers.rb +++ b/lib/http/headers.rb @@ -17,7 +17,7 @@ class Headers # Matches valid header field name according to RFC. # @see http://tools.ietf.org/html/rfc7230#section-3.2 - COMPLIANT_NAME_RE = /\A[A-Za-z0-9!#\$%&'*+\-.^_`|~]+\z/.freeze + COMPLIANT_NAME_RE = /\A[A-Za-z0-9!#$%&'*+\-.^_`|~]+\z/.freeze # Class constructor. def initialize diff --git a/lib/http/mime_type/adapter.rb b/lib/http/mime_type/adapter.rb index 82efb451..00dd8186 100644 --- a/lib/http/mime_type/adapter.rb +++ b/lib/http/mime_type/adapter.rb @@ -14,6 +14,7 @@ class << self def_delegators :instance, :encode, :decode end + # rubocop:disable Style/DocumentDynamicEvalDefinition %w[encode decode].each do |operation| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{operation}(*) @@ -21,6 +22,7 @@ def #{operation}(*) end RUBY end + # rubocop:enable Style/DocumentDynamicEvalDefinition end end end diff --git a/lib/http/options.rb b/lib/http/options.rb index 65f40aa2..753057ab 100644 --- a/lib/http/options.rb +++ b/lib/http/options.rb @@ -32,7 +32,7 @@ def register_feature(name, impl) def def_option(name, reader_only: false, &interpreter) defined_options << name.to_sym - interpreter ||= lambda { |v| v } + interpreter ||= ->(v) { v } if reader_only attr_reader name @@ -47,7 +47,7 @@ def def_option(name, reader_only: false, &interpreter) end end - def initialize(options = {}) # rubocop:disable Style/OptionHash + def initialize(options = {}) defaults = { :response => :auto, :proxy => {}, diff --git a/lib/http/redirector.rb b/lib/http/redirector.rb index 34ced658..5d2de095 100644 --- a/lib/http/redirector.rb +++ b/lib/http/redirector.rb @@ -39,7 +39,7 @@ class EndlessRedirectError < TooManyRedirectsError; end # @param [Hash] opts # @option opts [Boolean] :strict (true) redirector hops policy # @option opts [#to_i] :max_hops (5) maximum allowed amount of hops - def initialize(opts = {}) # rubocop:disable Style/OptionHash + def initialize(opts = {}) @strict = opts.fetch(:strict, true) @max_hops = opts.fetch(:max_hops, 5).to_i end diff --git a/lib/http/request.rb b/lib/http/request.rb index 4f6286b9..8e99e3ee 100644 --- a/lib/http/request.rb +++ b/lib/http/request.rb @@ -213,7 +213,7 @@ def port # @return [String] Default host (with port if needed) header value. def default_host_header_value - PORTS[@scheme] != port ? "#{host}:#{port}" : host + PORTS[@scheme] == port ? host : "#{host}:#{port}" end def prepare_body(body) @@ -223,8 +223,8 @@ def prepare_body(body) def prepare_headers(headers) headers = HTTP::Headers.coerce(headers || {}) - headers[Headers::HOST] ||= default_host_header_value - headers[Headers::USER_AGENT] ||= USER_AGENT + headers[Headers::HOST] ||= default_host_header_value + headers[Headers::USER_AGENT] ||= USER_AGENT headers end diff --git a/lib/http/response/body.rb b/lib/http/response/body.rb index 0997a49f..a1030043 100644 --- a/lib/http/response/body.rb +++ b/lib/http/response/body.rb @@ -27,8 +27,7 @@ def initialize(stream, encoding: Encoding::BINARY) # (see HTTP::Client#readpartial) def readpartial(*args) stream! - chunk = @stream.readpartial(*args) - chunk.force_encoding(@encoding) if chunk + @stream.readpartial(*args)&.force_encoding(@encoding) end # Iterate over the body, allowing it to be enumerable diff --git a/lib/http/response/status.rb b/lib/http/response/status.rb index c5c2e525..2682d512 100644 --- a/lib/http/response/status.rb +++ b/lib/http/response/status.rb @@ -58,7 +58,7 @@ def symbolize(str) # SYMBOLS[418] # => :im_a_teapot # # @return [Hash Symbol>] - SYMBOLS = Hash[REASONS.map { |k, v| [k, symbolize(v)] }].freeze + SYMBOLS = REASONS.transform_values { |v| symbolize(v) }.freeze # Reversed {SYMBOLS} map. # diff --git a/lib/http/timeout/null.rb b/lib/http/timeout/null.rb index 4628c4c7..a5ee19ca 100644 --- a/lib/http/timeout/null.rb +++ b/lib/http/timeout/null.rb @@ -12,7 +12,7 @@ class Null attr_reader :options, :socket - def initialize(options = {}) # rubocop:disable Style/OptionHash + def initialize(options = {}) @options = options end diff --git a/spec/lib/http/client_spec.rb b/spec/lib/http/client_spec.rb index 55ed0872..0aef24d0 100644 --- a/spec/lib/http/client_spec.rb +++ b/spec/lib/http/client_spec.rb @@ -204,7 +204,7 @@ def simple_response(body, status = 200) context "when passing an HTTP::FormData object directly" do it "creates url encoded form data object" do client = HTTP::Client.new - form_data = HTTP::FormData::Multipart.new(:foo => "bar") + form_data = HTTP::FormData::Multipart.new({ :foo => "bar" }) allow(client).to receive(:perform) @@ -318,7 +318,7 @@ def on_error(request, error) expect(response.code).to eq(200) expect(feature_instance.captured_request.verb).to eq(:get) - expect(feature_instance.captured_request.uri.to_s).to eq(dummy.endpoint + "/") + expect(feature_instance.captured_request.uri.to_s).to eq("#{dummy.endpoint}/") end it "is given a chance to wrap the Response" do diff --git a/spec/lib/http/features/instrumentation_spec.rb b/spec/lib/http/features/instrumentation_spec.rb index e44db43c..4e03d08c 100644 --- a/spec/lib/http/features/instrumentation_spec.rb +++ b/spec/lib/http/features/instrumentation_spec.rb @@ -2,8 +2,29 @@ RSpec.describe HTTP::Features::Instrumentation do subject(:feature) { HTTP::Features::Instrumentation.new(:instrumenter => instrumenter) } + let(:instrumenter) { TestInstrumenter.new } + before do + test_instrumenter = Class.new(HTTP::Features::Instrumentation::NullInstrumenter) do + attr_reader :output + + def initialize + @output = {} + end + + def start(_name, payload) + output[:start] = payload + end + + def finish(_name, payload) + output[:finish] = payload + end + end + + stub_const("TestInstrumenter", test_instrumenter) + end + describe "logging the request" do let(:request) do HTTP::Request.new( @@ -39,19 +60,4 @@ expect(instrumenter.output[:finish]).to eq(:response => response) end end - - class TestInstrumenter < HTTP::Features::Instrumentation::NullInstrumenter - attr_reader :output - def initialize - @output = {} - end - - def start(_name, payload) - output[:start] = payload - end - - def finish(_name, payload) - output[:finish] = payload - end - end end diff --git a/spec/lib/http/features/logging_spec.rb b/spec/lib/http/features/logging_spec.rb index b0f3cd7a..4b43adeb 100644 --- a/spec/lib/http/features/logging_spec.rb +++ b/spec/lib/http/features/logging_spec.rb @@ -4,10 +4,8 @@ RSpec.describe HTTP::Features::Logging do subject(:feature) do - logger = Logger.new(logdev) - logger.formatter = ->(severity, _, _, message) do - format("** %s **\n%s\n", severity, message) - end + logger = Logger.new(logdev) + logger.formatter = ->(severity, _, _, message) { format("** %s **\n%s\n", severity, message) } described_class.new(:logger => logger) end diff --git a/spec/lib/http/headers_spec.rb b/spec/lib/http/headers_spec.rb index 9dd60dcf..abf62269 100644 --- a/spec/lib/http/headers_spec.rb +++ b/spec/lib/http/headers_spec.rb @@ -321,17 +321,17 @@ it "yields header keys specified as symbols in normalized form" do keys = headers.each.map(&:first) - expect(keys).to eq(["Set-Cookie", "Content-Type", "Set-Cookie"]) + expect(keys).to eq(%w[Set-Cookie Content-Type Set-Cookie]) end it "yields headers specified as strings without conversion" do headers.add "X_kEy", "value" keys = headers.each.map(&:first) - expect(keys).to eq(["Set-Cookie", "Content-Type", "Set-Cookie", "X_kEy"]) + expect(keys).to eq(%w[Set-Cookie Content-Type Set-Cookie X_kEy]) end it "returns self instance if block given" do - expect(headers.each { |*| }).to be headers + expect(headers.each { |*| }).to be headers # rubocop:disable Lint/EmptyBlock end it "returns Enumerator if no block given" do diff --git a/spec/support/black_hole.rb b/spec/support/black_hole.rb index c5d61dd1..8c4c4281 100644 --- a/spec/support/black_hole.rb +++ b/spec/support/black_hole.rb @@ -2,7 +2,7 @@ module BlackHole class << self - def method_missing(*) # rubocop:disable Style/MethodMissingSuper + def method_missing(*) self end diff --git a/spec/support/dummy_server.rb b/spec/support/dummy_server.rb index baea6c0a..bcbde771 100644 --- a/spec/support/dummy_server.rb +++ b/spec/support/dummy_server.rb @@ -24,7 +24,7 @@ class DummyServer < WEBrick::HTTPServer :SSLStartImmediately => true ).freeze - def initialize(options = {}) # rubocop:disable Style/OptionHash + def initialize(options = {}) super(options[:ssl] ? SSL_CONFIG : CONFIG) mount("/", Servlet) end diff --git a/spec/support/dummy_server/servlet.rb b/spec/support/dummy_server/servlet.rb index 4d38da71..d24ef110 100644 --- a/spec/support/dummy_server/servlet.rb +++ b/spec/support/dummy_server/servlet.rb @@ -156,7 +156,7 @@ def do_#{method.upcase}(req, res) res.status = 200 res.body = case req["Accept-Encoding"] - when "gzip" then + when "gzip" res["Content-Encoding"] = "gzip" StringIO.open do |out| Zlib::GzipWriter.wrap(out) do |gz| @@ -165,7 +165,7 @@ def do_#{method.upcase}(req, res) out.tap(&:rewind).read end end - when "deflate" then + when "deflate" res["Content-Encoding"] = "deflate" Zlib::Deflate.deflate("#{req.body}-deflated") else @@ -178,9 +178,9 @@ def do_#{method.upcase}(req, res) res.body = "" case req["Accept-Encoding"] - when "gzip" then + when "gzip" res["Content-Encoding"] = "gzip" - when "deflate" then + when "deflate" res["Content-Encoding"] = "deflate" end end