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 65cbe80b2aba18644d8e485dc2f3a4f7814af36b Author: spacewander <[email protected]> AuthorDate: Wed May 26 10:41:05 2021 +0800 feat: add Request.Header --- internal/http/request.go | 78 ++++++++++++++++++++++++++++++++++++++++- internal/http/request_test.go | 81 +++++++++++++++++++++++++++++++++++++++++-- pkg/http/http.go | 26 +++++++++++++- 3 files changed, 180 insertions(+), 5 deletions(-) diff --git a/internal/http/request.go b/internal/http/request.go index 570c245..11c9ef4 100644 --- a/internal/http/request.go +++ b/internal/http/request.go @@ -16,7 +16,10 @@ package http import ( "net" + "net/http" + pkgHTTP "github.com/apache/apisix-go-plugin-runner/pkg/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" ) @@ -26,6 +29,9 @@ type Request struct { r *hrc.Req path []byte + + hdr *Header + rawHdr http.Header } func (r *Request) ConfToken() uint32 { @@ -55,8 +61,25 @@ func (r *Request) SetPath(path []byte) { r.path = path } +func (r *Request) Header() pkgHTTP.Header { + if r.hdr == nil { + hdr := newHeader() + hh := hdr.View() + size := r.r.HeadersLength() + obj := A6.TextEntry{} + for i := 0; i < size; i++ { + if r.r.Headers(&obj, i) { + hh.Add(string(obj.Name()), string(obj.Value())) + } + } + r.hdr = hdr + r.rawHdr = hdr.Clone() + } + return r.hdr +} + func (r *Request) FetchChanges(id uint32, builder *flatbuffers.Builder) bool { - if r.path == nil { + if r.path == nil && r.hdr == nil { return false } @@ -65,10 +88,49 @@ func (r *Request) FetchChanges(id uint32, builder *flatbuffers.Builder) bool { path = builder.CreateByteString(r.path) } + var hdrVec flatbuffers.UOffsetT + if r.hdr != nil { + hdrs := []flatbuffers.UOffsetT{} + oldHdr := r.rawHdr + newHdr := r.hdr.View() + for n := range oldHdr { + if _, ok := newHdr[n]; !ok { + // deleted + name := builder.CreateString(n) + A6.TextEntryStart(builder) + A6.TextEntryAddName(builder, name) + te := A6.TextEntryEnd(builder) + hdrs = append(hdrs, te) + } + } + for n, v := range newHdr { + if raw, ok := oldHdr[n]; !ok || raw[0] != v[0] { + // set + name := builder.CreateString(n) + value := builder.CreateString(v[0]) + A6.TextEntryStart(builder) + A6.TextEntryAddName(builder, name) + A6.TextEntryAddValue(builder, value) + te := A6.TextEntryEnd(builder) + hdrs = append(hdrs, te) + } + } + size := len(hdrs) + hrc.RewriteStartHeadersVector(builder, size) + for i := size - 1; i >= 0; i-- { + te := hdrs[i] + builder.PrependUOffsetT(te) + } + hdrVec = builder.EndVector(size) + } + hrc.RewriteStart(builder) if path > 0 { hrc.RewriteAddPath(builder, path) } + if hdrVec > 0 { + hrc.RewriteAddHeaders(builder, hdrVec) + } rewrite := hrc.RewriteEnd(builder) hrc.RespStart(builder) @@ -87,3 +149,17 @@ func CreateRequest(buf []byte) *Request { } return req } + +type Header struct { + http.Header +} + +func newHeader() *Header { + return &Header{ + Header: http.Header{}, + } +} + +func (h *Header) View() http.Header { + return h.Header +} diff --git a/internal/http/request_test.go b/internal/http/request_test.go index 667138e..54fed1e 100644 --- a/internal/http/request_test.go +++ b/internal/http/request_test.go @@ -16,6 +16,7 @@ package http import ( "net" + "net/http" "testing" "github.com/apache/apisix-go-plugin-runner/internal/util" @@ -38,10 +39,16 @@ func getRewriteAction(t *testing.T, b *flatbuffers.Builder) *hrc.Rewrite { return nil } +type pair struct { + name string + value string +} + type reqOpt struct { - srcIP []byte - method A6.Method - path string + srcIP []byte + method A6.Method + path string + headers []pair } func buildReq(opt reqOpt) []byte { @@ -57,6 +64,28 @@ func buildReq(opt reqOpt) []byte { path = builder.CreateString(opt.path) } + hdrLen := len(opt.headers) + var hdrVec flatbuffers.UOffsetT + if hdrLen > 0 { + hdrs := []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) + 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) + } + hrc.ReqStart(builder) hrc.ReqAddId(builder, 233) hrc.ReqAddConfToken(builder, 1) @@ -69,6 +98,9 @@ func buildReq(opt reqOpt) []byte { if path > 0 { hrc.ReqAddPath(builder, path) } + if hdrVec > 0 { + hrc.ReqAddHeaders(builder, hdrVec) + } r := hrc.ReqEnd(builder) builder.Finish(r) return builder.FinishedBytes() @@ -111,3 +143,46 @@ func TestPath(t *testing.T) { rewrite := getRewriteAction(t, builder) assert.Equal(t, "/go", string(rewrite.Path())) } + +func TestHeader(t *testing.T) { + out := buildReq(reqOpt{headers: []pair{ + {"k", "v"}, + {"cache-control", "no-cache"}, + {"cache-control", "no-store"}, + {"cat", "dog"}, + }}) + r := CreateRequest(out) + hdr := r.Header() + assert.Equal(t, "v", hdr.Get("k")) + assert.Equal(t, "no-cache", hdr.Get("Cache-Control")) + assert.Equal(t, "no-cache", hdr.Get("cache-control")) + + hdr.Del("empty") + hdr.Del("k") + assert.Equal(t, "", hdr.Get("k")) + + hdr.Set("cache-control", "max-age=10s") + assert.Equal(t, "max-age=10s", hdr.Get("Cache-Control")) + hdr.Del("cache-Control") + assert.Equal(t, "", hdr.Get("cache-control")) + + hdr.Set("k", "v2") + hdr.Del("cat") + + builder := util.GetBuilder() + assert.True(t, r.FetchChanges(1, builder)) + rewrite := getRewriteAction(t, builder) + assert.Equal(t, 3, rewrite.HeadersLength()) + + exp := http.Header{} + exp.Set("Cache-Control", "") + exp.Set("cat", "") + exp.Set("k", "v2") + res := http.Header{} + for i := 0; i < rewrite.HeadersLength(); i++ { + e := &A6.TextEntry{} + rewrite.Headers(e, i) + res.Add(string(e.Name()), string(e.Value())) + } + assert.Equal(t, exp, res) +} diff --git a/pkg/http/http.go b/pkg/http/http.go index ebae8ac..cbbceeb 100644 --- a/pkg/http/http.go +++ b/pkg/http/http.go @@ -14,7 +14,10 @@ // limitations under the License. package http -import "net" +import ( + "net" + "net/http" +) // Request represents the HTTP request received by APISIX. // We don't use net/http's Request because it doesn't suit our purpose. @@ -38,4 +41,25 @@ type Request interface { Path() []byte // SetPath is the setter for Path SetPath([]byte) + // Header returns the HTTP headers + Header() Header +} + +// Header is like http.Header, but only implements the subset of its methods +type Header interface { + // Set sets the header entries associated with key to the single element value. + // It replaces any existing values associated with key. + // The key is case insensitive + Set(key, value string) + // Del deletes the values associated with key. The key is case insensitive + Del(key string) + // Get gets the first value associated with the given key. + // If there are no values associated with the key, Get returns "". + // It is case insensitive + Get(key string) string + // View returns the internal structure. It is expected for read operations. Any write operation + // won't be recorded + View() http.Header + + // TODO: support Add }
