Skip to content

Commit

Permalink
Merge pull request #3 from JSainsburyPLC/feature/redirects
Browse files Browse the repository at this point in the history
Support redirects
  • Loading branch information
steinfletcher authored Nov 7, 2019
2 parents bf488f0 + 1e899f2 commit d5e3795
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 178 deletions.
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ See `examples/config.json`
"type": "proxy", // Required
"path_pattern": "^/test-ui/.*", // regex to match request path. Required
"backend": "http://localhost:3000", // backend scheme and host to proxy to. Required
"rewrite": { // optional rewrite rules
"/test-ui/(.*)": "/$1"
}
"rewrite": [{ // rewrite rules. Optional
"path_pattern": "/test-ui/(.*)",
"to": "/$1",
}],
}
```

Expand Down Expand Up @@ -76,6 +77,19 @@ See `examples/config.json`
}
```

### Redirect type rules

```json
{
"type": "redirect",
"path_pattern": "^/test-ui/(.*)",
"redirect": {
"to": "http://localhost:3000/$1",
"type": "temporary"
}
}
```

## Development

### Release
Expand Down
2 changes: 1 addition & 1 deletion commands/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package commands

import (
"github.com/JSainsburyPLC/ui-dev-proxy/domain"
"github.com/JSainsburyPLC/ui-dev-proxy/http/proxy"
"github.com/JSainsburyPLC/ui-dev-proxy/proxy"
"github.com/urfave/cli"
"log"
"net/url"
Expand Down
26 changes: 19 additions & 7 deletions domain/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,32 @@ import (
)

const (
RouteTypeProxy = "proxy"
RouteTypeMock = "mock"
RouteTypeProxy = "proxy"
RouteTypeMock = "mock"
RouteTypeRedirect = "redirect"
)

type Config struct {
Routes []Route `json:"routes"`
}

type Route struct {
Type string `json:"type"`
PathPattern *PathPattern `json:"path_pattern"`
Backend *Backend `json:"backend"`
Mock *Mock `json:"mock"`
Rewrite map[string]string `json:"rewrite"`
Type string `json:"type"`
PathPattern *PathPattern `json:"path_pattern"`
Backend *Backend `json:"backend"`
Mock *Mock `json:"mock"`
Rewrite []Rewrite `json:"rewrite"`
Redirect *Redirect `json:"redirect"`
}

type Rewrite struct {
PathPattern *PathPattern `json:"path_pattern"`
To string `json:"to"`
}

type Redirect struct {
To string `json:"to"`
Type string `json:"type"` // either permanent or temporary. Defaults to permanent if not provided
}

type PathPattern struct {
Expand Down
2 changes: 1 addition & 1 deletion domain/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewMatcher() Matcher {
}
}

// Matches a mock against all matchers
// Match matches a mock against all matchers
func (m Matcher) Match(r *http.Request, mock Mock) bool {
found := true
for _, matcher := range m.matchers {
Expand Down
17 changes: 17 additions & 0 deletions examples/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@
]
}
}
},
{
"type": "proxy",
"path_pattern": "^/test-ui/.*",
"backend": "http://localhost:3000",
"rewrite": [{
"path_pattern": "/test-ui/(.*)",
"to": "/$1"
}]
},
{
"type": "redirect",
"path_pattern": "^/test-ui/(.*)",
"redirect": {
"to": "http://localhost:3000/$1",
"type": "temporary"
}
}
]
}
11 changes: 10 additions & 1 deletion file/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package file
import (
"encoding/json"
"errors"
"github.com/JSainsburyPLC/ui-dev-proxy/domain"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/JSainsburyPLC/ui-dev-proxy/domain"
)

