Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5567f9e
revert: back to http/v1 payload protos
rustatian Aug 12, 2026
01f6e42
tests: pin sibling revert branches until the new betas are tagged
rustatian Aug 12, 2026
06e005d
fix: run tracetestOtel before gzip in the otel test configs
rustatian Aug 13, 2026
df1fb32
fix: count only the execTTL restart in TestHTTPExecTTL
rustatian Aug 13, 2026
fae0c35
fix: h2c upgrade requests are served as plain HTTP/1.1
rustatian Aug 13, 2026
738623f
chore: comment wording
rustatian Aug 13, 2026
a92bb5b
chore: rename test var
rustatian Aug 13, 2026
2bdf0fc
fix: measure plugin coverage in CI
rustatian Aug 13, 2026
b9ade93
chore: drop dead test fixtures
rustatian Aug 13, 2026
0b817d6
feature: unit tests for the plugin packages
rustatian Aug 13, 2026
c275368
chore: shared e2e test helpers
rustatian Aug 13, 2026
c3c2cc9
chore: group php fixtures by role
rustatian Aug 13, 2026
9ee3182
chore: regroup core, workers, and issue e2e tests
rustatian Aug 13, 2026
1e0a7be
chore: rework handler and upload e2e tests
rustatian Aug 13, 2026
a0aead1
chore: regroup TLS, protocol, and FCGI e2e tests
rustatian Aug 13, 2026
43763ab
chore: regroup static, streaming, and otel e2e tests
rustatian Aug 13, 2026
e19579c
feature: e2e tests for raw_body, mid-stream worker death, and trailers
rustatian Aug 13, 2026
8116b7e
chore: go mod tidy for the testutil dependency
rustatian Aug 13, 2026
b9091e0
fix: restore the near-limit body in the urlencoded size table
rustatian Aug 13, 2026
4de2897
fix: generate the https unit-test certificates in-test
rustatian Aug 13, 2026
967dd6b
Merge branch 'master' into revert/protos-v1
rustatian Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:

- name: Run HTTP unit tests with coverage
run: |
go test -timeout 20m -v -race -cover -tags=debug -failfast -coverpkg=./... -coverprofile=./tests/coverage-ci/httpu.out -covermode=atomic ./handler
go test -timeout 20m -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./tests/coverage-ci/httpu.out -covermode=atomic ./...

- name: Run golang tests with coverage
run: |
Expand All @@ -85,11 +85,7 @@ jobs:
cp -r localhost+2-client-key.pem localhost+2-client.pem localhost+2-key.pem localhost+2.pem test-certs/
cp -r $(mkcert -CAROOT)/rootCA.pem test-certs/

docker compose -f env/docker-compose-otel.yaml up -d
sleep 30
go test -timeout 20m -v -race -cover -tags=debug -failfast -coverpkg=./... -coverprofile=./coverage-ci/http.out -covermode=atomic attributes_test.go handler_test.go http_otlp_test.go http_plugin2_test.go http_plugin3_test.go http_plugin4_test.go http_plugin_test.go uploads_test.go

docker compose -f env/docker-compose-otel.yaml down
go test -timeout 20m -v -race -cover -tags=debug -coverpkg=github.com/roadrunner-server/http/v6/... -coverprofile=./coverage-ci/http.out -covermode=atomic .

- name: Archive code coverage results
uses: actions/upload-artifact@v7
Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ vendor/
**/tests/php_test_files/.composer.lock
**/composer.lock
**/.claude/settings.local.json
servers/buf/
servers/buf/

# Big test fixtures generated by tests/main_test.go
tests/sample-big.txt
tests/php_test_files/well
# Written by big-resp-worker.php during the test run
tests/php_test_files/big-resp
86 changes: 86 additions & 0 deletions acme/acme_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package acme

import (
"strings"
"testing"
)

