mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-29 21:54:13 +01:00
reset config
This commit is contained in:
parent
3b53b354d3
commit
c3626edd42
@ -1529,6 +1529,22 @@ paths:
|
|||||||
description: User does not have permission of admin role.
|
description: User does not have permission of admin role.
|
||||||
500:
|
500:
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
/configurations/reset:
|
||||||
|
post:
|
||||||
|
summary: Reset system configurations.
|
||||||
|
description: |
|
||||||
|
Reset system configurations from harbor.cfg. Can only be accessed by admin user.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Reset system configurations successfully.
|
||||||
|
401:
|
||||||
|
description: User need to log in first.
|
||||||
|
403:
|
||||||
|
description: User does not have permission of admin role.
|
||||||
|
500:
|
||||||
|
description: Unexpected internal errors.
|
||||||
/email/ping:
|
/email/ping:
|
||||||
post:
|
post:
|
||||||
summary: Test connection and authentication with email server.
|
summary: Test connection and authentication with email server.
|
||||||
|
@ -37,3 +37,4 @@ USE_COMPRESSED_JS=$use_compressed_js
|
|||||||
GODEBUG=netdns=cgo
|
GODEBUG=netdns=cgo
|
||||||
ADMIRAL_URL=$admiral_url
|
ADMIRAL_URL=$admiral_url
|
||||||
WITH_NOTARY=$with_notary
|
WITH_NOTARY=$with_notary
|
||||||
|
RESET=false
|
||||||
|
@ -104,3 +104,24 @@ func UpdateCfgs(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetCfgs resets configurations from environment variables
|
||||||
|
func ResetCfgs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
authenticated, err := isAuthenticated(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to check whether the request is authenticated or not: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !authenticated {
|
||||||
|
handleUnauthorized(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cfg.Reset(); err != nil {
|
||||||
|
log.Errorf("failed to reset system configurations: %v", err)
|
||||||
|
handleInternalServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,7 +43,7 @@ func TestConfigAPI(t *testing.T) {
|
|||||||
|
|
||||||
secret := "secret"
|
secret := "secret"
|
||||||
envs := map[string]string{
|
envs := map[string]string{
|
||||||
|
"AUTH_MODE": comcfg.DBAuth,
|
||||||
"JSON_CFG_STORE_PATH": configPath,
|
"JSON_CFG_STORE_PATH": configPath,
|
||||||
"KEY_PATH": secretKeyPath,
|
"KEY_PATH": secretKeyPath,
|
||||||
"UI_SECRET": secret,
|
"UI_SECRET": secret,
|
||||||
@ -164,7 +164,55 @@ func TestConfigAPI(t *testing.T) {
|
|||||||
|
|
||||||
mode := m[comcfg.AUTHMode].(string)
|
mode := m[comcfg.AUTHMode].(string)
|
||||||
if mode != comcfg.LDAPAuth {
|
if mode != comcfg.LDAPAuth {
|
||||||
t.Errorf("unexpected ldap scope: %s != %s", mode, comcfg.LDAPAuth)
|
t.Errorf("unexpected auth mode: %s != %s", mode, comcfg.LDAPAuth)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset configurations
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
r, err = http.NewRequest("POST", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.AddCookie(&http.Cookie{
|
||||||
|
Name: "secret",
|
||||||
|
Value: secret,
|
||||||
|
})
|
||||||
|
|
||||||
|
ResetCfgs(w, r)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm the reset
|
||||||
|
r, err = http.NewRequest("GET", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to create request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.AddCookie(&http.Cookie{
|
||||||
|
Name: "secret",
|
||||||
|
Value: secret,
|
||||||
|
})
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
ListCfgs(w, r)
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Errorf("unexpected status code: %d != %d", w.Code, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err = parse(w.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse response body: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = m[comcfg.AUTHMode].(string)
|
||||||
|
if mode != comcfg.DBAuth {
|
||||||
|
t.Errorf("unexpected auth mode: %s != %s", mode, comcfg.LDAPAuth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
syscfg "github.com/vmware/harbor/src/adminserver/systemcfg"
|
syscfg "github.com/vmware/harbor/src/adminserver/systemcfg"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
@ -52,7 +53,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
server := &Server{
|
server := &Server{
|
||||||
Port: port,
|
Port: port,
|
||||||
Handler: newHandler(),
|
Handler: handlers.LoggingHandler(os.Stdout, newHandler()),
|
||||||
}
|
}
|
||||||
if err := server.Serve(); err != nil {
|
if err := server.Serve(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -26,5 +26,6 @@ func newHandler() http.Handler {
|
|||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/api/configurations", api.ListCfgs).Methods("GET")
|
r.HandleFunc("/api/configurations", api.ListCfgs).Methods("GET")
|
||||||
r.HandleFunc("/api/configurations", api.UpdateCfgs).Methods("PUT")
|
r.HandleFunc("/api/configurations", api.UpdateCfgs).Methods("PUT")
|
||||||
|
r.HandleFunc("/api/configurations/reset", api.ResetCfgs).Methods("POST")
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,11 @@ func Init() (err error) {
|
|||||||
//init key provider
|
//init key provider
|
||||||
initKeyProvider()
|
initKeyProvider()
|
||||||
|
|
||||||
|
if os.Getenv("RESET") == "true" {
|
||||||
|
log.Info("RESET is set, resetting system configurations...")
|
||||||
|
return Reset()
|
||||||
|
}
|
||||||
|
|
||||||
cfg, err := GetSystemCfg()
|
cfg, err := GetSystemCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -332,3 +337,16 @@ func decrypt(m map[string]interface{}, keys []string, secretKey string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset clears old system configurations and reloads them
|
||||||
|
// from environment variables
|
||||||
|
func Reset() error {
|
||||||
|
cfg := map[string]interface{}{}
|
||||||
|
if err := loadFromEnv(cfg, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//sync configurations into cfg store
|
||||||
|
log.Info("updating system configurations...")
|
||||||
|
return UpdateSystemCfg(cfg)
|
||||||
|
}
|
||||||
|
@ -117,4 +117,21 @@ func TestSystemcfg(t *testing.T) {
|
|||||||
cfg[comcfg.AUTHMode], comcfg.DBAuth)
|
cfg[comcfg.AUTHMode], comcfg.DBAuth)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = Reset(); err != nil {
|
||||||
|
t.Errorf("failed to reset system configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err = GetSystemCfg()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get system configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg[comcfg.AUTHMode] != comcfg.DBAuth {
|
||||||
|
t.Errorf("unexpected auth mode: %s != %s",
|
||||||
|
cfg[comcfg.AUTHMode], comcfg.DBAuth)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,11 @@ func (m *Manager) Load() (map[string]interface{}, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset configurations
|
||||||
|
func (m *Manager) Reset() error {
|
||||||
|
return m.Loader.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
func getCfgExpiration(m map[string]interface{}) (int, error) {
|
func getCfgExpiration(m map[string]interface{}) (int, error) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return 0, fmt.Errorf("can not get cfg expiration as configurations are null")
|
return 0, fmt.Errorf("can not get cfg expiration as configurations are null")
|
||||||
@ -227,7 +232,7 @@ func (l *Loader) Load() ([]byte, error) {
|
|||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload configuratons to remote server
|
// Upload configurations to remote server
|
||||||
func (l *Loader) Upload(b []byte) error {
|
func (l *Loader) Upload(b []byte) error {
|
||||||
req, err := http.NewRequest("PUT", l.url+"/api/configurations", bytes.NewReader(b))
|
req, err := http.NewRequest("PUT", l.url+"/api/configurations", bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -253,6 +258,33 @@ func (l *Loader) Upload(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset sends configurations resetting command to
|
||||||
|
// remote server
|
||||||
|
func (l *Loader) Reset() error {
|
||||||
|
req, err := http.NewRequest("POST", l.url+"/api/configurations/reset", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: "secret",
|
||||||
|
Value: l.secret,
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, err := l.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected http status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("configurations resetted")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Parser parses configurations
|
// Parser parses configurations
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
}
|
}
|
||||||
|
@ -93,5 +93,13 @@ func NewAdminserver(config map[string]interface{}) (*httptest.Server, error) {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
m = append(m, &RequestHandlerMapping{
|
||||||
|
Method: "POST",
|
||||||
|
Pattern: "/api/configurations/reset",
|
||||||
|
Handler: Handler(&Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
return NewServer(m...), nil
|
return NewServer(m...), nil
|
||||||
}
|
}
|
||||||
|
@ -196,6 +196,14 @@ func (c *ConfigAPI) Put() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset system configurations
|
||||||
|
func (c *ConfigAPI) Reset() {
|
||||||
|
if err := config.Reset(); err != nil {
|
||||||
|
log.Errorf("failed to reset configurations: %v", err)
|
||||||
|
c.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func validateCfg(c map[string]string) (bool, error) {
|
func validateCfg(c map[string]string) (bool, error) {
|
||||||
isSysErr := false
|
isSysErr := false
|
||||||
|
|
||||||
|
@ -68,3 +68,37 @@ func TestPutConfig(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResetConfig(t *testing.T) {
|
||||||
|
fmt.Println("Testing resetting configurations")
|
||||||
|
assert := assert.New(t)
|
||||||
|
apiTest := newHarborAPI()
|
||||||
|
|
||||||
|
code, err := apiTest.ResetConfig(*admin)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assert.Equal(200, code, "unexpected response code") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code, cfgs, err := apiTest.GetConfig(*admin)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assert.Equal(200, code, "unexpected response code") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value, ok := cfgs[config.VerifyRemoteCert]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s not found", config.VerifyRemoteCert)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(value.Value.(bool), true, "unexpected value")
|
||||||
|
}
|
||||||
|
@ -102,6 +102,7 @@ func init() {
|
|||||||
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
|
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
|
||||||
beego.Router("/api/ldap/ping", &LdapAPI{}, "post:Ping")
|
beego.Router("/api/ldap/ping", &LdapAPI{}, "post:Ping")
|
||||||
beego.Router("/api/configurations", &ConfigAPI{})
|
beego.Router("/api/configurations", &ConfigAPI{})
|
||||||
|
beego.Router("/api/configurations/reset", &ConfigAPI{}, "post:Reset")
|
||||||
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
||||||
|
|
||||||
_ = updateInitPassword(1, "Harbor12345")
|
_ = updateInitPassword(1, "Harbor12345")
|
||||||
@ -1029,6 +1030,14 @@ func (a testapi) PutConfig(authInfo usrInfo, cfg map[string]string) (int, error)
|
|||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a testapi) ResetConfig(authInfo usrInfo) (int, error) {
|
||||||
|
_sling := sling.New().Base(a.basePath).Post("/api/configurations/reset")
|
||||||
|
|
||||||
|
code, _, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
|
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
|
||||||
func (a testapi) PingEmail(authInfo usrInfo, settings map[string]string) (int, string, error) {
|
func (a testapi) PingEmail(authInfo usrInfo, settings map[string]string) (int, string, error) {
|
||||||
_sling := sling.New().Base(a.basePath).Post("/api/email/ping").BodyJSON(settings)
|
_sling := sling.New().Base(a.basePath).Post("/api/email/ping").BodyJSON(settings)
|
||||||
|
|
||||||
|
@ -71,6 +71,11 @@ func Load() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset configurations
|
||||||
|
func Reset() error {
|
||||||
|
return mg.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
// Upload uploads all system configutations to admin server
|
// Upload uploads all system configutations to admin server
|
||||||
func Upload(cfg map[string]interface{}) error {
|
func Upload(cfg map[string]interface{}) error {
|
||||||
b, err := json.Marshal(cfg)
|
b, err := json.Marshal(cfg)
|
||||||
|
@ -140,4 +140,17 @@ func TestConfig(t *testing.T) {
|
|||||||
if extURL != "host01.com" {
|
if extURL != "host01.com" {
|
||||||
t.Errorf(`extURL should be "host01.com".`)
|
t.Errorf(`extURL should be "host01.com".`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset configurations
|
||||||
|
if err = Reset(); err != nil {
|
||||||
|
t.Errorf("failed to reset configurations: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mode, err = AuthMode()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get auth mode: %v", err)
|
||||||
|
}
|
||||||
|
if mode != "db_auth" {
|
||||||
|
t.Errorf("unexpected mode: %s != %s", mode, "db_auth")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ func initRouters() {
|
|||||||
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
beego.Router("/api/repositories/top", &api.RepositoryAPI{}, "get:GetTopRepos")
|
||||||
beego.Router("/api/logs", &api.LogAPI{})
|
beego.Router("/api/logs", &api.LogAPI{})
|
||||||
beego.Router("/api/configurations", &api.ConfigAPI{})
|
beego.Router("/api/configurations", &api.ConfigAPI{})
|
||||||
|
beego.Router("/api/configurations/reset", &api.ConfigAPI{}, "post:Reset")
|
||||||
|
|
||||||
beego.Router("/api/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo")
|
beego.Router("/api/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo")
|
||||||
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
beego.Router("/api/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
|
||||||
|
22
src/vendor/github.com/gorilla/handlers/LICENSE
generated
vendored
Normal file
22
src/vendor/github.com/gorilla/handlers/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2013 The Gorilla Handlers Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
55
src/vendor/github.com/gorilla/handlers/README.md
generated
vendored
Normal file
55
src/vendor/github.com/gorilla/handlers/README.md
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
gorilla/handlers
|
||||||
|
================
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gorilla/handlers?status.svg)](https://godoc.org/github.com/gorilla/handlers) [![Build Status](https://travis-ci.org/gorilla/handlers.svg?branch=master)](https://travis-ci.org/gorilla/handlers)
|
||||||
|
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/handlers/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/handlers?badge)
|
||||||
|
|
||||||
|
|
||||||
|
Package handlers is a collection of handlers (aka "HTTP middleware") for use
|
||||||
|
with Go's `net/http` package (or any framework supporting `http.Handler`), including:
|
||||||
|
|
||||||
|
* [**LoggingHandler**](https://godoc.org/github.com/gorilla/handlers#LoggingHandler) for logging HTTP requests in the Apache [Common Log
|
||||||
|
Format](http://httpd.apache.org/docs/2.2/logs.html#common).
|
||||||
|
* [**CombinedLoggingHandler**](https://godoc.org/github.com/gorilla/handlers#CombinedLoggingHandler) for logging HTTP requests in the Apache [Combined Log
|
||||||
|
Format](http://httpd.apache.org/docs/2.2/logs.html#combined) commonly used by
|
||||||
|
both Apache and nginx.
|
||||||
|
* [**CompressHandler**](https://godoc.org/github.com/gorilla/handlers#CompressHandler) for gzipping responses.
|
||||||
|
* [**ContentTypeHandler**](https://godoc.org/github.com/gorilla/handlers#ContentTypeHandler) for validating requests against a list of accepted
|
||||||
|
content types.
|
||||||
|
* [**MethodHandler**](https://godoc.org/github.com/gorilla/handlers#MethodHandler) for matching HTTP methods against handlers in a
|
||||||
|
`map[string]http.Handler`
|
||||||
|
* [**ProxyHeaders**](https://godoc.org/github.com/gorilla/handlers#ProxyHeaders) for populating `r.RemoteAddr` and `r.URL.Scheme` based on the
|
||||||
|
`X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` and RFC7239 `Forwarded`
|
||||||
|
headers when running a Go server behind a HTTP reverse proxy.
|
||||||
|
* [**CanonicalHost**](https://godoc.org/github.com/gorilla/handlers#CanonicalHost) for re-directing to the preferred host when handling multiple
|
||||||
|
domains (i.e. multiple CNAME aliases).
|
||||||
|
* [**RecoveryHandler**](https://godoc.org/github.com/gorilla/handlers#RecoveryHandler) for recovering from unexpected panics.
|
||||||
|
|
||||||
|
Other handlers are documented [on the Gorilla
|
||||||
|
website](http://www.gorillatoolkit.org/pkg/handlers).
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
A simple example using `handlers.LoggingHandler` and `handlers.CompressHandler`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := http.NewServeMux()
|
||||||
|
|
||||||
|
// Only log requests to our admin dashboard to stdout
|
||||||
|
r.Handle("/admin", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(ShowAdminDashboard)))
|
||||||
|
r.HandleFunc("/", ShowIndex)
|
||||||
|
|
||||||
|
// Wrap our server with our gzip handler to gzip compress all responses.
|
||||||
|
http.ListenAndServe(":8000", handlers.CompressHandler(r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD licensed. See the included LICENSE file for details.
|
||||||
|
|
74
src/vendor/github.com/gorilla/handlers/canonical.go
generated
vendored
Normal file
74
src/vendor/github.com/gorilla/handlers/canonical.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type canonical struct {
|
||||||
|
h http.Handler
|
||||||
|
domain string
|
||||||
|
code int
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanonicalHost is HTTP middleware that re-directs requests to the canonical
|
||||||
|
// domain. It accepts a domain and a status code (e.g. 301 or 302) and
|
||||||
|
// re-directs clients to this domain. The existing request path is maintained.
|
||||||
|
//
|
||||||
|
// Note: If the provided domain is considered invalid by url.Parse or otherwise
|
||||||
|
// returns an empty scheme or host, clients are not re-directed.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302)
|
||||||
|
// r.HandleFunc("/route", YourHandler)
|
||||||
|
//
|
||||||
|
// log.Fatal(http.ListenAndServe(":7000", canonical(r)))
|
||||||
|
//
|
||||||
|
func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler {
|
||||||
|
fn := func(h http.Handler) http.Handler {
|
||||||
|
return canonical{h, domain, code}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
dest, err := url.Parse(c.domain)
|
||||||
|
if err != nil {
|
||||||
|
// Call the next handler if the provided domain fails to parse.
|
||||||
|
c.h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if dest.Scheme == "" || dest.Host == "" {
|
||||||
|
// Call the next handler if the scheme or host are empty.
|
||||||
|
// Note that url.Parse won't fail on in this case.
|
||||||
|
c.h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.EqualFold(cleanHost(r.Host), dest.Host) {
|
||||||
|
// Re-build the destination URL
|
||||||
|
dest := dest.Scheme + "://" + dest.Host + r.URL.Path
|
||||||
|
if r.URL.RawQuery != "" {
|
||||||
|
dest += "?" + r.URL.RawQuery
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, dest, c.code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanHost cleans invalid Host headers by stripping anything after '/' or ' '.
|
||||||
|
// This is backported from Go 1.5 (in response to issue #11206) and attempts to
|
||||||
|
// mitigate malformed Host headers that do not match the format in RFC7230.
|
||||||
|
func cleanHost(in string) string {
|
||||||
|
if i := strings.IndexAny(in, " /"); i != -1 {
|
||||||
|
return in[:i]
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
148
src/vendor/github.com/gorilla/handlers/compress.go
generated
vendored
Normal file
148
src/vendor/github.com/gorilla/handlers/compress.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2013 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/flate"
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type compressResponseWriter struct {
|
||||||
|
io.Writer
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *compressResponseWriter) WriteHeader(c int) {
|
||||||
|
w.ResponseWriter.Header().Del("Content-Length")
|
||||||
|
w.ResponseWriter.WriteHeader(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *compressResponseWriter) Header() http.Header {
|
||||||
|
return w.ResponseWriter.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *compressResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
h := w.ResponseWriter.Header()
|
||||||
|
if h.Get("Content-Type") == "" {
|
||||||
|
h.Set("Content-Type", http.DetectContentType(b))
|
||||||
|
}
|
||||||
|
h.Del("Content-Length")
|
||||||
|
|
||||||
|
return w.Writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
type flusher interface {
|
||||||
|
Flush() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *compressResponseWriter) Flush() {
|
||||||
|
// Flush compressed data if compressor supports it.
|
||||||
|
if f, ok := w.Writer.(flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
// Flush HTTP response.
|
||||||
|
if w.Flusher != nil {
|
||||||
|
w.Flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompressHandler gzip compresses HTTP responses for clients that support it
|
||||||
|
// via the 'Accept-Encoding' header.
|
||||||
|
//
|
||||||
|
// Compressing TLS traffic may leak the page contents to an attacker if the
|
||||||
|
// page contains user input: http://security.stackexchange.com/a/102015/12208
|
||||||
|
func CompressHandler(h http.Handler) http.Handler {
|
||||||
|
return CompressHandlerLevel(h, gzip.DefaultCompression)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompressHandlerLevel gzip compresses HTTP responses with specified compression level
|
||||||
|
// for clients that support it via the 'Accept-Encoding' header.
|
||||||
|
//
|
||||||
|
// The compression level should be gzip.DefaultCompression, gzip.NoCompression,
|
||||||
|
// or any integer value between gzip.BestSpeed and gzip.BestCompression inclusive.
|
||||||
|
// gzip.DefaultCompression is used in case of invalid compression level.
|
||||||
|
func CompressHandlerLevel(h http.Handler, level int) http.Handler {
|
||||||
|
if level < gzip.DefaultCompression || level > gzip.BestCompression {
|
||||||
|
level = gzip.DefaultCompression
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
L:
|
||||||
|
for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") {
|
||||||
|
switch strings.TrimSpace(enc) {
|
||||||
|
case "gzip":
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
w.Header().Add("Vary", "Accept-Encoding")
|
||||||
|
|
||||||
|
gw, _ := gzip.NewWriterLevel(w, level)
|
||||||
|
defer gw.Close()
|
||||||
|
|
||||||
|
h, hok := w.(http.Hijacker)
|
||||||
|
if !hok { /* w is not Hijacker... oh well... */
|
||||||
|
h = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, fok := w.(http.Flusher)
|
||||||
|
if !fok {
|
||||||
|
f = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, cnok := w.(http.CloseNotifier)
|
||||||
|
if !cnok {
|
||||||
|
cn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w = &compressResponseWriter{
|
||||||
|
Writer: gw,
|
||||||
|
ResponseWriter: w,
|
||||||
|
Hijacker: h,
|
||||||
|
Flusher: f,
|
||||||
|
CloseNotifier: cn,
|
||||||
|
}
|
||||||
|
|
||||||
|
break L
|
||||||
|
case "deflate":
|
||||||
|
w.Header().Set("Content-Encoding", "deflate")
|
||||||
|
w.Header().Add("Vary", "Accept-Encoding")
|
||||||
|
|
||||||
|
fw, _ := flate.NewWriter(w, level)
|
||||||
|
defer fw.Close()
|
||||||
|
|
||||||
|
h, hok := w.(http.Hijacker)
|
||||||
|
if !hok { /* w is not Hijacker... oh well... */
|
||||||
|
h = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, fok := w.(http.Flusher)
|
||||||
|
if !fok {
|
||||||
|
f = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, cnok := w.(http.CloseNotifier)
|
||||||
|
if !cnok {
|
||||||
|
cn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w = &compressResponseWriter{
|
||||||
|
Writer: fw,
|
||||||
|
ResponseWriter: w,
|
||||||
|
Hijacker: h,
|
||||||
|
Flusher: f,
|
||||||
|
CloseNotifier: cn,
|
||||||
|
}
|
||||||
|
|
||||||
|
break L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
317
src/vendor/github.com/gorilla/handlers/cors.go
generated
vendored
Normal file
317
src/vendor/github.com/gorilla/handlers/cors.go
generated
vendored
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CORSOption represents a functional option for configuring the CORS middleware.
|
||||||
|
type CORSOption func(*cors) error
|
||||||
|
|
||||||
|
type cors struct {
|
||||||
|
h http.Handler
|
||||||
|
allowedHeaders []string
|
||||||
|
allowedMethods []string
|
||||||
|
allowedOrigins []string
|
||||||
|
allowedOriginValidator OriginValidator
|
||||||
|
exposedHeaders []string
|
||||||
|
maxAge int
|
||||||
|
ignoreOptions bool
|
||||||
|
allowCredentials bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// OriginValidator takes an origin string and returns whether or not that origin is allowed.
|
||||||
|
type OriginValidator func(string) bool
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultCorsMethods = []string{"GET", "HEAD", "POST"}
|
||||||
|
defaultCorsHeaders = []string{"Accept", "Accept-Language", "Content-Language", "Origin"}
|
||||||
|
// (WebKit/Safari v9 sends the Origin header by default in AJAX requests)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
corsOptionMethod string = "OPTIONS"
|
||||||
|
corsAllowOriginHeader string = "Access-Control-Allow-Origin"
|
||||||
|
corsExposeHeadersHeader string = "Access-Control-Expose-Headers"
|
||||||
|
corsMaxAgeHeader string = "Access-Control-Max-Age"
|
||||||
|
corsAllowMethodsHeader string = "Access-Control-Allow-Methods"
|
||||||
|
corsAllowHeadersHeader string = "Access-Control-Allow-Headers"
|
||||||
|
corsAllowCredentialsHeader string = "Access-Control-Allow-Credentials"
|
||||||
|
corsRequestMethodHeader string = "Access-Control-Request-Method"
|
||||||
|
corsRequestHeadersHeader string = "Access-Control-Request-Headers"
|
||||||
|
corsOriginHeader string = "Origin"
|
||||||
|
corsVaryHeader string = "Vary"
|
||||||
|
corsOriginMatchAll string = "*"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
origin := r.Header.Get(corsOriginHeader)
|
||||||
|
if !ch.isOriginAllowed(origin) {
|
||||||
|
ch.h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == corsOptionMethod {
|
||||||
|
if ch.ignoreOptions {
|
||||||
|
ch.h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := r.Header[corsRequestMethodHeader]; !ok {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
method := r.Header.Get(corsRequestMethodHeader)
|
||||||
|
if !ch.isMatch(method, ch.allowedMethods) {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestHeaders := strings.Split(r.Header.Get(corsRequestHeadersHeader), ",")
|
||||||
|
allowedHeaders := []string{}
|
||||||
|
for _, v := range requestHeaders {
|
||||||
|
canonicalHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
|
||||||
|
if canonicalHeader == "" || ch.isMatch(canonicalHeader, defaultCorsHeaders) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ch.isMatch(canonicalHeader, ch.allowedHeaders) {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedHeaders = append(allowedHeaders, canonicalHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allowedHeaders) > 0 {
|
||||||
|
w.Header().Set(corsAllowHeadersHeader, strings.Join(allowedHeaders, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.maxAge > 0 {
|
||||||
|
w.Header().Set(corsMaxAgeHeader, strconv.Itoa(ch.maxAge))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ch.isMatch(method, defaultCorsMethods) {
|
||||||
|
w.Header().Set(corsAllowMethodsHeader, method)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(ch.exposedHeaders) > 0 {
|
||||||
|
w.Header().Set(corsExposeHeadersHeader, strings.Join(ch.exposedHeaders, ","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.allowCredentials {
|
||||||
|
w.Header().Set(corsAllowCredentialsHeader, "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ch.allowedOrigins) > 1 {
|
||||||
|
w.Header().Set(corsVaryHeader, corsOriginHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set(corsAllowOriginHeader, origin)
|
||||||
|
|
||||||
|
if r.Method == corsOptionMethod {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch.h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS provides Cross-Origin Resource Sharing middleware.
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "net/http"
|
||||||
|
//
|
||||||
|
// "github.com/gorilla/handlers"
|
||||||
|
// "github.com/gorilla/mux"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HandleFunc("/users", UserEndpoint)
|
||||||
|
// r.HandleFunc("/projects", ProjectEndpoint)
|
||||||
|
//
|
||||||
|
// // Apply the CORS middleware to our top-level router, with the defaults.
|
||||||
|
// http.ListenAndServe(":8000", handlers.CORS()(r))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func CORS(opts ...CORSOption) func(http.Handler) http.Handler {
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
ch := parseCORSOptions(opts...)
|
||||||
|
ch.h = h
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCORSOptions(opts ...CORSOption) *cors {
|
||||||
|
ch := &cors{
|
||||||
|
allowedMethods: defaultCorsMethods,
|
||||||
|
allowedHeaders: defaultCorsHeaders,
|
||||||
|
allowedOrigins: []string{corsOriginMatchAll},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, option := range opts {
|
||||||
|
option(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Functional options for configuring CORS.
|
||||||
|
//
|
||||||
|
|
||||||
|
// AllowedHeaders adds the provided headers to the list of allowed headers in a
|
||||||
|
// CORS request.
|
||||||
|
// This is an append operation so the headers Accept, Accept-Language,
|
||||||
|
// and Content-Language are always allowed.
|
||||||
|
// Content-Type must be explicitly declared if accepting Content-Types other than
|
||||||
|
// application/x-www-form-urlencoded, multipart/form-data, or text/plain.
|
||||||
|
func AllowedHeaders(headers []string) CORSOption {
|
||||||
|
return func(ch *cors) error {
|
||||||
|
for _, v := range headers {
|
||||||
|
normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
|
||||||
|
if normalizedHeader == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ch.isMatch(normalizedHeader, ch.allowedHeaders) {
|
||||||
|
ch.allowedHeaders = append(ch.allowedHeaders, normalizedHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedMethods can be used to explicitly allow methods in the
|
||||||
|
// Access-Control-Allow-Methods header.
|
||||||
|
// This is a replacement operation so you must also
|
||||||
|
// pass GET, HEAD, and POST if you wish to support those methods.
|
||||||
|
func AllowedMethods(methods []string) CORSOption {
|
||||||
|
return func(ch *cors) error {
|
||||||
|
ch.allowedMethods = []string{}
|
||||||
|
for _, v := range methods {
|
||||||
|
normalizedMethod := strings.ToUpper(strings.TrimSpace(v))
|
||||||
|
if normalizedMethod == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ch.isMatch(normalizedMethod, ch.allowedMethods) {
|
||||||
|
ch.allowedMethods = append(ch.allowedMethods, normalizedMethod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedOrigins sets the allowed origins for CORS requests, as used in the
|
||||||
|
// 'Allow-Access-Control-Origin' HTTP header.
|
||||||
|
// Note: Passing in a []string{"*"} will allow any domain.
|
||||||
|
func AllowedOrigins(origins []string) CORSOption {
|
||||||
|
return func(ch *cors) error {
|
||||||
|
for _, v := range origins {
|
||||||
|
if v == corsOriginMatchAll {
|
||||||
|
ch.allowedOrigins = []string{corsOriginMatchAll}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ch.allowedOrigins = origins
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedOriginValidator sets a function for evaluating allowed origins in CORS requests, represented by the
|
||||||
|
// 'Allow-Access-Control-Origin' HTTP header.
|
||||||
|
func AllowedOriginValidator(fn OriginValidator) CORSOption {
|
||||||
|
return func(ch *cors) error {
|
||||||
|
ch.allowedOriginValidator = fn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExposeHeaders can be used to specify headers that are available
|
||||||
|
// and will not be stripped out by the user-agent.
|
||||||
|
func ExposedHeaders(headers []string) CORSOption {
|
||||||
|
return func(ch *cors) error {
|
||||||
|
ch.exposedHeaders = []string{}
|
||||||
|
for _, v := range headers {
|
||||||
|
normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
|
||||||
|
if normalizedHeader == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ch.isMatch(normalizedHeader, ch.exposedHeaders) {
|
||||||
|
ch.exposedHeaders = append(ch.exposedHeaders, normalizedHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxAge determines the maximum age (in seconds) between preflight requests. A
|
||||||
|
// maximum of 10 minutes is allowed. An age above this value will default to 10
|
||||||
|
// minutes.
|
||||||
|
func MaxAge(age int) CORSOption {
|
||||||
|
return func(ch *cors) error {
|
||||||
|
// Maximum of 10 minutes.
|
||||||
|
if age > 600 {
|
||||||
|
age = 600
|
||||||
|
}
|
||||||
|
|
||||||
|
ch.maxAge = age
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreOptions causes the CORS middleware to ignore OPTIONS requests, instead
|
||||||
|
// passing them through to the next handler. This is useful when your application
|
||||||
|
// or framework has a pre-existing mechanism for responding to OPTIONS requests.
|
||||||
|
func IgnoreOptions() CORSOption {
|
||||||
|
return func(ch *cors) error {
|
||||||
|
ch.ignoreOptions = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowCredentials can be used to specify that the user agent may pass
|
||||||
|
// authentication details along with the request.
|
||||||
|
func AllowCredentials() CORSOption {
|
||||||
|
return func(ch *cors) error {
|
||||||
|
ch.allowCredentials = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *cors) isOriginAllowed(origin string) bool {
|
||||||
|
if origin == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.allowedOriginValidator != nil {
|
||||||
|
return ch.allowedOriginValidator(origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, allowedOrigin := range ch.allowedOrigins {
|
||||||
|
if allowedOrigin == origin || allowedOrigin == corsOriginMatchAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *cors) isMatch(needle string, haystack []string) bool {
|
||||||
|
for _, v := range haystack {
|
||||||
|
if v == needle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
9
src/vendor/github.com/gorilla/handlers/doc.go
generated
vendored
Normal file
9
src/vendor/github.com/gorilla/handlers/doc.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
Package handlers is a collection of handlers (aka "HTTP middleware") for use
|
||||||
|
with Go's net/http package (or any framework supporting http.Handler).
|
||||||
|
|
||||||
|
The package includes handlers for logging in standardised formats, compressing
|
||||||
|
HTTP responses, validating content types and other useful tools for manipulating
|
||||||
|
requests and responses.
|
||||||
|
*/
|
||||||
|
package handlers
|
403
src/vendor/github.com/gorilla/handlers/handlers.go
generated
vendored
Normal file
403
src/vendor/github.com/gorilla/handlers/handlers.go
generated
vendored
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
// Copyright 2013 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MethodHandler is an http.Handler that dispatches to a handler whose key in the
|
||||||
|
// MethodHandler's map matches the name of the HTTP request's method, eg: GET
|
||||||
|
//
|
||||||
|
// If the request's method is OPTIONS and OPTIONS is not a key in the map then
|
||||||
|
// the handler responds with a status of 200 and sets the Allow header to a
|
||||||
|
// comma-separated list of available methods.
|
||||||
|
//
|
||||||
|
// If the request's method doesn't match any of its keys the handler responds
|
||||||
|
// with a status of HTTP 405 "Method Not Allowed" and sets the Allow header to a
|
||||||
|
// comma-separated list of available methods.
|
||||||
|
type MethodHandler map[string]http.Handler
|
||||||
|
|
||||||
|
func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if handler, ok := h[req.Method]; ok {
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
} else {
|
||||||
|
allow := []string{}
|
||||||
|
for k := range h {
|
||||||
|
allow = append(allow, k)
|
||||||
|
}
|
||||||
|
sort.Strings(allow)
|
||||||
|
w.Header().Set("Allow", strings.Join(allow, ", "))
|
||||||
|
if req.Method == "OPTIONS" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its
|
||||||
|
// friends
|
||||||
|
type loggingHandler struct {
|
||||||
|
writer io.Writer
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// combinedLoggingHandler is the http.Handler implementation for LoggingHandlerTo
|
||||||
|
// and its friends
|
||||||
|
type combinedLoggingHandler struct {
|
||||||
|
writer io.Writer
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
t := time.Now()
|
||||||
|
logger := makeLogger(w)
|
||||||
|
url := *req.URL
|
||||||
|
h.handler.ServeHTTP(logger, req)
|
||||||
|
writeLog(h.writer, req, url, t, logger.Status(), logger.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h combinedLoggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
t := time.Now()
|
||||||
|
logger := makeLogger(w)
|
||||||
|
url := *req.URL
|
||||||
|
h.handler.ServeHTTP(logger, req)
|
||||||
|
writeCombinedLog(h.writer, req, url, t, logger.Status(), logger.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLogger(w http.ResponseWriter) loggingResponseWriter {
|
||||||
|
var logger loggingResponseWriter = &responseLogger{w: w}
|
||||||
|
if _, ok := w.(http.Hijacker); ok {
|
||||||
|
logger = &hijackLogger{responseLogger{w: w}}
|
||||||
|
}
|
||||||
|
h, ok1 := logger.(http.Hijacker)
|
||||||
|
c, ok2 := w.(http.CloseNotifier)
|
||||||
|
if ok1 && ok2 {
|
||||||
|
return hijackCloseNotifier{logger, h, c}
|
||||||
|
}
|
||||||
|
if ok2 {
|
||||||
|
return &closeNotifyWriter{logger, c}
|
||||||
|
}
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type commonLoggingResponseWriter interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Flusher
|
||||||
|
Status() int
|
||||||
|
Size() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP
|
||||||
|
// status code and body size
|
||||||
|
type responseLogger struct {
|
||||||
|
w http.ResponseWriter
|
||||||
|
status int
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *responseLogger) Header() http.Header {
|
||||||
|
return l.w.Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *responseLogger) Write(b []byte) (int, error) {
|
||||||
|
if l.status == 0 {
|
||||||
|
// The status will be StatusOK if WriteHeader has not been called yet
|
||||||
|
l.status = http.StatusOK
|
||||||
|
}
|
||||||
|
size, err := l.w.Write(b)
|
||||||
|
l.size += size
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *responseLogger) WriteHeader(s int) {
|
||||||
|
l.w.WriteHeader(s)
|
||||||
|
l.status = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *responseLogger) Status() int {
|
||||||
|
return l.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *responseLogger) Size() int {
|
||||||
|
return l.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *responseLogger) Flush() {
|
||||||
|
f, ok := l.w.(http.Flusher)
|
||||||
|
if ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type hijackLogger struct {
|
||||||
|
responseLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
h := l.responseLogger.w.(http.Hijacker)
|
||||||
|
conn, rw, err := h.Hijack()
|
||||||
|
if err == nil && l.responseLogger.status == 0 {
|
||||||
|
// The status will be StatusSwitchingProtocols if there was no error and
|
||||||
|
// WriteHeader has not been called yet
|
||||||
|
l.responseLogger.status = http.StatusSwitchingProtocols
|
||||||
|
}
|
||||||
|
return conn, rw, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type closeNotifyWriter struct {
|
||||||
|
loggingResponseWriter
|
||||||
|
http.CloseNotifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type hijackCloseNotifier struct {
|
||||||
|
loggingResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
http.CloseNotifier
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerhex = "0123456789abcdef"
|
||||||
|
|
||||||
|
func appendQuoted(buf []byte, s string) []byte {
|
||||||
|
var runeTmp [utf8.UTFMax]byte
|
||||||
|
for width := 0; len(s) > 0; s = s[width:] {
|
||||||
|
r := rune(s[0])
|
||||||
|
width = 1
|
||||||
|
if r >= utf8.RuneSelf {
|
||||||
|
r, width = utf8.DecodeRuneInString(s)
|
||||||
|
}
|
||||||
|
if width == 1 && r == utf8.RuneError {
|
||||||
|
buf = append(buf, `\x`...)
|
||||||
|
buf = append(buf, lowerhex[s[0]>>4])
|
||||||
|
buf = append(buf, lowerhex[s[0]&0xF])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r == rune('"') || r == '\\' { // always backslashed
|
||||||
|
buf = append(buf, '\\')
|
||||||
|
buf = append(buf, byte(r))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strconv.IsPrint(r) {
|
||||||
|
n := utf8.EncodeRune(runeTmp[:], r)
|
||||||
|
buf = append(buf, runeTmp[:n]...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '\a':
|
||||||
|
buf = append(buf, `\a`...)
|
||||||
|
case '\b':
|
||||||
|
buf = append(buf, `\b`...)
|
||||||
|
case '\f':
|
||||||
|
buf = append(buf, `\f`...)
|
||||||
|
case '\n':
|
||||||
|
buf = append(buf, `\n`...)
|
||||||
|
case '\r':
|
||||||
|
buf = append(buf, `\r`...)
|
||||||
|
case '\t':
|
||||||
|
buf = append(buf, `\t`...)
|
||||||
|
case '\v':
|
||||||
|
buf = append(buf, `\v`...)
|
||||||
|
default:
|
||||||
|
switch {
|
||||||
|
case r < ' ':
|
||||||
|
buf = append(buf, `\x`...)
|
||||||
|
buf = append(buf, lowerhex[s[0]>>4])
|
||||||
|
buf = append(buf, lowerhex[s[0]&0xF])
|
||||||
|
case r > utf8.MaxRune:
|
||||||
|
r = 0xFFFD
|
||||||
|
fallthrough
|
||||||
|
case r < 0x10000:
|
||||||
|
buf = append(buf, `\u`...)
|
||||||
|
for s := 12; s >= 0; s -= 4 {
|
||||||
|
buf = append(buf, lowerhex[r>>uint(s)&0xF])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
buf = append(buf, `\U`...)
|
||||||
|
for s := 28; s >= 0; s -= 4 {
|
||||||
|
buf = append(buf, lowerhex[r>>uint(s)&0xF])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildCommonLogLine builds a log entry for req in Apache Common Log Format.
|
||||||
|
// ts is the timestamp with which the entry should be logged.
|
||||||
|
// status and size are used to provide the response HTTP status and size.
|
||||||
|
func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
|
||||||
|
username := "-"
|
||||||
|
if url.User != nil {
|
||||||
|
if name := url.User.Username(); name != "" {
|
||||||
|
username = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
host = req.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := req.RequestURI
|
||||||
|
|
||||||
|
// Requests using the CONNECT method over HTTP/2.0 must use
|
||||||
|
// the authority field (aka r.Host) to identify the target.
|
||||||
|
// Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
|
||||||
|
if req.ProtoMajor == 2 && req.Method == "CONNECT" {
|
||||||
|
uri = req.Host
|
||||||
|
}
|
||||||
|
if uri == "" {
|
||||||
|
uri = url.RequestURI()
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
|
||||||
|
buf = append(buf, host...)
|
||||||
|
buf = append(buf, " - "...)
|
||||||
|
buf = append(buf, username...)
|
||||||
|
buf = append(buf, " ["...)
|
||||||
|
buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
|
||||||
|
buf = append(buf, `] "`...)
|
||||||
|
buf = append(buf, req.Method...)
|
||||||
|
buf = append(buf, " "...)
|
||||||
|
buf = appendQuoted(buf, uri)
|
||||||
|
buf = append(buf, " "...)
|
||||||
|
buf = append(buf, req.Proto...)
|
||||||
|
buf = append(buf, `" `...)
|
||||||
|
buf = append(buf, strconv.Itoa(status)...)
|
||||||
|
buf = append(buf, " "...)
|
||||||
|
buf = append(buf, strconv.Itoa(size)...)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeLog writes a log entry for req to w in Apache Common Log Format.
|
||||||
|
// ts is the timestamp with which the entry should be logged.
|
||||||
|
// status and size are used to provide the response HTTP status and size.
|
||||||
|
func writeLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
|
||||||
|
buf := buildCommonLogLine(req, url, ts, status, size)
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
|
||||||
|
// ts is the timestamp with which the entry should be logged.
|
||||||
|
// status and size are used to provide the response HTTP status and size.
|
||||||
|
func writeCombinedLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
|
||||||
|
buf := buildCommonLogLine(req, url, ts, status, size)
|
||||||
|
buf = append(buf, ` "`...)
|
||||||
|
buf = appendQuoted(buf, req.Referer())
|
||||||
|
buf = append(buf, `" "`...)
|
||||||
|
buf = appendQuoted(buf, req.UserAgent())
|
||||||
|
buf = append(buf, '"', '\n')
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
|
||||||
|
// Apache Combined Log Format.
|
||||||
|
//
|
||||||
|
// See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
|
||||||
|
//
|
||||||
|
// LoggingHandler always sets the ident field of the log to -
|
||||||
|
func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
|
||||||
|
return combinedLoggingHandler{out, h}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggingHandler return a http.Handler that wraps h and logs requests to out in
|
||||||
|
// Apache Common Log Format (CLF).
|
||||||
|
//
|
||||||
|
// See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
|
||||||
|
//
|
||||||
|
// LoggingHandler always sets the ident field of the log to -
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// w.Write([]byte("This is a catch-all route"))
|
||||||
|
// })
|
||||||
|
// loggedRouter := handlers.LoggingHandler(os.Stdout, r)
|
||||||
|
// http.ListenAndServe(":1123", loggedRouter)
|
||||||
|
//
|
||||||
|
func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
|
||||||
|
return loggingHandler{out, h}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isContentType validates the Content-Type header matches the supplied
|
||||||
|
// contentType. That is, its type and subtype match.
|
||||||
|
func isContentType(h http.Header, contentType string) bool {
|
||||||
|
ct := h.Get("Content-Type")
|
||||||
|
if i := strings.IndexRune(ct, ';'); i != -1 {
|
||||||
|
ct = ct[0:i]
|
||||||
|
}
|
||||||
|
return ct == contentType
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentTypeHandler wraps and returns a http.Handler, validating the request
|
||||||
|
// content type is compatible with the contentTypes list. It writes a HTTP 415
|
||||||
|
// error if that fails.
|
||||||
|
//
|
||||||
|
// Only PUT, POST, and PATCH requests are considered.
|
||||||
|
func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ct := range contentTypes {
|
||||||
|
if isContentType(r.Header, ct) {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HTTPMethodOverrideHeader is a commonly used
|
||||||
|
// http header to override a request method.
|
||||||
|
HTTPMethodOverrideHeader = "X-HTTP-Method-Override"
|
||||||
|
// HTTPMethodOverrideFormKey is a commonly used
|
||||||
|
// HTML form key to override a request method.
|
||||||
|
HTTPMethodOverrideFormKey = "_method"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for
|
||||||
|
// the X-HTTP-Method-Override header or the _method form key, and overrides (if
|
||||||
|
// valid) request.Method with its value.
|
||||||
|
//
|
||||||
|
// This is especially useful for HTTP clients that don't support many http verbs.
|
||||||
|
// It isn't secure to override e.g a GET to a POST, so only POST requests are
|
||||||
|
// considered. Likewise, the override method can only be a "write" method: PUT,
|
||||||
|
// PATCH or DELETE.
|
||||||
|
//
|
||||||
|
// Form method takes precedence over header method.
|
||||||
|
func HTTPMethodOverrideHandler(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "POST" {
|
||||||
|
om := r.FormValue(HTTPMethodOverrideFormKey)
|
||||||
|
if om == "" {
|
||||||
|
om = r.Header.Get(HTTPMethodOverrideHeader)
|
||||||
|
}
|
||||||
|
if om == "PUT" || om == "PATCH" || om == "DELETE" {
|
||||||
|
r.Method = om
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
21
src/vendor/github.com/gorilla/handlers/handlers_go18.go
generated
vendored
Normal file
21
src/vendor/github.com/gorilla/handlers/handlers_go18.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type loggingResponseWriter interface {
|
||||||
|
commonLoggingResponseWriter
|
||||||
|
http.Pusher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *responseLogger) Push(target string, opts *http.PushOptions) error {
|
||||||
|
p, ok := l.w.(http.Pusher)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("responseLogger does not implement http.Pusher")
|
||||||
|
}
|
||||||
|
return p.Push(target, opts)
|
||||||
|
}
|
7
src/vendor/github.com/gorilla/handlers/handlers_pre18.go
generated
vendored
Normal file
7
src/vendor/github.com/gorilla/handlers/handlers_pre18.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
type loggingResponseWriter interface {
|
||||||
|
commonLoggingResponseWriter
|
||||||
|
}
|
120
src/vendor/github.com/gorilla/handlers/proxy_headers.go
generated
vendored
Normal file
120
src/vendor/github.com/gorilla/handlers/proxy_headers.go
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// De-facto standard header keys.
|
||||||
|
xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
|
||||||
|
xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host")
|
||||||
|
xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Proto")
|
||||||
|
xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
|
||||||
|
xRealIP = http.CanonicalHeaderKey("X-Real-IP")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// RFC7239 defines a new "Forwarded: " header designed to replace the
|
||||||
|
// existing use of X-Forwarded-* headers.
|
||||||
|
// e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
|
||||||
|
forwarded = http.CanonicalHeaderKey("Forwarded")
|
||||||
|
// Allows for a sub-match of the first value after 'for=' to the next
|
||||||
|
// comma, semi-colon or space. The match is case-insensitive.
|
||||||
|
forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`)
|
||||||
|
// Allows for a sub-match for the first instance of scheme (http|https)
|
||||||
|
// prefixed by 'proto='. The match is case-insensitive.
|
||||||
|
protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProxyHeaders inspects common reverse proxy headers and sets the corresponding
|
||||||
|
// fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP
|
||||||
|
// for the remote (client) IP address, X-Forwarded-Proto or X-Forwarded-Scheme
|
||||||
|
// for the scheme (http|https) and the RFC7239 Forwarded header, which may
|
||||||
|
// include both client IPs and schemes.
|
||||||
|
//
|
||||||
|
// NOTE: This middleware should only be used when behind a reverse
|
||||||
|
// proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are
|
||||||
|
// configured not to) strip these headers from client requests, or where these
|
||||||
|
// headers are accepted "as is" from a remote client (e.g. when Go is not behind
|
||||||
|
// a proxy), can manifest as a vulnerability if your application uses these
|
||||||
|
// headers for validating the 'trustworthiness' of a request.
|
||||||
|
func ProxyHeaders(h http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Set the remote IP with the value passed from the proxy.
|
||||||
|
if fwd := getIP(r); fwd != "" {
|
||||||
|
r.RemoteAddr = fwd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the scheme (proto) with the value passed from the proxy.
|
||||||
|
if scheme := getScheme(r); scheme != "" {
|
||||||
|
r.URL.Scheme = scheme
|
||||||
|
}
|
||||||
|
// Set the host with the value passed by the proxy
|
||||||
|
if r.Header.Get(xForwardedHost) != "" {
|
||||||
|
r.Host = r.Header.Get(xForwardedHost)
|
||||||
|
}
|
||||||
|
// Call the next handler in the chain.
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
|
||||||
|
// Forwarded headers (in that order).
|
||||||
|
func getIP(r *http.Request) string {
|
||||||
|
var addr string
|
||||||
|
|
||||||
|
if fwd := r.Header.Get(xForwardedFor); fwd != "" {
|
||||||
|
// Only grab the first (client) address. Note that '192.168.0.1,
|
||||||
|
// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
|
||||||
|
// the first may represent forwarding proxies earlier in the chain.
|
||||||
|
s := strings.Index(fwd, ", ")
|
||||||
|
if s == -1 {
|
||||||
|
s = len(fwd)
|
||||||
|
}
|
||||||
|
addr = fwd[:s]
|
||||||
|
} else if fwd := r.Header.Get(xRealIP); fwd != "" {
|
||||||
|
// X-Real-IP should only contain one IP address (the client making the
|
||||||
|
// request).
|
||||||
|
addr = fwd
|
||||||
|
} else if fwd := r.Header.Get(forwarded); fwd != "" {
|
||||||
|
// match should contain at least two elements if the protocol was
|
||||||
|
// specified in the Forwarded header. The first element will always be
|
||||||
|
// the 'for=' capture, which we ignore. In the case of multiple IP
|
||||||
|
// addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only
|
||||||
|
// extract the first, which should be the client IP.
|
||||||
|
if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
|
||||||
|
// IPv6 addresses in Forwarded headers are quoted-strings. We strip
|
||||||
|
// these quotes.
|
||||||
|
addr = strings.Trim(match[1], `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
|
||||||
|
// Forwarded headers (in that order).
|
||||||
|
func getScheme(r *http.Request) string {
|
||||||
|
var scheme string
|
||||||
|
|
||||||
|
// Retrieve the scheme from X-Forwarded-Proto.
|
||||||
|
if proto := r.Header.Get(xForwardedProto); proto != "" {
|
||||||
|
scheme = strings.ToLower(proto)
|
||||||
|
} else if proto = r.Header.Get(xForwardedScheme); proto != "" {
|
||||||
|
scheme = strings.ToLower(proto)
|
||||||
|
} else if proto = r.Header.Get(forwarded); proto != "" {
|
||||||
|
// match should contain at least two elements if the protocol was
|
||||||
|
// specified in the Forwarded header. The first element will always be
|
||||||
|
// the 'proto=' capture, which we ignore. In the case of multiple proto
|
||||||
|
// parameters (invalid) we only extract the first.
|
||||||
|
if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 {
|
||||||
|
scheme = strings.ToLower(match[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scheme
|
||||||
|
}
|
91
src/vendor/github.com/gorilla/handlers/recovery.go
generated
vendored
Normal file
91
src/vendor/github.com/gorilla/handlers/recovery.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RecoveryHandlerLogger is an interface used by the recovering handler to print logs.
|
||||||
|
type RecoveryHandlerLogger interface {
|
||||||
|
Println(...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type recoveryHandler struct {
|
||||||
|
handler http.Handler
|
||||||
|
logger RecoveryHandlerLogger
|
||||||
|
printStack bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoveryOption provides a functional approach to define
|
||||||
|
// configuration for a handler; such as setting the logging
|
||||||
|
// whether or not to print strack traces on panic.
|
||||||
|
type RecoveryOption func(http.Handler)
|
||||||
|
|
||||||
|
func parseRecoveryOptions(h http.Handler, opts ...RecoveryOption) http.Handler {
|
||||||
|
for _, option := range opts {
|
||||||
|
option(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoveryHandler is HTTP middleware that recovers from a panic,
|
||||||
|
// logs the panic, writes http.StatusInternalServerError, and
|
||||||
|
// continues to the next handler.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// panic("Unexpected error!")
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// http.ListenAndServe(":1123", handlers.RecoveryHandler()(r))
|
||||||
|
func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler {
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
r := &recoveryHandler{handler: h}
|
||||||
|
return parseRecoveryOptions(r, opts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoveryLogger is a functional option to override
|
||||||
|
// the default logger
|
||||||
|
func RecoveryLogger(logger RecoveryHandlerLogger) RecoveryOption {
|
||||||
|
return func(h http.Handler) {
|
||||||
|
r := h.(*recoveryHandler)
|
||||||
|
r.logger = logger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintRecoveryStack is a functional option to enable
|
||||||
|
// or disable printing stack traces on panic.
|
||||||
|
func PrintRecoveryStack(print bool) RecoveryOption {
|
||||||
|
return func(h http.Handler) {
|
||||||
|
r := h.(*recoveryHandler)
|
||||||
|
r.printStack = print
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
h.log(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
h.handler.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h recoveryHandler) log(v ...interface{}) {
|
||||||
|
if h.logger != nil {
|
||||||
|
h.logger.Println(v...)
|
||||||
|
} else {
|
||||||
|
log.Println(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.printStack {
|
||||||
|
debug.PrintStack()
|
||||||
|
}
|
||||||
|
}
|
6
src/vendor/vendor.json
vendored
6
src/vendor/vendor.json
vendored
@ -398,6 +398,12 @@
|
|||||||
"revision": "aed02d124ae4a0e94fea4541c8effd05bf0c8296",
|
"revision": "aed02d124ae4a0e94fea4541c8effd05bf0c8296",
|
||||||
"revisionTime": "2016-05-25T20:33:19Z"
|
"revisionTime": "2016-05-25T20:33:19Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "+/oy2SsS8YvHVvQGcWUDbaajeXQ=",
|
||||||
|
"path": "github.com/gorilla/handlers",
|
||||||
|
"revision": "13d73096a474cac93275c679c7b8a2dc17ddba82",
|
||||||
|
"revisionTime": "2017-02-24T19:39:55Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "TfZ8ulM+No3vdOTjK6qF1y9yVlI=",
|
"checksumSHA1": "TfZ8ulM+No3vdOTjK6qF1y9yVlI=",
|
||||||
"path": "github.com/gorilla/mux",
|
"path": "github.com/gorilla/mux",
|
||||||
|
Loading…
Reference in New Issue
Block a user