Skip to content

Commit

Permalink
client can now define if the targets response status should also be f…
Browse files Browse the repository at this point in the history
…orwarded
  • Loading branch information
rainu committed Aug 13, 2023
1 parent 95419be commit a485116
Show file tree
Hide file tree
Showing 12 changed files with 56 additions and 11 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ curl -v -u "user:secret" localhost:8080/https%3A%2F%2Fgit.luolix.top%2Frainu%2Fr-ray
| BINDING_ADDRESS | :8080 | Specifies the TCP address for the server to listen on, in the form "host:port" |
| CREDENTIALS | | Comma seperated list of credentials in the form "user:password" |
| REQUEST_HEADER_PREFIX | R- | All request header with that prefix will be transfer to target (without prefix). All response Header from target will be prefixed with that prefix. |
| FORWARD_REQUEST_HEADER_REPFIX | R-Forward-Request-Header- | The prefix of the headers which contains the expression of the forward request headers. See below. |
| FORWARD_REQUEST_HEADER_PREFIX | R-Forward-Request-Header- | The prefix of the headers which contains the expression of the forward request headers. See below. |
| FORWARD_RESPONSE_HEADER_PREFIX | R-Forward-Response-Header- | The prefix of the headers which contains the expression of the forward response headers. See below. |
| FORWARD_RESPONSE_STATUS_HEADER | R-Forward-Response-Status | The name of the header which control the status code forwarding. See below. |
| CORS_ALLOW_ORIGIN | | Comma seperated list of allowed origins. If empty: **every** origin is allowed! |
| CORS_ALLOW_METHODS | | Comma seperated list of allowed methods. If empty: **every** method is allowed! |
| CORS_ALLOW_HEADERS | | Comma seperated list of allowed headers. If empty: **every** header is allowed! |
Expand All @@ -92,6 +93,7 @@ curl -v -u "user:secret" localhost:8080/https%3A%2F%2Fgit.luolix.top%2Frainu%2Fr-ray
* If a header with _FORWARD_REQUEST_HEADER_ (default **R-Forward-Request-Header-**) is sent, all request headers which match the regular expression, will be transferred to the target too
* All headers from the target will be prefixed with _REQUEST_HEADER_PREFIX_ (default **R-**) and sent to the client
* If a header with _FORWARD_RESPONSE_HEADER_ (default **R-Forward-Response-Header-**) was sent, all response headers which match the regular expression, will be transferred as is to the client
* If the header _FORWARD_RESPONSE_STATUS_HEADER_ (default **R-Forward-Response-Status**) was sent with any value, the targets status code will be forwarded to the client.