func TestConfigInitDefaults(t *testing.T) {
tests := []struct {
name string
cfg Config
wantErr string
wantCacheDir string
wantChallengeType string
wantAltHTTPPort int
}{
{
name: "minimal config gets every default",
cfg: Config{Email: "a@b.c", Domains: []string{"x.com"}},
wantCacheDir: "rr_cache_dir",
wantChallengeType: "http-01",
wantAltHTTPPort: 80,
},
{
name: "email is mandatory",
cfg: Config{Domains: []string{"x.com"}},
wantErr: "email could not be empty",
},
{
name: "at least one domain is mandatory",
cfg: Config{Email: "a@b.c"},
wantErr: "should be at least 1 domain",
},
{
name: "explicit cache dir is preserved",
cfg: Config{Email: "a@b.c", Domains: []string{"x.com"}, CacheDir: "/tmp/le"},
wantCacheDir: "/tmp/le",
wantChallengeType: "http-01",
wantAltHTTPPort: 80,
},
{
name: "tlsalpn challenge leaves the http port alone",
cfg: Config{Email: "a@b.c", Domains: []string{"x.com"}, ChallengeType: "tlsalpn-01"},
wantCacheDir: "rr_cache_dir",
wantChallengeType: "tlsalpn-01",
wantAltHTTPPort: 0,
},
{
name: "explicit alt http port is preserved",
cfg: Config{Email: "a@b.c", Domains: []string{"x.com"}, AltHTTPPort: 8080},
wantCacheDir: "rr_cache_dir",
wantChallengeType: "http-01",
wantAltHTTPPort: 8080,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := tt.cfg
err := cfg.InitDefaults()

if tt.wantErr != "" {
if err == nil {
t.Fatalf("expected an error containing %q", tt.wantErr)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("error = %v, want it to contain %q", err, tt.wantErr)
}
return
}

if err != nil {
t.Fatal(err)
}
if cfg.CacheDir != tt.wantCacheDir {
t.Errorf("CacheDir = %q, want %q", cfg.CacheDir, tt.wantCacheDir)
}
if cfg.ChallengeType != tt.wantChallengeType {
t.Errorf("ChallengeType = %q, want %q", cfg.ChallengeType, tt.wantChallengeType)
}
if cfg.AltHTTPPort != tt.wantAltHTTPPort {
t.Errorf("AltHTTPPort = %d, want %d", cfg.AltHTTPPort, tt.wantAltHTTPPort)
}
})
}
}
129 changes: 129 additions & 0 deletions attributes/attributes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package attributes

