From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Status: No, score=-3.2 required=3.0 tests=AWL,BAYES_00,RCVD_IN_DNSWL_HI, RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_PASS, T_SCC_BODY_TEXT_LINE shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 Received: from mail-ot1-f53.google.com (mail-ot1-f53.google.com [209.85.210.53]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by dcvr.yhbt.net (Postfix) with ESMTPS id 119BA1F405 for ; Tue, 23 Jan 2024 03:11:55 +0000 (UTC) Received: by mail-ot1-f53.google.com with SMTP id 46e09a7af769-6ddf05b1922so2914684a34.2 for ; Mon, 22 Jan 2024 19:11:55 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1705979513; x=1706584313; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=mJDI8xJW3eSGU7BOCXFTlNb7FjeKRmlhMar2OKPL1go=; b=YIN3jwKRXhYzqhUfjnKSkB4XIttRVrKGJ6wG7ETP43SonHuM9f5kBOxRoO9ogpeK86 mRbfj8KCvTylRGJmG3I5o4AOChF1NqJHGYrcGU87Tff32hzu8TgjclUSC9C5qpdKgjBP FLFE94Z2YzehaRVkfV/M5seueUU5FCv6M1W4nvX8huOvGeInxWZ07ncRlWAMfUelhc0v GiZn3gHAEIvCPs+rpyjiKlNGlUSAI570IB7KhhmaGb1MOaStw+oYIvgnYgPMs9BO2AVm AUknEsidHeIbtDiwygjGCFNHt3wz+SqeyotUFJ9oohlRlcu3p5F9fR3gVCABARwpYw3y t7uw== X-Gm-Message-State: AOJu0Yx6PlCNGnHyoCXotnO3RMCXHyfWu5vF9Tzls67uLgjNVBbpmH8J z9AwJ7oxQ59AMBC5Gt1VSn4n5Foe4+1cRiQzWdjr0PUo/HNgRkjeWi2D3DwMGhTvPmy0ABNPiBp 3 X-Google-Smtp-Source: AGHT+IGCEZXTHbZ1W618VXiq6EjH2hQT3xwqK2vjqtE+0lkJX9Rhxw6ZnMWhvidBDJWVRoQc0egHKg== X-Received: by 2002:a05:6830:480e:b0:6dd:eac2:fcd3 with SMTP id dg14-20020a056830480e00b006ddeac2fcd3mr6154057otb.69.1705979513238; Mon, 22 Jan 2024 19:11:53 -0800 (PST) Received: from spot.lan ([2601:602:a300:5ffb:858:76dd:2bb6:ee4f]) by smtp.gmail.com with ESMTPSA id h5-20020aa786c5000000b006dbd2fb0451sm4319555pfo.166.2024.01.22.19.11.52 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 22 Jan 2024 19:11:52 -0800 (PST) From: Stefan Sundin To: clogger-public@yhbt.net Cc: Stefan Sundin Subject: [PATCH 2/2] rack 3.x compatibility Date: Mon, 22 Jan 2024 19:08:27 -0800 Message-ID: <20240123031126.70784-3-git@stefansundin.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240123031126.70784-1-git@stefansundin.com> References: <20240123031126.70784-1-git@stefansundin.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: Rack::Utils::HeaderHash will be removed in rack 3.1 so these changes mostly address that. The initializer in Rack::Headers inherits from Hash, so switching to the ::[] class method to achieve the same result. The test test_broken_header_response was removed as Rack::Headers now throws an error in that case. I don't know if that necessarily is a good test case? Since the rackup functionality was split out to its own gem, the test case test_lint_error_wrapper now only works with rack 3.x. Other than that exception, all tests are passing with rack 1.6.13, 2.2.8, and 3.0.8. --- clogger.gemspec | 3 ++- ext/clogger_ext/clogger.c | 23 ++++++++++-------- lib/clogger/pure.rb | 12 ++++++++-- test/test_clogger.rb | 45 +++++++++++++++++++----------------- test/test_clogger_to_path.rb | 14 ++++++----- 5 files changed, 58 insertions(+), 39 deletions(-) diff --git a/clogger.gemspec b/clogger.gemspec index ab479c5..7a08dc5 100644 --- a/clogger.gemspec +++ b/clogger.gemspec @@ -16,8 +16,9 @@ Gem::Specification.new do |s| s.test_files = %w(test/test_clogger.rb test/test_clogger_to_path.rb) # HeaderHash wasn't case-insensitive in old versions - s.add_dependency(%q, ['>= 1.0', '< 3.0']) + s.add_dependency(%q, ['>= 1.0', '< 4.0']) s.add_development_dependency('test-unit', '~> 3.0') + s.add_development_dependency('rackup', '~> 2.0') s.extensions = %w(ext/clogger_ext/extconf.rb) s.licenses = %w(LGPL-2.1-or-later) end diff --git a/ext/clogger_ext/clogger.c b/ext/clogger_ext/clogger.c index 23aea39..9ccafb5 100644 --- a/ext/clogger_ext/clogger.c +++ b/ext/clogger_ext/clogger.c @@ -136,7 +136,7 @@ static ID to_path_id; static ID respond_to_id; static VALUE cClogger; static VALUE mFormat; -static VALUE cHeaderHash; +static VALUE cRackHeaders; /* common hash lookup keys */ static VALUE g_HTTP_X_FORWARDED_FOR; @@ -628,7 +628,7 @@ static void append_response(struct clogger *c, VALUE key) { VALUE v; - assert(rb_obj_is_kind_of(c->headers, cHeaderHash) && "not HeaderHash"); + assert(rb_obj_is_kind_of(c->headers, cRackHeaders) && "not Rack::Headers"); v = rb_funcall(c->headers, sq_brace_id, 1, key); v = NIL_P(v) ? g_dash : byte_xs(v); @@ -900,10 +900,9 @@ static VALUE ccall(struct clogger *c, VALUE env) rv = rb_ary_dup(rv); if (c->need_resp && - ! rb_obj_is_kind_of(c->headers, cHeaderHash)) { - c->headers = rb_funcall(cHeaderHash, new_id, 1, - c->headers); - rb_ary_store(rv, 1, c->headers); + ! rb_obj_is_kind_of(c->headers, cRackHeaders)) { + c->headers = rb_funcall(cRackHeaders, sq_brace_id, + 1, c->headers); } } else { VALUE tmp = rb_inspect(rv); @@ -1114,9 +1113,15 @@ void Init_clogger_ext(void) CONST_GLOBAL_STR2(rack_request_cookie_hash, "rack.request.cookie_hash"); tmp = rb_const_get(rb_cObject, rb_intern("Rack")); - tmp = rb_const_get(tmp, rb_intern("Utils")); - cHeaderHash = rb_const_get(tmp, rb_intern("HeaderHash")); - rb_ary_push(mark_ary, cHeaderHash); + if (rb_const_defined(tmp, rb_intern("Headers"))) { + // Rack >= 3.0 + cRackHeaders = rb_const_get(tmp, rb_intern("Headers")); + } else { + // Rack < 3.0 + tmp = rb_const_get(tmp, rb_intern("Utils")); + cRackHeaders = rb_const_get(tmp, rb_intern("HeaderHash")); + } + rb_ary_push(mark_ary, cRackHeaders); rb_obj_freeze(mark_ary); } diff --git a/lib/clogger/pure.rb b/lib/clogger/pure.rb index 4b38e90..1ddd7f2 100644 --- a/lib/clogger/pure.rb +++ b/lib/clogger/pure.rb @@ -8,9 +8,17 @@ class Clogger attr_accessor :env, :status, :headers, :body attr_writer :body_bytes_sent, :start + RackHeaders = if Object.const_defined?("Rack::Headers") + # Rack >= 3.0 + Rack::Headers + else + # Rack < 3.0 + Rack::Utils::HeaderHash + end + def initialize(app, opts = {}) # trigger autoload to avoid thread-safety issues later on - Rack::Utils::HeaderHash + RackHeaders @app = app @logger = opts[:logger] @@ -35,7 +43,7 @@ class Clogger raise TypeError, "app response not a 3 element Array: #{resp.inspect}" end status, headers, body = resp - headers = Rack::Utils::HeaderHash.new(headers) if @need_resp + headers = RackHeaders[headers] if @need_resp if @wrap_body @reentrant = env['rack.multithread'] if @reentrant.nil? wbody = @reentrant ? self.dup : self diff --git a/test/test_clogger.rb b/test/test_clogger.rb index 1dee652..bbb5791 100644 --- a/test/test_clogger.rb +++ b/test/test_clogger.rb @@ -10,6 +10,14 @@ require "rack" require "clogger" +RackHeaders = if Object.const_defined?("Rack::Headers") + # Rack >= 3.0 + Rack::Headers +else + # Rack < 3.0 + Rack::Utils::HeaderHash +end + # used to test subclasses class FooString < String end @@ -22,6 +30,7 @@ class TestClogger < Test::Unit::TestCase @nginx_fmt = "%d/%b/%Y:%H:%M:%S %z" @req = { "REQUEST_METHOD" => "GET", + "SERVER_PROTOCOL" => "HTTP/1.0", "HTTP_VERSION" => "HTTP/1.0", "HTTP_USER_AGENT" => 'echo and socat \o/', "PATH_INFO" => "/hello", @@ -85,13 +94,13 @@ class TestClogger < Test::Unit::TestCase def test_clen_stringio start = DateTime.now - 1 str = StringIO.new - app = lambda { |env| [ 301, {'Content-Length' => '5'}, ['abcde'] ] } + app = lambda { |env| [ 301, {'content-length' => '5'}, ['abcde'] ] } format = Common.dup assert format.gsub!(/response_length/, 'sent_http_content_length') cl = Clogger.new(app, :logger => str, :format => format) status, headers, body = cl.call(@req) assert_equal(301, status) - assert_equal({'Content-Length' => '5'}, headers) + assert_equal({'content-length' => '5'}, headers) body.each { |part| assert_equal('abcde', part) } str = str.string r = %r{\Ahome - - \[[^\]]+\] "GET /hello\?goodbye=true HTTP/1.0" 301 5\n\z} @@ -253,7 +262,7 @@ class TestClogger < Test::Unit::TestCase def test_rack_1_0 start = DateTime.now - 1 str = StringIO.new - app = lambda { |env| [ 200, {'Content-Length'=>'0'}, %w(a b c)] } + app = lambda { |env| [ 200, {'content-length'=>'0'}, %w(a b c)] } cl = Clogger.new(app, :logger => str, :format => Rack_1_0) status, headers, body = cl.call(@req) tmp = [] @@ -344,7 +353,7 @@ class TestClogger < Test::Unit::TestCase def test_combined start = DateTime.now - 1 str = StringIO.new - app = lambda { |env| [ 200, {'Content-Length'=>'3'}, %w(a b c)] } + app = lambda { |env| [ 200, {'content-length'=>'3'}, %w(a b c)] } cl = Clogger.new(app, :logger => str, :format => Combined) status, headers, body = cl.call(@req) tmp = [] @@ -365,7 +374,7 @@ class TestClogger < Test::Unit::TestCase def test_rack_errors_fallback err = StringIO.new - app = lambda { |env| [ 200, {'Content-Length'=>'3'}, %w(a b c)] } + app = lambda { |env| [ 200, {'content-length'=>'3'}, %w(a b c)] } cl = Clogger.new(app, :format => '$pid') req = @req.merge('rack.errors' => err) status, headers, body = cl.call(req) @@ -374,7 +383,7 @@ class TestClogger < Test::Unit::TestCase def test_body_close s_body = StringIO.new(%w(a b c).join("\n")) - app = lambda { |env| [ 200, {'Content-Length'=>'5'}, s_body] } + app = lambda { |env| [ 200, {'content-length'=>'5'}, s_body] } cl = Clogger.new(app, :logger => [], :format => '$pid') status, headers, body = cl.call(@req) assert ! s_body.closed? @@ -384,7 +393,7 @@ class TestClogger < Test::Unit::TestCase def test_escape str = StringIO.new - app = lambda { |env| [ 200, {'Content-Length'=>'5'}, [] ] } + app = lambda { |env| [ 200, {'content-length'=>'5'}, [] ] } cl = Clogger.new(app, :logger => str, :format => '$http_user_agent "$request"') @@ -399,12 +408,12 @@ class TestClogger < Test::Unit::TestCase end # rack allows repeated headers with "\n": - # { 'Set-Cookie' => "a\nb" } => - # Set-Cookie: a - # Set-Cookie: b + # { 'set-cookie' => "a\nb" } => + # set-cookie: a + # set-cookie: b def test_escape_header_newlines str = StringIO.new - app = lambda { |env| [302, { 'Set-Cookie' => "a\nb" }, [] ] } + app = lambda { |env| [302, { 'set-cookie' => "a\nb" }, [] ] } cl = Clogger.new(app, :logger => str, :format => '$sent_http_set_cookie') cl.call(@req) assert_equal "a\\x0Ab\n", str.string @@ -459,16 +468,9 @@ class TestClogger < Test::Unit::TestCase assert_match %r{#{e}$}m, str end - def test_broken_header_response - str = StringIO.new - app = lambda { |env| [302, [ %w(a) ], []] } - cl = Clogger.new(app, :logger => str, :format => '$sent_http_set_cookie') - assert_nothing_raised { cl.call(@req) } - end - def test_subclass_hash str = StringIO.new - req = Rack::Utils::HeaderHash.new(@req) + req = RackHeaders[@req] app = lambda { |env| [302, [ %w(a) ], []] } cl = Clogger.new(app, :logger => str, :format => Rack_1_0) assert_nothing_raised { cl.call(req).last.each {}.close } @@ -877,14 +879,15 @@ class TestClogger < Test::Unit::TestCase end def test_lint_error_wrapper - require 'rack/lobster' + # This test only works on Rack >= 3.0 + require 'rackup/lobster' @req["SERVER_NAME"] = "FOO" @req["SERVER_PORT"] = "666" @req["rack.version"] = [1,1] @req["rack.multithread"] = true @req["rack.multiprocess"] = true @req["rack.run_once"] = false - app = Rack::ContentLength.new(Rack::ContentType.new(Rack::Lobster.new)) + app = Rack::ContentLength.new(Rack::ContentType.new(Rackup::Lobster.new)) cl = Clogger.new(app, :format => :Combined) @req["rack.errors"] = err = StringIO.new status, headers, body = r = Rack::Lint.new(cl).call(@req) diff --git a/test/test_clogger_to_path.rb b/test/test_clogger_to_path.rb index f74b991..4cc1738 100644 --- a/test/test_clogger_to_path.rb +++ b/test/test_clogger_to_path.rb @@ -3,6 +3,7 @@ $stderr.sync = $stdout.sync = true require "test/unit" require "date" require "stringio" +require "tempfile" require "rack" require "clogger" @@ -26,6 +27,7 @@ class TestCloggerToPath < Test::Unit::TestCase @req = { "REQUEST_METHOD" => "GET", "HTTP_VERSION" => "HTTP/1.0", + "SERVER_PROTOCOL" => "HTTP/1.0", "HTTP_USER_AGENT" => 'echo and socat \o/', "PATH_INFO" => "/", "QUERY_STRING" => "", @@ -50,8 +52,8 @@ class TestCloggerToPath < Test::Unit::TestCase app = Rack::Builder.new do tmp.syswrite(' ' * 365) h = { - 'Content-Length' => '0', - 'Content-Type' => 'text/plain', + 'content-length' => '0', + 'content-type' => 'text/plain', } use Clogger, :logger => logger, @@ -82,8 +84,8 @@ class TestCloggerToPath < Test::Unit::TestCase app = Rack::Builder.new do tmp.syswrite(' ' * 365) h = { - 'Content-Length' => '0', - 'Content-Type' => 'text/plain', + 'content-length' => '0', + 'content-type' => 'text/plain', } use Clogger, :logger => logger, @@ -106,8 +108,8 @@ class TestCloggerToPath < Test::Unit::TestCase logger = StringIO.new app = Rack::Builder.new do h = { - 'Content-Length' => '3', - 'Content-Type' => 'text/plain', + 'content-length' => '3', + 'content-type' => 'text/plain', } use Clogger, :logger => logger, -- 2.43.0