diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index e25ce8cc..6d017b0f 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -31,6 +31,7 @@ const ( OnCookie = "cookie" OnForm = "form" OnPostForm = "postform" + OnProto = "proto" OnMethod = "method" OnHost = "host" OnPath = "path" @@ -226,6 +227,41 @@ var checkers = map[string]struct { } }, }, + OnProto: { + help: Help{ + command: OnProto, + args: map[string]string{ + "proto": "the http protocol (http, https, h3)", + }, + }, + validate: func(args []string) (any, gperr.Error) { + if len(args) != 1 { + return nil, ErrExpectOneArg + } + proto := args[0] + if proto != "http" && proto != "https" && proto != "h3" { + return nil, ErrInvalidArguments.Withf("proto: %q", proto) + } + return proto, nil + }, + builder: func(args any) CheckFunc { + proto := args.(string) + switch proto { + case "http": + return func(w http.ResponseWriter, r *http.Request) bool { + return r.TLS == nil + } + case "https": + return func(w http.ResponseWriter, r *http.Request) bool { + return r.TLS != nil + } + default: // h3 + return func(w http.ResponseWriter, r *http.Request) bool { + return r.TLS != nil && r.ProtoMajor == 3 + } + } + }, + }, OnMethod: { help: Help{ command: OnMethod, diff --git a/internal/route/rules/on_test.go b/internal/route/rules/on_test.go index 1b47d19b..7b1492b3 100644 --- a/internal/route/rules/on_test.go +++ b/internal/route/rules/on_test.go @@ -1,6 +1,7 @@ package rules_test import ( + "crypto/tls" "encoding/base64" "fmt" "net/http" @@ -65,6 +66,30 @@ func genCorrectnessTestCases(field string, genRequest func(k, v string) *http.Re func TestOnCorrectness(t *testing.T) { tests := []testCorrectness{ + { + name: "proto_match_http", + checker: "proto http", + input: &http.Request{TLS: nil}, + want: true, + }, + { + name: "proto_match_https", + checker: "proto https", + input: &http.Request{TLS: &tls.ConnectionState{}}, + want: true, + }, + { + name: "proto_match_h3", + checker: "proto h3", + input: &http.Request{TLS: &tls.ConnectionState{}, ProtoMajor: 3}, + want: true, + }, + { + name: "proto_no_match_h3", + checker: "proto h3", + input: &http.Request{TLS: &tls.ConnectionState{}, ProtoMajor: 2}, + want: false, + }, { name: "method_match", checker: "method GET",