mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-12 16:47:41 +02:00
proxy: use internal/httputil for error handling (#36)
- General formatting and comment cleanup. - Inject pomerium version at compiletime via template package.
This commit is contained in:
parent
236e5cd7de
commit
ebc1453292
7 changed files with 42 additions and 66 deletions
|
@ -47,15 +47,12 @@ type Options struct {
|
||||||
|
|
||||||
SessionLifetimeTTL time.Duration `envconfig:"SESSION_LIFETIME_TTL"`
|
SessionLifetimeTTL time.Duration `envconfig:"SESSION_LIFETIME_TTL"`
|
||||||
|
|
||||||
// Authentication provider configuration vars
|
// Authentication provider configuration variables as specified by RFC6749
|
||||||
// See: https://openid.net/specs/openid-connect-basic-1_0.html#RFC6749
|
// See: https://openid.net/specs/openid-connect-basic-1_0.html#RFC6749
|
||||||
ClientID string `envconfig:"IDP_CLIENT_ID"`
|
ClientID string `envconfig:"IDP_CLIENT_ID"`
|
||||||
ClientSecret string `envconfig:"IDP_CLIENT_SECRET"`
|
ClientSecret string `envconfig:"IDP_CLIENT_SECRET"`
|
||||||
Provider string `envconfig:"IDP_PROVIDER"`
|
Provider string `envconfig:"IDP_PROVIDER"`
|
||||||
ProviderURL string `envconfig:"IDP_PROVIDER_URL"`
|
ProviderURL string `envconfig:"IDP_PROVIDER_URL"`
|
||||||
// Scopes is an optional setting corresponding to OAuth 2.0 specification's access scopes
|
|
||||||
// issuing an Access Token. Named providers are already set with good defaults.
|
|
||||||
// Most likely only overrides if using the generic OIDC provider.
|
|
||||||
Scopes []string `envconfig:"IDP_SCOPE"`
|
Scopes []string `envconfig:"IDP_SCOPE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ func (p *Authenticate) Handler() http.Handler {
|
||||||
middleware.ValidateSignature(p.SharedKey),
|
middleware.ValidateSignature(p.SharedKey),
|
||||||
middleware.ValidateRedirectURI(p.ProxyRootDomains))
|
middleware.ValidateRedirectURI(p.ProxyRootDomains))
|
||||||
|
|
||||||
validateClientSecret := stdMiddleware.Append(middleware.ValidateClientSecret(p.SharedKey))
|
validateClientSecretMiddleware := stdMiddleware.Append(middleware.ValidateClientSecret(p.SharedKey))
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("/robots.txt", stdMiddleware.ThenFunc(p.RobotsTxt))
|
mux.Handle("/robots.txt", stdMiddleware.ThenFunc(p.RobotsTxt))
|
||||||
|
@ -62,10 +62,10 @@ func (p *Authenticate) Handler() http.Handler {
|
||||||
// authenticate-server endpoints
|
// authenticate-server endpoints
|
||||||
mux.Handle("/sign_in", validateSignatureMiddleware.ThenFunc(p.SignIn))
|
mux.Handle("/sign_in", validateSignatureMiddleware.ThenFunc(p.SignIn))
|
||||||
mux.Handle("/sign_out", validateSignatureMiddleware.ThenFunc(p.SignOut)) // "GET", "POST"
|
mux.Handle("/sign_out", validateSignatureMiddleware.ThenFunc(p.SignOut)) // "GET", "POST"
|
||||||
mux.Handle("/profile", validateClientSecret.ThenFunc(p.GetProfile)) // GET
|
mux.Handle("/profile", validateClientSecretMiddleware.ThenFunc(p.GetProfile)) // GET
|
||||||
mux.Handle("/validate", validateClientSecret.ThenFunc(p.ValidateToken)) // GET
|
mux.Handle("/validate", validateClientSecretMiddleware.ThenFunc(p.ValidateToken)) // GET
|
||||||
mux.Handle("/redeem", validateClientSecret.ThenFunc(p.Redeem)) // POST
|
mux.Handle("/redeem", validateClientSecretMiddleware.ThenFunc(p.Redeem)) // POST
|
||||||
mux.Handle("/refresh", validateClientSecret.ThenFunc(p.Refresh)) //POST
|
mux.Handle("/refresh", validateClientSecretMiddleware.ThenFunc(p.Refresh)) //POST
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
@ -431,7 +431,7 @@ func (p *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request)
|
||||||
// - for p.provider.ValidateGroup see providers/google.go#ValidateGroup for more info
|
// - for p.provider.ValidateGroup see providers/google.go#ValidateGroup for more info
|
||||||
if !p.Validator(session.Email) {
|
if !p.Validator(session.Email) {
|
||||||
log.FromRequest(r).Error().Err(err).Str("email", session.Email).Msg("invalid email permissions denied")
|
log.FromRequest(r).Error().Err(err).Str("email", session.Email).Msg("invalid email permissions denied")
|
||||||
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "Invalid Account"}
|
return "", httputil.HTTPError{Code: http.StatusForbidden, Message: "You don't have access"}
|
||||||
}
|
}
|
||||||
log.FromRequest(r).Info().Str("email", session.Email).Msg("authentication complete")
|
log.FromRequest(r).Info().Str("email", session.Email).Msg("authentication complete")
|
||||||
err = p.sessionStore.SaveSession(w, r, session)
|
err = p.sessionStore.SaveSession(w, r, session)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/templates"
|
"github.com/pomerium/pomerium/internal/templates"
|
||||||
"github.com/pomerium/pomerium/internal/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -51,12 +50,10 @@ func ErrorResponse(rw http.ResponseWriter, req *http.Request, message string, co
|
||||||
Code int
|
Code int
|
||||||
Title string
|
Title string
|
||||||
Message string
|
Message string
|
||||||
Version string
|
|
||||||
}{
|
}{
|
||||||
Code: code,
|
Code: code,
|
||||||
Title: title,
|
Title: title,
|
||||||
Message: message,
|
Message: message,
|
||||||
Version: version.FullVersion(),
|
|
||||||
}
|
}
|
||||||
templates.New().ExecuteTemplate(rw, "error.html", t)
|
templates.New().ExecuteTemplate(rw, "error.html", t)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package templates // import "github.com/pomerium/pomerium/internal/templates"
|
package templates // import "github.com/pomerium/pomerium/internal/templates"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New loads html and style resources directly. Panics on failure.
|
// New loads html and style resources directly. Panics on failure.
|
||||||
func New() *template.Template {
|
func New() *template.Template {
|
||||||
t := template.New("authenticate-templates")
|
t := template.New("pomerium-templates")
|
||||||
template.Must(t.Parse(`
|
template.Must(t.Parse(`
|
||||||
{{define "header.html"}}
|
{{define "header.html"}}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
@ -92,8 +95,7 @@ footer {
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{{end}}`))
|
{{end}}`))
|
||||||
|
t = template.Must(t.Parse(fmt.Sprintf(`{{define "footer.html"}}Secured by <b>pomerium</b> %s {{end}}`, version.FullVersion())))
|
||||||
t = template.Must(t.Parse(`{{define "footer.html"}}Secured by <b>pomerium</b> {{end}}`))
|
|
||||||
|
|
||||||
t = template.Must(t.Parse(`
|
t = template.Must(t.Parse(`
|
||||||
{{define "sign_in_message.html"}}
|
{{define "sign_in_message.html"}}
|
||||||
|
@ -134,7 +136,7 @@ footer {
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>{{template "footer.html"}} </br> {{.Version}} </footer>
|
<footer>{{template "footer.html"}} </footer>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -159,7 +161,7 @@ footer {
|
||||||
<span class="details">HTTP {{.Code}}</span>
|
<span class="details">HTTP {{.Code}}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<footer>{{template "footer.html"}} </br> {{.Version}} </footer>
|
<footer>{{template "footer.html"}} </footer>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>{{end}}`))
|
</html>{{end}}`))
|
||||||
|
@ -190,7 +192,7 @@ footer {
|
||||||
<button type="submit">Sign out</button>
|
<button type="submit">Sign out</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<footer>{{template "footer.html"}} </br> {{.Version}}</footer>
|
<footer>{{template "footer.html"}}</footer>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -73,10 +73,9 @@ func (p *Proxy) RobotsTxt(w http.ResponseWriter, _ *http.Request) {
|
||||||
fmt.Fprintf(w, "User-agent: *\nDisallow: /")
|
fmt.Fprintf(w, "User-agent: *\nDisallow: /")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Favicon will proxy the request as usual if the user is already authenticated
|
// Favicon will proxy the request as usual if the user is already authenticated but responds
|
||||||
// but responds with a 404 otherwise, to avoid spurious and confusing
|
// with a 404 otherwise, to avoid spurious and confusing authentication attempts when a browser
|
||||||
// authentication attempts when a browser automatically requests the favicon on
|
// automatically requests the favicon on an error page.
|
||||||
// an error page.
|
|
||||||
func (p *Proxy) Favicon(w http.ResponseWriter, r *http.Request) {
|
func (p *Proxy) Favicon(w http.ResponseWriter, r *http.Request) {
|
||||||
err := p.Authenticate(w, r)
|
err := p.Authenticate(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -98,23 +97,6 @@ func (p *Proxy) SignOut(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, fullURL.String(), http.StatusFound)
|
http.Redirect(w, r, fullURL.String(), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorPage renders an error page with a given status code, title, and message.
|
|
||||||
func (p *Proxy) ErrorPage(w http.ResponseWriter, r *http.Request, code int, title string, message string) {
|
|
||||||
w.WriteHeader(code)
|
|
||||||
t := struct {
|
|
||||||
Code int
|
|
||||||
Title string
|
|
||||||
Message string
|
|
||||||
Version string
|
|
||||||
}{
|
|
||||||
Code: code,
|
|
||||||
Title: title,
|
|
||||||
Message: message,
|
|
||||||
Version: version.FullVersion(),
|
|
||||||
}
|
|
||||||
p.templates.ExecuteTemplate(w, "error.html", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuthStart begins the authentication flow, encrypting the redirect url
|
// OAuthStart begins the authentication flow, encrypting the redirect url
|
||||||
// in a request to the provider's sign in endpoint.
|
// in a request to the provider's sign in endpoint.
|
||||||
func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -134,7 +116,7 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
||||||
encryptedCSRF, err := p.cipher.Marshal(state)
|
encryptedCSRF, err := p.cipher.Marshal(state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Err(err).Msg("failed to marshal csrf")
|
log.FromRequest(r).Error().Err(err).Msg("failed to marshal csrf")
|
||||||
p.ErrorPage(w, r, http.StatusInternalServerError, "Internal Error", err.Error())
|
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.csrfStore.SetCSRF(w, r, encryptedCSRF)
|
p.csrfStore.SetCSRF(w, r, encryptedCSRF)
|
||||||
|
@ -144,7 +126,7 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
||||||
encryptedState, err := p.cipher.Marshal(state)
|
encryptedState, err := p.cipher.Marshal(state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Err(err).Msg("failed to encrypt cookie")
|
log.FromRequest(r).Error().Err(err).Msg("failed to encrypt cookie")
|
||||||
p.ErrorPage(w, r, http.StatusInternalServerError, "Internal Error", err.Error())
|
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,9 +135,8 @@ func (p *Proxy) OAuthStart(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, signinURL.String(), http.StatusFound)
|
http.Redirect(w, r, signinURL.String(), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuthCallback validates the cookie sent back from the provider, then validates
|
// OAuthCallback validates the cookie sent back from the provider, then validates he user
|
||||||
// the user information, and if authorized, redirects the user back to the original
|
// information, and if authorized, redirects the user back to the original application.
|
||||||
// application.
|
|
||||||
func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
// We receive the callback from the SSO Authenticator. This request will either contain an
|
// We receive the callback from the SSO Authenticator. This request will either contain an
|
||||||
// error, or it will contain a `code`; the code can be used to fetch an access token, and
|
// error, or it will contain a `code`; the code can be used to fetch an access token, and
|
||||||
|
@ -164,12 +145,12 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Err(err).Msg("failed parsing request form")
|
log.FromRequest(r).Error().Err(err).Msg("failed parsing request form")
|
||||||
p.ErrorPage(w, r, http.StatusInternalServerError, "Internal Error", err.Error())
|
httputil.ErrorResponse(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
errorString := r.Form.Get("error")
|
errorString := r.Form.Get("error")
|
||||||
if errorString != "" {
|
if errorString != "" {
|
||||||
p.ErrorPage(w, r, http.StatusForbidden, "Permission Denied", errorString)
|
httputil.ErrorResponse(w, r, errorString, http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +158,7 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
session, err := p.redeemCode(r.Host, r.Form.Get("code"))
|
session, err := p.redeemCode(r.Host, r.Form.Get("code"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Err(err).Msg("error redeeming authorization code")
|
log.FromRequest(r).Error().Err(err).Msg("error redeeming authorization code")
|
||||||
p.ErrorPage(w, r, http.StatusInternalServerError, "Internal Error", "Internal Error")
|
httputil.ErrorResponse(w, r, "Internal error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,14 +167,14 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
err = p.cipher.Unmarshal(encryptedState, stateParameter)
|
err = p.cipher.Unmarshal(encryptedState, stateParameter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Err(err).Msg("could not unmarshal state")
|
log.FromRequest(r).Error().Err(err).Msg("could not unmarshal state")
|
||||||
p.ErrorPage(w, r, http.StatusInternalServerError, "Internal Error", "Internal Error")
|
httputil.ErrorResponse(w, r, "Internal error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := p.csrfStore.GetCSRF(r)
|
c, err := p.csrfStore.GetCSRF(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Err(err).Msg("failed parsing csrf cookie")
|
log.FromRequest(r).Error().Err(err).Msg("failed parsing csrf cookie")
|
||||||
p.ErrorPage(w, r, http.StatusBadRequest, "Bad Request", err.Error())
|
httputil.ErrorResponse(w, r, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.csrfStore.ClearCSRF(w, r)
|
p.csrfStore.ClearCSRF(w, r)
|
||||||
|
@ -203,19 +184,19 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
err = p.cipher.Unmarshal(encryptedCSRF, csrfParameter)
|
err = p.cipher.Unmarshal(encryptedCSRF, csrfParameter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Err(err).Msg("couldn't unmarshal CSRF")
|
log.FromRequest(r).Error().Err(err).Msg("couldn't unmarshal CSRF")
|
||||||
p.ErrorPage(w, r, http.StatusInternalServerError, "Internal Error", "Internal Error")
|
httputil.ErrorResponse(w, r, "Internal error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if encryptedState == encryptedCSRF {
|
if encryptedState == encryptedCSRF {
|
||||||
log.FromRequest(r).Error().Msg("encrypted state and CSRF should not be equal")
|
log.FromRequest(r).Error().Msg("encrypted state and CSRF should not be equal")
|
||||||
p.ErrorPage(w, r, http.StatusBadRequest, "Bad Request", "Bad Request")
|
httputil.ErrorResponse(w, r, "Bad request", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(stateParameter, csrfParameter) {
|
if !reflect.DeepEqual(stateParameter, csrfParameter) {
|
||||||
log.FromRequest(r).Error().Msg("state and CSRF should be equal")
|
log.FromRequest(r).Error().Msg("state and CSRF should be equal")
|
||||||
p.ErrorPage(w, r, http.StatusBadRequest, "Bad Request", "Bad Request")
|
httputil.ErrorResponse(w, r, "Bad request", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +204,7 @@ func (p *Proxy) OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
err = p.sessionStore.SaveSession(w, r, session)
|
err = p.sessionStore.SaveSession(w, r, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromRequest(r).Error().Msg("error saving session")
|
log.FromRequest(r).Error().Msg("error saving session")
|
||||||
p.ErrorPage(w, r, http.StatusInternalServerError, "Internal Error", "Internal Error")
|
httputil.ErrorResponse(w, r, "Error saving session", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +233,7 @@ func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
||||||
case ErrUserNotAuthorized:
|
case ErrUserNotAuthorized:
|
||||||
//todo(bdd) : custom forbidden page with details and troubleshooting info
|
//todo(bdd) : custom forbidden page with details and troubleshooting info
|
||||||
log.FromRequest(r).Debug().Err(err).Msg("proxy: user access forbidden")
|
log.FromRequest(r).Debug().Err(err).Msg("proxy: user access forbidden")
|
||||||
p.ErrorPage(w, r, http.StatusForbidden, "Forbidden", "You don't have access")
|
httputil.ErrorResponse(w, r, "You don't have access", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
case http.ErrNoCookie, sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
case http.ErrNoCookie, sessions.ErrLifetimeExpired, sessions.ErrInvalidSession:
|
||||||
log.FromRequest(r).Debug().Err(err).Msg("proxy: starting auth flow")
|
log.FromRequest(r).Debug().Err(err).Msg("proxy: starting auth flow")
|
||||||
|
@ -260,7 +241,7 @@ func (p *Proxy) Proxy(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
log.FromRequest(r).Error().Err(err).Msg("proxy: unexpected error")
|
log.FromRequest(r).Error().Err(err).Msg("proxy: unexpected error")
|
||||||
p.ErrorPage(w, r, http.StatusInternalServerError, "Internal Error", "An unexpected error occurred")
|
httputil.ErrorResponse(w, r, "An unexpected error occurred", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProxy_RobotsTxt(t *testing.T) {
|
func TestProxy_RobotsTxt(t *testing.T) {
|
||||||
auth := Proxy{}
|
proxy := Proxy{}
|
||||||
req, err := http.NewRequest("GET", "/robots.txt", nil)
|
req, err := http.NewRequest("GET", "/robots.txt", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := http.HandlerFunc(auth.RobotsTxt)
|
handler := http.HandlerFunc(proxy.RobotsTxt)
|
||||||
handler.ServeHTTP(rr, req)
|
handler.ServeHTTP(rr, req)
|
||||||
if status := rr.Code; status != http.StatusOK {
|
if status := rr.Code; status != http.StatusOK {
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||||
|
@ -22,6 +22,5 @@ func TestProxy_RobotsTxt(t *testing.T) {
|
||||||
expected := fmt.Sprintf("User-agent: *\nDisallow: /")
|
expected := fmt.Sprintf("User-agent: *\nDisallow: /")
|
||||||
if rr.Body.String() != expected {
|
if rr.Body.String() != expected {
|
||||||
t.Errorf("handler returned wrong body: got %v want %v", rr.Body.String(), expected)
|
t.Errorf("handler returned wrong body: got %v want %v", rr.Body.String(), expected)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -301,7 +301,7 @@ func NewReverseProxyHandler(opts *Options, reverseProxy *httputil.ReverseProxy,
|
||||||
}
|
}
|
||||||
|
|
||||||
// urlParse adds a scheme if none-exists, addressesing a quirk in how
|
// urlParse adds a scheme if none-exists, addressesing a quirk in how
|
||||||
// one may expect url.Parse to function when a "naked" domain is sent.
|
// one may expect url.Parse to function when given scheme-less domain is provided.
|
||||||
//
|
//
|
||||||
// see: https://github.com/golang/go/issues/12585
|
// see: https://github.com/golang/go/issues/12585
|
||||||
// see: https://golang.org/pkg/net/url/#Parse
|
// see: https://golang.org/pkg/net/url/#Parse
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue