Skip to content

Commit

Permalink
feat: support redirect and requestHeaderModifier in HTTPRoute filter (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
AlinsRan authored Nov 9, 2022
1 parent bfd058d commit 6f83da5
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 6 deletions.
72 changes: 72 additions & 0 deletions pkg/providers/gateway/translation/gateway_httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,76 @@ import (
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

func (t *translator) generatePluginsFromHTTPRouteFilter(filters []gatewayv1alpha2.HTTPRouteFilter) apisixv1.Plugins {
plugins := apisixv1.Plugins{}
for _, filter := range filters {
switch filter.Type {
case gatewayv1alpha2.HTTPRouteFilterRequestHeaderModifier:
t.generatePluginFromHTTPRequestHeaderFilter(plugins, filter.RequestHeaderModifier)
case gatewayv1alpha2.HTTPRouteFilterRequestRedirect:
t.generatePluginFromHTTPRequestRedirectFilter(plugins, filter.RequestRedirect)
case gatewayv1alpha2.HTTPRouteFilterRequestMirror:
// to do
}
}
return plugins
}

func (t *translator) generatePluginFromHTTPRequestHeaderFilter(plugins apisixv1.Plugins, reqHeaderModifier *gatewayv1alpha2.HTTPRequestHeaderFilter) {
if reqHeaderModifier == nil {
return
}
headers := map[string]any{}
// TODO: The current apisix plugin does not conform to the specification.
for _, header := range reqHeaderModifier.Add {
headers[string(header.Name)] = header.Value
}
for _, header := range reqHeaderModifier.Set {
headers[string(header.Name)] = header.Value
}
for _, header := range reqHeaderModifier.Remove {
headers[header] = ""
}

plugins["proxy-rewrite"] = apisixv1.RewriteConfig{
Headers: headers,
}
}

func (t *translator) generatePluginFromHTTPRequestRedirectFilter(plugins apisixv1.Plugins, reqRedirect *gatewayv1alpha2.HTTPRequestRedirectFilter) {
if reqRedirect == nil {
return
}

var uri string

code := 302
if reqRedirect.StatusCode != nil {
code = *reqRedirect.StatusCode
}

hostname := "$host"
if reqRedirect.Hostname != nil {
hostname = string(*reqRedirect.Hostname)
}

scheme := "$scheme"
if reqRedirect.Scheme != nil {
scheme = *reqRedirect.Scheme
}

if reqRedirect.Port != nil {
uri = fmt.Sprintf("%s://%s:%d$request_uri", scheme, hostname, int(*reqRedirect.Port))
} else {
uri = fmt.Sprintf("%s://%s$request_uri", scheme, hostname)
}

plugins["redirect"] = apisixv1.RedirectConfig{
RetCode: code,
URI: uri,
}
}

func (t *translator) TranslateGatewayHTTPRouteV1Alpha2(httpRoute *gatewayv1alpha2.HTTPRoute) (*translation.TranslateContext, error) {
ctx := translation.DefaultEmptyTranslateContext()

Expand Down Expand Up @@ -140,6 +210,7 @@ func (t *translator) TranslateGatewayHTTPRouteV1Alpha2(httpRoute *gatewayv1alpha
},
}
}
plugins := t.generatePluginsFromHTTPRouteFilter(rule.Filters)

for j, match := range matches {
route, err := t.translateGatewayHTTPRouteMatch(&match)
Expand All @@ -150,6 +221,7 @@ func (t *translator) TranslateGatewayHTTPRouteV1Alpha2(httpRoute *gatewayv1alpha
name := apisixv1.ComposeRouteName(httpRoute.Namespace, httpRoute.Name, fmt.Sprintf("%d-%d", i, j))
route.ID = id.GenID(name)
route.Hosts = hosts
route.Plugins = plugins

// Bind Upstream
if len(ruleUpstreams) == 1 {
Expand Down
19 changes: 19 additions & 0 deletions pkg/types/apisix/v1/plugin_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// limitations under the License.
package v1

import "encoding/json"

// TrafficSplitConfig is the config of traffic-split plugin.
// +k8s:deepcopy-gen=true
type TrafficSplitConfig struct {
Expand Down Expand Up @@ -123,6 +125,7 @@ type WolfRBACConsumerConfig struct {
type RewriteConfig struct {
RewriteTarget string `json:"uri,omitempty"`
RewriteTargetRegex []string `json:"regex_uri,omitempty"`
Headers Headers `json:"headers,omitempty"`
}

// RedirectConfig is the rule config for redirect plugin.
Expand Down Expand Up @@ -152,3 +155,19 @@ type BasicAuthConfig struct {
// +k8s:deepcopy-gen=true
type KeyAuthConfig struct {
}

type Headers map[string]any

func (p *Headers) DeepCopyInto(out *Headers) {
b, _ := json.Marshal(&p)
_ = json.Unmarshal(b, out)
}

func (p *Headers) DeepCopy() *Headers {
if p == nil {
return nil
}
out := new(Headers)
p.DeepCopyInto(out)
return out
}
1 change: 1 addition & 0 deletions pkg/types/apisix/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions test/e2e/scaffold/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ import (
"time"

"github.com/apache/apisix-ingress-controller/pkg/apisix"
"github.com/apache/apisix-ingress-controller/pkg/log"
"github.com/apache/apisix-ingress-controller/pkg/metrics"
v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
"github.com/gruntwork-io/terratest/modules/k8s"
"github.com/gruntwork-io/terratest/modules/retry"
"github.com/gruntwork-io/terratest/modules/testing"
ginkgo "github.com/onsi/ginkgo/v2"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
Expand Down Expand Up @@ -128,9 +126,7 @@ func (s *Scaffold) CreateResourceFromString(yaml string) error {

// if the error raised, it may be a &shell.ErrWithCmdOutput, which is useless in debug
if err != nil {
log.Errorw("create resource failed",
zap.Error(err),
)
err = fmt.Errorf(err.Error())
}
return err
}
Expand Down
125 changes: 124 additions & 1 deletion test/e2e/suite-gateway/gateway_httproute.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
)

var _ = ginkgo.Describe("suite-gateway: HTTP Route", func() {
var _ = ginkgo.Describe("suite-gateway: HTTPRoute", func() {
s := scaffold.NewDefaultScaffold()

ginkgo.It("Basic HTTPRoute with 1 Hosts 1 Rule 1 Match 1 BackendRef", func() {
Expand Down Expand Up @@ -236,3 +236,126 @@ spec:
Status(http.StatusNotFound)
})
})

var _ = ginkgo.Describe("suite-gateway: HTTPRoute with filter", func() {
s := scaffold.NewDefaultScaffold()
ginkgo.It("HTTPRoute with RequestHeaderModifier", func() {
backendSvc, backendPorts := s.DefaultHTTPBackend()
time.Sleep(time.Second * 15)
httproute := fmt.Sprintf(`
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: http-route
spec:
hostnames: ["httpbin.org"]
rules:
- matches:
- path:
type: PathPrefix
value: /headers
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Api-Version
value: v1
- name: X-api-key
value: api-value
set:
- name: X-Auth
value: filter
remove:
- Remove-header
- Host
backendRefs:
- name: %s
port: %d
`, backendSvc, backendPorts[0])

assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(httproute), "creating HTTPRoute")
time.Sleep(time.Second * 6)
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1), "Checking number of routes")
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams")

_ = s.NewAPISIXClient().GET("/headers").
WithHeader("Host", "httpbin.org").
WithHeader("Remove-Header", "remove").
WithHeader("X-Auth", "ingress").
Expect().
Status(http.StatusOK).
Body().
Contains(`"X-Api-Version": "v1"`).
Contains(`"X-Api-Key": "api-value"`).
Contains(`"X-Auth": "filter"`).
NotContains(`"Remove-Header"`)
})

ginkgo.It("HTTPRoute with RequestRidrect", func() {
backendSvc, backendPorts := s.DefaultHTTPBackend()

httproute := fmt.Sprintf(`
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: http-route
spec:
hostnames: ["httpbin.org"]
rules:
- matches:
- path:
type: PathPrefix
value: /headers
filters:
- type: RequestRedirect
requestRedirect:
scheme: https
port: 9443
backendRefs:
- name: %s
port: %d
`, backendSvc, backendPorts[0])

assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(httproute), "creating HTTPRoute")
time.Sleep(time.Second * 6)
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1), "Checking number of routes")

_ = s.NewAPISIXClient().GET("/headers").
WithHeader("Host", "httpbin.org").
Expect().
Status(http.StatusFound).
Header("Location").Equal("https://httpbin.org:9443/headers")

httproute2 := fmt.Sprintf(`
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: http-route2
spec:
hostnames: ["httpbin.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /ip
filters:
- type: RequestRedirect
requestRedirect:
hostname: httpbin.org
statusCode: 301
backendRefs:
- name: %s
port: %d
`, backendSvc, backendPorts[0])

assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(httproute2), "creating HTTPRoute")
time.Sleep(time.Second * 6)
assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(2), "Checking number of routes")

_ = s.NewAPISIXClient().GET("/ip").
WithHeader("Host", "httpbin.com").
Expect().
Status(http.StatusMovedPermanently).
Header("Location").Equal("http://httpbin.org/ip")
})
})

0 comments on commit 6f83da5

Please sign in to comment.