mirror of
https://github.com/umputun/reproxy.git
synced 2024-09-11 08:55:25 +03:00
Redirect (#87)
* add @code redirect prefix * add proxy handling for redirects #86 * add info about redirects
This commit is contained in:
parent
680d988d42
commit
aea74d717f
@ -170,6 +170,15 @@ There are two ways to set cache duration:
|
||||
1. A single value for all static assets. This is as simple as `--assets.cache=48h`.
|
||||
2. Custom duration for different mime types. It should include two parts - the default value and the pairs of mime:duration. In command line this looks like multiple `--assets.cache` options, i.e. `--assets.cache=48h --assets.cache=text/html:24h --assets.cache=image/png:2h`. Environment values should be comma-separated, i.e. `ASSETS_CACHE=48h,text/html:24h,image/png:2h`
|
||||
|
||||
## Redirects
|
||||
|
||||
By default reproxy treats destination as a proxy location, i.e. it invokes http call internally and returns response back to the client. However by prefixing destination url with `@code` this behaviour can be changed to a permanent (status code 301) or temporary (status code 302) redirects. I.e. destination set to `@301 https://example.com/something` with cause permanent http redirect to `Location: https://example.com/something`
|
||||
|
||||
supported codes:
|
||||
|
||||
- `@301`, `@perm` - permanent redirect
|
||||
- `@302`, `@temp`, `@tmp` - temporary redirect
|
||||
|
||||
## More options
|
||||
|
||||
- `--gzip` enables gzip compression for responses.
|
||||
|
@ -29,12 +29,13 @@ type Service struct {
|
||||
|
||||
// URLMapper contains all info about source and destination routes
|
||||
type URLMapper struct {
|
||||
Server string
|
||||
SrcMatch regexp.Regexp
|
||||
Dst string
|
||||
ProviderID ProviderID
|
||||
PingURL string
|
||||
MatchType MatchType
|
||||
Server string
|
||||
SrcMatch regexp.Regexp
|
||||
Dst string
|
||||
ProviderID ProviderID
|
||||
PingURL string
|
||||
MatchType MatchType
|
||||
RedirectType RedirectType
|
||||
|
||||
AssetsLocation string
|
||||
AssetsWebRoot string
|
||||
@ -92,6 +93,16 @@ func (m MatchType) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// RedirectType defines types of redirects
|
||||
type RedirectType int
|
||||
|
||||
// enum of all redirect types
|
||||
const (
|
||||
RTNone RedirectType = 0
|
||||
RTPerm RedirectType = 301
|
||||
RTTemp RedirectType = 302
|
||||
)
|
||||
|
||||
// NewService makes service with given providers
|
||||
func NewService(providers []Provider, interval time.Duration) *Service {
|
||||
return &Service{providers: providers, interval: interval}
|
||||
@ -306,6 +317,7 @@ func (s *Service) mergeLists() (res []URLMapper) {
|
||||
continue
|
||||
}
|
||||
for i := range lst {
|
||||
lst[i] = s.redirects(lst[i])
|
||||
lst[i] = s.extendMapper(lst[i])
|
||||
}
|
||||
res = append(res, lst...)
|
||||
@ -362,6 +374,27 @@ func (s *Service) extendMapper(m URLMapper) URLMapper {
|
||||
return res
|
||||
}
|
||||
|
||||
// redirects process @code prefix and sets redirect type
|
||||
func (s *Service) redirects(m URLMapper) URLMapper {
|
||||
switch {
|
||||
case strings.HasPrefix(m.Dst, "@301 ") && len(m.Dst) > 4:
|
||||
m.Dst = m.Dst[5:]
|
||||
m.RedirectType = RTPerm
|
||||
case strings.HasPrefix(m.Dst, "@perm ") && len(m.Dst) > 5:
|
||||
m.Dst = m.Dst[6:]
|
||||
m.RedirectType = RTPerm
|
||||
case (strings.HasPrefix(m.Dst, "@302 ") || strings.HasPrefix(m.Dst, "@tmp ")) && len(m.Dst) > 4:
|
||||
m.Dst = m.Dst[5:]
|
||||
m.RedirectType = RTTemp
|
||||
case strings.HasPrefix(m.Dst, "@temp ") && len(m.Dst) > 5:
|
||||
m.Dst = m.Dst[6:]
|
||||
m.RedirectType = RTTemp
|
||||
default:
|
||||
m.RedirectType = RTNone
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *Service) mergeEvents(ctx context.Context, chs ...<-chan ProviderID) <-chan ProviderID {
|
||||
var wg sync.WaitGroup
|
||||
out := make(chan ProviderID)
|
||||
|
@ -248,6 +248,56 @@ func TestService_extendRule(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestService_redirects(t *testing.T) {
|
||||
|
||||
tbl := []struct {
|
||||
inp URLMapper
|
||||
out URLMapper
|
||||
}{
|
||||
{
|
||||
URLMapper{Dst: "/blah"},
|
||||
URLMapper{Dst: "/blah", RedirectType: RTNone},
|
||||
},
|
||||
{
|
||||
URLMapper{Dst: "http://example.com/blah"},
|
||||
URLMapper{Dst: "http://example.com/blah", RedirectType: RTNone},
|
||||
},
|
||||
{
|
||||
URLMapper{Dst: "@301 http://example.com/blah"},
|
||||
URLMapper{Dst: "http://example.com/blah", RedirectType: RTPerm},
|
||||
},
|
||||
{
|
||||
URLMapper{Dst: "@perm http://example.com/blah"},
|
||||
URLMapper{Dst: "http://example.com/blah", RedirectType: RTPerm},
|
||||
},
|
||||
{
|
||||
URLMapper{Dst: "@302 http://example.com/blah"},
|
||||
URLMapper{Dst: "http://example.com/blah", RedirectType: RTTemp},
|
||||
},
|
||||
{
|
||||
URLMapper{Dst: "@tmp http://example.com/blah"},
|
||||
URLMapper{Dst: "http://example.com/blah", RedirectType: RTTemp},
|
||||
},
|
||||
{
|
||||
URLMapper{Dst: "@temp http://example.com/blah"},
|
||||
URLMapper{Dst: "http://example.com/blah", RedirectType: RTTemp},
|
||||
},
|
||||
{
|
||||
URLMapper{Dst: "@blah http://example.com/blah"},
|
||||
URLMapper{Dst: "@blah http://example.com/blah", RedirectType: RTNone},
|
||||
},
|
||||
}
|
||||
|
||||
svc := &Service{}
|
||||
for i, tt := range tbl {
|
||||
tt := tt
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
res := svc.redirects(tt.inp)
|
||||
assert.Equal(t, tt.out, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_ScheduleHealthCheck(t *testing.T) {
|
||||
randomPort := rand.Intn(10000) + 40000
|
||||
|
||||
|
@ -228,9 +228,19 @@ func (h *Http) proxyHandler() http.HandlerFunc {
|
||||
|
||||
switch matchType {
|
||||
case discovery.MTProxy:
|
||||
uu := r.Context().Value(ctxURL).(*url.URL)
|
||||
log.Printf("[DEBUG] proxy to %s", uu)
|
||||
reverseProxy.ServeHTTP(w, r)
|
||||
switch match.Mapper.RedirectType {
|
||||
case discovery.RTNone:
|
||||
uu := r.Context().Value(ctxURL).(*url.URL)
|
||||
log.Printf("[DEBUG] proxy to %s", uu)
|
||||
reverseProxy.ServeHTTP(w, r)
|
||||
case discovery.RTPerm:
|
||||
log.Printf("[DEBUG] redirect (301) to %s", match.Destination)
|
||||
http.Redirect(w, r, match.Destination, http.StatusMovedPermanently)
|
||||
case discovery.RTTemp:
|
||||
log.Printf("[DEBUG] redirect (302) to %s", match.Destination)
|
||||
http.Redirect(w, r, match.Destination, http.StatusFound)
|
||||
}
|
||||
|
||||
case discovery.MTStatic:
|
||||
// static match result has webroot:location, i.e. /www:/var/somedir/
|
||||
ae := strings.Split(match.Destination, ":")
|
||||
|
@ -221,21 +221,21 @@ func TestHttp_DoWithAssetRules(t *testing.T) {
|
||||
|
||||
client := http.Client{}
|
||||
|
||||
// {
|
||||
// req, err := http.NewRequest("GET", "http://127.0.0.1:"+strconv.Itoa(port)+"/api/something", nil)
|
||||
// require.NoError(t, err)
|
||||
// resp, err := client.Do(req)
|
||||
// require.NoError(t, err)
|
||||
// defer resp.Body.Close()
|
||||
// assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
// t.Logf("%+v", resp.Header)
|
||||
//
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// require.NoError(t, err)
|
||||
// assert.Equal(t, "response /567/something", string(body))
|
||||
// assert.Equal(t, "", resp.Header.Get("App-Method"))
|
||||
// assert.Equal(t, "v1", resp.Header.Get("h1"))
|
||||
// }
|
||||
{
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:"+strconv.Itoa(port)+"/api/something", nil)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
t.Logf("%+v", resp.Header)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "response /567/something", string(body))
|
||||
assert.Equal(t, "", resp.Header.Get("App-Method"))
|
||||
assert.Equal(t, "v1", resp.Header.Get("h1"))
|
||||
}
|
||||
|
||||
{
|
||||
resp, err := client.Get("http://localhost:" + strconv.Itoa(port) + "/web/1.html")
|
||||
@ -253,6 +253,62 @@ func TestHttp_DoWithAssetRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttp_DoWithRedirects(t *testing.T) {
|
||||
port := rand.Intn(10000) + 40000
|
||||
cc := NewCacheControl(time.Hour * 12)
|
||||
h := Http{Timeouts: Timeouts{ResponseHeader: 200 * time.Millisecond}, Address: fmt.Sprintf("127.0.0.1:%d", port),
|
||||
AccessLog: io.Discard, CacheControl: cc, Reporter: &ErrorReporter{}}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
svc := discovery.NewService([]discovery.Provider{
|
||||
&provider.Static{Rules: []string{
|
||||
"localhost,^/api/(.*),@perm http://example.com/123/$1,",
|
||||
"127.0.0.1,^/api/(.*),@302 http://example.com/567/$1,",
|
||||
},
|
||||
}}, time.Millisecond*10)
|
||||
|
||||
go func() {
|
||||
_ = svc.Run(context.Background())
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
h.Matcher = svc
|
||||
h.Metrics = mgmt.NewMetrics()
|
||||
|
||||
go func() {
|
||||
_ = h.Run(ctx)
|
||||
}()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
client := &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
{
|
||||
req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(port)+"/api/something", nil)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusMovedPermanently, resp.StatusCode)
|
||||
t.Logf("%+v", resp.Header)
|
||||
assert.Equal(t, "http://example.com/123/something", resp.Header.Get("Location"))
|
||||
}
|
||||
|
||||
{
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1:"+strconv.Itoa(port)+"/api/something", nil)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusFound, resp.StatusCode)
|
||||
t.Logf("%+v", resp.Header)
|
||||
assert.Equal(t, "http://example.com/567/something", resp.Header.Get("Location"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttp_DoLimitedReq(t *testing.T) {
|
||||
port := rand.Intn(10000) + 40000
|
||||
h := Http{Timeouts: Timeouts{ResponseHeader: 200 * time.Millisecond}, Address: fmt.Sprintf("127.0.0.1:%d", port),
|
||||
|
Loading…
Reference in New Issue
Block a user