Skip to content

Commit

Permalink
Add custom parser
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyaw Myint Thein committed Oct 12, 2021
1 parent b177500 commit 3f93cee
Show file tree
Hide file tree
Showing 7 changed files with 417 additions and 0 deletions.
28 changes: 28 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package rzluramartian

import "context"

// NewContext returns a context wrapping the received parent
func NewContext(parent context.Context) *Context {
return &Context{
Context: parent,
}
}

// Context provides information for a single request/response pair.
type Context struct {
context.Context
skipRoundTrip bool
}

// SkipRoundTrip flags the context to skip the round trip
func (c *Context) SkipRoundTrip() {
c.skipRoundTrip = true
}

// SkippingRoundTrip returns the flag for skipping the round trip
func (c *Context) SkippingRoundTrip() bool {
return c.skipRoundTrip
}

var _ context.Context = &Context{Context: context.Background()}
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/kyawmyintthein/rz-lura-martian

go 1.13

require (
github.com/google/martian v2.1.0+incompatible
github.com/luraproject/lura v1.4.1
)
31 changes: 31 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/devopsfaith/flatmap v0.0.0-20200601181759-8521186182fc h1:WM1VdC8LW8GIZUJuvXgQWm6LcZ8niL0D0WVuC3lKkQU=
github.com/devopsfaith/flatmap v0.0.0-20200601181759-8521186182fc/go.mod h1:J9Y/58s7wx7HbHT3i4UKNwLGuBB9qCf0/JUdEFGDPmA=
github.com/dimfeld/httptreemux v5.0.1+incompatible/go.mod h1:rbUlSV+CCpv/SuqUTP/8Bk2O3LyUV436/yaRGkhP6Z0=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.1.5-0.20170702092826-d459835d2b07/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/go-chi/chi v4.0.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/luraproject/lura v1.4.1 h1:zNzLYMzM13EaSrb9bfaPICD4bXtNqMCdyi42R1eBgro=
github.com/luraproject/lura v1.4.1/go.mod h1:KIo1/+nsRZVxIO04Hkbth0GXSSzypvkFpF5KaIoLvlo=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ugorji/go v0.0.0-20180112141927-9831f2c3ac10/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/urfave/negroni v0.3.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
73 changes: 73 additions & 0 deletions header2body.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package rzluramartian

import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"

"github.com/google/martian/parse"
)

type (
HeaderModifierConfig struct {
KeysToExtract []string `json:"keys_to_extract"`
}

Header2BodyModifier struct {
keysToExtract []string
}
)

func headerModifierFromJSON(b []byte) (*parse.Result, error) {
cfg := &HeaderModifierConfig{}
if err := json.Unmarshal(b, cfg); err != nil {
return nil, err
}

mod := &Header2BodyModifier{
keysToExtract: cfg.KeysToExtract,
}
return parse.NewResult(mod, []parse.ModifierType{parse.Request})
}

func (m *Header2BodyModifier) ModifyRequest(req *http.Request) error {
var buf []byte
if req.Body != nil {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return err
}

data := make(map[string]interface{})
err = json.Unmarshal(body, &data)
if err != nil {
return err
}

for _, k := range m.keysToExtract {
data[k] = req.Header.Get(k)
req.Header.Del(k)
}

buf, err = json.Marshal(data)
if err != nil {
return err
}
} else {
data := make(map[string]interface{})
for _, k := range m.keysToExtract {
data[k] = req.Header.Get(k)
req.Header.Del(k)
}

var err error
buf, err = json.Marshal(data)
if err != nil {
return err
}
}
req.ContentLength = int64(len(buf))
req.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
return nil
}
164 changes: 164 additions & 0 deletions martian.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package rzluramartian

import (
"bytes"
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"

"github.com/luraproject/lura/config"
"github.com/luraproject/lura/logging"
"github.com/luraproject/lura/proxy"
"github.com/luraproject/lura/transport/http/client"

// import the required martian packages so they can be used
"github.com/google/martian"
_ "github.com/google/martian/body"
_ "github.com/google/martian/cookie"
_ "github.com/google/martian/fifo"
_ "github.com/google/martian/header"
_ "github.com/google/martian/martianurl"
"github.com/google/martian/parse"
_ "github.com/google/martian/port"
_ "github.com/google/martian/priority"
_ "github.com/google/martian/stash"
_ "github.com/google/martian/status"
)

// NewBackendFactory creates a proxy.BackendFactory with the martian request executor wrapping the injected one.
// If there is any problem parsing the extra config data, it just uses the injected request executor.
func NewBackendFactory(logger logging.Logger, re client.HTTPRequestExecutor) proxy.BackendFactory {
return NewConfiguredBackendFactory(logger, func(_ *config.Backend) client.HTTPRequestExecutor { return re })
}

