You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2021/05/31 02:07:20 UTC
[apisix-go-plugin-runner] 13/22: feat: record response
This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-go-plugin-runner.git
commit 38309c734030f838e90b4660df3376d4622047ec
Author: spacewander <sp...@gmail.com>
AuthorDate: Mon May 24 13:04:12 2021 +0800
feat: record response
---
internal/http/request.go | 4 ++
internal/http/response.go | 92 ++++++++++++++++++++++++++++++++++++++---
internal/http/response_test.go | 93 ++++++++++++++++++++++++++++++++++++++++++
internal/plugin/plugin.go | 11 ++++-
internal/plugin/plugin_test.go | 2 +
pkg/http/http.go | 2 +
6 files changed, 196 insertions(+), 8 deletions(-)
diff --git a/internal/http/request.go b/internal/http/request.go
index 4c96aa2..596b0c4 100644
--- a/internal/http/request.go
+++ b/internal/http/request.go
@@ -27,6 +27,10 @@ func (r Request) ConfToken() uint32 {
return r.r.ConfToken()
}
+func (r Request) Id() uint32 {
+ return r.r.Id()
+}
+
func CreateRequest(buf []byte) *Request {
req := &Request{
r: hrc.GetRootAsReq(buf, 0),
diff --git a/internal/http/response.go b/internal/http/response.go
index 46fbeeb..ef28b9a 100644
--- a/internal/http/response.go
+++ b/internal/http/response.go
@@ -14,24 +14,104 @@
// limitations under the License.
package http
-import "net/http"
+import (
+ "bytes"
+ "net/http"
+
+ "github.com/api7/ext-plugin-proto/go/A6"
+ hrc "github.com/api7/ext-plugin-proto/go/A6/HTTPReqCall"
+ flatbuffers "github.com/google/flatbuffers/go"
+)
type Response struct {
+ hdr http.Header
+ body *bytes.Buffer
code int
}
-func (r Response) Header() http.Header {
- return nil
+func (r *Response) Header() http.Header {
+ r.hdr = http.Header{}
+ return r.hdr
}
-func (r Response) Write([]byte) (int, error) {
- return 0, nil
+func (r *Response) Write(b []byte) (int, error) {
+ if r.body == nil {
+ r.body = &bytes.Buffer{}
+ }
+
+ return r.body.Write(b)
}
-func (r Response) WriteHeader(statusCode int) {
+func (r *Response) WriteHeader(statusCode int) {
+ if r.code != 0 {
+ // official WriteHeader can't override written status
+ // keep the same behavior
+ return
+ }
r.code = statusCode
}
+func (r *Response) FetchChanges(id uint32, builder *flatbuffers.Builder) bool {
+ if r.body == nil && r.code == 0 && len(r.hdr) == 0 {
+ return false
+ }
+
+ hdrLen := len(r.hdr)
+ var hdrVec flatbuffers.UOffsetT
+ if hdrLen > 0 {
+ hdrs := []flatbuffers.UOffsetT{}
+ for n, arr := range r.hdr {
+ 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)
+ hdrs = append(hdrs, te)
+ }
+ }
+ size := len(hdrs)
+ hrc.StopStartHeadersVector(builder, size)
+ for i := size - 1; i >= 0; i-- {
+ te := hdrs[i]
+ builder.PrependUOffsetT(te)
+ }
+ hdrVec = builder.EndVector(size)
+ }
+
+ var bodyVec flatbuffers.UOffsetT
+ if r.body != nil {
+ b := r.body.Bytes()
+ if len(b) > 0 {
+ bodyVec = builder.CreateByteVector(b)
+ }
+ }
+
+ hrc.StopStart(builder)
+ if r.code == 0 {
+ hrc.StopAddStatus(builder, 200)
+ } else {
+ hrc.StopAddStatus(builder, uint16(r.code))
+ }
+ if hdrLen > 0 {
+ hrc.StopAddHeaders(builder, hdrVec)
+ }
+ if r.body != nil {
+ hrc.StopAddBody(builder, bodyVec)
+ }
+ stop := hrc.StopEnd(builder)
+
+ hrc.RespStart(builder)
+ hrc.RespAddId(builder, id)
+ hrc.RespAddActionType(builder, hrc.ActionStop)
+ hrc.RespAddAction(builder, stop)
+ res := hrc.RespEnd(builder)
+ builder.Finish(res)
+
+ return true
+}
+
func CreateResponse() *Response {
return &Response{}
}
diff --git a/internal/http/response_test.go b/internal/http/response_test.go
new file mode 100644
index 0000000..5eddd4c
--- /dev/null
+++ b/internal/http/response_test.go
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package http
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/api7/ext-plugin-proto/go/A6"
+ hrc "github.com/api7/ext-plugin-proto/go/A6/HTTPReqCall"
+ flatbuffers "github.com/google/flatbuffers/go"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/apache/apisix-go-plugin-runner/internal/util"
+)
+
+func getStopAction(t *testing.T, b *flatbuffers.Builder) *hrc.Stop {
+ buf := b.FinishedBytes()
+ res := hrc.GetRootAsResp(buf, 0)
+ tab := &flatbuffers.Table{}
+ if res.Action(tab) {
+ assert.Equal(t, hrc.ActionStop, res.ActionType())
+ stop := &hrc.Stop{}
+ stop.Init(tab.Bytes, tab.Pos)
+ return stop
+ }
+ return nil
+}
+
+func TestFetchChanges(t *testing.T) {
+ r := CreateResponse()
+ r.Write([]byte("hello"))
+ h := r.Header()
+ h.Set("foo", "bar")
+ h.Add("foo", "baz")
+ h.Add("cat", "dog")
+ r.Write([]byte(" world"))
+ assert.Equal(t, "dog", h.Get("cat"))
+ builder := util.GetBuilder()
+ assert.True(t, r.FetchChanges(1, builder))
+
+ stop := getStopAction(t, builder)
+ assert.Equal(t, uint16(200), stop.Status())
+ assert.Equal(t, []byte("hello world"), stop.BodyBytes())
+
+ res := http.Header{}
+ assert.Equal(t, 3, stop.HeadersLength())
+ for i := 0; i < stop.HeadersLength(); i++ {
+ e := &A6.TextEntry{}
+ stop.Headers(e, i)
+ res.Add(string(e.Name()), string(e.Value()))
+ }
+ assert.Equal(t, h, res)
+}
+
+func TestFetchChangesEmptyResponse(t *testing.T) {
+ r := CreateResponse()
+ builder := util.GetBuilder()
+ assert.False(t, r.FetchChanges(1, builder))
+}
+
+func TestFetchChangesStatusOnly(t *testing.T) {
+ r := CreateResponse()
+ r.WriteHeader(400)
+ builder := util.GetBuilder()
+ assert.True(t, r.FetchChanges(1, builder))
+
+ stop := getStopAction(t, builder)
+ assert.Equal(t, uint16(400), stop.Status())
+}
+
+func TestWriteHeaderTwice(t *testing.T) {
+ r := CreateResponse()
+ r.WriteHeader(400)
+ r.WriteHeader(503)
+ builder := util.GetBuilder()
+ assert.True(t, r.FetchChanges(1, builder))
+
+ stop := getStopAction(t, builder)
+ assert.Equal(t, uint16(400), stop.Status())
+}
diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go
index ba07c8a..e4d9ef6 100644
--- a/internal/plugin/plugin.go
+++ b/internal/plugin/plugin.go
@@ -29,9 +29,15 @@ func handle(conf RuleConf, w http.ResponseWriter, r pkgHTTP.Request) error {
return nil
}
-func reportAction(req *inHTTP.Request, resp *inHTTP.Response) *flatbuffers.Builder {
+func reportAction(id uint32, req *inHTTP.Request, resp *inHTTP.Response) *flatbuffers.Builder {
builder := util.GetBuilder()
+
+ if resp != nil && resp.FetchChanges(id, builder) {
+ return builder
+ }
+
hrc.RespStart(builder)
+ hrc.RespAddId(builder, id)
res := hrc.RespEnd(builder)
builder.Finish(res)
return builder
@@ -51,6 +57,7 @@ func HTTPReqCall(buf []byte) (*flatbuffers.Builder, error) {
return nil, err
}
- builder := reportAction(req, resp)
+ id := req.Id()
+ builder := reportAction(id, req, resp)
return builder, nil
}
diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go
index 5433cca..c433f3f 100644
--- a/internal/plugin/plugin_test.go
+++ b/internal/plugin/plugin_test.go
@@ -29,6 +29,7 @@ func TestHTTPReqCall(t *testing.T) {
builder := flatbuffers.NewBuilder(1024)
hrc.ReqStart(builder)
+ hrc.ReqAddId(builder, 233)
hrc.ReqAddConfToken(builder, 1)
r := hrc.ReqEnd(builder)
builder.Finish(r)
@@ -39,5 +40,6 @@ func TestHTTPReqCall(t *testing.T) {
out = b.FinishedBytes()
resp := hrc.GetRootAsResp(out, 0)
+ assert.Equal(t, uint32(233), resp.Id())
assert.Equal(t, hrc.ActionNONE, resp.ActionType())
}
diff --git a/pkg/http/http.go b/pkg/http/http.go
index 54edde1..de83956 100644
--- a/pkg/http/http.go
+++ b/pkg/http/http.go
@@ -24,4 +24,6 @@ package http
// So the server must parse all the headers, ...". The official API is suboptimal, which
// is even worse in our case as it is not a real HTTP server.
type Request interface {
+ // Id returns the request id
+ Id() uint32
}