mirror of
https://github.com/containous/traefik.git
synced 2024-10-27 18:55:25 +03:00
Merge pull request #178 from emilevauge/add-multiple-entrypoints-support
Add multiple entry points support
This commit is contained in:
commit
4ecb919787
9
Makefile
9
Makefile
@ -6,6 +6,9 @@ TRAEFIK_ENVS := \
|
|||||||
-e TESTFLAGS \
|
-e TESTFLAGS \
|
||||||
-e CIRCLECI
|
-e CIRCLECI
|
||||||
|
|
||||||
|
|
||||||
|
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
|
||||||
|
|
||||||
BIND_DIR := "dist"
|
BIND_DIR := "dist"
|
||||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/emilevauge/traefik/$(BIND_DIR)"
|
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/emilevauge/traefik/$(BIND_DIR)"
|
||||||
|
|
||||||
@ -78,3 +81,9 @@ generate-webui:
|
|||||||
mkdir -p static
|
mkdir -p static
|
||||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp
|
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp
|
||||||
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md
|
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md
|
||||||
|
|
||||||
|
lint:
|
||||||
|
$(foreach file,$(SRCS),golint $(file) || exit;)
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
gofmt -s -l -w $(SRCS)
|
20
README.md
20
README.md
@ -1,8 +1,8 @@
|
|||||||
![Træfɪk](http://traefik.github.io/traefik.logo.svg "Træfɪk")
|
![Træfɪk](http://traefik.github.io/traefik.logo.svg "Træfɪk")
|
||||||
___
|
___
|
||||||
|
|
||||||
[![Circle CI](https://circleci.com/gh/emilevauge/traefik.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/emilevauge/traefik)
|
[![Circle CI](https://circleci.com/gh/emilevauge/traefik/tree/master.png?circle-token)](https://circleci.com/gh/emilevauge/traefik)
|
||||||
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
|
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
|
||||||
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
|
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
|
||||||
|
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
|
|||||||
- Circuit breakers on backends
|
- Circuit breakers on backends
|
||||||
- Round Robin, rebalancer load-balancers
|
- Round Robin, rebalancer load-balancers
|
||||||
- Rest Metrics
|
- Rest Metrics
|
||||||
- Tiny docker image included
|
- Tiny docker image included [![Image Layers](https://badge.imagelayers.io/emilevauge/traefik:latest.svg)](https://imagelayers.io/?images=emilevauge/traefik:latest 'Image Layers')
|
||||||
- SSL backends support
|
- SSL backends support
|
||||||
- SSL frontend support
|
- SSL frontend support
|
||||||
- Clean AngularJS Web UI
|
- Clean AngularJS Web UI
|
||||||
@ -76,6 +76,20 @@ You can find the complete documentation [here](docs/index.md).
|
|||||||
|
|
||||||
Refer to the [benchmarks section](docs/index.md#benchmarks) in the documentation.
|
Refer to the [benchmarks section](docs/index.md#benchmarks) in the documentation.
|
||||||
|
|
||||||
|
## Træfɪk here and there
|
||||||
|
|
||||||
|
These projects use Træfɪk internally. If your company uses Træfɪk, we would be glad to get your feedback :) Contact us on [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
|
||||||
|
|
||||||
|
- Project [Mantl](http://http://mantl.io/) from Cisco
|
||||||
|
|
||||||
|
![Web UI Providers](docs/img/mantl-logo.png)
|
||||||
|
> Mantl is a modern platform for rapidly deploying globally distributed services. A container orchestrator, docker, a network stack, something to pool your logs, something to monitor health, a sprinkle of service discovery and some automation.
|
||||||
|
|
||||||
|
- Project [Apollo](http://capgemini.github.io/devops/apollo/) from Cap Gemini
|
||||||
|
|
||||||
|
![Web UI Providers](docs/img/apollo-logo.png)
|
||||||
|
> Apollo is an open source project to aid with building and deploying IAAS and PAAS services. It is particularly geared towards managing containerized applications across multiple hosts, and big data type workloads. Apollo leverages other open source components to provide basic mechanisms for deployment, maintenance, and scaling of infrastructure and applications.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OxyLogger implements oxy Logger interface with logrus.
|
// OxyLogger implements oxy Logger interface with logrus.
|
||||||
@ -33,10 +32,3 @@ func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
//templatesRenderer.HTML(w, http.StatusNotFound, "notFound", nil)
|
//templatesRenderer.HTML(w, http.StatusNotFound, "notFound", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadDefaultConfig returns a default gorrilla.mux router from the specified configuration.
|
|
||||||
func LoadDefaultConfig(globalConfiguration GlobalConfiguration) *mux.Router {
|
|
||||||
router := mux.NewRouter()
|
|
||||||
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
|
12
cmd.go
12
cmd.go
@ -4,6 +4,7 @@ Copyright
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -49,6 +50,7 @@ var arguments = struct {
|
|||||||
boltdb bool
|
boltdb bool
|
||||||
}{
|
}{
|
||||||
GlobalConfiguration{
|
GlobalConfiguration{
|
||||||
|
EntryPoints: make(EntryPoints),
|
||||||
Docker: &provider.Docker{
|
Docker: &provider.Docker{
|
||||||
TLS: &provider.DockerTLS{},
|
TLS: &provider.DockerTLS{},
|
||||||
},
|
},
|
||||||
@ -78,7 +80,8 @@ func init() {
|
|||||||
traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads")
|
traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads")
|
||||||
traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file")
|
traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file")
|
||||||
traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file")
|
traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file")
|
||||||
traefikCmd.PersistentFlags().Var(&arguments.Certificates, "certificates", "SSL certificates and keys pair, ie 'tests/traefik.crt,tests/traefik.key'. You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI")
|
traefikCmd.PersistentFlags().Var(&arguments.EntryPoints, "entryPoints", "Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'")
|
||||||
|
traefikCmd.PersistentFlags().Var(&arguments.DefaultEntryPoints, "defaultEntryPoints", "Entrypoints to be used by frontends that do not specify any entrypoint")
|
||||||
traefikCmd.PersistentFlags().StringP("logLevel", "l", "ERROR", "Log level")
|
traefikCmd.PersistentFlags().StringP("logLevel", "l", "ERROR", "Log level")
|
||||||
traefikCmd.PersistentFlags().DurationVar(&arguments.ProvidersThrottleDuration, "providersThrottleDuration", time.Duration(2*time.Second), "Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time.")
|
traefikCmd.PersistentFlags().DurationVar(&arguments.ProvidersThrottleDuration, "providersThrottleDuration", time.Duration(2*time.Second), "Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time.")
|
||||||
traefikCmd.PersistentFlags().Int("maxIdleConnsPerHost", 0, "If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used")
|
traefikCmd.PersistentFlags().Int("maxIdleConnsPerHost", 0, "If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used")
|
||||||
@ -138,13 +141,13 @@ func init() {
|
|||||||
viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
||||||
viper.BindPFlag("port", traefikCmd.PersistentFlags().Lookup("port"))
|
viper.BindPFlag("port", traefikCmd.PersistentFlags().Lookup("port"))
|
||||||
viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
||||||
// viper.BindPFlag("certificates", TraefikCmd.PersistentFlags().Lookup("certificates"))
|
//viper.BindPFlag("defaultEntryPoints", traefikCmd.PersistentFlags().Lookup("defaultEntryPoints"))
|
||||||
viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
|
viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
|
||||||
// TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105
|
// TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105
|
||||||
viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration"))
|
viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration"))
|
||||||
viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost"))
|
viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost"))
|
||||||
viper.SetDefault("certificates", &Certificates{})
|
|
||||||
viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second))
|
viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second))
|
||||||
|
viper.SetDefault("logLevel", "ERROR")
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() {
|
func run() {
|
||||||
@ -176,7 +179,8 @@ func run() {
|
|||||||
} else {
|
} else {
|
||||||
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||||
}
|
}
|
||||||
log.Debugf("Global configuration loaded %+v", globalConfiguration)
|
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||||
|
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||||
server := NewServer(*globalConfiguration)
|
server := NewServer(*globalConfiguration)
|
||||||
server.Start()
|
server.Start()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
139
configuration.go
139
configuration.go
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -16,12 +17,12 @@ import (
|
|||||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||||
type GlobalConfiguration struct {
|
type GlobalConfiguration struct {
|
||||||
Port string
|
|
||||||
GraceTimeOut int64
|
GraceTimeOut int64
|
||||||
AccessLogsFile string
|
AccessLogsFile string
|
||||||
TraefikLogsFile string
|
TraefikLogsFile string
|
||||||
Certificates Certificates
|
|
||||||
LogLevel string
|
LogLevel string
|
||||||
|
EntryPoints EntryPoints
|
||||||
|
DefaultEntryPoints DefaultEntryPoints
|
||||||
ProvidersThrottleDuration time.Duration
|
ProvidersThrottleDuration time.Duration
|
||||||
MaxIdleConnsPerHost int
|
MaxIdleConnsPerHost int
|
||||||
Docker *provider.Docker
|
Docker *provider.Docker
|
||||||
@ -34,6 +35,110 @@ type GlobalConfiguration struct {
|
|||||||
Boltdb *provider.BoltDb
|
Boltdb *provider.BoltDb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultEntryPoints holds default entry points
|
||||||
|
type DefaultEntryPoints []string
|
||||||
|
|
||||||
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
|
// The String method's output will be used in diagnostics.
|
||||||
|
func (dep *DefaultEntryPoints) String() string {
|
||||||
|
return fmt.Sprintf("%#v", dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||||
|
// Set's argument is a string to be parsed to set the flag.
|
||||||
|
// It's a comma-separated list, so we split it.
|
||||||
|
func (dep *DefaultEntryPoints) Set(value string) error {
|
||||||
|
entrypoints := strings.Split(value, ",")
|
||||||
|
if len(entrypoints) == 0 {
|
||||||
|
return errors.New("Bad DefaultEntryPoints format: " + value)
|
||||||
|
}
|
||||||
|
for _, entrypoint := range entrypoints {
|
||||||
|
*dep = append(*dep, entrypoint)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type is type of the struct
|
||||||
|
func (dep *DefaultEntryPoints) Type() string {
|
||||||
|
return fmt.Sprint("defaultentrypoints²")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
||||||
|
type EntryPoints map[string]*EntryPoint
|
||||||
|
|
||||||
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
|
// The String method's output will be used in diagnostics.
|
||||||
|
func (ep *EntryPoints) String() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||||
|
// Set's argument is a string to be parsed to set the flag.
|
||||||
|
// It's a comma-separated list, so we split it.
|
||||||
|
func (ep *EntryPoints) Set(value string) error {
|
||||||
|
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
|
||||||
|
match := regex.FindAllStringSubmatch(value, -1)
|
||||||
|
if match == nil {
|
||||||
|
return errors.New("Bad EntryPoints format: " + value)
|
||||||
|
}
|
||||||
|
matchResult := match[0]
|
||||||
|
result := make(map[string]string)
|
||||||
|
for i, name := range regex.SubexpNames() {
|
||||||
|
if i != 0 {
|
||||||
|
result[name] = matchResult[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var tls *TLS
|
||||||
|
if len(result["TLS"]) > 0 {
|
||||||
|
certs := Certificates{}
|
||||||
|
certs.Set(result["TLS"])
|
||||||
|
tls = &TLS{
|
||||||
|
Certificates: certs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var redirect *Redirect
|
||||||
|
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
|
||||||
|
redirect = &Redirect{
|
||||||
|
EntryPoint: result["RedirectEntryPoint"],
|
||||||
|
Regex: result["RedirectRegex"],
|
||||||
|
Replacement: result["RedirectReplacement"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(*ep)[result["Name"]] = &EntryPoint{
|
||||||
|
Address: result["Address"],
|
||||||
|
TLS: tls,
|
||||||
|
Redirect: redirect,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type is type of the struct
|
||||||
|
func (ep *EntryPoints) Type() string {
|
||||||
|
return fmt.Sprint("entrypoints²")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||||
|
type EntryPoint struct {
|
||||||
|
Network string
|
||||||
|
Address string
|
||||||
|
TLS *TLS
|
||||||
|
Redirect *Redirect
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect configures a redirection of an entry point to another, or to an URL
|
||||||
|
type Redirect struct {
|
||||||
|
EntryPoint string
|
||||||
|
Regex string
|
||||||
|
Replacement string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS configures TLS for an entry point
|
||||||
|
type TLS struct {
|
||||||
|
Certificates Certificates
|
||||||
|
}
|
||||||
|
|
||||||
// Certificates defines traefik certificates type
|
// Certificates defines traefik certificates type
|
||||||
type Certificates []Certificate
|
type Certificates []Certificate
|
||||||
|
|
||||||
@ -74,14 +179,7 @@ type Certificate struct {
|
|||||||
|
|
||||||
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
|
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
|
||||||
func NewGlobalConfiguration() *GlobalConfiguration {
|
func NewGlobalConfiguration() *GlobalConfiguration {
|
||||||
globalConfiguration := new(GlobalConfiguration)
|
return new(GlobalConfiguration)
|
||||||
// default values
|
|
||||||
globalConfiguration.Port = ":80"
|
|
||||||
globalConfiguration.GraceTimeOut = 10
|
|
||||||
globalConfiguration.LogLevel = "ERROR"
|
|
||||||
globalConfiguration.ProvidersThrottleDuration = time.Duration(2 * time.Second)
|
|
||||||
|
|
||||||
return globalConfiguration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfiguration returns a GlobalConfiguration.
|
// LoadConfiguration returns a GlobalConfiguration.
|
||||||
@ -101,8 +199,12 @@ func LoadConfiguration() *GlobalConfiguration {
|
|||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
fmtlog.Fatalf("Error reading file: %s", err)
|
fmtlog.Fatalf("Error reading file: %s", err)
|
||||||
}
|
}
|
||||||
if len(arguments.Certificates) > 0 {
|
|
||||||
viper.Set("certificates", arguments.Certificates)
|
if len(arguments.EntryPoints) > 0 {
|
||||||
|
viper.Set("entryPoints", arguments.EntryPoints)
|
||||||
|
}
|
||||||
|
if len(arguments.DefaultEntryPoints) > 0 {
|
||||||
|
viper.Set("defaultEntryPoints", arguments.DefaultEntryPoints)
|
||||||
}
|
}
|
||||||
if arguments.web {
|
if arguments.web {
|
||||||
viper.Set("web", arguments.Web)
|
viper.Set("web", arguments.Web)
|
||||||
@ -135,6 +237,19 @@ func LoadConfiguration() *GlobalConfiguration {
|
|||||||
fmtlog.Fatalf("Error reading file: %s", err)
|
fmtlog.Fatalf("Error reading file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(configuration.EntryPoints) == 0 {
|
||||||
|
configuration.EntryPoints = make(map[string]*EntryPoint)
|
||||||
|
configuration.EntryPoints["http"] = &EntryPoint{
|
||||||
|
Address: ":80",
|
||||||
|
}
|
||||||
|
configuration.DefaultEntryPoints = []string{"http"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if configuration.File != nil && len(configuration.File.Filename) == 0 {
|
||||||
|
// no filename, setting to global config file
|
||||||
|
configuration.File.Filename = viper.ConfigFileUsed()
|
||||||
|
}
|
||||||
|
|
||||||
return configuration
|
return configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
docs/img/apollo-logo.png
Normal file
BIN
docs/img/apollo-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
BIN
docs/img/mantl-logo.png
Normal file
BIN
docs/img/mantl-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
107
docs/index.md
107
docs/index.md
@ -39,8 +39,7 @@ Frontends can be defined using the following rules:
|
|||||||
|
|
||||||
|
|
||||||
A frontend is a set of rules that forwards the incoming http traffic to a backend.
|
A frontend is a set of rules that forwards the incoming http traffic to a backend.
|
||||||
You can optionally enable `passHostHeader` to
|
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
||||||
- []forward client `Host` header to the backend.
|
|
||||||
|
|
||||||
### HTTP Backends
|
### HTTP Backends
|
||||||
|
|
||||||
@ -103,13 +102,13 @@ Flags:
|
|||||||
--boltdb.filename string Override default configuration template. For advanced users :)
|
--boltdb.filename string Override default configuration template. For advanced users :)
|
||||||
--boltdb.prefix string Prefix used for KV store (default "/traefik")
|
--boltdb.prefix string Prefix used for KV store (default "/traefik")
|
||||||
--boltdb.watch Watch provider (default true)
|
--boltdb.watch Watch provider (default true)
|
||||||
--certificates value SSL certificates and keys pair, ie 'tests/traefik.crt,tests/traefik.key'. You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI
|
|
||||||
-c, --configFile string Configuration file to use (TOML, JSON, YAML, HCL).
|
-c, --configFile string Configuration file to use (TOML, JSON, YAML, HCL).
|
||||||
--consul Enable Consul backend
|
--consul Enable Consul backend
|
||||||
--consul.endpoint string Consul server endpoint (default "127.0.0.1:8500")
|
--consul.endpoint string Consul server endpoint (default "127.0.0.1:8500")
|
||||||
--consul.filename string Override default configuration template. For advanced users :)
|
--consul.filename string Override default configuration template. For advanced users :)
|
||||||
--consul.prefix string Prefix used for KV store (default "/traefik")
|
--consul.prefix string Prefix used for KV store (default "/traefik")
|
||||||
--consul.watch Watch provider (default true)
|
--consul.watch Watch provider (default true)
|
||||||
|
--defaultEntryPoints value Entrypoints to be used by frontends that do not specify any entrypoint (default &main.DefaultEntryPoints(nil))
|
||||||
--docker Enable Docker backend
|
--docker Enable Docker backend
|
||||||
--docker.domain string Default domain used
|
--docker.domain string Default domain used
|
||||||
--docker.endpoint string Docker server endpoint. Can be a tcp or a unix socket endpoint (default "unix:///var/run/docker.sock")
|
--docker.endpoint string Docker server endpoint. Can be a tcp or a unix socket endpoint (default "unix:///var/run/docker.sock")
|
||||||
@ -120,6 +119,7 @@ Flags:
|
|||||||
--docker.tls.insecureSkipVerify TLS insecure skip verify
|
--docker.tls.insecureSkipVerify TLS insecure skip verify
|
||||||
--docker.tls.key string TLS key
|
--docker.tls.key string TLS key
|
||||||
--docker.watch Watch provider (default true)
|
--docker.watch Watch provider (default true)
|
||||||
|
--entryPoints value Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'
|
||||||
--etcd Enable Etcd backend
|
--etcd Enable Etcd backend
|
||||||
--etcd.endpoint string Etcd server endpoint (default "127.0.0.1:4001")
|
--etcd.endpoint string Etcd server endpoint (default "127.0.0.1:4001")
|
||||||
--etcd.filename string Override default configuration template. For advanced users :)
|
--etcd.filename string Override default configuration template. For advanced users :)
|
||||||
@ -162,12 +162,45 @@ Use "traefik [command] --help" for more information about a command.
|
|||||||
# Global configuration
|
# Global configuration
|
||||||
################################################################
|
################################################################
|
||||||
|
|
||||||
# Reverse proxy port
|
# Entrypoints definition
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: ":80"
|
# Default:
|
||||||
|
# [entryPoints]
|
||||||
|
# [entryPoints.http]
|
||||||
|
# address = ":80"
|
||||||
#
|
#
|
||||||
# port = ":80"
|
# To redirect an http entrypoint to an https entrypoint (with SNI support):
|
||||||
|
# [entryPoints]
|
||||||
|
# [entryPoints.http]
|
||||||
|
# address = ":80"
|
||||||
|
# [entryPoints.http.redirect]
|
||||||
|
# entryPoint = "https"
|
||||||
|
# [entryPoints.https]
|
||||||
|
# address = ":443"
|
||||||
|
# [entryPoints.https.tls]
|
||||||
|
# [[entryPoints.https.tls.certificates]]
|
||||||
|
# CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
# KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
# [[entryPoints.https.tls.certificates]]
|
||||||
|
# CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
|
# KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
|
#
|
||||||
|
# To redirect an entrypoint rewriting the URL:
|
||||||
|
# [entryPoints]
|
||||||
|
# [entryPoints.http]
|
||||||
|
# address = ":80"
|
||||||
|
# [entryPoints.http.redirect]
|
||||||
|
# regex = "^http://localhost/(.*)"
|
||||||
|
# replacement = "http://mydomain/$1"
|
||||||
|
|
||||||
|
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||||
|
# Each frontend can specify its own entrypoints.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: ["http"]
|
||||||
|
#
|
||||||
|
# defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
# Timeout in seconds.
|
# Timeout in seconds.
|
||||||
# Duration to give active requests a chance to finish during hot-reloads
|
# Duration to give active requests a chance to finish during hot-reloads
|
||||||
@ -197,15 +230,6 @@ Use "traefik [command] --help" for more information about a command.
|
|||||||
#
|
#
|
||||||
# logLevel = "ERROR"
|
# logLevel = "ERROR"
|
||||||
|
|
||||||
# SSL certificates and keys
|
|
||||||
# You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# [[certificates]]
|
|
||||||
# CertFile = "traefik.crt"
|
|
||||||
# KeyFile = "traefik.key"
|
|
||||||
|
|
||||||
# Backends throttle duration: minimum duration between 2 events from providers
|
# Backends throttle duration: minimum duration between 2 events from providers
|
||||||
# before applying a new configuration. It avoids unnecessary reloads if multiples events
|
# before applying a new configuration. It avoids unnecessary reloads if multiples events
|
||||||
# are sent in a short amount of time.
|
# are sent in a short amount of time.
|
||||||
@ -234,7 +258,21 @@ Like any other reverse proxy, Træfɪk can be configured with a file. You have t
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
# traefik.toml
|
# traefik.toml
|
||||||
port = ":80"
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
entryPoint = "https"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
graceTimeOut = 10
|
graceTimeOut = 10
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
@ -270,7 +308,13 @@ logLevel = "DEBUG"
|
|||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
[frontends.frontend2.routes.test_2]
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
|
[frontends.frontend2.routes.test_1]
|
||||||
|
rule = "Host"
|
||||||
|
value = "{subdomain:[a-z]+}.localhost"
|
||||||
|
[frontends.frontend3]
|
||||||
|
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
|
backend = "backend2"
|
||||||
rule = "Path"
|
rule = "Path"
|
||||||
value = "/test"
|
value = "/test"
|
||||||
```
|
```
|
||||||
@ -279,7 +323,20 @@ logLevel = "DEBUG"
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
# traefik.toml
|
# traefik.toml
|
||||||
port = ":80"
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
entryPoint = "https"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
graceTimeOut = 10
|
graceTimeOut = 10
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
@ -318,10 +375,15 @@ filename = "rules.toml"
|
|||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
[frontends.frontend2.routes.test_2]
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
|
[frontends.frontend2.routes.test_1]
|
||||||
|
rule = "Host"
|
||||||
|
value = "{subdomain:[a-z]+}.localhost"
|
||||||
|
[frontends.frontend3]
|
||||||
|
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
|
backend = "backend2"
|
||||||
rule = "Path"
|
rule = "Path"
|
||||||
value = "/test"
|
value = "/test"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want Træfɪk to watch file changes automatically, just add:
|
If you want Træfɪk to watch file changes automatically, just add:
|
||||||
@ -534,6 +596,7 @@ Labels can be used on containers to override default behaviour:
|
|||||||
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
||||||
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{containerName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
|
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{containerName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
|
||||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||||
|
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||||
* `traefik.domain=traefik.localhost`: override the default domain
|
* `traefik.domain=traefik.localhost`: override the default domain
|
||||||
|
|
||||||
|
|
||||||
@ -598,6 +661,7 @@ Labels can be used on containers to override default behaviour:
|
|||||||
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
||||||
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{appName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
|
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{appName}.{domain}`) See [frontends](#frontends). Must be associated with label traefik.frontend.rule.
|
||||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||||
|
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||||
* `traefik.domain=traefik.localhost`: override the default domain
|
* `traefik.domain=traefik.localhost`: override the default domain
|
||||||
|
|
||||||
## <a id="consul"></a> Consul backend
|
## <a id="consul"></a> Consul backend
|
||||||
@ -676,6 +740,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
|||||||
|----------------------------------------------------|------------|
|
|----------------------------------------------------|------------|
|
||||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||||
|
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||||
|
|
||||||
@ -756,6 +821,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
|||||||
|----------------------------------------------------|------------|
|
|----------------------------------------------------|------------|
|
||||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||||
|
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||||
|
|
||||||
@ -835,6 +901,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
|||||||
|----------------------------------------------------|------------|
|
|----------------------------------------------------|------------|
|
||||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||||
|
| `/traefik/frontends/frontend2/entrypoints` |`http,https`|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ import:
|
|||||||
- package: github.com/donovanhide/eventsource
|
- package: github.com/donovanhide/eventsource
|
||||||
ref: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
ref: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
||||||
- package: github.com/golang/glog
|
- package: github.com/golang/glog
|
||||||
ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||||
- package: github.com/spf13/cast
|
- package: github.com/spf13/cast
|
||||||
ref: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
ref: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
||||||
- package: github.com/mitchellh/mapstructure
|
- package: github.com/mitchellh/mapstructure
|
||||||
@ -161,4 +161,5 @@ import:
|
|||||||
- package: github.com/spf13/cobra
|
- package: github.com/spf13/cobra
|
||||||
subpackages:
|
subpackages:
|
||||||
- /cobra
|
- /cobra
|
||||||
|
- package: github.com/vulcand/vulcand/plugin/rewrite
|
||||||
|
|
||||||
|
@ -46,9 +46,10 @@ func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
|||||||
// TODO validate : run on 80
|
// TODO validate : run on 80
|
||||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||||
|
|
||||||
// Expected a 404 as we did not configure anything
|
// Expected no response as we did not configure anything
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(resp, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
c.Assert(err, checker.NotNil)
|
||||||
|
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
check "gopkg.in/check.v1"
|
check "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
@ -19,7 +20,8 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
|||||||
// TODO validate : run on 80
|
// TODO validate : run on 80
|
||||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||||
|
|
||||||
// Expected a 404 as we did not comfigure anything
|
// Expected no response as we did not configure anything
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(resp, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
c.Assert(err, checker.NotNil)
|
||||||
|
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
# Reverse proxy port
|
defaultEntryPoints = ["http"]
|
||||||
#
|
|
||||||
# Optional
|
[entryPoints]
|
||||||
# Default: ":80"
|
[entryPoints.http]
|
||||||
#
|
address = ":8000"
|
||||||
# port = ":80"
|
|
||||||
port = ":8000"
|
|
||||||
#
|
|
||||||
# LogLevel
|
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[consul]
|
[consul]
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
# Reverse proxy port
|
defaultEntryPoints = ["http"]
|
||||||
#
|
|
||||||
# Optional
|
[entryPoints]
|
||||||
# Default: ":80"
|
[entryPoints.http]
|
||||||
#
|
address = ":8000"
|
||||||
# port = ":80"
|
|
||||||
port = ":8000"
|
|
||||||
#
|
|
||||||
# LogLevel
|
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[docker]
|
[docker]
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
# Reverse proxy port
|
defaultEntryPoints = ["http"]
|
||||||
#
|
|
||||||
# Optional
|
[entryPoints]
|
||||||
# Default: ":80"
|
[entryPoints.http]
|
||||||
#
|
address = ":8000"
|
||||||
# port = ":80"
|
|
||||||
port = ":8000"
|
|
||||||
#
|
|
||||||
# LogLevel
|
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
# Reverse proxy port
|
defaultEntryPoints = ["http"]
|
||||||
#
|
|
||||||
# Optional
|
[entryPoints]
|
||||||
# Default: ":80"
|
[entryPoints.http]
|
||||||
#
|
address = ":8000"
|
||||||
# port = ":80"
|
|
||||||
port = ":8000"
|
|
||||||
#
|
|
||||||
# LogLevel
|
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
port = ":4443"
|
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[[certificates]]
|
defaultEntryPoints = ["https"]
|
||||||
CertFile = "fixtures/https/snitest.com.cert"
|
|
||||||
KeyFile = "fixtures/https/snitest.com.key"
|
|
||||||
|
|
||||||
[[certificates]]
|
[entryPoints]
|
||||||
CertFile = "fixtures/https/snitest.org.cert"
|
[entryPoints.https]
|
||||||
KeyFile = "fixtures/https/snitest.org.key"
|
address = ":4443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "fixtures/https/snitest.com.cert"
|
||||||
|
KeyFile = "fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "fixtures/https/snitest.org.cert"
|
||||||
|
KeyFile = "fixtures/https/snitest.org.key"
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
|
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
# Reverse proxy port
|
defaultEntryPoints = ["http"]
|
||||||
#
|
|
||||||
# Optional
|
[entryPoints]
|
||||||
# Default: ":80"
|
[entryPoints.http]
|
||||||
#
|
address = ":8000"
|
||||||
# port = ":80"
|
|
||||||
port = ":8000"
|
|
||||||
#
|
|
||||||
# LogLevel
|
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
[marathon]
|
[marathon]
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
# Reverse proxy port
|
defaultEntryPoints = ["http"]
|
||||||
#
|
|
||||||
# Optional
|
[entryPoints]
|
||||||
# Default: ":80"
|
[entryPoints.http]
|
||||||
#
|
address = ":8000"
|
||||||
port = ":8000"
|
|
||||||
#
|
|
||||||
# LogLevel
|
|
||||||
logLevel = "DEBUG"
|
|
@ -1,6 +1,9 @@
|
|||||||
port = ":8000"
|
|
||||||
logLevel = "DEBUG"
|
logLevel = "DEBUG"
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":8000"
|
||||||
|
|
||||||
[web]
|
[web]
|
||||||
|
|
||||||
address = ":8080"
|
address = ":8080"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
@ -19,7 +20,8 @@ func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
|||||||
// TODO validate : run on 80
|
// TODO validate : run on 80
|
||||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||||
|
|
||||||
// Expected a 404 as we did not configure anything
|
// Expected no response as we did not configure anything
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(resp, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
c.Assert(err, checker.NotNil)
|
||||||
|
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
|
||||||
}
|
}
|
||||||
|
31
middlewares/rewrite.go
Normal file
31
middlewares/rewrite.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/vulcand/vulcand/plugin/rewrite"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rewrite is a middleware that allows redirections
|
||||||
|
type Rewrite struct {
|
||||||
|
rewriter *rewrite.Rewrite
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRewrite creates a Rewrite middleware
|
||||||
|
func NewRewrite(regex, replacement string, redirect bool) (*Rewrite, error) {
|
||||||
|
rewriter, err := rewrite.NewRewrite(regex, replacement, false, redirect)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Rewrite{rewriter: rewriter}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
func (rewrite *Rewrite) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
handler, err := rewrite.rewriter.NewHandler(next)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error in rewrite middleware ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(rw, r)
|
||||||
|
}
|
@ -107,6 +107,7 @@ func (provider *Docker) loadDockerConfig(containersInspected []docker.Container)
|
|||||||
"getDomain": provider.getDomain,
|
"getDomain": provider.getDomain,
|
||||||
"getProtocol": provider.getProtocol,
|
"getProtocol": provider.getProtocol,
|
||||||
"getPassHostHeader": provider.getPassHostHeader,
|
"getPassHostHeader": provider.getPassHostHeader,
|
||||||
|
"getEntryPoints": provider.getEntryPoints,
|
||||||
"getFrontendValue": provider.getFrontendValue,
|
"getFrontendValue": provider.getFrontendValue,
|
||||||
"getFrontendRule": provider.getFrontendRule,
|
"getFrontendRule": provider.getFrontendRule,
|
||||||
"replace": replace,
|
"replace": replace,
|
||||||
@ -234,6 +235,13 @@ func (provider *Docker) getPassHostHeader(container docker.Container) string {
|
|||||||
return "false"
|
return "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Docker) getEntryPoints(container docker.Container) []string {
|
||||||
|
if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil {
|
||||||
|
return strings.Split(entryPoints, ",")
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
func getLabel(container docker.Container, label string) (string, error) {
|
func getLabel(container docker.Container, label string) (string, error) {
|
||||||
for key, value := range container.Config.Labels {
|
for key, value := range container.Config.Labels {
|
||||||
if key == label {
|
if key == label {
|
||||||
|
@ -682,7 +682,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
`"frontend-Host-test-docker-localhost"`: {
|
`"frontend-Host-test-docker-localhost"`: {
|
||||||
Backend: "backend-test",
|
Backend: "backend-test",
|
||||||
|
EntryPoints: []string{},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test-docker-localhost"`: {
|
`"route-frontend-Host-test-docker-localhost"`: {
|
||||||
Rule: "Host",
|
Rule: "Host",
|
||||||
@ -709,7 +710,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
Name: "test1",
|
Name: "test1",
|
||||||
Config: &docker.Config{
|
Config: &docker.Config{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"traefik.backend": "foobar",
|
"traefik.backend": "foobar",
|
||||||
|
"traefik.frontend.entryPoints": "http,https",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NetworkSettings: &docker.NetworkSettings{
|
NetworkSettings: &docker.NetworkSettings{
|
||||||
@ -736,7 +738,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
`"frontend-Host-test1-docker-localhost"`: {
|
`"frontend-Host-test1-docker-localhost"`: {
|
||||||
Backend: "backend-foobar",
|
Backend: "backend-foobar",
|
||||||
|
EntryPoints: []string{"http", "https"},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test1-docker-localhost"`: {
|
`"route-frontend-Host-test1-docker-localhost"`: {
|
||||||
Rule: "Host",
|
Rule: "Host",
|
||||||
@ -745,7 +748,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
`"frontend-Host-test2-docker-localhost"`: {
|
`"frontend-Host-test2-docker-localhost"`: {
|
||||||
Backend: "backend-foobar",
|
Backend: "backend-foobar",
|
||||||
|
EntryPoints: []string{},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test2-docker-localhost"`: {
|
`"route-frontend-Host-test2-docker-localhost"`: {
|
||||||
Rule: "Host",
|
Rule: "Host",
|
||||||
|
@ -73,9 +73,10 @@ func (provider *Kv) loadConfig() *types.Configuration {
|
|||||||
provider.Prefix,
|
provider.Prefix,
|
||||||
}
|
}
|
||||||
var KvFuncMap = template.FuncMap{
|
var KvFuncMap = template.FuncMap{
|
||||||
"List": provider.list,
|
"List": provider.list,
|
||||||
"Get": provider.get,
|
"Get": provider.get,
|
||||||
"Last": provider.last,
|
"SplitGet": provider.splitGet,
|
||||||
|
"Last": provider.last,
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration, err := provider.getConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
|
configuration, err := provider.getConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
|
||||||
@ -89,7 +90,7 @@ func (provider *Kv) list(keys ...string) []string {
|
|||||||
joinedKeys := strings.Join(keys, "")
|
joinedKeys := strings.Join(keys, "")
|
||||||
keysPairs, err := provider.kvclient.List(joinedKeys)
|
keysPairs, err := provider.kvclient.List(joinedKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error getting keys: ", joinedKeys, err)
|
log.Errorf("Error getting keys %s %s ", joinedKeys, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
directoryKeys := make(map[string]string)
|
directoryKeys := make(map[string]string)
|
||||||
@ -100,18 +101,32 @@ func (provider *Kv) list(keys ...string) []string {
|
|||||||
return fun.Values(directoryKeys).([]string)
|
return fun.Values(directoryKeys).([]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kv) get(keys ...string) string {
|
func (provider *Kv) get(defaultValue string, keys ...string) string {
|
||||||
joinedKeys := strings.Join(keys, "")
|
joinedKeys := strings.Join(keys, "")
|
||||||
keyPair, err := provider.kvclient.Get(joinedKeys)
|
keyPair, err := provider.kvclient.Get(joinedKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error getting key: ", joinedKeys, err)
|
log.Warnf("Error getting key %s %s, setting default %s", joinedKeys, err, defaultValue)
|
||||||
return ""
|
return defaultValue
|
||||||
} else if keyPair == nil {
|
} else if keyPair == nil {
|
||||||
return ""
|
log.Warnf("Error getting key %s, setting default %s", joinedKeys, defaultValue)
|
||||||
|
return defaultValue
|
||||||
}
|
}
|
||||||
return string(keyPair.Value)
|
return string(keyPair.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Kv) splitGet(keys ...string) []string {
|
||||||
|
joinedKeys := strings.Join(keys, "")
|
||||||
|
keyPair, err := provider.kvclient.Get(joinedKeys)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Error getting key %s %s, setting default empty", joinedKeys, err)
|
||||||
|
return []string{}
|
||||||
|
} else if keyPair == nil {
|
||||||
|
log.Warnf("Error getting key %s, setting default %empty", joinedKeys)
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return strings.Split(string(keyPair.Value), ",")
|
||||||
|
}
|
||||||
|
|
||||||
func (provider *Kv) last(key string) string {
|
func (provider *Kv) last(key string) string {
|
||||||
splittedKey := strings.Split(key, "/")
|
splittedKey := strings.Split(key, "/")
|
||||||
return splittedKey[len(splittedKey)-1]
|
return splittedKey[len(splittedKey)-1]
|
||||||
|
@ -176,7 +176,7 @@ func TestKvGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
actual := c.provider.get(c.keys...)
|
actual := c.provider.get("", c.keys...)
|
||||||
if actual != c.expected {
|
if actual != c.expected {
|
||||||
t.Fatalf("expected %v, got %v for %v and %v", c.expected, actual, c.keys, c.provider)
|
t.Fatalf("expected %v, got %v for %v and %v", c.expected, actual, c.keys, c.provider)
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ func TestKvGet(t *testing.T) {
|
|||||||
Error: true,
|
Error: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual := provider.get("anything")
|
actual := provider.get("", "anything")
|
||||||
if actual != "" {
|
if actual != "" {
|
||||||
t.Fatalf("Should have return nil, got %v", actual)
|
t.Fatalf("Should have return nil, got %v", actual)
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
@ -86,6 +87,7 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
|||||||
"getDomain": provider.getDomain,
|
"getDomain": provider.getDomain,
|
||||||
"getProtocol": provider.getProtocol,
|
"getProtocol": provider.getProtocol,
|
||||||
"getPassHostHeader": provider.getPassHostHeader,
|
"getPassHostHeader": provider.getPassHostHeader,
|
||||||
|
"getEntryPoints": provider.getEntryPoints,
|
||||||
"getFrontendValue": provider.getFrontendValue,
|
"getFrontendValue": provider.getFrontendValue,
|
||||||
"getFrontendRule": provider.getFrontendRule,
|
"getFrontendRule": provider.getFrontendRule,
|
||||||
"replace": replace,
|
"replace": replace,
|
||||||
@ -286,6 +288,13 @@ func (provider *Marathon) getPassHostHeader(application marathon.Application) st
|
|||||||
return "false"
|
return "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Marathon) getEntryPoints(application marathon.Application) []string {
|
||||||
|
if entryPoints, err := provider.getLabel(application, "traefik.frontend.entryPoints"); err == nil {
|
||||||
|
return strings.Split(entryPoints, ",")
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
// getFrontendValue returns the frontend value for the specified application, using
|
// getFrontendValue returns the frontend value for the specified application, using
|
||||||
// it's label. It returns a default one if the label is not present.
|
// it's label. It returns a default one if the label is not present.
|
||||||
func (provider *Marathon) getFrontendValue(application marathon.Application) string {
|
func (provider *Marathon) getFrontendValue(application marathon.Application) string {
|
||||||
|
@ -84,7 +84,8 @@ func TestMarathonLoadConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
`frontend-test`: {
|
`frontend-test`: {
|
||||||
Backend: "backend-test",
|
Backend: "backend-test",
|
||||||
|
EntryPoints: []string{},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`route-host-test`: {
|
`route-host-test`: {
|
||||||
Rule: "Host",
|
Rule: "Host",
|
||||||
@ -735,6 +736,36 @@ func TestMarathonGetPassHostHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarathonGetEntryPoints(t *testing.T) {
|
||||||
|
provider := &Marathon{}
|
||||||
|
|
||||||
|
applications := []struct {
|
||||||
|
application marathon.Application
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
application: marathon.Application{},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
application: marathon.Application{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"traefik.frontend.entryPoints": "http,https",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []string{"http", "https"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range applications {
|
||||||
|
actual := provider.getEntryPoints(a.application)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, a.expected) {
|
||||||
|
t.Fatalf("expected %#v, got %#v", a.expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarathonGetFrontendValue(t *testing.T) {
|
func TestMarathonGetFrontendValue(t *testing.T) {
|
||||||
provider := &Marathon{
|
provider := &Marathon{
|
||||||
Domain: "docker.localhost",
|
Domain: "docker.localhost",
|
||||||
|
307
server.go
307
server.go
@ -5,6 +5,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
@ -16,12 +17,12 @@ import (
|
|||||||
"github.com/mailgun/oxy/cbreaker"
|
"github.com/mailgun/oxy/cbreaker"
|
||||||
"github.com/mailgun/oxy/forward"
|
"github.com/mailgun/oxy/forward"
|
||||||
"github.com/mailgun/oxy/roundrobin"
|
"github.com/mailgun/oxy/roundrobin"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@ -31,11 +32,10 @@ var oxyLogger = &OxyLogger{}
|
|||||||
|
|
||||||
// Server is the reverse-proxy/load-balancer engine
|
// Server is the reverse-proxy/load-balancer engine
|
||||||
type Server struct {
|
type Server struct {
|
||||||
srv *manners.GracefulServer
|
serverEntryPoints map[string]serverEntryPoint
|
||||||
configurationRouter *mux.Router
|
|
||||||
configurationChan chan types.ConfigMessage
|
configurationChan chan types.ConfigMessage
|
||||||
configurationChanValidated chan types.ConfigMessage
|
configurationValidatedChan chan types.ConfigMessage
|
||||||
sigs chan os.Signal
|
signals chan os.Signal
|
||||||
stopChan chan bool
|
stopChan chan bool
|
||||||
providers []provider.Provider
|
providers []provider.Provider
|
||||||
serverLock sync.Mutex
|
serverLock sync.Mutex
|
||||||
@ -44,16 +44,22 @@ type Server struct {
|
|||||||
loggerMiddleware *middlewares.Logger
|
loggerMiddleware *middlewares.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serverEntryPoint struct {
|
||||||
|
httpServer *manners.GracefulServer
|
||||||
|
httpRouter *mux.Router
|
||||||
|
}
|
||||||
|
|
||||||
// NewServer returns an initialized Server.
|
// NewServer returns an initialized Server.
|
||||||
func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||||
server := new(Server)
|
server := new(Server)
|
||||||
|
|
||||||
|
server.serverEntryPoints = make(map[string]serverEntryPoint)
|
||||||
server.configurationChan = make(chan types.ConfigMessage, 10)
|
server.configurationChan = make(chan types.ConfigMessage, 10)
|
||||||
server.configurationChanValidated = make(chan types.ConfigMessage, 10)
|
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
|
||||||
server.sigs = make(chan os.Signal, 1)
|
server.signals = make(chan os.Signal, 1)
|
||||||
server.stopChan = make(chan bool)
|
server.stopChan = make(chan bool)
|
||||||
server.providers = []provider.Provider{}
|
server.providers = []provider.Provider{}
|
||||||
signal.Notify(server.sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(server.signals, syscall.SIGINT, syscall.SIGTERM)
|
||||||
server.currentConfigurations = make(configs)
|
server.currentConfigurations = make(configs)
|
||||||
server.globalConfiguration = globalConfiguration
|
server.globalConfiguration = globalConfiguration
|
||||||
server.loggerMiddleware = middlewares.NewLogger(globalConfiguration.AccessLogsFile)
|
server.loggerMiddleware = middlewares.NewLogger(globalConfiguration.AccessLogsFile)
|
||||||
@ -63,38 +69,27 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
|||||||
|
|
||||||
// Start starts the server and blocks until server is shutted down.
|
// Start starts the server and blocks until server is shutted down.
|
||||||
func (server *Server) Start() {
|
func (server *Server) Start() {
|
||||||
server.configurationRouter = LoadDefaultConfig(server.globalConfiguration)
|
|
||||||
go server.listenProviders()
|
go server.listenProviders()
|
||||||
go server.enableRouter()
|
go server.listenConfigurations()
|
||||||
server.configureProviders()
|
server.configureProviders()
|
||||||
server.startProviders()
|
server.startProviders()
|
||||||
go server.listenSignals()
|
go server.listenSignals()
|
||||||
|
|
||||||
var er error
|
|
||||||
server.serverLock.Lock()
|
|
||||||
server.srv, er = server.prepareServer(server.configurationRouter, server.globalConfiguration, nil, server.loggerMiddleware, metrics)
|
|
||||||
if er != nil {
|
|
||||||
log.Fatal("Error preparing server: ", er)
|
|
||||||
}
|
|
||||||
go server.startServer(server.srv, server.globalConfiguration)
|
|
||||||
//TODO change that!
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
server.serverLock.Unlock()
|
|
||||||
|
|
||||||
<-server.stopChan
|
<-server.stopChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the server
|
// Stop stops the server
|
||||||
func (server *Server) Stop() {
|
func (server *Server) Stop() {
|
||||||
server.srv.Close()
|
for _, serverEntryPoint := range server.serverEntryPoints {
|
||||||
|
serverEntryPoint.httpServer.Close()
|
||||||
|
}
|
||||||
server.stopChan <- true
|
server.stopChan <- true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close destroys the server
|
// Close destroys the server
|
||||||
func (server *Server) Close() {
|
func (server *Server) Close() {
|
||||||
close(server.configurationChan)
|
close(server.configurationChan)
|
||||||
close(server.configurationChanValidated)
|
close(server.configurationValidatedChan)
|
||||||
close(server.sigs)
|
close(server.signals)
|
||||||
close(server.stopChan)
|
close(server.stopChan)
|
||||||
server.loggerMiddleware.Close()
|
server.loggerMiddleware.Close()
|
||||||
}
|
}
|
||||||
@ -104,19 +99,20 @@ func (server *Server) listenProviders() {
|
|||||||
lastConfigs := make(map[string]*types.ConfigMessage)
|
lastConfigs := make(map[string]*types.ConfigMessage)
|
||||||
for {
|
for {
|
||||||
configMsg := <-server.configurationChan
|
configMsg := <-server.configurationChan
|
||||||
log.Debugf("Configuration receveived from provider %s: %#v", configMsg.ProviderName, configMsg.Configuration)
|
jsonConf, _ := json.Marshal(configMsg.Configuration)
|
||||||
|
log.Debugf("Configuration receveived from provider %s: %s", configMsg.ProviderName, string(jsonConf))
|
||||||
lastConfigs[configMsg.ProviderName] = &configMsg
|
lastConfigs[configMsg.ProviderName] = &configMsg
|
||||||
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
||||||
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
||||||
// last config received more than n s ago
|
// last config received more than n s ago
|
||||||
server.configurationChanValidated <- configMsg
|
server.configurationValidatedChan <- configMsg
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
|
||||||
go func() {
|
go func() {
|
||||||
<-time.After(server.globalConfiguration.ProvidersThrottleDuration)
|
<-time.After(server.globalConfiguration.ProvidersThrottleDuration)
|
||||||
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
|
||||||
log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
|
log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
|
||||||
server.configurationChanValidated <- *lastConfigs[configMsg.ProviderName]
|
server.configurationValidatedChan <- *lastConfigs[configMsg.ProviderName]
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -124,9 +120,9 @@ func (server *Server) listenProviders() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) enableRouter() {
|
func (server *Server) listenConfigurations() {
|
||||||
for {
|
for {
|
||||||
configMsg := <-server.configurationChanValidated
|
configMsg := <-server.configurationValidatedChan
|
||||||
if configMsg.Configuration == nil {
|
if configMsg.Configuration == nil {
|
||||||
log.Info("Skipping empty Configuration")
|
log.Info("Skipping empty Configuration")
|
||||||
} else if reflect.DeepEqual(server.currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
|
} else if reflect.DeepEqual(server.currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
|
||||||
@ -139,22 +135,26 @@ func (server *Server) enableRouter() {
|
|||||||
}
|
}
|
||||||
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
|
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
|
||||||
|
|
||||||
newConfigurationRouter, err := server.loadConfig(newConfigurations, server.globalConfiguration)
|
newServerEntryPoints, err := server.loadConfig(newConfigurations, server.globalConfiguration)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
server.serverLock.Lock()
|
server.serverLock.Lock()
|
||||||
server.currentConfigurations = newConfigurations
|
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
|
||||||
server.configurationRouter = newConfigurationRouter
|
currentServerEntryPoint := server.serverEntryPoints[newServerEntryPointName]
|
||||||
oldServer := server.srv
|
server.currentConfigurations = newConfigurations
|
||||||
newsrv, err := server.prepareServer(server.configurationRouter, server.globalConfiguration, oldServer, server.loggerMiddleware, metrics)
|
currentServerEntryPoint.httpRouter = newServerEntryPoint.httpRouter
|
||||||
if err != nil {
|
oldServer := currentServerEntryPoint.httpServer
|
||||||
log.Fatal("Error preparing server: ", err)
|
newsrv, err := server.prepareServer(currentServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], oldServer, server.loggerMiddleware, metrics)
|
||||||
}
|
if err != nil {
|
||||||
go server.startServer(newsrv, server.globalConfiguration)
|
log.Fatal("Error preparing server: ", err)
|
||||||
server.srv = newsrv
|
}
|
||||||
time.Sleep(1 * time.Second)
|
go server.startServer(newsrv, server.globalConfiguration)
|
||||||
if oldServer != nil {
|
currentServerEntryPoint.httpServer = newsrv
|
||||||
log.Info("Stopping old server")
|
server.serverEntryPoints[newServerEntryPointName] = currentServerEntryPoint
|
||||||
oldServer.Close()
|
time.Sleep(1 * time.Second)
|
||||||
|
if oldServer != nil {
|
||||||
|
log.Info("Stopping old server")
|
||||||
|
oldServer.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
server.serverLock.Unlock()
|
server.serverLock.Unlock()
|
||||||
} else {
|
} else {
|
||||||
@ -173,10 +173,6 @@ func (server *Server) configureProviders() {
|
|||||||
server.providers = append(server.providers, server.globalConfiguration.Marathon)
|
server.providers = append(server.providers, server.globalConfiguration.Marathon)
|
||||||
}
|
}
|
||||||
if server.globalConfiguration.File != nil {
|
if server.globalConfiguration.File != nil {
|
||||||
if len(server.globalConfiguration.File.Filename) == 0 {
|
|
||||||
// no filename, setting to global config file
|
|
||||||
server.globalConfiguration.File.Filename = viper.GetString("configFile")
|
|
||||||
}
|
|
||||||
server.providers = append(server.providers, server.globalConfiguration.File)
|
server.providers = append(server.providers, server.globalConfiguration.File)
|
||||||
}
|
}
|
||||||
if server.globalConfiguration.Web != nil {
|
if server.globalConfiguration.Web != nil {
|
||||||
@ -200,7 +196,8 @@ func (server *Server) configureProviders() {
|
|||||||
func (server *Server) startProviders() {
|
func (server *Server) startProviders() {
|
||||||
// start providers
|
// start providers
|
||||||
for _, provider := range server.providers {
|
for _, provider := range server.providers {
|
||||||
log.Infof("Starting provider %v %+v", reflect.TypeOf(provider), provider)
|
jsonConf, _ := json.Marshal(provider)
|
||||||
|
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
|
||||||
currentProvider := provider
|
currentProvider := provider
|
||||||
go func() {
|
go func() {
|
||||||
err := currentProvider.Provide(server.configurationChan)
|
err := currentProvider.Provide(server.configurationChan)
|
||||||
@ -212,15 +209,18 @@ func (server *Server) startProviders() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) listenSignals() {
|
func (server *Server) listenSignals() {
|
||||||
sig := <-server.sigs
|
sig := <-server.signals
|
||||||
log.Infof("I have to go... %+v", sig)
|
log.Infof("I have to go... %+v", sig)
|
||||||
log.Info("Stopping server")
|
log.Info("Stopping server")
|
||||||
server.Stop()
|
server.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI
|
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI
|
||||||
func (server *Server) createTLSConfig(certs []Certificate) (*tls.Config, error) {
|
func (server *Server) createTLSConfig(tlsOption *TLS) (*tls.Config, error) {
|
||||||
if len(certs) == 0 {
|
if tlsOption == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if len(tlsOption.Certificates) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,8 +230,8 @@ func (server *Server) createTLSConfig(certs []Certificate) (*tls.Config, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
config.Certificates = make([]tls.Certificate, len(certs))
|
config.Certificates = make([]tls.Certificate, len(tlsOption.Certificates))
|
||||||
for i, v := range certs {
|
for i, v := range tlsOption.Certificates {
|
||||||
config.Certificates[i], err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
|
config.Certificates[i], err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -244,7 +244,7 @@ func (server *Server) createTLSConfig(certs []Certificate) (*tls.Config, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) startServer(srv *manners.GracefulServer, globalConfiguration GlobalConfiguration) {
|
func (server *Server) startServer(srv *manners.GracefulServer, globalConfiguration GlobalConfiguration) {
|
||||||
log.Info("Starting server")
|
log.Info("Starting server on ", srv.Addr)
|
||||||
if srv.TLSConfig != nil {
|
if srv.TLSConfig != nil {
|
||||||
err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig)
|
err := srv.ListenAndServeTLSWithConfig(srv.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -259,7 +259,7 @@ func (server *Server) startServer(srv *manners.GracefulServer, globalConfigurati
|
|||||||
log.Info("Server stopped")
|
log.Info("Server stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) prepareServer(router *mux.Router, globalConfiguration GlobalConfiguration, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
|
func (server *Server) prepareServer(router *mux.Router, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
|
||||||
log.Info("Preparing server")
|
log.Info("Preparing server")
|
||||||
// middlewares
|
// middlewares
|
||||||
var negroni = negroni.New()
|
var negroni = negroni.New()
|
||||||
@ -267,7 +267,7 @@ func (server *Server) prepareServer(router *mux.Router, globalConfiguration Glob
|
|||||||
negroni.Use(middleware)
|
negroni.Use(middleware)
|
||||||
}
|
}
|
||||||
negroni.UseHandler(router)
|
negroni.UseHandler(router)
|
||||||
tlsConfig, err := server.createTLSConfig(globalConfiguration.Certificates)
|
tlsConfig, err := server.createTLSConfig(entryPoint.TLS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error creating TLS config %s", err)
|
log.Fatalf("Error creating TLS config %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -276,13 +276,13 @@ func (server *Server) prepareServer(router *mux.Router, globalConfiguration Glob
|
|||||||
if oldServer == nil {
|
if oldServer == nil {
|
||||||
return manners.NewWithServer(
|
return manners.NewWithServer(
|
||||||
&http.Server{
|
&http.Server{
|
||||||
Addr: globalConfiguration.Port,
|
Addr: entryPoint.Address,
|
||||||
Handler: negroni,
|
Handler: negroni,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
gracefulServer, err := oldServer.HijackListener(&http.Server{
|
gracefulServer, err := oldServer.HijackListener(&http.Server{
|
||||||
Addr: globalConfiguration.Port,
|
Addr: entryPoint.Address,
|
||||||
Handler: negroni,
|
Handler: negroni,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
}, tlsConfig)
|
}, tlsConfig)
|
||||||
@ -293,80 +293,147 @@ func (server *Server) prepareServer(router *mux.Router, globalConfiguration Glob
|
|||||||
return gracefulServer, nil
|
return gracefulServer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]serverEntryPoint {
|
||||||
|
serverEntryPoints := make(map[string]serverEntryPoint)
|
||||||
|
for entryPointName := range globalConfiguration.EntryPoints {
|
||||||
|
router := server.buildDefaultHTTPRouter()
|
||||||
|
serverEntryPoints[entryPointName] = serverEntryPoint{
|
||||||
|
httpRouter: router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serverEntryPoints
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
||||||
// provider configurations.
|
// provider configurations.
|
||||||
func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (*mux.Router, error) {
|
func (server *Server) loadConfig(configurations configs, globalConfiguration GlobalConfiguration) (map[string]serverEntryPoint, error) {
|
||||||
router := mux.NewRouter()
|
serverEntryPoints := server.buildEntryPoints(globalConfiguration)
|
||||||
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
redirectHandlers := make(map[string]http.Handler)
|
||||||
|
|
||||||
backends := map[string]http.Handler{}
|
backends := map[string]http.Handler{}
|
||||||
for _, configuration := range configurations {
|
for _, configuration := range configurations {
|
||||||
for frontendName, frontend := range configuration.Frontends {
|
for frontendName, frontend := range configuration.Frontends {
|
||||||
log.Infof("Creating frontend %s", frontendName)
|
log.Debugf("Creating frontend %s", frontendName)
|
||||||
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
|
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
|
||||||
newRoute := router.NewRoute().Name(frontendName)
|
// default endpoints if not defined in frontends
|
||||||
for routeName, route := range frontend.Routes {
|
if len(frontend.EntryPoints) == 0 {
|
||||||
log.Infof("Creating route %s %s:%s", routeName, route.Rule, route.Value)
|
frontend.EntryPoints = globalConfiguration.DefaultEntryPoints
|
||||||
newRouteReflect, err := invoke(newRoute, route.Rule, route.Value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newRoute = newRouteReflect[0].Interface().(*mux.Route)
|
|
||||||
}
|
}
|
||||||
if backends[frontend.Backend] == nil {
|
for _, entryPointName := range frontend.EntryPoints {
|
||||||
log.Infof("Creating backend %s", frontend.Backend)
|
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
|
||||||
var lb http.Handler
|
if _, ok := serverEntryPoints[entryPointName]; !ok {
|
||||||
rr, _ := roundrobin.New(fwd)
|
return nil, errors.New("Undefined entrypoint: " + entryPointName)
|
||||||
if configuration.Backends[frontend.Backend] == nil {
|
|
||||||
return nil, errors.New("Backend not found: " + frontend.Backend)
|
|
||||||
}
|
}
|
||||||
lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
|
newRoute := serverEntryPoints[entryPointName].httpRouter.NewRoute().Name(frontendName)
|
||||||
if err != nil {
|
for routeName, route := range frontend.Routes {
|
||||||
configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"}
|
log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value)
|
||||||
}
|
newRouteReflect, err := invoke(newRoute, route.Rule, route.Value)
|
||||||
switch lbMethod {
|
if err != nil {
|
||||||
case types.Drr:
|
return nil, err
|
||||||
log.Infof("Creating load-balancer drr")
|
|
||||||
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
|
|
||||||
lb = rebalancer
|
|
||||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
|
||||||
url, err := url.Parse(server.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Infof("Creating server %s %s", serverName, url.String())
|
|
||||||
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
|
|
||||||
}
|
|
||||||
case types.Wrr:
|
|
||||||
log.Infof("Creating load-balancer wrr")
|
|
||||||
lb = middlewares.NewWebsocketUpgrader(rr)
|
|
||||||
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
|
||||||
url, err := url.Parse(server.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Infof("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
|
||||||
rr.UpsertServer(url, roundrobin.Weight(server.Weight))
|
|
||||||
}
|
}
|
||||||
|
newRoute = newRouteReflect[0].Interface().(*mux.Route)
|
||||||
}
|
}
|
||||||
var negroni = negroni.New()
|
entryPoint := globalConfiguration.EntryPoints[entryPointName]
|
||||||
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
|
if entryPoint.Redirect != nil {
|
||||||
log.Infof("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
|
if redirectHandlers[entryPointName] != nil {
|
||||||
negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)))
|
newRoute.Handler(redirectHandlers[entryPointName])
|
||||||
|
} else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
newRoute.Handler(handler)
|
||||||
|
redirectHandlers[entryPointName] = handler
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
negroni.UseHandler(lb)
|
if backends[frontend.Backend] == nil {
|
||||||
|
log.Debugf("Creating backend %s", frontend.Backend)
|
||||||
|
var lb http.Handler
|
||||||
|
rr, _ := roundrobin.New(fwd)
|
||||||
|
if configuration.Backends[frontend.Backend] == nil {
|
||||||
|
return nil, errors.New("Undefined backend: " + frontend.Backend)
|
||||||
|
}
|
||||||
|
lbMethod, err := types.NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
|
||||||
|
if err != nil {
|
||||||
|
configuration.Backends[frontend.Backend].LoadBalancer = &types.LoadBalancer{Method: "wrr"}
|
||||||
|
}
|
||||||
|
switch lbMethod {
|
||||||
|
case types.Drr:
|
||||||
|
log.Debugf("Creating load-balancer drr")
|
||||||
|
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
|
||||||
|
lb = rebalancer
|
||||||
|
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||||
|
url, err := url.Parse(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
||||||
|
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
|
||||||
|
}
|
||||||
|
case types.Wrr:
|
||||||
|
log.Debugf("Creating load-balancer wrr")
|
||||||
|
lb = middlewares.NewWebsocketUpgrader(rr)
|
||||||
|
for serverName, server := range configuration.Backends[frontend.Backend].Servers {
|
||||||
|
url, err := url.Parse(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugf("Creating server %s at %s with weight %d", serverName, url.String(), server.Weight)
|
||||||
|
rr.UpsertServer(url, roundrobin.Weight(server.Weight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var negroni = negroni.New()
|
||||||
|
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
|
||||||
|
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
|
||||||
|
negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)))
|
||||||
|
} else {
|
||||||
|
negroni.UseHandler(lb)
|
||||||
|
}
|
||||||
|
backends[frontend.Backend] = negroni
|
||||||
|
} else {
|
||||||
|
log.Debugf("Reusing backend %s", frontend.Backend)
|
||||||
|
}
|
||||||
|
newRoute.Handler(backends[frontend.Backend])
|
||||||
|
}
|
||||||
|
err := newRoute.GetError()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error building route: %s", err)
|
||||||
}
|
}
|
||||||
backends[frontend.Backend] = negroni
|
|
||||||
} else {
|
|
||||||
log.Infof("Reusing backend %s", frontend.Backend)
|
|
||||||
}
|
|
||||||
// stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger))
|
|
||||||
|
|
||||||
newRoute.Handler(backends[frontend.Backend])
|
|
||||||
err := newRoute.GetError()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error building route: %s", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return router, nil
|
return serverEntryPoints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *EntryPoint) (http.Handler, error) {
|
||||||
|
regex := entryPoint.Redirect.Regex
|
||||||
|
replacement := entryPoint.Redirect.Replacement
|
||||||
|
if len(entryPoint.Redirect.EntryPoint) > 0 {
|
||||||
|
regex = "^(?:https?:\\/\\/)?([\\da-z\\.-]+)(?::\\d+)(.*)$"
|
||||||
|
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint] == nil {
|
||||||
|
return nil, errors.New("Unkown entrypoint " + entryPoint.Redirect.EntryPoint)
|
||||||
|
}
|
||||||
|
protocol := "http"
|
||||||
|
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].TLS != nil {
|
||||||
|
protocol = "https"
|
||||||
|
}
|
||||||
|
r, _ := regexp.Compile("(:\\d+)")
|
||||||
|
match := r.FindStringSubmatch(server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].Address)
|
||||||
|
if len(match) == 0 {
|
||||||
|
return nil, errors.New("Bad Address format: " + server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].Address)
|
||||||
|
}
|
||||||
|
replacement = protocol + "://$1" + match[0] + "$2"
|
||||||
|
}
|
||||||
|
rewrite, err := middlewares.NewRewrite(regex, replacement, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugf("Creating entryPoint redirect %s -> %s : %s -> %s", entryPointName, entryPoint.Redirect.EntryPoint, regex, replacement)
|
||||||
|
negroni := negroni.New()
|
||||||
|
negroni.Use(rewrite)
|
||||||
|
return negroni, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) buildDefaultHTTPRouter() *mux.Router {
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||||
|
return router
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
[frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}}
|
[frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}}
|
||||||
backend = "backend-{{getBackend $container}}"
|
backend = "backend-{{getBackend $container}}"
|
||||||
passHostHeader = {{getPassHostHeader $container}}
|
passHostHeader = {{getPassHostHeader $container}}
|
||||||
|
entryPoints = [{{range getEntryPoints $container}}
|
||||||
|
"{{.}}",
|
||||||
|
{{end}}]
|
||||||
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
|
||||||
rule = "{{getFrontendRule $container}}"
|
rule = "{{getFrontendRule $container}}"
|
||||||
value = "{{getFrontendValue $container}}"
|
value = "{{getFrontendValue $container}}"
|
||||||
|
@ -5,13 +5,13 @@
|
|||||||
{{$backend := .}}
|
{{$backend := .}}
|
||||||
{{$servers := List $backend "/servers/" }}
|
{{$servers := List $backend "/servers/" }}
|
||||||
|
|
||||||
{{$circuitBreaker := Get . "/circuitbreaker/" "expression"}}
|
{{$circuitBreaker := Get "" . "/circuitbreaker/" "expression"}}
|
||||||
{{with $circuitBreaker}}
|
{{with $circuitBreaker}}
|
||||||
[backends.{{Last $backend}}.circuitBreaker]
|
[backends.{{Last $backend}}.circuitBreaker]
|
||||||
expression = "{{$circuitBreaker}}"
|
expression = "{{$circuitBreaker}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{$loadBalancer := Get . "/loadbalancer/" "method"}}
|
{{$loadBalancer := Get "" . "/loadbalancer/" "method"}}
|
||||||
{{with $loadBalancer}}
|
{{with $loadBalancer}}
|
||||||
[backends.{{Last $backend}}.loadBalancer]
|
[backends.{{Last $backend}}.loadBalancer]
|
||||||
method = "{{$loadBalancer}}"
|
method = "{{$loadBalancer}}"
|
||||||
@ -19,20 +19,24 @@
|
|||||||
|
|
||||||
{{range $servers}}
|
{{range $servers}}
|
||||||
[backends.{{Last $backend}}.servers.{{Last .}}]
|
[backends.{{Last $backend}}.servers.{{Last .}}]
|
||||||
url = "{{Get . "/url"}}"
|
url = "{{Get "" . "/url"}}"
|
||||||
weight = {{Get . "/weight"}}
|
weight = {{Get "" . "/weight"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
[frontends]{{range $frontends}}
|
[frontends]{{range $frontends}}
|
||||||
{{$frontend := Last .}}
|
{{$frontend := Last .}}
|
||||||
|
{{$entryPoints := SplitGet . "/entrypoints"}}
|
||||||
[frontends.{{$frontend}}]
|
[frontends.{{$frontend}}]
|
||||||
backend = "{{Get . "/backend"}}"
|
backend = "{{Get "" . "/backend"}}"
|
||||||
passHostHeader = {{Get . "/passHostHeader"}}
|
passHostHeader = {{Get "false" . "/passHostHeader"}}
|
||||||
|
entryPoints = [{{range $entryPoints}}
|
||||||
|
"{{.}}",
|
||||||
|
{{end}}]
|
||||||
{{$routes := List . "/routes/"}}
|
{{$routes := List . "/routes/"}}
|
||||||
{{range $routes}}
|
{{range $routes}}
|
||||||
[frontends.{{$frontend}}.routes.{{Last .}}]
|
[frontends.{{$frontend}}.routes.{{Last .}}]
|
||||||
rule = "{{Get . "/rule"}}"
|
rule = "{{Get "" . "/rule"}}"
|
||||||
value = "{{Get . "/value"}}"
|
value = "{{Get "" . "/value"}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -9,6 +9,9 @@
|
|||||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||||
backend = "backend{{getBackend .}}"
|
backend = "backend{{getBackend .}}"
|
||||||
passHostHeader = {{getPassHostHeader .}}
|
passHostHeader = {{getPassHostHeader .}}
|
||||||
|
entryPoints = [{{range getEntryPoints .}}
|
||||||
|
"{{.}}",
|
||||||
|
{{end}}]
|
||||||
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
|
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
|
||||||
rule = "{{getFrontendRule .}}"
|
rule = "{{getFrontendRule .}}"
|
||||||
value = "{{getFrontendValue .}}"
|
value = "{{getFrontendValue .}}"
|
||||||
|
@ -16,10 +16,12 @@ curl -i -H "Accept: application/json" -X PUT -d "2" ht
|
|||||||
|
|
||||||
# frontend 1
|
# frontend 1
|
||||||
curl -i -H "Accept: application/json" -X PUT -d "backend2" http://localhost:8500/v1/kv/traefik/frontends/frontend1/backend
|
curl -i -H "Accept: application/json" -X PUT -d "backend2" http://localhost:8500/v1/kv/traefik/frontends/frontend1/backend
|
||||||
|
curl -i -H "Accept: application/json" -X PUT -d "http" http://localhost:8500/v1/kv/traefik/frontends/frontend1/entrypoints
|
||||||
curl -i -H "Accept: application/json" -X PUT -d "Host" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/rule
|
curl -i -H "Accept: application/json" -X PUT -d "Host" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/rule
|
||||||
curl -i -H "Accept: application/json" -X PUT -d "test.localhost" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/value
|
curl -i -H "Accept: application/json" -X PUT -d "test.localhost" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/value
|
||||||
|
|
||||||
# frontend 2
|
# frontend 2
|
||||||
curl -i -H "Accept: application/json" -X PUT -d "backend1" http://localhost:8500/v1/kv/traefik/frontends/frontend2/backend
|
curl -i -H "Accept: application/json" -X PUT -d "backend1" http://localhost:8500/v1/kv/traefik/frontends/frontend2/backend
|
||||||
|
curl -i -H "Accept: application/json" -X PUT -d "http,https" http://localhost:8500/v1/kv/traefik/frontends/frontend2/entrypoints
|
||||||
curl -i -H "Accept: application/json" -X PUT -d "Path" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/rule
|
curl -i -H "Accept: application/json" -X PUT -d "Path" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/rule
|
||||||
curl -i -H "Accept: application/json" -X PUT -d "/test" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/value
|
curl -i -H "Accept: application/json" -X PUT -d "/test" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/value
|
||||||
|
@ -2,12 +2,45 @@
|
|||||||
# Global configuration
|
# Global configuration
|
||||||
################################################################
|
################################################################
|
||||||
|
|
||||||
# Reverse proxy port
|
# Entrypoints definition
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: ":80"
|
# Default:
|
||||||
|
# [entryPoints]
|
||||||
|
# [entryPoints.http]
|
||||||
|
# address = ":80"
|
||||||
#
|
#
|
||||||
# port = ":80"
|
# To redirect an http entrypoint to an https entrypoint (with SNI support):
|
||||||
|
# [entryPoints]
|
||||||
|
# [entryPoints.http]
|
||||||
|
# address = ":80"
|
||||||
|
# [entryPoints.http.redirect]
|
||||||
|
# entryPoint = "https"
|
||||||
|
# [entryPoints.https]
|
||||||
|
# address = ":443"
|
||||||
|
# [entryPoints.https.tls]
|
||||||
|
# [[entryPoints.https.tls.certificates]]
|
||||||
|
# CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
# KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
# [[entryPoints.https.tls.certificates]]
|
||||||
|
# CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
|
# KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
|
#
|
||||||
|
# To redirect an entrypoint rewriting the URL:
|
||||||
|
# [entryPoints]
|
||||||
|
# [entryPoints.http]
|
||||||
|
# address = ":80"
|
||||||
|
# [entryPoints.http.redirect]
|
||||||
|
# regex = "^http://localhost/(.*)"
|
||||||
|
# replacement = "http://mydomain/$1"
|
||||||
|
|
||||||
|
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||||
|
# Each frontend can specify its own entrypoints.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: ["http"]
|
||||||
|
#
|
||||||
|
# defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
# Timeout in seconds.
|
# Timeout in seconds.
|
||||||
# Duration to give active requests a chance to finish during hot-reloads
|
# Duration to give active requests a chance to finish during hot-reloads
|
||||||
@ -37,15 +70,6 @@
|
|||||||
#
|
#
|
||||||
# logLevel = "ERROR"
|
# logLevel = "ERROR"
|
||||||
|
|
||||||
# SSL certificates and keys
|
|
||||||
# You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# [[certificates]]
|
|
||||||
# CertFile = "traefik.crt"
|
|
||||||
# KeyFile = "traefik.key"
|
|
||||||
|
|
||||||
# Backends throttle duration: minimum duration between 2 events from providers
|
# Backends throttle duration: minimum duration between 2 events from providers
|
||||||
# before applying a new configuration. It avoids unnecessary reloads if multiples events
|
# before applying a new configuration. It avoids unnecessary reloads if multiples events
|
||||||
# are sent in a short amount of time.
|
# are sent in a short amount of time.
|
||||||
@ -390,6 +414,12 @@
|
|||||||
# [frontends.frontend2]
|
# [frontends.frontend2]
|
||||||
# backend = "backend1"
|
# backend = "backend1"
|
||||||
# passHostHeader = true
|
# passHostHeader = true
|
||||||
# [frontends.frontend2.routes.test_2]
|
# entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
|
# [frontends.frontend2.routes.test_1]
|
||||||
|
# rule = "Host"
|
||||||
|
# value = "{subdomain:[a-z]+}.localhost"
|
||||||
|
# [frontends.frontend3]
|
||||||
|
# entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
|
# backend = "backend2"
|
||||||
# rule = "Path"
|
# rule = "Path"
|
||||||
# value = "/test"
|
# value = "/test"
|
||||||
|
@ -36,6 +36,7 @@ type Route struct {
|
|||||||
|
|
||||||
// Frontend holds frontend configuration.
|
// Frontend holds frontend configuration.
|
||||||
type Frontend struct {
|
type Frontend struct {
|
||||||
|
EntryPoints []string `json:"entryPoints,omitempty"`
|
||||||
Backend string `json:"backend,omitempty"`
|
Backend string `json:"backend,omitempty"`
|
||||||
Routes map[string]Route `json:"routes,omitempty"`
|
Routes map[string]Route `json:"routes,omitempty"`
|
||||||
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
PassHostHeader bool `json:"passHostHeader,omitempty"`
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer">
|
<div data-bg-show="frontendCtrl.frontend.backend" class="panel-footer">
|
||||||
|
<span data-ng-repeat="entryPoint in frontendCtrl.frontend.entryPoints"><span class="label label-primary">{{entryPoint}}</span><span data-ng-hide="$last"> </span></span>
|
||||||
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">{{frontendCtrl.frontend.backend}}</span>
|
<span class="label label-warning" role="button" data-toggle="collapse" href="#{{frontendCtrl.frontend.backend}}" aria-expanded="false">{{frontendCtrl.frontend.backend}}</span>
|
||||||
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">Pass Host Header</span>
|
<span data-ng-show="frontendCtrl.frontend.passHostHeader" class="label label-warning">Pass Host Header</span>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user