// NewConfiguredBackendFactory creates a proxy.BackendFactory with the martian request executor wrapping the injected one.
// If there is any problem parsing the extra config data, it just uses the injected request executor.
func NewConfiguredBackendFactory(logger logging.Logger, ref func(*config.Backend) client.HTTPRequestExecutor) proxy.BackendFactory {
parse.Register("static.Modifier", staticModifierFromJSON)
parse.Register("body.FromQueryString", queryModifierFromJSON)
parse.Register("body.FromHeader", headerModifierFromJSON)
return func(remote *config.Backend) proxy.Proxy {
re := ref(remote)
result, ok := ConfigGetter(remote.ExtraConfig).(Result)
if !ok {
return proxy.NewHTTPProxyWithHTTPExecutor(remote, re, remote.Decoder)
}
switch result.Err {
case nil:
return proxy.NewHTTPProxyWithHTTPExecutor(remote, HTTPRequestExecutor(result.Result, re), remote.Decoder)
case ErrEmptyValue:
return proxy.NewHTTPProxyWithHTTPExecutor(remote, re, remote.Decoder)
default:
logger.Error(result, remote.ExtraConfig)
return proxy.NewHTTPProxyWithHTTPExecutor(remote, re, remote.Decoder)
}
}
}

// HTTPRequestExecutor creates a wrapper over the received request executor, so the martian modifiers can be
// executed before and after the execution of the request
func HTTPRequestExecutor(result *parse.Result, re client.HTTPRequestExecutor) client.HTTPRequestExecutor {
return func(ctx context.Context, req *http.Request) (resp *http.Response, err error) {
if err = modifyRequest(result.RequestModifier(), req); err != nil {
return
}

mctx, ok := req.Context().(*Context)
if !ok || !mctx.SkippingRoundTrip() {
resp, err = re(ctx, req)
if err != nil {
return
}
if resp == nil {
err = ErrEmptyResponse
return
}
} else if resp == nil {
resp = &http.Response{
Request: req,
Header: http.Header{},
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewBufferString("")),
}
}

err = modifyResponse(result.ResponseModifier(), resp)
return
}
}

func modifyRequest(mod martian.RequestModifier, req *http.Request) error {
if req.Body == nil {
req.Body = ioutil.NopCloser(bytes.NewBufferString(""))
}
if req.Header == nil {
req.Header = http.Header{}
}

if mod == nil {
return nil
}
return mod.ModifyRequest(req)
}

func modifyResponse(mod martian.ResponseModifier, resp *http.Response) error {
if resp.Body == nil {
resp.Body = ioutil.NopCloser(bytes.NewBufferString(""))
}
if resp.Header == nil {
resp.Header = http.Header{}
}
if resp.StatusCode == 0 {
resp.StatusCode = http.StatusOK
}

if mod == nil {
return nil
}
return mod.ModifyResponse(resp)
}

// Namespace is the key to look for extra configuration details
const Namespace = "github.com/kyawmyintthein/rz-martian"

// Result is a simple wrapper over the parse.FromJSON response tuple
type Result struct {
Result *parse.Result
Err error
}

// ConfigGetter implements the config.ConfigGetter interface. It parses the extra config for the
// martian adapter and returns a Result wrapping the results.
func ConfigGetter(e config.ExtraConfig) interface{} {
cfg, ok := e[Namespace]
if !ok {
return Result{nil, ErrEmptyValue}
}

data, ok := cfg.(map[string]interface{})
if !ok {
return Result{nil, ErrBadValue}
}

raw, err := json.Marshal(data)
if err != nil {
return Result{nil, ErrMarshallingValue}
}

r, err := parse.FromJSON(raw)

return Result{r, err}
}

var (
// ErrEmptyValue is the error returned when there is no config under the namespace
ErrEmptyValue = errors.New("getting the extra config for the martian module")
// ErrBadValue is the error returned when the config is not a map
ErrBadValue = errors.New("casting the extra config for the martian module")
// ErrMarshallingValue is the error returned when the config map can not be marshalled again
ErrMarshallingValue = errors.New("marshalling the extra config for the martian module")
// ErrEmptyResponse is the error returned when the modifier receives a nil response
ErrEmptyResponse = errors.New("getting the http response from the request executor")
)
58 changes: 58 additions & 0 deletions querystring2body.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package rzluramartian

import (
"bytes"
"encoding/json"
"html/template"
"io/ioutil"
"net/http"

"github.com/google/martian/parse"
)

type (
QueryModifierConfig struct {
KeysToExtract []string `json:"keys_to_extract"`
Template string `json:"template"`
}
Query2BodyModifier struct {
keysToExtract []string
template *template.Template
}
)

func queryModifierFromJSON(b []byte) (*parse.Result, error) {
cfg := &QueryModifierConfig{}
if err := json.Unmarshal(b, cfg); err != nil {
return nil, err
}

tmpl, err := template.New("query2body_modifier").Parse(cfg.Template)
if err != nil {
return nil, err
}

mod := &Query2BodyModifier{
keysToExtract: cfg.KeysToExtract,
template: tmpl,
}
return parse.NewResult(mod, []parse.ModifierType{parse.Request})
}

func (m *Query2BodyModifier) ModifyRequest(req *http.Request) error {
query := req.URL.Query()

buf := new(bytes.Buffer)
if err := m.template.Execute(buf, query); err != nil {
return err
}

for _, k := range m.keysToExtract {
query.Del(k)
}

req.ContentLength = int64(buf.Len())
req.Body = ioutil.NopCloser(buf)
req.URL.RawQuery = query.Encode()
return nil
}
Loading

0 comments on commit 3f93cee

Please sign in to comment.