From 3292217c5b76ace2199fdb49c9d55db05432982f Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Fri, 21 Aug 2026 07:56:02 +0200 Subject: [PATCH 1/2] fix: incorrect handling http 103 Signed-off-by: Valery Piashchynski --- go.mod | 7 +- go.sum | 19 +-- go.work.sum | 33 ++++- handler/response.go | 60 +++++++- handler/response_test.go | 159 +++++++++++++++++++++ middleware/log.go | 10 +- middleware/log_test.go | 112 +++++++++++++++ tests/configs/.rr-issue2381.yaml | 23 +++ tests/go.mod | 7 +- tests/go.sum | 13 +- tests/issues_test.go | 41 ++++++ tests/php_test_files/workers/issue2381.php | 20 +++ tests/streaming_test.go | 61 +++++--- 13 files changed, 499 insertions(+), 66 deletions(-) create mode 100644 middleware/log_test.go create mode 100644 tests/configs/.rr-issue2381.yaml create mode 100644 tests/php_test_files/workers/issue2381.php diff --git a/go.mod b/go.mod index 41f67a93..00c9a5f9 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/roadrunner-server/http/v6 go 1.26 -toolchain go1.26.6 +toolchain go1.27.0 require ( github.com/caddyserver/certmagic v0.25.4 @@ -18,14 +18,14 @@ require ( github.com/roadrunner-server/goridge/v4 v4.0.0-beta.3 github.com/roadrunner-server/pool/v2 v2.0.0-beta.1 github.com/roadrunner-server/tcplisten v1.5.2 - github.com/stretchr/testify v1.12.0 + github.com/stretchr/testify v1.12.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 go.opentelemetry.io/contrib/propagators/jaeger v1.45.0 go.opentelemetry.io/otel v1.45.0 go.opentelemetry.io/otel/trace v1.45.0 golang.org/x/net v0.58.0 golang.org/x/sys v0.47.0 - google.golang.org/genproto v0.0.0-20260818201246-1b0934165a6f + google.golang.org/genproto v0.0.0-20260819154853-08b0e4226688 google.golang.org/protobuf v1.36.12 ) @@ -65,5 +65,4 @@ require ( golang.org/x/crypto v0.55.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/text v0.41.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 06f734eb..27468b77 100644 --- a/go.sum +++ b/go.sum @@ -26,10 +26,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= @@ -78,12 +74,10 @@ github.com/roadrunner-server/pool/v2 v2.0.0-beta.1 h1:jpYXFtdD6QGAdAGPgMxrNi3j1C github.com/roadrunner-server/pool/v2 v2.0.0-beta.1/go.mod h1:Bo1wT7RtL3eyQHXBUohNhtj/yAmRt6Rq8smuBg5pWkY= github.com/roadrunner-server/tcplisten v1.5.2 h1:nn8yXYrhRDkfQ9AAu4V075uT4fZRmOnpxkawgE+bWPA= github.com/roadrunner-server/tcplisten v1.5.2/go.mod h1:DufGBz7Dlx2KrNe/4RukEvGMTqZKB0Uve1GztwcyyR8= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= -github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= @@ -138,12 +132,7 @@ golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -google.golang.org/genproto v0.0.0-20260818201246-1b0934165a6f h1:mIpuZeRssq9TYLzKcruaLOCbOgJt8WDF1FSyEPZBw24= -google.golang.org/genproto v0.0.0-20260818201246-1b0934165a6f/go.mod h1:UpDDw2l68z31m5UZN/Qi0Kow16ohhlJVlmqC3qRM5Q8= +google.golang.org/genproto v0.0.0-20260819154853-08b0e4226688 h1:2ficYAs+4TBEqohUu9hr7J2YeNTie+3BVyqERjs5Hm0= +google.golang.org/genproto v0.0.0-20260819154853-08b0e4226688/go.mod h1:icDJeJwWhZtQDn/1WGql+0n01hbizh4G7/T75RoxcHs= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.work.sum b/go.work.sum index 9709d15d..de177b9f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -117,6 +117,7 @@ cloud.google.com/go/aiplatform v1.120.0 h1:jKWTpEs+xoUhDa1FMdSuhMcEQYyUiMdufGyX3 cloud.google.com/go/aiplatform v1.120.0/go.mod h1:6mDthfmy0oS1EQhVFdijoxkVdI2+HIZkpuGTBpedeCg= cloud.google.com/go/aiplatform v1.125.0 h1:QUGv+XaHN9wcWdb0/J0NFIcaP/veQSvDcqg4GH6QiP4= cloud.google.com/go/aiplatform v1.125.0/go.mod h1:yWTZiCunYDnyxeWWD14tDo6+BMlvAUCC5VxuxhvbrVI= +cloud.google.com/go/aiplatform v1.126.0/go.mod h1:iR3za3evdprLe1XL2pLu0cYVCuTbc87QG0pgvcgiJlE= cloud.google.com/go/analytics v0.21.3 h1:TFBC1ZAqX9/jL56GEXdLrVe5vT3I22bDVWyDwZX4IEg= cloud.google.com/go/analytics v0.21.4 h1:SScWR8i/M8h7h3lFKtOYcj0r4272aL+KvRRrsu39Vec= cloud.google.com/go/analytics v0.21.4/go.mod h1:zZgNCxLCy8b2rKKVfC1YkC2vTrpfZmeRCySM3aUbskA= @@ -205,6 +206,7 @@ cloud.google.com/go/appengine v1.13.0 h1:njiT9ijm2foLFwO8CGkkILEmN0uuKHnlBKGNmZt cloud.google.com/go/appengine v1.13.0/go.mod h1:JMjrVFg+YgfksZCWbtA3TgbKbPfZZtapB9cGL/5WVnM= cloud.google.com/go/appengine v1.14.0 h1:dTww1xDqBpeR0BpLsiqfjyAnaK7S1vniJ5YR7L83Jh4= cloud.google.com/go/appengine v1.14.0/go.mod h1:JMjrVFg+YgfksZCWbtA3TgbKbPfZZtapB9cGL/5WVnM= +cloud.google.com/go/appengine v1.15.0/go.mod h1:/8gGZsOX5GDjOo4mAWk8IV59p2991dxTbEtKIlhDjzU= cloud.google.com/go/area120 v0.8.1 h1:wiOq3KDpdqXmaHzvZwKdpoM+3lDcqsI2Lwhyac7stss= cloud.google.com/go/area120 v0.8.2 h1:h/wMtPPsgFJfMce1b9M24Od8RuKt8CWENwr+X24tBhE= cloud.google.com/go/area120 v0.8.2/go.mod h1:a5qfo+x77SRLXnCynFWPUZhnZGeSgvQ+Y0v1kSItkh4= @@ -394,6 +396,7 @@ cloud.google.com/go/bigquery v1.76.0 h1:wnfVSXN6GEMlsAoHWdhzTC8NMsptOx2hsqPiI+lT cloud.google.com/go/bigquery v1.76.0/go.mod h1:J4wuqka/1hEpdJxH2oBrUR0vjTD+r7drGkpcA3yqERM= cloud.google.com/go/bigquery v1.77.0 h1:L5AW3jhzEKpFVg4i0mVHxKpxogrqT7dczWBSr4m9MKU= cloud.google.com/go/bigquery v1.77.0/go.mod h1:J4wuqka/1hEpdJxH2oBrUR0vjTD+r7drGkpcA3yqERM= +cloud.google.com/go/bigquery v1.80.0/go.mod h1:cc0XscySNQNuHBxuZSg5yyxFsg/ZHAfViAG49gJbWew= cloud.google.com/go/bigtable v1.35.0 h1:UEacPwaejN2mNbz67i1Iy3G812rxtgcs6ePj1TAg7dw= cloud.google.com/go/bigtable v1.35.0/go.mod h1:EabtwwmTcOJFXp+oMZAT/jZkyDIjNwrv53TrS4DGrrM= cloud.google.com/go/bigtable v1.37.0/go.mod h1:HXqddP6hduwzrtiTCqZPpj9ij4hGZb4Zy1WF/dT+yaU= @@ -540,6 +543,7 @@ cloud.google.com/go/cloudtasks v1.17.0 h1:3+U2YGq0fvEqQxdgknsjD5m+QNwah0QZYJOylN cloud.google.com/go/cloudtasks v1.17.0/go.mod h1:3KeCxwtGEyaySL7CR3lMmEa2I4mq1ynXdgmfNiO4RYE= cloud.google.com/go/cloudtasks v1.18.0 h1:KzT7hfix/9/xAf20tNPIxwX59XGpRF0Lun2t8LHOj9E= cloud.google.com/go/cloudtasks v1.18.0/go.mod h1:3KeCxwtGEyaySL7CR3lMmEa2I4mq1ynXdgmfNiO4RYE= +cloud.google.com/go/cloudtasks v1.19.0/go.mod h1:8q8wNubq0jFvXW5Pz8P3O7QWJBXOmfrY918FqTgIqHA= cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= @@ -619,6 +623,7 @@ cloud.google.com/go/container v1.46.0 h1:xX94Lo3xrS5OkdMWKvpEVAbBwjN9uleVv6vOi02 cloud.google.com/go/container v1.46.0/go.mod h1:A7gMqdQduTk46+zssWDTKbGS2z46UsJNXfKqvMI1ZO4= cloud.google.com/go/container v1.49.0 h1:K4nmtmJezHOzsIyedAOv1Ok36krw1apFmo4zXBaRL1A= cloud.google.com/go/container v1.49.0/go.mod h1:EvqoT2eXfxLweXXUlhAMGR0sOAB00XPzEjoL01esSDs= +cloud.google.com/go/container v1.53.1/go.mod h1:/ZI9J3uuAQh0O3/n9qxkdTVCDB6kuwxEpC9MvEWnf/Q= cloud.google.com/go/containeranalysis v0.11.0 h1:/EsoP+UTIjvl4yqrLA4WgUG83kwQhqZmbXEfqirT2LM= cloud.google.com/go/containeranalysis v0.11.0/go.mod h1:4n2e99ZwpGxpNcz+YsFT1dfOHPQFGcAC8FN2M2/ne/U= cloud.google.com/go/containeranalysis v0.11.1 h1:PHh4KTcMpCjYgxfV+TzvP24wolTGP9lGbqh9sBNHxjs= @@ -784,6 +789,7 @@ cloud.google.com/go/dataproc/v2 v2.21.0 h1:sSyWnDNpcPlSJpNYqfdCAC/i9J4WzO5aLn+96 cloud.google.com/go/dataproc/v2 v2.21.0/go.mod h1:oARVSa38kAHvSuG+cozsrY2sE6UajGuvOOf9vS+ADHI= cloud.google.com/go/dataproc/v2 v2.22.0 h1:ypUlQKOHMHGv8FQCCNYd0XyM6tAaMDdbcSFBcjYWhbg= cloud.google.com/go/dataproc/v2 v2.22.0/go.mod h1:oARVSa38kAHvSuG+cozsrY2sE6UajGuvOOf9vS+ADHI= +cloud.google.com/go/dataproc/v2 v2.25.0/go.mod h1:hkiM6kzc8CwLGoquMN1oghyhuI1fE0girmChH4h9W7w= cloud.google.com/go/dataqna v0.8.1 h1:ITpUJep04hC9V7C+gcK390HO++xesQFSUJ7S4nSnF3U= cloud.google.com/go/dataqna v0.8.2 h1:vJ9JVKDgDG7AQMbTD8pdWaogJ4c/yHn0qer+q0nFIaw= cloud.google.com/go/dataqna v0.8.2/go.mod h1:KNEqgx8TTmUipnQsScOoDpq/VlXVptUqVMZnt30WAPs= @@ -923,6 +929,7 @@ cloud.google.com/go/documentai v1.47.0 h1:XFEXBeYa9wSc69O34+wuhvRcY44OO1KzZ9Y0gw cloud.google.com/go/documentai v1.47.0/go.mod h1:mGjfbNf0cqCHKgxMZZV7frbfoF9T2hKkU1h88QyOy3c= cloud.google.com/go/documentai v1.48.0 h1:qodgYZJgA89EWNJeeWXWBe/5kq9C5cQhthJweI6Z6CE= cloud.google.com/go/documentai v1.48.0/go.mod h1:mGjfbNf0cqCHKgxMZZV7frbfoF9T2hKkU1h88QyOy3c= +cloud.google.com/go/documentai v1.49.0/go.mod h1:VyQA+SxPnCPlVLSJ5UcFx+LQm8JCzK7uUXdkOaAHvG8= cloud.google.com/go/domains v0.9.1 h1:rqz6KY7mEg7Zs/69U6m6LMbB7PxFDWmT3QWNXIqhHm0= cloud.google.com/go/domains v0.9.2 h1:SjpTtaTNRPPajrGiZEtxz9dpElO4PxuDWFvU4JpV1gk= cloud.google.com/go/domains v0.9.2/go.mod h1:3YvXGYzZG1Temjbk7EyGCuGGiXHJwVNmwIf+E/cUp5I= @@ -940,6 +947,7 @@ cloud.google.com/go/domains v0.14.0 h1:55cOBt0EXd02Tsv8FIE82VohHQPlVpBsxPW9XLl3A cloud.google.com/go/domains v0.14.0/go.mod h1:BjoSVNc+LVwoHMnE2fxTQNzGLSWWb6f3a8VAN6+VjVk= cloud.google.com/go/domains v0.15.0 h1:X5RjcYzpsVkuTMZ3OfuSDIv9pBtMlDEk7XXunlfB518= cloud.google.com/go/domains v0.15.0/go.mod h1:BjoSVNc+LVwoHMnE2fxTQNzGLSWWb6f3a8VAN6+VjVk= +cloud.google.com/go/domains v0.16.0/go.mod h1:O5AhaEyUAgZC2X4M10nSu3dQt2cJLtbjhtrNrdeSPF8= cloud.google.com/go/edgecontainer v1.1.1 h1:zhHWnLzg6AqzE+I3gzJqiIwHfjEBhWctNQEzqb+FaRo= cloud.google.com/go/edgecontainer v1.1.2 h1:B+Acb/0frXUxc60i6lC0JtXrBFAKoS7ZELmet9+ySo8= cloud.google.com/go/edgecontainer v1.1.2/go.mod h1:wQRjIzqxEs9e9wrtle4hQPSR1Y51kqN75dgF7UllZZ4= @@ -1170,6 +1178,7 @@ cloud.google.com/go/iam v1.10.0 h1:cWWt8u8jXv3MzpvBmQgNClvvbVCRukruCJAnoK3fIJY= cloud.google.com/go/iam v1.10.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM= cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= +cloud.google.com/go/iam v1.13.0/go.mod h1:gHXdDEiPDvqd1q1KwBDGQlgZY/BwY760zU2LhOZS5w0= cloud.google.com/go/iap v1.9.0 h1:RNhVq/6OMI99/wjPVhqFxjlBxYOBRdaG6rLpBvyaqYY= cloud.google.com/go/iap v1.9.0/go.mod h1:01OFxd1R+NFrg78S+hoPV5PxEzv22HXaNqUUlmNHFuY= cloud.google.com/go/iap v1.9.1 h1:J5r6CL6EakRmsMRIm2yV0PF5zfIm4sMQbQfPhSTnRzA= @@ -1290,6 +1299,7 @@ cloud.google.com/go/logging v1.17.0 h1:rUFekZYwHiKElXCyz3zYBGz4BOeIqzgCKxVLdgrZ5 cloud.google.com/go/logging v1.17.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI= cloud.google.com/go/logging v1.18.0 h1:KhzZq+1cSkPH9YUaKLLhLtQxIHitVayBmk0sGfoM9+k= cloud.google.com/go/logging v1.18.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI= +cloud.google.com/go/logging v1.19.1/go.mod h1:2IkQ/d8jVJqV2qW8ZUGUiMjdZG1gkLD2JReGbZ8isqg= cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI= cloud.google.com/go/longrunning v0.5.2 h1:u+oFqfEwwU7F9dIELigxbe0XVnBAo9wqMuQLA50CZ5k= cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= @@ -1310,6 +1320,7 @@ cloud.google.com/go/longrunning v0.13.0 h1:dUfqF8y0bHOeZzF5+tKPZ6RBCeEEDOejvwGwE cloud.google.com/go/longrunning v0.13.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM= cloud.google.com/go/longrunning v1.0.0 h1:lwzWEYD8+NkYV7dhexOz6kmlvajZA70+bW/xMhRVVdY= cloud.google.com/go/longrunning v1.0.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM= +cloud.google.com/go/longrunning v1.2.0/go.mod h1:5KMQALFGOCtFoi2xSOA1u3H7WKlhmckgiyFw7+LGQp0= cloud.google.com/go/managedidentities v1.6.1 h1:2/qZuOeLgUHorSdxSQGtnOu9xQkBn37+j+oZQv/KHJY= cloud.google.com/go/managedidentities v1.6.2 h1:QijSmmWHb3EzYQr8SrjWe941ba9G5sTCF5PvhhMM8CM= cloud.google.com/go/managedidentities v1.6.2/go.mod h1:5c2VG66eCa0WIq6IylRk3TBW83l161zkFvCj28X7jn8= @@ -1724,6 +1735,7 @@ cloud.google.com/go/recommender v1.17.0 h1:pvtOLPVTCwMoG8FIQcvsCu9KeHXEIASPFXFoM cloud.google.com/go/recommender v1.17.0/go.mod h1:INRBLfBQJCrgPqjBVFht4OjaFq/WhB/c5V1sqBOdX8g= cloud.google.com/go/recommender v1.18.0 h1:NgL7zkQ4lSUQIe+aS5dMmDcOO6+DltH6E8bFD7wMjwk= cloud.google.com/go/recommender v1.18.0/go.mod h1:INRBLfBQJCrgPqjBVFht4OjaFq/WhB/c5V1sqBOdX8g= +cloud.google.com/go/recommender v1.19.0/go.mod h1:LRh+1HJjLx2kDE3S65AIlG/lvwA0llEFWYPD/QtgoaU= cloud.google.com/go/redis v1.13.1 h1:YrjQnCC7ydk+k30op7DSjSHw1yAYhqYXFcOq1bSXRYA= cloud.google.com/go/redis v1.13.2 h1:2ZtIGspMT65wern2rjX35XPCCJxVKF4J0P1S99bac3k= cloud.google.com/go/redis v1.13.2/go.mod h1:0Hg7pCMXS9uz02q+LoEVl5dNHUkIQv+C/3L76fandSA= @@ -1881,6 +1893,7 @@ cloud.google.com/go/securitycenter v1.43.0 h1:+I3wwAFjRxp12Avil9RfKiTa3B6E24a/XP cloud.google.com/go/securitycenter v1.43.0/go.mod h1:7BMMbSTAddVfiE+HrC8tKS6SuRkyK7FRPlkpAZBRV3U= cloud.google.com/go/securitycenter v1.44.0 h1:/jinB3GeXuNkWfrzK1EdWR+kD4J0z0YGyEe52+gPIoM= cloud.google.com/go/securitycenter v1.44.0/go.mod h1:7BMMbSTAddVfiE+HrC8tKS6SuRkyK7FRPlkpAZBRV3U= +cloud.google.com/go/securitycenter v1.46.0/go.mod h1:7mAlzsCsKlEVmciAFORl431laDGpoKGFkSQndAzFs30= cloud.google.com/go/servicecontrol v1.11.1 h1:d0uV7Qegtfaa7Z2ClDzr9HJmnbJW7jn0WhZ7wOX6hLE= cloud.google.com/go/servicedirectory v1.11.0 h1:pBWpjCFVGWkzVTkqN3TBBIqNSoSHY86/6RL0soSQ4z8= cloud.google.com/go/servicedirectory v1.11.1 h1:SXhbxsfQJBsUDeo743x5AnVe8ifC7qjXU3bSTT6t/+Q= @@ -1944,6 +1957,7 @@ cloud.google.com/go/spanner v1.88.0 h1:HS+5TuEYZOVOXj9K+0EtrbTw7bKBLrMe3vgGsbneh cloud.google.com/go/spanner v1.88.0/go.mod h1:MzulBwuuYwQUVdkZXBBFapmXee3N+sQrj2T/yup6uEE= cloud.google.com/go/spanner v1.91.0 h1:XwXfcZ0kc1NT9Uu2IsThFiWtYptB+WgLn/KZEZcyzRg= cloud.google.com/go/spanner v1.91.0/go.mod h1:8NB5a7qgwIhGD19Ly+vkpKffPL78vIG9RcrgsuREha0= +cloud.google.com/go/spanner v1.94.0/go.mod h1:Z2+83J5oVDmd1n5ntVMmjEuiNoXOpAyNeG7y1tuEHk0= cloud.google.com/go/speech v1.19.0 h1:MCagaq8ObV2tr1kZJcJYgXYbIn8Ai5rp42tyGYw9rls= cloud.google.com/go/speech v1.19.1 h1:z035FMLs98jpnqcP5xZZ6Es+g6utbeVoUH64BaTzTSU= cloud.google.com/go/speech v1.19.1/go.mod h1:WcuaWz/3hOlzPFOVo9DUsblMIHwxP589y6ZMtaG+iAA= @@ -2113,6 +2127,7 @@ cloud.google.com/go/video v1.31.0 h1:L+GyZG3h7Hu2J3B9cz5dD3R+YShuq5wAtdLUOxhBrBw cloud.google.com/go/video v1.31.0/go.mod h1:KxDL728ZzH+FJwtEb9XkiLTETW5bI37hTWbJiRYeXkk= cloud.google.com/go/video v1.32.0 h1:9Us/tkhNRg3WY9wIrVC3Jcs1P0nXKE4XnS1zYJ3xTTY= cloud.google.com/go/video v1.32.0/go.mod h1:KxDL728ZzH+FJwtEb9XkiLTETW5bI37hTWbJiRYeXkk= +cloud.google.com/go/video v1.33.0/go.mod h1:hEx8TNpQT6kdjMVsywePvT8BCb63Ee3F/R0GRa9wnzo= cloud.google.com/go/videointelligence v1.11.1 h1:MBMWnkQ78GQnRz5lfdTAbBq/8QMCF3wahgtHh3s/J+k= cloud.google.com/go/videointelligence v1.11.2 h1:vAKuM4YHwZy1W5P7hGJdfXriovqHHUZKhDBq8o4nqfg= cloud.google.com/go/videointelligence v1.11.2/go.mod h1:ocfIGYtIVmIcWk1DsSGOoDiXca4vaZQII1C85qtoplc= @@ -2128,6 +2143,7 @@ cloud.google.com/go/videointelligence v1.12.7 h1:FisUrSZ+y3oLuGdlFQQgZoNTDm7FAfb cloud.google.com/go/videointelligence v1.12.7/go.mod h1:XAk5hCMY+GihxJ55jNoMdwdXSNZnCl3wGs2+94gK7MA= cloud.google.com/go/videointelligence v1.16.0 h1:WSvC2OI6Su3ulwz0aS7qOVQHO7ZtohUyI6GMqvETY/o= cloud.google.com/go/videointelligence v1.16.0/go.mod h1:mmX1JpIWzwozaigrdRNjikZc3aFLNHFKh+OFwAdfiW4= +cloud.google.com/go/videointelligence v1.17.0/go.mod h1:Phxz7AQpvXoOvz+KrrOZEJRo4CDgYXMDVDqhCtdF1jc= cloud.google.com/go/vision v1.2.0 h1:/CsSTkbmO9HC8iQpxbK8ATms3OQaX3YQUeTMGCxlaK4= cloud.google.com/go/vision/v2 v2.7.2 h1:ccK6/YgPfGHR/CyESz1mvIbsht5Y2xRsWCPqmTNydEw= cloud.google.com/go/vision/v2 v2.7.3 h1:o8iiH4UsI6O8wO2Ax2r88fLG1RzYQIFevUQY7hXPZeM= @@ -2437,6 +2453,7 @@ github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -2552,6 +2569,7 @@ github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= @@ -2597,6 +2615,7 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= @@ -2801,8 +2820,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3 h1:/Um6a/ZmD5tF7peoOJ5oN5KMQ0DrGVQSXLNwyckutPk= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= @@ -2921,6 +2938,7 @@ github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3D github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= @@ -2940,8 +2958,6 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA= -github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA= -github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs= github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= @@ -3101,6 +3117,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -3332,6 +3349,7 @@ golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -3698,6 +3716,8 @@ golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= @@ -3848,6 +3868,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348/go.mod h1:Yzdzr5OOZFgSsEV2D/Xi9NL3bszpXFAg0hFJiRohcD8= google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 h1:3WsB1FAbiRIf2tOxscWKs3pQBD9he1NsrnbhMuWfekc= google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60/go.mod h1:7yoXV7RIh5gblj/xVYoogxAWvA9wUeVbpsK/M694l00= +google.golang.org/genproto/googleapis/api v0.0.0-20260818201246-1b0934165a6f/go.mod h1:q/3oV3jAi5vwelxsVAprMBC8BcM2zmNe+IjRGd+9/ks= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc h1:g3hIDl0jRNd9PPTs2uBzYuaD5mQuwOkZY0vSc0LR32o= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe h1:weYsP+dNijSQVoLAb5bpUos3ciBpNU/NEVlHFKrk8pg= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= @@ -3881,6 +3902,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260818201246-1b0934165a6f/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -3913,6 +3935,7 @@ google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhH google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6 h1:ExN12ndbJ608cboPYflpTny6mXSzPrDLh0iTaVrRrds= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8= @@ -3941,6 +3964,7 @@ google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -3952,6 +3976,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= diff --git a/handler/response.go b/handler/response.go index 264e731b..6faec79d 100644 --- a/handler/response.go +++ b/handler/response.go @@ -3,6 +3,7 @@ package handler import ( "fmt" "net/http" + "slices" "strings" httpV1proto "github.com/roadrunner-server/api-go/v6/http/v1" @@ -48,6 +49,13 @@ func (h *Handler) handlePROTOresponse(pld *payload.Payload, w http.ResponseWrite return err } + // The provided code must be a valid HTTP 1xx-5xx status code. + if rsp.Status < 100 || rsp.Status >= 600 { + http.Error(w, fmt.Sprintf("unknown status code from worker: %d", rsp.Status), http.StatusInternalServerError) + return errors.Errorf("unknown status code from worker: %d", rsp.Status) + } + status := int(rsp.Status) + // handle push headers if rsp.GetHeaders() != nil && rsp.GetHeaders()[HTTP2Push] != nil { push := rsp.GetHeaders()[HTTP2Push].GetValue() @@ -66,6 +74,18 @@ func (h *Handler) handlePROTOresponse(pld *payload.Payload, w http.ResponseWrite handleProtoTrailers(rsp.GetHeaders()) } + switch { + case status == http.StatusSwitchingProtocols: + h.log.Error("101 Switching Protocols is not supported, the frame was ignored") + return nil + case informational(status): + if len(pld.Body) != 0 { + h.log.Warn("informational response body was dropped", "status", status) + } + writeInformational(status, rsp.GetHeaders(), w) + return nil + } + // write all headers from the response to the writer for k, hv := range rsp.GetHeaders() { for _, v := range hv.GetValue() { @@ -73,13 +93,7 @@ func (h *Handler) handlePROTOresponse(pld *payload.Payload, w http.ResponseWrite } } - // The provided code must be a valid HTTP 1xx-5xx status code. - if rsp.Status < 100 || rsp.Status >= 600 { - http.Error(w, fmt.Sprintf("unknown status code from worker: %d", rsp.Status), http.StatusInternalServerError) - return errors.Errorf("unknown status code from worker: %d", rsp.Status) - } - - w.WriteHeader(int(rsp.Status)) + w.WriteHeader(status) } // do not write body if it is empty @@ -99,6 +113,38 @@ func (h *Handler) handlePROTOresponse(pld *payload.Payload, w http.ResponseWrite return nil } +func informational(status int) bool { + return status >= 100 && status < 200 && status != http.StatusSwitchingProtocols +} + +// writeInformational sends a 1xx response +func writeInformational(status int, headers map[string]*httpV1proto.HeaderValue, w http.ResponseWriter) { + hdr := w.Header() + saved := make(map[string][]string, len(headers)) + + for k := range headers { + ck := http.CanonicalHeaderKey(k) + if _, ok := saved[ck]; !ok { + saved[ck] = slices.Clone(hdr[ck]) + } + } + + for k, hv := range headers { + for _, v := range hv.GetValue() { + hdr.Add(k, string(v)) + } + } + + w.WriteHeader(status) + for ck, prev := range saved { + if prev == nil { + hdr.Del(ck) + continue + } + hdr[ck] = prev + } +} + func handleProtoTrailers(h map[string]*httpV1proto.HeaderValue) { for _, tr := range h[Trailer].GetValue() { for n := range strings.SplitSeq(string(tr), ",") { diff --git a/handler/response_test.go b/handler/response_test.go index aba97d98..c5c5ba12 100644 --- a/handler/response_test.go +++ b/handler/response_test.go @@ -290,6 +290,165 @@ func TestHandleProtoTrailers_RenamesAnnouncedHeaders(t *testing.T) { } } +type hintRecorder struct { + hdr http.Header + hints []hintFrame + code int + body []byte + wrote bool +} + +type hintFrame struct { + code int + header http.Header +} + +func (h *hintRecorder) Header() http.Header { + if h.hdr == nil { + h.hdr = http.Header{} + } + return h.hdr +} + +func (h *hintRecorder) WriteHeader(code int) { + if h.wrote { + return + } + if code >= 100 && code < 200 && code != http.StatusSwitchingProtocols { + h.hints = append(h.hints, hintFrame{code, h.Header().Clone()}) + return + } + h.wrote = true + h.code = code +} + +func (h *hintRecorder) Write(b []byte) (int, error) { + if !h.wrote { + h.WriteHeader(http.StatusOK) + } + h.body = append(h.body, b...) + return len(b), nil +} + +func TestWrite_EarlyHints_ScopedToInformationalResponse(t *testing.T) { + h := newTestHandler(t, defaultCfg(), nil) + rr := &hintRecorder{} + + hint := &payload.Payload{ + Codec: frame.CodecProto, + Context: marshalRsp(t, http.StatusEarlyHints, map[string]*httpV1proto.HeaderValue{ + "Link": headerValue("; rel=preload"), + }), + } + if err := h.Write(hint, rr); err != nil { + t.Fatal(err) + } + + if len(rr.hints) != 1 || rr.hints[0].code != http.StatusEarlyHints { + t.Fatalf("hints = %+v, want a single 103", rr.hints) + } + if got := rr.hints[0].header.Get("Link"); got != "; rel=preload" { + t.Errorf("hint Link = %q, want the preload link", got) + } + if got := rr.Header().Get("Link"); got != "" { + t.Errorf("Link = %q, want it removed after the informational response", got) + } + if rr.wrote { + t.Error("the informational frame must not close the response") + } + + final := &payload.Payload{ + Codec: frame.CodecProto, + Context: marshalRsp(t, http.StatusNotFound, map[string]*httpV1proto.HeaderValue{ + "X-Marker": headerValue("probe"), + }), + Body: []byte("body"), + } + if err := h.Write(final, rr); err != nil { + t.Fatal(err) + } + + if rr.code != http.StatusNotFound { + t.Errorf("status = %d, want %d", rr.code, http.StatusNotFound) + } + if got := rr.Header().Get("X-Marker"); got != "probe" { + t.Errorf("X-Marker = %q, want %q", got, "probe") + } + if got := rr.Header().Get("Link"); got != "" { + t.Errorf("Link = %q, want it absent from the final response", got) + } + if string(rr.body) != "body" { + t.Errorf("body = %q, want %q", rr.body, "body") + } +} + +func TestWrite_EarlyHints_PreexistingHeaderRestored(t *testing.T) { + h := newTestHandler(t, defaultCfg(), nil) + rr := &hintRecorder{} + rr.Header().Set("Link", "; rel=preload") + + hint := &payload.Payload{ + Codec: frame.CodecProto, + Context: marshalRsp(t, http.StatusEarlyHints, map[string]*httpV1proto.HeaderValue{ + "Link": headerValue("; rel=preload"), + }), + } + if err := h.Write(hint, rr); err != nil { + t.Fatal(err) + } + + want := []string{"; rel=preload", "; rel=preload"} + if got := rr.hints[0].header.Values("Link"); len(got) != 2 || got[0] != want[0] || got[1] != want[1] { + t.Errorf("hint Link = %v, want %v", got, want) + } + if got := rr.Header().Values("Link"); len(got) != 1 || got[0] != want[0] { + t.Errorf("Link = %v, want only the pre-existing %q", got, want[0]) + } +} + +func TestWrite_EarlyHints_BodyDropped(t *testing.T) { + h := newTestHandler(t, defaultCfg(), nil) + rr := &hintRecorder{} + + hint := &payload.Payload{ + Codec: frame.CodecProto, + Context: marshalRsp(t, http.StatusEarlyHints, nil), + Body: []byte("bogus"), + } + if err := h.Write(hint, rr); err != nil { + t.Fatal(err) + } + + if len(rr.body) != 0 { + t.Errorf("body = %q, want empty", rr.body) + } + if rr.wrote { + t.Error("the informational frame must not close the response") + } +} + +func TestWrite_SwitchingProtocols_Dropped(t *testing.T) { + h := newTestHandler(t, defaultCfg(), nil) + rr := &hintRecorder{} + + pld := &payload.Payload{ + Codec: frame.CodecProto, + Context: marshalRsp(t, http.StatusSwitchingProtocols, map[string]*httpV1proto.HeaderValue{ + "Upgrade": headerValue("websocket"), + }), + } + if err := h.Write(pld, rr); err != nil { + t.Fatal(err) + } + + if rr.wrote || len(rr.hints) != 0 { + t.Errorf("recorder = %+v, want no writes for a 101 frame", rr) + } + if got := rr.Header().Get("Upgrade"); got != "" { + t.Errorf("Upgrade = %q, want no headers from a dropped frame", got) + } +} + func TestWrite_Trailers_RenamedOnTheWire(t *testing.T) { h := newTestHandler(t, defaultCfg(), nil) diff --git a/middleware/log.go b/middleware/log.go index b1a265e8..98876d98 100644 --- a/middleware/log.go +++ b/middleware/log.go @@ -26,8 +26,9 @@ type wrapper struct { read int write int - // TwoXXSent is true if the response headers with >= 2xx code were sent - // 1xx header might be sent unlimited number of times + // wc is true once the final response header went out: a status >= 200, or + // a 101, which net/http treats as final. Informational 1xx headers may be + // written any number of times before that. wc bool w http.ResponseWriter @@ -41,13 +42,12 @@ func (w *wrapper) Read(b []byte) (int, error) { } func (w *wrapper) WriteHeader(code int) { - w.code = code if w.wc { return } - // do not allow sending 200 twice - if code >= 100 && code < 200 { + if code >= 200 || code == http.StatusSwitchingProtocols { + w.code = code w.wc = true } diff --git a/middleware/log_test.go b/middleware/log_test.go new file mode 100644 index 00000000..8b2c1735 --- /dev/null +++ b/middleware/log_test.go @@ -0,0 +1,112 @@ +package middleware + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +// recorderWriter records every call forwarded by the wrapper. +type recorderWriter struct { + hdr http.Header + codes []int + body []byte +} + +func (r *recorderWriter) Header() http.Header { + if r.hdr == nil { + r.hdr = http.Header{} + } + return r.hdr +} + +func (r *recorderWriter) WriteHeader(code int) { + r.codes = append(r.codes, code) +} + +func (r *recorderWriter) Write(b []byte) (int, error) { + r.body = append(r.body, b...) + return len(b), nil +} + +func newTestWrapper() (*wrapper, *recorderWriter) { + rec := &recorderWriter{} + return &wrapper{code: http.StatusOK, w: rec}, rec +} + +// A 1xx informational header does not lock the response: the final status +// written after it must reach the underlying writer (issue roadrunner#2381). +func TestWrapper_EarlyHintThenFinal(t *testing.T) { + w, rec := newTestWrapper() + + w.WriteHeader(http.StatusEarlyHints) + w.WriteHeader(http.StatusNotFound) + + assert.Equal(t, []int{http.StatusEarlyHints, http.StatusNotFound}, rec.codes) + assert.Equal(t, http.StatusNotFound, w.code) +} + +func TestWrapper_SecondFinalDropped(t *testing.T) { + w, rec := newTestWrapper() + + w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusInternalServerError) + + assert.Equal(t, []int{http.StatusOK}, rec.codes) + assert.Equal(t, http.StatusOK, w.code) +} + +// A 1xx after the final status is invalid and must not reach the writer. +func TestWrapper_HintAfterFinalDropped(t *testing.T) { + w, rec := newTestWrapper() + + w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusEarlyHints) + + assert.Equal(t, []int{http.StatusOK}, rec.codes) +} + +// net/http treats 101 as a final status, so it locks the wrapper too. +func TestWrapper_SwitchingProtocolsIsFinal(t *testing.T) { + w, rec := newTestWrapper() + + w.WriteHeader(http.StatusSwitchingProtocols) + w.WriteHeader(http.StatusOK) + + assert.Equal(t, []int{http.StatusSwitchingProtocols}, rec.codes) + assert.Equal(t, http.StatusSwitchingProtocols, w.code) +} + +// A body written without a final status keeps the implicit 200 for the log. +func TestWrapper_HintThenBodyKeepsImplicit200(t *testing.T) { + w, rec := newTestWrapper() + + w.WriteHeader(http.StatusEarlyHints) + _, err := w.Write([]byte("body")) + + assert.NoError(t, err) + assert.Equal(t, []int{http.StatusEarlyHints}, rec.codes) + assert.Equal(t, http.StatusOK, w.code) + assert.Equal(t, 4, w.write) + + // the response is locked by the body write + w.WriteHeader(http.StatusNotFound) + assert.Equal(t, []int{http.StatusEarlyHints}, rec.codes) + assert.Equal(t, http.StatusOK, w.code) +} + +func TestWrapper_ResetClearsState(t *testing.T) { + w, _ := newTestWrapper() + + w.WriteHeader(http.StatusNotFound) + _, err := w.Write([]byte("body")) + assert.NoError(t, err) + + w.reset() + + assert.Equal(t, http.StatusOK, w.code) + assert.False(t, w.wc) + assert.Zero(t, w.read) + assert.Zero(t, w.write) +} diff --git a/tests/configs/.rr-issue2381.yaml b/tests/configs/.rr-issue2381.yaml new file mode 100644 index 00000000..ccbb1ff0 --- /dev/null +++ b/tests/configs/.rr-issue2381.yaml @@ -0,0 +1,23 @@ +version: "3" + +rpc: + listen: tcp://127.0.0.1:6066 + +server: + command: "php php_test_files/workers/issue2381.php" + relay: "pipes" + relay_timeout: "20s" + +http: + address: 127.0.0.1:19984 + max_request_size: 1024 + middleware: [] + uploads: + forbid: [".php", ".exe", ".bat"] + pool: + num_workers: 1 + destroy_timeout: 100ms + +logs: + mode: development + level: panic diff --git a/tests/go.mod b/tests/go.mod index 904c0194..eff7dc02 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -2,7 +2,7 @@ module tests go 1.26 -toolchain go1.26.6 +toolchain go1.27.0 require ( github.com/quic-go/quic-go v0.61.0 @@ -21,14 +21,14 @@ require ( github.com/roadrunner-server/send/v6 v6.0.0-beta.4 github.com/roadrunner-server/server/v6 v6.0.0-beta.6 github.com/roadrunner-server/static/v6 v6.0.0-beta.4 - github.com/stretchr/testify v1.12.0 + github.com/stretchr/testify v1.12.1 github.com/yookoala/gofast v0.8.0 golang.org/x/net v0.58.0 ) require ( go.uber.org/zap v1.28.0 // indirect - google.golang.org/genproto v0.0.0-20260818201246-1b0934165a6f // indirect + google.golang.org/genproto v0.0.0-20260819154853-08b0e4226688 // indirect ) replace github.com/roadrunner-server/http/v6 => ../ @@ -102,5 +102,4 @@ require ( golang.org/x/text v0.41.0 // indirect golang.org/x/tools/godoc v0.1.0-deprecated // indirect google.golang.org/protobuf v1.36.12 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tests/go.sum b/tests/go.sum index 25b81079..84e3a265 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -149,8 +149,8 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= -github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= @@ -238,13 +238,8 @@ golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndn golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20260818201246-1b0934165a6f h1:mIpuZeRssq9TYLzKcruaLOCbOgJt8WDF1FSyEPZBw24= -google.golang.org/genproto v0.0.0-20260818201246-1b0934165a6f/go.mod h1:UpDDw2l68z31m5UZN/Qi0Kow16ohhlJVlmqC3qRM5Q8= +google.golang.org/genproto v0.0.0-20260819154853-08b0e4226688 h1:2ficYAs+4TBEqohUu9hr7J2YeNTie+3BVyqERjs5Hm0= +google.golang.org/genproto v0.0.0-20260819154853-08b0e4226688/go.mod h1:icDJeJwWhZtQDn/1WGql+0n01hbizh4G7/T75RoxcHs= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tests/issues_test.go b/tests/issues_test.go index 407974bb..822a1f46 100644 --- a/tests/issues_test.go +++ b/tests/issues_test.go @@ -1,6 +1,10 @@ package tests import ( + "io" + "net/http" + "net/http/httptrace" + "net/textproto" "runtime" "testing" @@ -48,6 +52,43 @@ func TestBug1843(t *testing.T) { require.Equal(t, 1, rr.Logs.FilterMessageSnippet("PHP Fatal error: Uncaught RuntimeException").Len()) } +func TestHTTPIssue2381(t *testing.T) { + helpers.Start(t, "configs/.rr-issue2381.yaml", []any{ + &server.Plugin{}, + &httpPlugin.Plugin{}, + }, helpers.WithTCPProbe("127.0.0.1:19984")) + + // control request without a hint frame + helpers.AssertGet(t, "http://127.0.0.1:19984/plain", http.StatusNotFound, "body") + + var hints []hint + ctx := httptrace.WithClientTrace(t.Context(), &httptrace.ClientTrace{ + Got1xxResponse: func(code int, header textproto.MIMEHeader) error { + hints = append(hints, hint{code, http.Header(header).Clone()}) + return nil + }, + }) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1:19984/hint", nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + + require.Equal(t, http.StatusNotFound, resp.StatusCode) + require.Equal(t, "body", string(body)) + require.Equal(t, "probe", resp.Header.Get("X-Marker")) + require.Empty(t, resp.Header.Get("Link"), "the hint Link header leaked into the final response") + + require.Len(t, hints, 1) + require.Equal(t, http.StatusEarlyHints, hints[0].code) + require.Equal(t, "; rel=preload", hints[0].header.Get("Link")) +} + func TestHttpBrokenPipes(t *testing.T) { _ = helpers.StartExpectServeError(t, "configs/.rr-broken-pipes.yaml", []any{ &server.Plugin{}, diff --git a/tests/php_test_files/workers/issue2381.php b/tests/php_test_files/workers/issue2381.php new file mode 100644 index 00000000..ad1a91a3 --- /dev/null +++ b/tests/php_test_files/workers/issue2381.php @@ -0,0 +1,20 @@ +waitRequest()) { + if (str_contains($request->uri, "hint")) { + $http->respond( + 103, + "", + ["Link" => ["; rel=preload"]], + endOfStream: false, + ); + } + $http->respond(404, "body", ["X-Marker" => ["probe"]]); +} diff --git a/tests/streaming_test.go b/tests/streaming_test.go index 828efdf2..041faa94 100644 --- a/tests/streaming_test.go +++ b/tests/streaming_test.go @@ -5,6 +5,8 @@ import ( "context" "io" "net/http" + "net/http/httptrace" + "net/textproto" "os" "strconv" "strings" @@ -55,26 +57,43 @@ func TestStreamDie(t *testing.T) { require.Equal(t, 1, rr.Logs.FilterMessageSnippet("read stream").Len()) } -// The worker sends four informational responses (100 to 103, the last one an -// Early Hints with a Link header) before the 200 carrying the body; the headers -// of all of them reach the client. +type hint struct { + code int + header http.Header +} + func TestStream103(t *testing.T) { helpers.Start(t, "configs/.rr-stream-103.yaml", []any{ &server.Plugin{}, &httpPlugin.Plugin{}, }, helpers.WithConfigVersion("2023.3.0"), helpers.WithTCPProbe("127.0.0.1:19983")) - header := requireStream(t, "http://127.0.0.1:19983", 10) - - for k, v := range map[string]string{ - "Link": "; rel=preload; as=style", - "X-100": "100", - "X-101": "101", - "X-102": "102", - "X-103": "103", - "X-200": "200", - } { - require.Equal(t, v, header.Get(k)) + var hints []hint + ctx := httptrace.WithClientTrace(t.Context(), &httptrace.ClientTrace{ + Got1xxResponse: func(code int, header textproto.MIMEHeader) error { + hints = append(hints, hint{code, http.Header(header).Clone()}) + return nil + }, + }) + + header := requireStreamCtx(t, ctx, "http://127.0.0.1:19983", 10) + + require.Len(t, hints, 3) + + require.Equal(t, http.StatusContinue, hints[0].code) + require.Equal(t, "100", hints[0].header.Get("X-100")) + + require.Equal(t, http.StatusProcessing, hints[1].code) + require.Equal(t, "102", hints[1].header.Get("X-102")) + require.Empty(t, hints[1].header.Get("X-100"), "an interim response must carry only its own headers") + + require.Equal(t, http.StatusEarlyHints, hints[2].code) + require.Equal(t, "103", hints[2].header.Get("X-103")) + require.Equal(t, "; rel=preload; as=style", hints[2].header.Get("Link")) + + require.Equal(t, "200", header.Get("X-200")) + for _, k := range []string{"Link", "X-100", "X-101", "X-102", "X-103"} { + require.Empty(t, header.Get(k), "%s leaked into the final response", k) } } @@ -130,12 +149,18 @@ func TestHTTPBigRespMaxReqSize(t *testing.T) { // requireStream issues a GET, requires a 200 and reads the streamed body line by // line: the workers write the 1-based index of every chunk they send, so wantLines -// lines in that order say the chunks arrived one by one and intact. The response -// headers are returned with the body already closed. -func requireStream(t *testing.T, url string, wantLines int) http.Header { +// lines in that order say the chunks arrived one by one and intact. +func requireStream(t *testing.T, url string, wantLines int) { + t.Helper() + requireStreamCtx(t, t.Context(), url, wantLines) +} + +// requireStreamCtx is requireStream with a caller-supplied context and returns +// the final response headers with the body already closed. +func requireStreamCtx(t *testing.T, ctx context.Context, url string, wantLines int) http.Header { t.Helper() - req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) require.NoError(t, err) resp, err := http.DefaultClient.Do(req) From ab8f509e4ac3302759a7dd4f9c2b1904ae3b6150 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Fri, 21 Aug 2026 07:58:55 +0200 Subject: [PATCH 2/2] chore: update Go to 1.27 Signed-off-by: Valery Piashchynski --- go.mod | 2 +- go.work | 2 +- tests/go.mod | 21 +++++++++------------ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 00c9a5f9..fa59961b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/roadrunner-server/http/v6 -go 1.26 +go 1.27 toolchain go1.27.0 diff --git a/go.work b/go.work index 41406b99..e18a1270 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.26 +go 1.27 use ( . diff --git a/tests/go.mod b/tests/go.mod index eff7dc02..5cc01712 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -1,12 +1,13 @@ module tests -go 1.26 +go 1.27 toolchain go1.27.0 require ( github.com/quic-go/quic-go v0.61.0 github.com/roadrunner-server/config/v6 v6.0.0-beta.3 + github.com/roadrunner-server/context v1.3.0 github.com/roadrunner-server/endure/v2 v2.6.2 github.com/roadrunner-server/fileserver/v6 v6.0.0-beta.5 github.com/roadrunner-server/goridge/v4 v4.0.0-beta.3 @@ -23,16 +24,12 @@ require ( github.com/roadrunner-server/static/v6 v6.0.0-beta.4 github.com/stretchr/testify v1.12.1 github.com/yookoala/gofast v0.8.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 + go.opentelemetry.io/otel v1.45.0 + go.opentelemetry.io/otel/sdk v1.45.0 golang.org/x/net v0.58.0 ) -require ( - go.uber.org/zap v1.28.0 // indirect - google.golang.org/genproto v0.0.0-20260819154853-08b0e4226688 // indirect -) - -replace github.com/roadrunner-server/http/v6 => ../ - require ( github.com/andybalholm/brotli v1.2.2 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -68,7 +65,6 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/roadrunner-server/api-go/v6 v6.0.0-beta.14 // indirect github.com/roadrunner-server/api-plugins/v6 v6.0.0-beta.2 // indirect - github.com/roadrunner-server/context v1.3.0 github.com/roadrunner-server/errors v1.5.0 // indirect github.com/roadrunner-server/events v1.0.1 // indirect github.com/roadrunner-server/tcplisten v1.5.2 // indirect @@ -87,13 +83,11 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 go.opentelemetry.io/contrib/propagators/jaeger v1.45.0 // indirect - go.opentelemetry.io/otel v1.45.0 go.opentelemetry.io/otel/metric v1.45.0 // indirect - go.opentelemetry.io/otel/sdk v1.45.0 go.opentelemetry.io/otel/trace v1.45.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.28.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/crypto v0.55.0 // indirect @@ -101,5 +95,8 @@ require ( golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/tools/godoc v0.1.0-deprecated // indirect + google.golang.org/genproto v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/protobuf v1.36.12 // indirect ) + +replace github.com/roadrunner-server/http/v6 => ../