Compare commits

..

10 Commits

Author SHA1 Message Date
NicoMen
54e09b98c7 Prepare release v1.5.1 2018-01-29 15:04:03 +01:00
Ludovic Fernandez
4eebaa1a80 Enhance file provider documentation. 2018-01-29 14:36:03 +01:00
NicoMen
cb9bf3ce68 Fix domain names in dynamic TLS configuration 2018-01-29 10:48:03 +01:00
SALLEYRON Julien
49a8cb76f5 Add note on redirect for ACME http challenge 2018-01-26 09:22:03 +01:00
SALLEYRON Julien
bf12306f17 Change gzipwriter receiver to implement CloseNotifier 2018-01-25 21:46:04 +01:00
SALLEYRON Julien
323b8237a0 Handle undefined entrypoint on ACME config and frontend config 2018-01-25 12:02:04 +01:00
Michael
039ccaf4f1 Fix tar gz source only on tags on travis 2018-01-24 16:10:04 +01:00
Michael
4afb39778a Fix add src.tar.gz in Træfik release 2018-01-24 10:40:04 +01:00
Ludovic Fernandez
751781a3b7 Increase integration tests timeout. 2018-01-24 09:14:02 +01:00
Ludovic Fernandez
f5d150c3b4 Fix the k8s redirection template. 2018-01-24 08:12:03 +01:00
16 changed files with 324 additions and 173 deletions

View File

@@ -27,6 +27,7 @@ before_deploy:
if [ "$TRAVIS_TAG" ]; then
make -j${N_MAKE_JOBS} crossbinary-parallel;
make image-dirty;
tar cfz dist/traefik-${VERSION}.src.tar.gz --exclude-vcs --exclude dist .;
fi;
curl -sI https://github.com/containous/structor/releases/latest | grep -Fi Location | tr -d '\r' | sed "s/tag/download/g" | awk -F " " '{ print $2 "/structor_linux-amd64"}' | wget --output-document=$GOPATH/bin/structor -i -;
chmod +x $GOPATH/bin/structor;

View File

