From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS3215 2.6.0.0/16 X-Spam-Status: No, score=-3.9 required=3.0 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_EF,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI, RCVD_IN_DNSWL_NONE,SPF_PASS shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from mail-qk1-x73f.google.com (mail-qk1-x73f.google.com [IPv6:2607:f8b0:4864:20::73f]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by dcvr.yhbt.net (Postfix) with ESMTPS id 0CB6D1F5CB for ; Tue, 23 Apr 2019 08:27:10 +0000 (UTC) Received: by mail-qk1-x73f.google.com with SMTP id a12sf410372qkb.3 for ; Tue, 23 Apr 2019 01:27:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20161025; h=sender:date:from:to:message-id:in-reply-to:references:subject :mime-version:x-original-sender:reply-to:precedence:mailing-list :list-id:list-post:list-help:list-archive:list-subscribe :list-unsubscribe; bh=aUxreJYiRyVNxTOOOvCeG1kFYDKSf67mSrd7uBt4/Yk=; b=Uz+zX4fVg1kytouJZaAp6URs6umfbE5ExIc759aCOt8wvVAP04O6BoiQtn1mq80lGS DI5uTBnTevVLzFX5+WYst2yltqBb3ItIeQqRQttULP1u9TA/hww5HoFQ1RErdx1g54yf HNvbhIdk2q4rIK+JqxlzY3Ev2J1sOn7WKt4qLGD0QcORWlwuL6YmtKEBDxSHEWU41A8c Lkt+ASalhkXlIlifoo63EVCM+JbNcTaLBu94v7WlAERJLSe32wCCopcU4+aC/F+1+Sjj fps5PEoMy9IqqzlcTr08EGekxaBbdACZnfyX/k0po1BT26O5dDO2jqg5vlW8vzlVM7Ms gkWQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=sender:x-gm-message-state:date:from:to:message-id:in-reply-to :references:subject:mime-version:x-original-sender:reply-to :precedence:mailing-list:list-id:list-post:list-help:list-archive :list-subscribe:list-unsubscribe; bh=aUxreJYiRyVNxTOOOvCeG1kFYDKSf67mSrd7uBt4/Yk=; b=UOukckMd7HopEH6xQXDCp0epTyOEXqSHfRQNq7UzNaNvEp5JjRBez3Xd+2Yze8b7s8 3OvATQwkYmiwKpGH+3Qc3fhMoy/gcaGCeXBa5By+N9qm1uClS5EWhRBUS2oZ0OAB3/5j l/aOOjrKKkp/JazA6+OxYGgiIa/HG2nBzA8GpewYP44TsrTzWm9TX7o2A4yClFbAWDCo rBM5qyXynh+eDvtid9S/lEc6AzojpjdJHta6hG51AA0NXbueTEhIeKKivHej3URlfSvs MFxHAUN7OB70tfEXRX+cl3xYPikTwdJPbxC1tbEyJFutDv5fAaXAX8xJBSUmnAXnzK8M cjcA== Sender: rack-devel@googlegroups.com X-Gm-Message-State: APjAAAUADv4yYbzJ3f41zGgyExGWeOTAxhRDplVwNjwrlS+qZrqBCiyX xeNqGRYPaB5z7VvgQpsl5qk= X-Google-Smtp-Source: APXvYqy/AIT329hP100pjzg0pmxk10moBAJ9Y7h4YF3hKKX6+RgHoSqFPKKLq13tEzUiiXDionWS0g== X-Received: by 2002:ac8:37ab:: with SMTP id d40mr6143700qtc.171.1556008028812; Tue, 23 Apr 2019 01:27:08 -0700 (PDT) X-BeenThere: rack-devel@googlegroups.com Received: by 2002:a37:40d:: with SMTP id 13ls1257084qke.4.gmail; Tue, 23 Apr 2019 01:27:08 -0700 (PDT) X-Received: by 2002:a05:620a:165c:: with SMTP id c28mr14292988qko.100.1556008028614; Tue, 23 Apr 2019 01:27:08 -0700 (PDT) Received: by 2002:a37:70c6:0:0:0:0:0 with SMTP id l189msqkc; Sun, 21 Apr 2019 12:51:48 -0700 (PDT) X-Received: by 2002:a05:620a:118f:: with SMTP id b15mr11580863qkk.162.1555876307978; Sun, 21 Apr 2019 12:51:47 -0700 (PDT) Date: Sun, 21 Apr 2019 12:51:47 -0700 (PDT) From: Vy Nguyen To: Rack Development Message-Id: In-Reply-To: References: <20160727021048.GA27519@starla> Subject: Re: [PATCH] deflater: remove "deflate" encoding support MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_257_244974510.1555876307820" X-Original-Sender: Thaovy231011@gmail.com Reply-To: rack-devel@googlegroups.com Precedence: list Mailing-list: list rack-devel@googlegroups.com; contact rack-devel+owners@googlegroups.com List-ID: X-Google-Group-Id: 486215384060 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , ------=_Part_257_244974510.1555876307820 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable V=C3=A0o 09:44:33 UTC+7 Th=E1=BB=A9 T=C6=B0, ng=C3=A0y 27 th=C3=A1ng 7 n=C4= =83m 2016, raggi =C4=91=C3=A3 vi=E1=BA=BFt: > Can you explain the cache hit rate assertions you open with? Sorry for ev= il post, mobile client. >=20 >=20 >=20 > On Jul 26, 2016 7:10 PM, "Eric Wong" wrote: > This will improve cache hit rates and reduce caching overhead at >=20 > small expense of increased header overhead for some user agents. >=20 > For reference, Varnish cache supports only gzip as well: >=20 >=20 >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 https://www.varnish-cache.org/docs/4.1/phk/gz= ip.html >=20 >=20 >=20 > In the past, "deflate" encoding was more likely to trigger >=20 > user-agent bugs, as noted in the comments removed with this >=20 > change as well as the Varnish documentation referenced above. >=20 > --- >=20 > =C2=A0 The following changes since commit 25a549883b85fb33970b4a1530a365c= 0c9e51f95: >=20 >=20 >=20 > =C2=A0 =C2=A0 bumping to 2.0.1 to work around Rack (2016-06-30 10:33:09 -= 0700) >=20 >=20 >=20 > =C2=A0 are available in the git repository at: >=20 >=20 >=20 > =C2=A0 =C2=A0 git://80x24.org/rack.git no-deflate >=20 >=20 >=20 > =C2=A0 for you to fetch changes up to d6380043a8953dca63743c947c8027f465d= 29a5d: >=20 >=20 >=20 > =C2=A0 =C2=A0 deflater: remove "deflate" encoding support (2016-07-26 23:= 14:55 +0000) >=20 >=20 >=20 > =C2=A0 ---------------------------------------------------------------- >=20 > =C2=A0 Eric Wong (1): >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 deflater: remove "deflate" encoding support >=20 >=20 >=20 > =C2=A0lib/rack/deflater.rb=C2=A0 | 37 +----------------------------------= -- >=20 > =C2=A0test/spec_deflater.rb | 38 ++++++++++++++++++++++++-------------- >=20 > =C2=A02 files changed, 25 insertions(+), 50 deletions(-) >=20 >=20 >=20 > diff --git a/lib/rack/deflater.rb b/lib/rack/deflater.rb >=20 > index 62a1124..79e39f5 100644 >=20 > --- a/lib/rack/deflater.rb >=20 > +++ b/lib/rack/deflater.rb >=20 > @@ -8,7 +8,6 @@ module Rack >=20 > =C2=A0 =C2=A0# Currently supported compression algorithms: >=20 > =C2=A0 =C2=A0# >=20 > =C2=A0 =C2=A0#=C2=A0 =C2=A0* gzip >=20 > -=C2=A0 #=C2=A0 =C2=A0* deflate >=20 > =C2=A0 =C2=A0#=C2=A0 =C2=A0* identity (no transformation) >=20 > =C2=A0 =C2=A0# >=20 > =C2=A0 =C2=A0# The middleware automatically detects when compression is s= upported >=20 > @@ -41,7 +40,7 @@ module Rack >=20 >=20 >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0request =3D Request.new(env) >=20 >=20 >=20 > -=C2=A0 =C2=A0 =C2=A0 encoding =3D Utils.select_best_encoding(%w(gzip def= late identity), >=20 > +=C2=A0 =C2=A0 =C2=A0 encoding =3D Utils.select_best_encoding(%w(gzip ide= ntity), >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0request.accept_encoding) >=20 >=20 >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0# Set the Vary HTTP header. >=20 > @@ -57,10 +56,6 @@ module Rack >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0mtime =3D headers.key?("Last-Modified")= ? >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Time.httpdate(headers["Last-Modi= fied"]) : Time.now >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0[status, headers, GzipStream.new(body, = mtime)] >=20 > -=C2=A0 =C2=A0 =C2=A0 when "deflate" >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 headers['Content-Encoding'] =3D "deflate" >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 headers.delete(CONTENT_LENGTH) >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 [status, headers, DeflateStream.new(body)] >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0when "identity" >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0[status, headers, body] >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0when nil >=20 > @@ -101,36 +96,6 @@ module Rack >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0end >=20 > =C2=A0 =C2=A0 =C2=A0end >=20 >=20 >=20 > -=C2=A0 =C2=A0 class DeflateStream >=20 > -=C2=A0 =C2=A0 =C2=A0 DEFLATE_ARGS =3D [ >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 Zlib::DEFAULT_COMPRESSION, >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 # drop the zlib header which causes both Saf= ari and IE to choke >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 -Zlib::MAX_WBITS, >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 Zlib::DEF_MEM_LEVEL, >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 Zlib::DEFAULT_STRATEGY >=20 > -=C2=A0 =C2=A0 =C2=A0 ] >=20 > - >=20 > -=C2=A0 =C2=A0 =C2=A0 def initialize(body) >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 @body =3D body >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 @closed =3D false >=20 > -=C2=A0 =C2=A0 =C2=A0 end >=20 > - >=20 > -=C2=A0 =C2=A0 =C2=A0 def each >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 deflator =3D ::Zlib::Deflate.new(*DEFLATE_AR= GS) >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 @body.each { |part| yield deflator.deflate(p= art, Zlib::SYNC_FLUSH) } >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 yield fin =3D deflator.finish >=20 > -=C2=A0 =C2=A0 =C2=A0 ensure >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 deflator.finish unless fin >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 deflator.close >=20 > -=C2=A0 =C2=A0 =C2=A0 end >=20 > - >=20 > -=C2=A0 =C2=A0 =C2=A0 def close >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 return if @closed >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 @closed =3D true >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 @body.close if @body.respond_to?(:close) >=20 > -=C2=A0 =C2=A0 =C2=A0 end >=20 > -=C2=A0 =C2=A0 end >=20 > - >=20 > =C2=A0 =C2=A0 =C2=A0private >=20 >=20 >=20 > =C2=A0 =C2=A0 =C2=A0def should_deflate?(env, status, headers, body) >=20 > diff --git a/test/spec_deflater.rb b/test/spec_deflater.rb >=20 > index ba7ec5d..0f27c85 100644 >=20 > --- a/test/spec_deflater.rb >=20 > +++ b/test/spec_deflater.rb >=20 > @@ -81,13 +81,22 @@ describe Rack::Deflater do >=20 > =C2=A0 =C2=A0 =C2=A0yield(status, headers, body) if block_given? >=20 > =C2=A0 =C2=A0end >=20 >=20 >=20 > +=C2=A0 # automatic gzip detection (streamable) >=20 > +=C2=A0 def auto_inflater >=20 > +=C2=A0 =C2=A0 Zlib::Inflate.new(32 + Zlib::MAX_WBITS) >=20 > +=C2=A0 end >=20 > + >=20 > +=C2=A0 def deflate_or_gzip >=20 > +=C2=A0 =C2=A0 {'deflate, gzip' =3D> 'gzip'} >=20 > +=C2=A0 end >=20 > + >=20 > =C2=A0 =C2=A0it 'be able to deflate bodies that respond to each' do >=20 > =C2=A0 =C2=A0 =C2=A0app_body =3D Object.new >=20 > =C2=A0 =C2=A0 =C2=A0class << app_body; def each; yield('foo'); yield('bar= '); end; end >=20 >=20 >=20 > -=C2=A0 =C2=A0 verify(200, 'foobar', 'deflate', { 'app_body' =3D> app_bod= y }) do |status, headers, body| >=20 > +=C2=A0 =C2=A0 verify(200, 'foobar', deflate_or_gzip, { 'app_body' =3D> a= pp_body }) do |status, headers, body| >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0headers.must_equal({ >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 'Content-Encoding' =3D> 'deflate', >=20 > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 'Content-Encoding' =3D> 'gzip', >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Vary' =3D> 'Accept-Encoding', >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Content-Type' =3D> 'text/plain' >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0}) >=20 > @@ -98,15 +107,15 @@ describe Rack::Deflater do >=20 > =C2=A0 =C2=A0 =C2=A0app_body =3D Object.new >=20 > =C2=A0 =C2=A0 =C2=A0class << app_body; def each; yield('foo'); yield('bar= '); end; end >=20 >=20 >=20 > -=C2=A0 =C2=A0 verify(200, app_body, 'deflate', { 'skip_body_verify' =3D>= true }) do |status, headers, body| >=20 > +=C2=A0 =C2=A0 verify(200, app_body, deflate_or_gzip, { 'skip_body_verify= ' =3D> true }) do |status, headers, body| >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0headers.must_equal({ >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 'Content-Encoding' =3D> 'deflate', >=20 > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 'Content-Encoding' =3D> 'gzip', >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Vary' =3D> 'Accept-Encoding', >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Content-Type' =3D> 'text/plain' >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0}) >=20 >=20 >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0buf =3D [] >=20 > -=C2=A0 =C2=A0 =C2=A0 inflater =3D Zlib::Inflate.new(-Zlib::MAX_WBITS) >=20 > +=C2=A0 =C2=A0 =C2=A0 inflater =3D auto_inflater >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0body.each { |part| buf << inflater.inflate(par= t) } >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0buf << inflater.finish >=20 >=20 >=20 > @@ -118,32 +127,33 @@ describe Rack::Deflater do >=20 > =C2=A0 =C2=A0 =C2=A0app_body =3D Object.new >=20 > =C2=A0 =C2=A0 =C2=A0class << app_body; def each; yield('foo'); yield('bar= '); end; end >=20 > =C2=A0 =C2=A0 =C2=A0opts =3D { 'skip_body_verify' =3D> true } >=20 > -=C2=A0 =C2=A0 verify(200, app_body, 'deflate', opts) do |status, headers= , body| >=20 > +=C2=A0 =C2=A0 verify(200, app_body, 'gzip', opts) do |status, headers, b= ody| >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0headers.must_equal({ >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 'Content-Encoding' =3D> 'deflate', >=20 > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 'Content-Encoding' =3D> 'gzip', >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Vary' =3D> 'Accept-Encoding', >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Content-Type' =3D> 'text/plain' >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0}) >=20 >=20 >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0buf =3D [] >=20 > -=C2=A0 =C2=A0 =C2=A0 inflater =3D Zlib::Inflate.new(-Zlib::MAX_WBITS) >=20 > +=C2=A0 =C2=A0 =C2=A0 inflater =3D auto_inflater >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0FakeDisconnect =3D Class.new(RuntimeError) >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0assert_raises(FakeDisconnect, "not Zlib::DataE= rror not raised") do >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0body.each do |part| >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 buf << inflater.inflate(part) >=20 > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 tmp =3D inflater.inflate(part) >=20 > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 buf << tmp if tmp.bytesize > 0 >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0raise FakeDisconnect >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0end >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0end >=20 > -=C2=A0 =C2=A0 =C2=A0 assert_raises(Zlib::BufError) { inflater.finish } >=20 > +=C2=A0 =C2=A0 =C2=A0 inflater.finish >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0buf.must_equal(%w(foo)) >=20 > =C2=A0 =C2=A0 =C2=A0end >=20 > =C2=A0 =C2=A0end >=20 >=20 >=20 > =C2=A0 =C2=A0# TODO: This is really just a special case of the above... >=20 > =C2=A0 =C2=A0it 'be able to deflate String bodies' do >=20 > -=C2=A0 =C2=A0 verify(200, 'Hello world!', 'deflate') do |status, headers= , body| >=20 > +=C2=A0 =C2=A0 verify(200, 'Hello world!', deflate_or_gzip) do |status, h= eaders, body| >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0headers.must_equal({ >=20 > -=C2=A0 =C2=A0 =C2=A0 =C2=A0 'Content-Encoding' =3D> 'deflate', >=20 > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 'Content-Encoding' =3D> 'gzip', >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Vary' =3D> 'Accept-Encoding', >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Content-Type' =3D> 'text/plain' >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0}) >=20 > @@ -280,7 +290,7 @@ describe Rack::Deflater do >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'Content-Encoding' =3D> 'identity' >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0} >=20 > =C2=A0 =C2=A0 =C2=A0} >=20 > -=C2=A0 =C2=A0 verify(200, 'Hello World!', 'deflate', options) >=20 > +=C2=A0 =C2=A0 verify(200, 'Hello World!', deflate_or_gzip, options) >=20 > =C2=A0 =C2=A0end >=20 >=20 >=20 > =C2=A0 =C2=A0it "deflate if content-type matches :include" do >=20 > @@ -334,7 +344,7 @@ describe Rack::Deflater do >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0:if =3D> lambda { |env, status, headers= , body| true } >=20 > =C2=A0 =C2=A0 =C2=A0 =C2=A0} >=20 > =C2=A0 =C2=A0 =C2=A0} >=20 > -=C2=A0 =C2=A0 verify(200, 'Hello World!', 'deflate', options) >=20 > +=C2=A0 =C2=A0 verify(200, 'Hello World!', deflate_or_gzip, options) >=20 > =C2=A0 =C2=A0end >=20 >=20 >=20 > =C2=A0 =C2=A0it "not deflate if :if lambda evaluates to false" do >=20 > -- >=20 > EW >=20 >=20 >=20 > -- >=20 >=20 >=20 > --- >=20 > You received this message because you are subscribed to the Google Groups= "Rack Development" group. >=20 > To unsubscribe from this group and stop receiving emails from it, send an= email to rack-...@googlegroups.com. >=20 > For more options, visit https://groups.google.com/d/optout. --=20 ---=20 You received this message because you are subscribed to the Google Groups "= Rack Development" group. To unsubscribe from this group and stop receiving emails from it, send an e= mail to rack-devel+unsubscribe@googlegroups.com. For more options, visit https://groups.google.com/d/optout. ------=_Part_257_244974510.1555876307820--