Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add rewrite:RespHeaders and modify the upstream response ers via request implementation #68

Merged
merged 10 commits into from
Mar 8, 2022
4 changes: 2 additions & 2 deletions docs/en/latest/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ func (p *Say) Filter(conf interface{}, w http.ResponseWriter, r pkgHTTP.Request)
```

We can see that the Filter takes the value of the body set in the configuration as the response body. If we call `Write` or `WriteHeader` of the `http.ResponseWriter`
(respond directly in the plugin), it will response directly in the APISIX without touching the upstream. As we only support setting headers of local response,
calling `Header` won't take any effects without calling `Write` or `WriteHeader`. (TODO: support setting response header separately)
(respond directly in the plugin), it will response directly in the APISIX without touching the upstream. We can also set response headers in the plugin and touch the upstream
at the same time by set RespHeader in `pkgHTTP.Request`.

For the `pkgHTTP.Request`, you can refer to the API documentation provided by the Go Runner SDK:
https://pkg.go.dev/github.com/apache/apisix-go-plugin-runner
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.15

require (
github.com/ReneKroon/ttlcache/v2 v2.4.0
github.com/api7/ext-plugin-proto v0.3.0
github.com/api7/ext-plugin-proto v0.4.0
github.com/google/flatbuffers v2.0.0+incompatible
github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.7.0
Expand Down
38 changes: 36 additions & 2 deletions internal/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ type Request struct {

ctx context.Context
cancel context.CancelFunc

respHdr http.Header
}

func (r *Request) ConfToken() uint32 {
Expand Down Expand Up @@ -103,6 +105,13 @@ func (r *Request) Header() pkgHTTP.Header {
return r.hdr
}

func (r *Request) RespHeader() http.Header {
if r.respHdr == nil {
r.respHdr = http.Header{}
}
return r.respHdr
}

func cloneUrlValues(oldV url.Values) url.Values {
nv := 0
for _, vv := range oldV {
Expand Down Expand Up @@ -177,7 +186,7 @@ func (r *Request) Reset() {
}

func (r *Request) FetchChanges(id uint32, builder *flatbuffers.Builder) bool {
if r.path == nil && r.hdr == nil && r.args == nil {
if r.path == nil && r.hdr == nil && r.args == nil && r.respHdr == nil{
return false
}

Expand All @@ -186,7 +195,7 @@ func (r *Request) FetchChanges(id uint32, builder *flatbuffers.Builder) bool {
path = builder.CreateByteString(r.path)
}

var hdrVec flatbuffers.UOffsetT
var hdrVec,respHdrVec flatbuffers.UOffsetT
if r.hdr != nil {
hdrs := []flatbuffers.UOffsetT{}
oldHdr := r.rawHdr
Expand Down Expand Up @@ -222,6 +231,28 @@ func (r *Request) FetchChanges(id uint32, builder *flatbuffers.Builder) bool {
hdrVec = builder.EndVector(size)
}

if r.respHdr != nil {
respHdrs := []flatbuffers.UOffsetT{}
for n, arr := range r.respHdr {
for _, v := range arr {
name := builder.CreateString(n)
value := builder.CreateString(v)
A6.TextEntryStart(builder)
A6.TextEntryAddName(builder, name)
A6.TextEntryAddValue(builder, value)
te := A6.TextEntryEnd(builder)
respHdrs = append(respHdrs, te)
}
}
size := len(respHdrs)
hrc.RewriteStartRespHeadersVector(builder, size)
for i := size - 1; i >= 0; i-- {
te := respHdrs[i]
builder.PrependUOffsetT(te)
}
respHdrVec = builder.EndVector(size)
}

var argsVec flatbuffers.UOffsetT
if r.args != nil {
args := []flatbuffers.UOffsetT{}
Expand Down Expand Up @@ -267,6 +298,9 @@ func (r *Request) FetchChanges(id uint32, builder *flatbuffers.Builder) bool {
if hdrVec > 0 {
hrc.RewriteAddHeaders(builder, hdrVec)
}
if respHdrVec > 0{
hrc.RewriteAddRespHeaders(builder,respHdrVec)
}
if argsVec > 0 {
hrc.RewriteAddArgs(builder, argsVec)
}
Expand Down
55 changes: 54 additions & 1 deletion internal/http/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type reqOpt struct {
method A6.Method
path string
headers []pair
respHeader []pair
args []pair
}

Expand All @@ -87,7 +88,7 @@ func buildReq(opt reqOpt) []byte {
}

hdrLen := len(opt.headers)
var hdrVec flatbuffers.UOffsetT
var hdrVec,respHdrVec flatbuffers.UOffsetT
if hdrLen > 0 {
hdrs := []flatbuffers.UOffsetT{}
for _, v := range opt.headers {
Expand All @@ -108,6 +109,26 @@ func buildReq(opt reqOpt) []byte {
hdrVec = builder.EndVector(size)
}

if len(opt.respHeader) > 0 {
respHdrs := []flatbuffers.UOffsetT{}
for _, v := range opt.headers {
name := builder.CreateString(v.name)
value := builder.CreateString(v.value)
A6.TextEntryStart(builder)
A6.TextEntryAddName(builder, name)
A6.TextEntryAddValue(builder, value)
te := A6.TextEntryEnd(builder)
respHdrs = append(respHdrs, te)
}
size := len(respHdrs)
hrc.RewriteStartRespHeadersVector(builder, size)
for i := size - 1; i >= 0; i-- {
te := respHdrs[i]
builder.PrependUOffsetT(te)
}
respHdrVec = builder.EndVector(size)
}

argsLen := len(opt.args)
var argsVec flatbuffers.UOffsetT
if argsLen > 0 {
Expand Down Expand Up @@ -145,6 +166,9 @@ func buildReq(opt reqOpt) []byte {
if hdrVec > 0 {
hrc.ReqAddHeaders(builder, hdrVec)
}
if respHdrVec > 0{
hrc.RewriteAddRespHeaders(builder,respHdrVec)
}
if argsVec > 0 {
hrc.ReqAddArgs(builder, argsVec)
}
Expand Down Expand Up @@ -235,6 +259,8 @@ func TestHeader(t *testing.T) {
assert.Equal(t, exp, res)
}



func TestArgs(t *testing.T) {
out := buildReq(reqOpt{args: []pair{
{"del", "a"},
Expand Down Expand Up @@ -398,3 +424,30 @@ func TestContext(t *testing.T) {
ReuseRequest(r)
assert.Equal(t, r.ctx, nil)
}

func TestRespHeader(t *testing.T){
out := buildReq(reqOpt{})
r := CreateRequest(out)
respHdr := r.RespHeader()

respHdr.Set("resp-header","this is resp-header")
respHdr.Set("Set-Cookie","mycookie=test")

respHdr.Del("resp-header")

builder := util.GetBuilder()
assert.True(t, r.FetchChanges(1, builder))
rewrite := getRewriteAction(t, builder)
assert.Equal(t, 1, rewrite.RespHeadersLength())

exp := http.Header{}
exp.Set("Set-Cookie", "mycookie=test")
res := http.Header{}
for i := 0; i < rewrite.RespHeadersLength(); i++ {
e := &A6.TextEntry{}
rewrite.RespHeaders(e, i)
res.Add(string(e.Name()), string(e.Value()))
}
assert.Equal(t, exp, res)

}
32 changes: 32 additions & 0 deletions internal/plugin/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,35 @@ func TestFilter_SetRespHeaderDoNotBreakReq(t *testing.T) {
assert.Equal(t, "bar", req.Header().Get("foo"))
assert.Equal(t, "baz", resp.Header().Get("foo"))
}

func TestFilter_SetRespHeader(t *testing.T) {
InitConfCache(1 * time.Millisecond)

filterSetRespHeaderParseConf := func(in []byte) (conf interface{}, err error) {
return "", nil
}
filterSetRespHeaderFilter := func(conf interface{}, w http.ResponseWriter, r pkgHTTP.Request) {
r.RespHeader().Set("foo", "baz")
}

RegisterPlugin("filterSetRespHeader", filterSetRespHeaderParseConf, filterSetRespHeaderFilter)

builder := flatbuffers.NewBuilder(1024)
filterSetRespName := builder.CreateString("filterSetRespHeader")
filterSetRespConf := builder.CreateString("a")
prepareConfWithData(builder, filterSetRespName, filterSetRespConf)

res, _ := GetRuleConf(1)
hrc.ReqStart(builder)
hrc.ReqAddId(builder, 233)
hrc.ReqAddConfToken(builder, 1)
r := hrc.ReqEnd(builder)
builder.Finish(r)
out := builder.FinishedBytes()

req := inHTTP.CreateRequest(out)
resp := inHTTP.CreateResponse()
filter(res, resp, req)

assert.Equal(t, "baz", req.RespHeader().Get("foo"))
}
3 changes: 3 additions & 0 deletions pkg/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ type Request interface {
//
// For run plugin, the context controls cancellation.
Context() context.Context
// RespHeader returns the response header
rampagecong marked this conversation as resolved.
Show resolved Hide resolved
// Add or set response headers that exclude important headers likes `connection`,`content-length`,`transfer-encoding`,`location,server`,`www-authenticate`,`content-encoding`,`content-type`,`content-location` and `content-language`.
RespHeader() http.Header
}

// Header is like http.Header, but only implements the subset of its methods
Expand Down