import (
"context"
"net/http"
"testing"

rrcontext "github.com/roadrunner-server/context"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// withPsrValue returns a request whose context holds an arbitrary value under
// the psr attributes key.
func withPsrValue(value any) *http.Request {
r := &http.Request{}
return r.WithContext(context.WithValue(r.Context(), rrcontext.PsrContextKey, value))
}

func TestAllAttributes(t *testing.T) {
r := &http.Request{}
r = Init(r)

require.NoError(t, Set(r, "key", "value"))
assert.Equal(t, map[string][]string{"key": {"value"}}, All(r))
}

func TestAllAttributesNone(t *testing.T) {
r := &http.Request{}
r = Init(r)

assert.Equal(t, map[string][]string{}, All(r))
}

func TestAllAttributesNone2(t *testing.T) {
r := &http.Request{}

assert.Nil(t, All(r))
}

func TestGetAttribute(t *testing.T) {
r := &http.Request{}
r = Init(r)

require.NoError(t, Set(r, "key", "value"))
assert.Equal(t, []string{"value"}, Get(r, "key"))
}

func TestGetAttributeNone(t *testing.T) {
r := &http.Request{}
r = Init(r)
assert.Nil(t, Get(r, "key"))
}

func TestGetAttributeNone2(t *testing.T) {
r := &http.Request{}

assert.Nil(t, Get(r, "key"))
}

func TestSetAttribute(t *testing.T) {
r := &http.Request{}
r = Init(r)

require.NoError(t, Set(r, "key", "value"))
assert.Equal(t, []string{"value"}, Get(r, "key"))
}

func TestSetAttributeNone(t *testing.T) {
r := &http.Request{}
err := Set(r, "key", "value")
assert.Error(t, err)
assert.Nil(t, Get(r, "key"))
}

// All accepts the two foreign map shapes middleware may store under the key.
func TestAllForeignMapShapes(t *testing.T) {
multiValue := withPsrValue(map[string][]string{"a": {"1", "2"}})
assert.Equal(t, map[string][]string{"a": {"1", "2"}}, All(multiValue))

singleValue := withPsrValue(map[string]string{"a": "1"})
assert.Equal(t, map[string][]string{"a": {"1"}}, All(singleValue))
}

func TestAllUnexpectedType(t *testing.T) {
assert.Nil(t, All(withPsrValue(42)))
}

// Init must not overwrite attributes stored by an earlier middleware.
func TestInitKeepsExistingBag(t *testing.T) {
r := Init(&http.Request{})
require.NoError(t, Set(r, "key", "value"))

same := Init(r)
assert.Same(t, r, same)
assert.Equal(t, []string{"value"}, Get(same, "key"))
}

func TestGetUnexpectedType(t *testing.T) {
assert.Nil(t, Get(withPsrValue(42), "key"))
}

func TestSetUnexpectedType(t *testing.T) {
err := Set(withPsrValue(42), "key", "value")
require.Error(t, err)
assert.Contains(t, err.Error(), "unexpected type stored under")
}

// Set appends to an existing key instead of replacing it.
func TestSetAppends(t *testing.T) {
r := Init(&http.Request{})

require.NoError(t, Set(r, "key", "v1"))
require.NoError(t, Set(r, "key", "v2"))

assert.Equal(t, []string{"v1", "v2"}, Get(r, "key"))
}

func TestAttrsGetNil(t *testing.T) {
assert.Equal(t, "", attrs(nil).get("key"))
}

func TestAttrsDelete(t *testing.T) {
assert.NotPanics(t, func() { attrs(nil).Delete("key") })

bag := attrs{"key": {"value"}, "other": {"value"}}
bag.Delete("key")
assert.Equal(t, attrs{"other": {"value"}}, bag)
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/mholt/acmez v1.2.0
github.com/prometheus/client_golang v1.24.1
github.com/quic-go/quic-go v0.61.0
github.com/roadrunner-server/api-go/v6 v6.0.0-beta.13
github.com/roadrunner-server/api-go/v6 v6.0.0-beta.14
github.com/roadrunner-server/api-plugins/v6 v6.0.0-beta.2
github.com/roadrunner-server/context v1.3.0
github.com/roadrunner-server/endure/v2 v2.6.2
Expand Down Expand Up @@ -41,6 +41,7 @@ require (
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/mholt/acmez/v3 v3.1.6 // indirect
github.com/miekg/dns v1.1.72 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ 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=
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
Expand Down Expand Up @@ -62,8 +64,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
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/roadrunner-server/api-go/v6 v6.0.0-beta.13 h1:BAV1aKkRp51C1OXDfEYZXgfrXqn4O7bpr6Z/m5otwd8=
github.com/roadrunner-server/api-go/v6 v6.0.0-beta.13/go.mod h1:Y4rsabWjr4Y10Jg6H8J5NDitQqlnXmGhCdgR+zyLYkI=
github.com/roadrunner-server/api-go/v6 v6.0.0-beta.14 h1:sTskv/3ImOZlUdtHuj9uT24gm1gQl/qU8rFNvn3MzhU=
github.com/roadrunner-server/api-go/v6 v6.0.0-beta.14/go.mod h1:Y4rsabWjr4Y10Jg6H8J5NDitQqlnXmGhCdgR+zyLYkI=
github.com/roadrunner-server/api-plugins/v6 v6.0.0-beta.2 h1:GqsZzWQ5jMXRF1O/b8IqFz9PLpS7Ui0K4OyACLql2MI=
github.com/roadrunner-server/api-plugins/v6 v6.0.0-beta.2/go.mod h1:2v4yUK5Kvbvq8C3IkDoBkuamq9h+7i/JLjyf7k1j5JM=
github.com/roadrunner-server/context v1.3.0 h1:iyTXVORhPU2/26z7kdzEaggwG5P8yhIKUDLiePjylFQ=
Expand Down
20 changes: 11 additions & 9 deletions handler/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,42 @@ package handler
import (
"net/http"

httpV2proto "github.com/roadrunner-server/api-go/v6/http/v2"
httpV1proto "github.com/roadrunner-server/api-go/v6/http/v1"
)

func convert(headers http.Header) map[string]*httpV2proto.HttpHeaderValue {
func convert(headers http.Header) map[string]*httpV1proto.HeaderValue {
if len(headers) == 0 {
return nil
}

resp := make(map[string]*httpV2proto.HttpHeaderValue, len(headers))
resp := make(map[string]*httpV1proto.HeaderValue, len(headers))

for k, v := range headers {
if resp[k] == nil {
resp[k] = &httpV2proto.HttpHeaderValue{}
resp[k] = &httpV1proto.HeaderValue{}
}

resp[k].Values = append(resp[k].GetValues(), v...)
for _, vv := range v {
resp[k].Value = append(resp[k].GetValue(), []byte(vv))
}
}

return resp
}

func convertCookies(headers map[string]string) map[string]*httpV2proto.HttpHeaderValue {
func convertCookies(headers map[string]string) map[string]*httpV1proto.HeaderValue {
if len(headers) == 0 {
return nil
}

resp := make(map[string]*httpV2proto.HttpHeaderValue, len(headers))
resp := make(map[string]*httpV1proto.HeaderValue, len(headers))

for k, v := range headers {
if resp[k] == nil {
resp[k] = &httpV2proto.HttpHeaderValue{}
resp[k] = &httpV1proto.HeaderValue{}
}

resp[k].Values = append(resp[k].GetValues(), v)
resp[k].Value = append(resp[k].GetValue(), []byte(v))
}

return resp
Expand Down
8 changes: 4 additions & 4 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

"github.com/roadrunner-server/http/v6/api"

httpV2proto "github.com/roadrunner-server/api-go/v6/http/v2"
httpV1proto "github.com/roadrunner-server/api-go/v6/http/v1"
"github.com/roadrunner-server/errors"
"github.com/roadrunner-server/goridge/v4/pkg/frame"
"github.com/roadrunner-server/http/v6/config"
Expand Down Expand Up @@ -90,15 +90,15 @@ func NewHandler(cfg *config.Config, pool api.Pool, log *slog.Logger) (*Handler,
},
protoRespPool: sync.Pool{
New: func() any {
return &httpV2proto.HttpHandlerResponse{
Headers: make(map[string]*httpV2proto.HttpHeaderValue),
return &httpV1proto.Response{
Headers: make(map[string]*httpV1proto.HeaderValue),
Status: -1,
}
},
},
protoReqPool: sync.Pool{
New: func() any {
return &httpV2proto.HttpHandlerRequest{}
return &httpV1proto.Request{}
},
},
pldPool: sync.Pool{
Expand Down
Loading
Loading