@@ -1,5 +1,18 @@
# Change Log
## [v1.5.1](https://github.com/containous/traefik/tree/v1.5.1) (2018-01-29)
[All Commits](https://github.com/containous/traefik/compare/v1.5.0...v1.5.1)
**Bug fixes:**
- **[acme]** Handle undefined entrypoint on ACME config and frontend config ([#2756](https://github.com/containous/traefik/pull/2756) by [Juliens](https://github.com/Juliens))
- **[k8s]** Fix the k8s redirection template. ([#2748](https://github.com/containous/traefik/pull/2748) by [ldez](https://github.com/ldez))
- **[middleware]** Change gzipwriter receiver to implement CloseNotifier ([#2766](https://github.com/containous/traefik/pull/2766) by [Juliens](https://github.com/Juliens))
- **[tls]** Fix domain names in dynamic TLS configuration ([#2768](https://github.com/containous/traefik/pull/2768) by [nmengin](https://github.com/nmengin))
**Documentation:**
- **[acme]** Add note on redirect for ACME http challenge ([#2767](https://github.com/containous/traefik/pull/2767) by [Juliens](https://github.com/Juliens))
- **[file]** Enhance file provider documentation. ([#2777](https://github.com/containous/traefik/pull/2777) by [ldez](https://github.com/ldez))
## [v1.5.0](https://github.com/containous/traefik/tree/v1.5.0) (2018-01-23)
[All Commits](https://github.com/containous/traefik/compare/v1.4.0-rc1...v1.5.0)

2
Gopkg.lock generated
View File

@@ -89,7 +89,7 @@
branch = "master"
name = "github.com/NYTimes/gziphandler"
packages = ["."]
revision = "47ca22a0aeea4c9ceddfb935d818d636d934c312"
revision = "289a3b81f5aedc99f8d6eb0f67827c142f1310d8"
[[projects]]
name = "github.com/Nvveen/Gotty"

View File

@@ -441,9 +441,9 @@ var _templatesKubernetesTmpl = []byte(`[backends]{{range $backendName, $backend
{{if $frontend.Redirect}}
[frontends."{{$frontendName}}".redirect]
entryPoint = "{{$frontend.RedirectEntryPoint}}"
regex = "{{$frontend.RedirectRegex}}"
replacement = "{{$frontend.RedirectReplacement}}"
entryPoint = "{{$frontend.Redirect.EntryPoint}}"
regex = "{{$frontend.Redirect.Regex}}"
replacement = "{{$frontend.Redirect.Replacement}}"
{{end}}
{{ if $frontend.Headers }}

View File

@@ -142,6 +142,7 @@ func run(globalConfiguration *configuration.GlobalConfiguration, configFile stri
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
globalConfiguration.SetEffectiveConfiguration(configFile)
globalConfiguration.ValidateConfiguration()
jsonConf, _ := json.Marshal(globalConfiguration)
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)

View File

@@ -259,6 +259,19 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) {
}
}
// ValidateConfiguration validate that configuration is coherent
func (gc *GlobalConfiguration) ValidateConfiguration() {
if gc.ACME != nil {
if _, ok := gc.EntryPoints[gc.ACME.EntryPoint]; !ok {
log.Fatalf("Unknown entrypoint %q for ACME configuration", gc.ACME.EntryPoint)
} else {
if gc.EntryPoints[gc.ACME.EntryPoint].TLS == nil {
log.Fatalf("Entrypoint without TLS %q for ACME configuration", gc.ACME.EntryPoint)
}
}
}
}
// DefaultEntryPoints holds default entry points
type DefaultEntryPoints []string

View File

@@ -612,6 +612,7 @@ Those data help us prioritize our developments and focus on what's more importan
### What ?
Once a day (the first call begins 10 minutes after the start of Træfik), we collect:
- the Træfik version
- a hash of the configuration
- an **anonymous version** of the static configuration:

View File

@@ -186,6 +186,9 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
Use `HTTP-01` challenge to generate/renew ACME certificates.
The redirection is fully compatible with the HTTP-01 challenge.
You can use redirection with HTTP-01 challenge without problem.
```toml
[acme]
# ...

View File

@@ -1,6 +1,140 @@
# File Backends
Like any other reverse proxy, Træfik can be configured with a file.
Træfik can be configured with a file.
## Reference
```toml
# Backends
[backends]
[backends.backend1]
[backends.backend1.servers]
[backends.backend1.servers.server0]
url = "http://10.10.10.1:80"
weight = 1
[backends.backend1.servers.server1]
url = "http://10.10.10.2:80"
weight = 2
# ...
[backends.backend1.circuitBreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.backend1.loadBalancer]
method = "drr"
[backends.backend1.loadBalancer.stickiness]
cookieName = "foobar"
[backends.backend1.maxConn]
amount = 10
extractorfunc = "request.host"
[backends.backend1.healthCheck]
path = "/health"
port = 88
interval = "30s"
[backends.backend2]
# ...
# Frontends
[frontends]
[frontends.frontend1]
entryPoints = ["http", "https"]
backend = "backend1"
passHostHeader = true
passTLSCert = true
priority = 42
basicAuth = [
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
]
whitelistSourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
[frontends.frontend1.routes]
[frontends.frontend1.routes.route0]
rule = "Host:test.localhost"
[frontends.frontend1.routes.Route1]
rule = "Method:GET"
# ...
[frontends.frontend1.headers]
allowedHosts = ["foobar", "foobar"]
hostsProxyHeaders = ["foobar", "foobar"]
SSLRedirect = true
SSLTemporaryRedirect = true
SSLHost = "foobar"
STSSeconds = 42
STSIncludeSubdomains = true
STSPreload = true
forceSTSHeader = true
frameDeny = true
customFrameOptionsValue = "foobar"
contentTypeNosniff = true
browserXSSFilter = true
contentSecurityPolicy = "foobar"
publicKey = "foobar"
referrerPolicy = "foobar"
isDevelopment = true
[frontends.frontend1.headers.customRequestHeaders]
X-Foo-Bar-01 = "foobar"
X-Foo-Bar-02 = "foobar"
# ...
[frontends.frontend1.headers.customResponseHeaders]
X-Foo-Bar-03 = "foobar"
X-Foo-Bar-04 = "foobar"
# ...
[frontends.frontend1.headers.SSLProxyHeaders]
X-Foo-Bar-05 = "foobar"
X-Foo-Bar-06 = "foobar"
# ...
[frontends.frontend1.errors]
[frontends.frontend1.errors.errorPage0]
status = ["500-599"]
backend = "error"
query = "/{status}.html"
[frontends.frontend1.errors.errorPage1]
status = ["404", "403"]
backend = "error"
query = "/{status}.html"
# ...
[frontends.frontend1.ratelimit]
extractorfunc = "client.ip"
[frontends.frontend1.ratelimit.rateset.rateset1]
period = "10s"
average = 100
burst = 200
[frontends.frontend1.ratelimit.rateset.rateset2]
period = "3s"
average = 5
burst = 10
# ...
[frontends.frontend1.redirect]
entryPoint = "https"
regex = "^http://localhost/(.*)"
replacement = "http://mydomain/$1"
[frontends.frontend2]
# ...
# HTTPS certificates
[[tls]]
entryPoints = ["https"]
[tls.certificate]
certFile = "path/to/my.cert"
keyFile = "path/to/my.key"
[[tls]]
# ...
```
## Configuration mode
You have three choices:
@@ -12,7 +146,7 @@ To enable the file backend, you must either pass the `--file` option to the Træ
The configuration file allows managing both backends/frontends and HTTPS certificates (which are not [Let's Encrypt](https://letsencrypt.org) certificates generated through Træfik).
## Simple
### Simple
Add your configuration at the end of the global configuration file `traefik.toml`:
@@ -21,172 +155,93 @@ 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.org.cert"
keyFile = "integration/fixtures/https/snitest.org.key"
# ...
[file]
# rules
[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.backend1.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
# ...
[backends.backend2]
[backends.backend2.maxconn]
amount = 10
extractorfunc = "request.host"
[backends.backend2.LoadBalancer]
method = "drr"
[backends.backend2.servers.server1]
url = "http://172.17.0.4:80"
weight = 1
[backends.backend2.servers.server2]
url = "http://172.17.0.5:80"
weight = 2
# ...
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
# ...
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
priority = 10
# restrict access to this frontend to the specified list of IPv4/IPv6 CIDR Nets
# an unset or empty list allows all Source-IPs to access
# if one of the Net-Specifications are invalid, the whole list is invalid
# and allows all Source-IPs to access.
whitelistSourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
# ...
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
# ...
# HTTPS certificate
[[tls]]
entryPoints = ["https"]
[tls.certificate]
certFile = "path/to/my.cert"
keyFile = "path/to/my.key"
# ...
[[tls]]
entryPoints = ["https"]
[tls.certificate]
certFile = "path/to/my/other.cert"
keyFile = "path/to/my/other.key"
# ...
```
!!! note
adding certificates directly to the entrypoint is still maintained but certificates declared in this way cannot be managed dynamically.
It's recommended to use the file provider to declare certificates.
## Rules in a Separate File
### Rules in a Separate File
Put your rules in a separate file, for example `rules.toml`:
```toml
# traefik.toml
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
# ...
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
# ...
[file]
filename = "rules.toml"
filename = "rules.toml"
```
```toml
# rules.toml
[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.backend1.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
# ...
[backends.backend2]
[backends.backend2.maxconn]
amount = 10
extractorfunc = "request.host"
[backends.backend2.LoadBalancer]
method = "drr"
[backends.backend2.servers.server1]
url = "http://172.17.0.4:80"
weight = 1
[backends.backend2.servers.server2]
url = "http://172.17.0.5:80"
weight = 2
# ...
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
# ...
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
# ...
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
# ...
# HTTPS certificate
[[tls]]
entryPoints = ["https"]
[tls.certificate]
certFile = "path/to/my.cert"
keyFile = "path/to/my.key"
[[tls]]
entryPoints = ["https"]
[tls.certificate]
certFile = "path/to/my/other.cert"
keyFile = "path/to/my/other.key"
# ...
## Multiple `.toml` Files
[[tls]]
# ...
```
### Multiple `.toml` Files
You could have multiple `.toml` files in a directory (and recursively in its sub-directories):
```toml
[file]
directory = "/path/to/config/"
directory = "/path/to/config/"
```
If you want Træfik to watch file changes automatically, just add:
```toml
[file]
watch = true
watch = true
```

View File

@@ -285,21 +285,17 @@ Multiple sets of rates can be added to each frontend, but the time periods must
```toml
[frontends]
[frontends.frontend1]
passHostHeader = true
entrypoints = ["http"]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Path:/"
[frontends.frontend1.ratelimit]
extractorfunc = "client.ip"
[frontends.frontend1.ratelimit.rateset.rateset1]
period = "10s"
average = 100
burst = 200
[frontends.frontend1.ratelimit.rateset.rateset2]
period = "3s"
average = 5
burst = 10
# ...
[frontends.frontend1.ratelimit]
extractorfunc = "client.ip"
[frontends.frontend1.ratelimit.rateset.rateset1]
period = "10s"
average = 100
burst = 200
[frontends.frontend1.ratelimit.rateset.rateset2]
period = "3s"
average = 5
burst = 10
```
In the above example, frontend1 is configured to limit requests by the client's ip address.

View File

@@ -1,5 +1,72 @@
# Entry Points Definition
## Reference
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
whitelistSourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
compress = true
[entryPoints.http.tls]
minVersion = "VersionTLS12"
cipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"]
[[entryPoints.http.tls.certificates]]
certFile = "path/to/my.cert"
keyFile = "path/to/my.key"
[[entryPoints.http.tls.certificates]]
certFile = "path/to/other.cert"
keyFile = "path/to/other.key"
# ...
[entryPoints.http.tls.clientCA]
files = ["path/to/ca1.crt", "path/to/ca2.crt"]
optional = false
[entryPoints.http.redirect]
entryPoint = "https"
regex = "^http://localhost/(.*)"
replacement = "http://mydomain/$1"
permanent = true
[entryPoints.http.auth]
headerField = "X-WebAuth-User"
[entryPoints.http.auth.basic]
users = [
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
]
usersFile = "/path/to/.htpasswd"
[entryPoints.http.auth.digest]
users = [
"test:traefik:a2688e031edb4be6a3797f3882655c05",
"test2:traefik:518845800f9e2bfb1f1f740ec24f074e",
]
usersFile = "/path/to/.htdigest"
[entryPoints.http.auth.forward]
address = "https://authserver.com/auth"
trustForwardHeader = true
[entryPoints.http.auth.forward.tls]
ca = [ "path/to/local.crt"]
caOptional = true
cert = "path/to/foo.cert"
key = "path/to/foo.key"
insecureSkipVerify = true
[entryPoints.http.proxyProtocol]
insecure = true
trustedIPs = ["10.10.10.1", "10.10.10.2"]
[entryPoints.http.forwardedHeaders]
trustedIPs = ["10.10.10.1", "10.10.10.2"]
[entryPoints.https]
# ...
```
## Basic
```toml
# Entrypoints definition
#
@@ -71,7 +138,7 @@ Define an entrypoint with SNI support.
!!! note
If an empty TLS configuration is done, default self-signed certificates are generated.
### Dynamic Certificates
@@ -162,7 +229,7 @@ Otherwise, the response from the auth server is returned.
# To enable forward auth on an entrypoint
[entryPoints.http.auth.forward]
address = "https://authserver.com/auth"
# Trust existing X-Forwarded-* headers.
# Useful with another reverse proxy in front of Traefik.
#
@@ -170,7 +237,7 @@ Otherwise, the response from the auth server is returned.
# Default: false
#
trustForwardHeader = true
# Enable forward auth TLS connection.
#
# Optional
@@ -234,7 +301,7 @@ Only IPs in `trustedIPs` will lead to remote client address replacement: you sho
!!! danger
When queuing Træfik behind another load-balancer, be sure to carefully configure Proxy Protocol on both sides.
Otherwise, it could introduce a security risk in your system by forging requests.
Otherwise, it could introduce a security risk in your system by forging requests.
```toml
[entryPoints]

View File

@@ -4,7 +4,7 @@ set -e
export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export DEST=.
TESTFLAGS="${TESTFLAGS} -test.timeout=9m -check.v"
TESTFLAGS="${TESTFLAGS} -test.timeout=20m -check.v"
if [ -n "$VERBOSE" ]; then
TESTFLAGS="${TESTFLAGS} -v"

View File

@@ -673,31 +673,27 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefikTls.TL
}
if s.globalConfiguration.ACME != nil {
if _, ok := s.serverEntryPoints[s.globalConfiguration.ACME.EntryPoint]; ok {
if entryPointName == s.globalConfiguration.ACME.EntryPoint {
checkOnDemandDomain := func(domain string) bool {
routeMatch := &mux.RouteMatch{}
router := router.GetHandler()
match := router.Match(&http.Request{URL: &url.URL{}, Host: domain}, routeMatch)
if match && routeMatch.Route != nil {
return true
}
return false
if entryPointName == s.globalConfiguration.ACME.EntryPoint {
checkOnDemandDomain := func(domain string) bool {
routeMatch := &mux.RouteMatch{}
router := router.GetHandler()
match := router.Match(&http.Request{URL: &url.URL{}, Host: domain}, routeMatch)
if match && routeMatch.Route != nil {
return true
}
if s.leadership == nil {
err := s.globalConfiguration.ACME.CreateLocalConfig(config, &s.serverEntryPoints[entryPointName].certs, checkOnDemandDomain)
if err != nil {
return nil, err
}
} else {
err := s.globalConfiguration.ACME.CreateClusterConfig(s.leadership, config, &s.serverEntryPoints[entryPointName].certs, checkOnDemandDomain)
if err != nil {
return nil, err
}
return false
}
if s.leadership == nil {
err := s.globalConfiguration.ACME.CreateLocalConfig(config, &s.serverEntryPoints[entryPointName].certs, checkOnDemandDomain)
if err != nil {
return nil, err
}
} else {
err := s.globalConfiguration.ACME.CreateClusterConfig(s.leadership, config, &s.serverEntryPoints[entryPointName].certs, checkOnDemandDomain)
if err != nil {
return nil, err
}
}
} else {
return nil, errors.New("Unknown entrypoint " + s.globalConfiguration.ACME.EntryPoint + " for ACME configuration")
}
} else {
config.GetCertificate = s.serverEntryPoints[entryPointName].getCertificate
@@ -925,23 +921,23 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
log.Debugf("Creating frontend %s", frontendName)
var frontendEntryPoints []string
for _, entryPointName := range frontend.EntryPoints {
if _, ok := serverEntryPoints[entryPointName]; !ok {
log.Errorf("Undefined entrypoint '%s' for frontend %s", entryPointName, frontendName)
} else {
frontendEntryPoints = append(frontendEntryPoints, entryPointName)
}
}
frontend.EntryPoints = frontendEntryPoints
if len(frontend.EntryPoints) == 0 {
log.Errorf("No entrypoint defined for frontend %s, defaultEntryPoints:%s", frontendName, globalConfiguration.DefaultEntryPoints)
log.Errorf("No entrypoint defined for frontend %s", frontendName)
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
}
var failedEntrypoints int
for _, entryPointName := range frontend.EntryPoints {
log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName)
if _, ok := serverEntryPoints[entryPointName]; !ok {
log.Errorf("Undefined entrypoint '%s' for frontend %s", entryPointName, frontendName)
failedEntrypoints++
if failedEntrypoints == len(frontend.EntryPoints) {
log.Errorf("Skipping frontend %s...", frontendName)
continue frontend
}
continue
}
newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)}
for routeName, route := range frontend.Routes {

View File

@@ -37,9 +37,9 @@
{{if $frontend.Redirect}}
[frontends."{{$frontendName}}".redirect]
entryPoint = "{{$frontend.RedirectEntryPoint}}"
regex = "{{$frontend.RedirectRegex}}"
replacement = "{{$frontend.RedirectReplacement}}"
entryPoint = "{{$frontend.Redirect.EntryPoint}}"
regex = "{{$frontend.Redirect.Regex}}"
replacement = "{{$frontend.Redirect.Replacement}}"
{{end}}
{{ if $frontend.Headers }}

View File

@@ -144,7 +144,12 @@ func (c *Certificate) AppendCertificates(certs map[string]*DomainsCertificates,
certKey := parsedCert.Subject.CommonName
if parsedCert.DNSNames != nil {
sort.Strings(parsedCert.DNSNames)
certKey += fmt.Sprintf("%s,%s", parsedCert.Subject.CommonName, strings.Join(parsedCert.DNSNames, ","))
for _, dnsName := range parsedCert.DNSNames {
if dnsName != parsedCert.Subject.CommonName {
certKey += fmt.Sprintf(",%s", dnsName)
}
}
}
certExists := false

View File

@@ -88,7 +88,7 @@ type GzipResponseWriterWithCloseNotify struct {
*GzipResponseWriter
}
func (w *GzipResponseWriterWithCloseNotify) CloseNotify() <-chan bool {
func (w GzipResponseWriterWithCloseNotify) CloseNotify() <-chan bool {
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
}