```
Expand Down
8 changes: 7 additions & 1 deletion cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ func main() {

p := processor.New(userStore)
cors := ihttp.CorsMiddleware{
Delegate: ihttp.NewMetaMiddleware(cfg, controller.NewProxy(cfg.RequestHeaderPrefix, cfg.ForwardRequestHeaderPrefix, cfg.ForwardResponseHeaderPrefix, p)),
Delegate: ihttp.NewMetaMiddleware(cfg, controller.NewProxy(
cfg.RequestHeaderPrefix,
cfg.ForwardRequestHeaderPrefix,
cfg.ForwardResponseHeaderPrefix,
cfg.ForwardResponseStatusHeader,
p,
)),

Origins: cfg.CorsAllowOrigin,
Methods: cfg.CorsAllowMethods,
Expand Down
4 changes: 4 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Config struct {
RequestHeaderPrefix string `required:"false" envconfig:"REQUEST_HEADER_PREFIX"`
ForwardRequestHeaderPrefix string `required:"false" envconfig:"FORWARD_REQUEST_HEADER_PREFIX"`
ForwardResponseHeaderPrefix string `required:"false" envconfig:"FORWARD_RESPONSE_HEADER_PREFIX"`
ForwardResponseStatusHeader string `required:"false" envconfig:"FORWARD_RESPONSE_STATUS_HEADER"`

CorsAllowOrigin []string `required:"false" envconfig:"CORS_ALLOW_ORIGIN"`
CorsAllowMethods []string `required:"false" envconfig:"CORS_ALLOW_METHODS"`
Expand All @@ -38,6 +39,9 @@ func ReadConfig() (*Config, error) {
if c.ForwardResponseHeaderPrefix == "" {
c.ForwardResponseHeaderPrefix = c.RequestHeaderPrefix + "Forward-Response-Header-"
}
if c.ForwardResponseStatusHeader == "" {
c.ForwardResponseStatusHeader = c.RequestHeaderPrefix + "Forward-Response-Status"
}

if len(c.Credentials) == 0 {
logrus.Warn("There are no user credentials configured!")
Expand Down
1 change: 1 addition & 0 deletions internal/http/controller/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ type context struct {

forwardRequestExpressions []*regexp.Regexp
forwardResponseExpressions []*regexp.Regexp
forwardResponseStatus bool
}
6 changes: 5 additions & 1 deletion internal/http/controller/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ func (p *proxy) transferForwardResponseHeader(ctx *context) bool {
}

func (p *proxy) transferStatusCode(ctx *context) bool {
ctx.response.Header()[p.headerPrefix+StatusLineHeaderSuffix] = []string{ctx.output.StatusLine}
if ctx.forwardResponseStatus {
ctx.response.WriteHeader(ctx.output.StatusCode)
} else {
ctx.response.Header()[p.headerPrefix+StatusLineHeaderSuffix] = []string{ctx.output.StatusLine}
}

return true
}
Expand Down
18 changes: 12 additions & 6 deletions internal/http/controller/pre.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ func (p *proxy) validateRequest(ctx *context) bool {
return true
}

func (p *proxy) extractAuthorization(ctx *context) bool {
ctx.input.User.Username, ctx.input.User.Password, _ = ctx.request.BasicAuth()

return true
}

func (p *proxy) checkForwardResponseStatus(ctx *context) bool {
ctx.forwardResponseStatus = ctx.request.Header.Get(p.forwardResponseStatus) != ""

return true
}

func (p *proxy) compileForwardExpressions(ctx *context) bool {
ctx.forwardRequestExpressions = make([]*regexp.Regexp, 0, 5)
for name, value := range ctx.request.Header {
Expand Down Expand Up @@ -70,12 +82,6 @@ func (p *proxy) compileForwardExpressions(ctx *context) bool {
return true
}

func (p *proxy) extractAuthorization(ctx *context) bool {
ctx.input.User.Username, ctx.input.User.Password, _ = ctx.request.BasicAuth()

return true
}

func (p *proxy) transferRequestHeader(ctx *context) bool {
for name, values := range ctx.request.Header {
if !strings.HasPrefix(strings.ToLower(name), strings.ToLower(p.headerPrefix)) {
Expand Down
5 changes: 4 additions & 1 deletion internal/http/controller/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,26 @@ type proxy struct {
headerPrefix string
forwardRequestHeader string
forwardResponseHeader string
forwardResponseStatus string

preProcessing []processingStep
processor Processor
postProcessing []processingStep
}

func NewProxy(headerPrefix, forwardRequestHeader, forwardResponseHeader string, processor Processor) *proxy {
func NewProxy(headerPrefix, forwardRequestHeader, forwardResponseHeader, forwardResponseStatus string, processor Processor) *proxy {
result := &proxy{
headerPrefix: headerPrefix,
forwardRequestHeader: forwardRequestHeader,
forwardResponseHeader: forwardResponseHeader,
forwardResponseStatus: forwardResponseStatus,

processor: processor,
}
result.preProcessing = []processingStep{
result.validateRequest,
result.extractAuthorization,
result.checkForwardResponseStatus,
result.compileForwardExpressions,
result.transferForwardRequestHeader,
result.transferRequestHeader,
Expand Down
1 change: 1 addition & 0 deletions internal/http/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func NewMetaMiddleware(cfg *config.Config, delegate http.Handler) http.Handler {
"headerPrefix": cfg.RequestHeaderPrefix,
"forwardRequestHeaderPrefix": cfg.ForwardRequestHeaderPrefix,
"forwardResponseHeaderPrefix": cfg.ForwardResponseHeaderPrefix,
"forwardResponseStatusHeader": cfg.ForwardResponseStatusHeader,
"statusHeader": cfg.RequestHeaderPrefix + controller.StatusLineHeaderSuffix,
})
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/processor/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Input struct {
}

type Output struct {
StatusCode int
StatusLine string
Header map[string][]string
Body io.ReadCloser
Expand Down
1 change: 1 addition & 0 deletions internal/processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func (p *processor) Process(input Input) (Output, error) {
Debug("Process request done.")

return Output{
StatusCode: resp.StatusCode,
StatusLine: fmt.Sprintf("%s %s", resp.Proto, resp.Status),
Body: resp.Body,
Header: resp.Header,
Expand Down
15 changes: 15 additions & 0 deletions test/forward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,18 @@ func TestResponseHeaderForward(t *testing.T) {
headerPrefix + "Status-Line": {"HTTP/1.1 202 Accepted"}, //origin: proxy (concatenation of targets response code)
}), resp.Header)
}

func TestResponseStatusForward(t *testing.T) {
mock = func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAccepted)
}
resp := do(validRequest(http.MethodGet, "/", map[string][]string{
fwdRespStatusHeader: {"1"}, //tell the server that the targets status should be forwarded
}, nil))

assert.Equal(t, http.StatusAccepted, resp.StatusCode)
assert.Equal(t, http.Header(map[string][]string{
"Content-Length": {"0"}, //origin: proxy (because the target body is less than 512b)
headerPrefix + "Content-Length": {"0"}, //origin: target (because the target body is less than 512b)
}), resp.Header)
}
3 changes: 2 additions & 1 deletion test/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
headerPrefix = "R-"
fwdReqHeaderPrefix = headerPrefix + "Forward-Request-"
fwdRespHeaderPrefix = headerPrefix + "Forward-Response-"
fwdRespStatusHeader = headerPrefix + "Forward-Response-Status"
)

var (
Expand All @@ -49,7 +50,7 @@ func init() {
userStore.Add(username, password)

p := processor.New(userStore)
h := controller.NewProxy(headerPrefix, fwdReqHeaderPrefix, fwdRespHeaderPrefix, p)
h := controller.NewProxy(headerPrefix, fwdReqHeaderPrefix, fwdRespHeaderPrefix, fwdRespStatusHeader, p)
toTest := ihttp.NewServer(fmt.Sprintf(":%d", appPort), h)
shutdowner = toTest

Expand Down

0 comments on commit a485116

Please sign in to comment.