func ConfigProvider() domain.ConfigProvider {
Expand All @@ -30,6 +32,13 @@ func ConfigProvider() domain.ConfigProvider {

for _, r := range c.Routes {
if r.Type != domain.RouteTypeMock {
if r.Redirect != nil {
redirectType := r.Redirect.Type
if redirectType != "permanent" && redirectType != "temporary" {
return domain.Config{}, fmt.Errorf("invalid redirect type '%s'", redirectType)
}
}

continue
}

Expand Down
58 changes: 0 additions & 58 deletions http/rewrite/rewrite.go

This file was deleted.

73 changes: 0 additions & 73 deletions http/rewrite/rewrite_test.go

This file was deleted.

75 changes: 57 additions & 18 deletions http/proxy/proxy.go → proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/JSainsburyPLC/ui-dev-proxy/domain"
"github.com/JSainsburyPLC/ui-dev-proxy/http/rewrite"

"log"
"net/http"
"net/http/httputil"
"net/url"
"path"
"time"

"github.com/JSainsburyPLC/ui-dev-proxy/domain"
)

const routeCtxKey = "route"
Expand Down Expand Up @@ -68,27 +68,19 @@ func director(defaultBackend *url.URL, logger *log.Logger) func(req *http.Reques
req.Host = defaultBackend.Host
return
}

// if route is set redirect to route backend
req.URL.Scheme = route.Backend.Scheme
req.URL.Host = route.Backend.Host
req.Host = route.Backend.Host

// apply any defined rewrite rules
for pattern, to := range route.Rewrite {
rule, err := rewrite.NewRule(pattern, to)
if err != nil {
logger.Println(fmt.Sprintf("error creating rewrite rule. %v", err))
continue
}

matched, err := rule.Rewrite(req)
if err != nil {
logger.Println(fmt.Sprintf("failed to rewrite request. %v", err))
continue
}

// recursive rewrites are not supported, exit on first rewrite
if matched {
for _, rule := range route.Rewrite {
if matches := rule.PathPattern.MatchString(path.Clean(req.URL.Path)); matches {
if err := rewrite(rule, req); err != nil {
logger.Println(fmt.Sprintf("failed to rewrite request. %v", err))
continue
}
break
}
}
Expand Down Expand Up @@ -131,6 +123,16 @@ func handler(
logger.Printf("directing to route backend '%s'\n", matchedRoute.Backend.Host)
r = r.WithContext(context.WithValue(r.Context(), routeCtxKey, matchedRoute))
reverseProxy.ServeHTTP(w, r)
case domain.RouteTypeRedirect:
to := replaceURL(matchedRoute.PathPattern, matchedRoute.Redirect.To, r.URL)
u, err := url.Parse(to)
if err != nil {
logger.Printf(err.Error())
w.WriteHeader(http.StatusBadGateway)
_, _ = w.Write([]byte("Bad gateway"))
}

http.Redirect(w, r, u.String(), redirectStatusCode(matchedRoute.Redirect.Type))
case domain.RouteTypeMock:
if !mocksEnabled {
logger.Println("directing to default backend")
Expand All @@ -150,6 +152,13 @@ func matchRoute(conf domain.Config, matcher domain.Matcher, r *http.Request, moc
if route.PathPattern.MatchString(r.URL.Path) {
return &route, nil
}
case domain.RouteTypeRedirect:
if route.Redirect == nil {
return nil, errors.New("missing redirect in config")
}
if route.PathPattern.MatchString(r.URL.Path) {
return &route, nil
}
case domain.RouteTypeMock:
if mocksEnabled {
if route.Mock == nil {
Expand Down Expand Up @@ -192,3 +201,33 @@ func addCookie(w http.ResponseWriter, cookie domain.Cookie) {
}
http.SetCookie(w, &c)
}

func rewrite(rule domain.Rewrite, req *http.Request) error {
to := path.Clean(replaceURL(rule.PathPattern, rule.To, req.URL))
u, e := url.Parse(to)
if e != nil {
return fmt.Errorf("rewritten URL is not valid. %w", e)
}

req.URL.Path = u.Path
req.URL.RawPath = u.RawPath
if u.RawQuery != "" {
req.URL.RawQuery = u.RawQuery
}

return nil
}

func replaceURL(pattern *domain.PathPattern, to string, u *url.URL) string {
uri := u.RequestURI()
match := pattern.FindStringSubmatchIndex(uri)
result := pattern.ExpandString([]byte(""), to, uri, match)
return string(result[:])
}

func redirectStatusCode(method string) int {
if method == "permanent" || method == "" {
return http.StatusMovedPermanently
}
return http.StatusFound
}
Loading

0 comments on commit d5e3795

Please sign in to comment.