diff --git a/cmd/registry/main.go b/cmd/registry/main.go
index 52eecf8f..df135917 100644
--- a/cmd/registry/main.go
+++ b/cmd/registry/main.go
@@ -21,6 +21,7 @@ import (
_ "github.com/docker/distribution/registry/auth/silly"
_ "github.com/docker/distribution/registry/auth/token"
"github.com/docker/distribution/registry/handlers"
+ "github.com/docker/distribution/registry/listener"
_ "github.com/docker/distribution/registry/storage/driver/azure"
_ "github.com/docker/distribution/registry/storage/driver/filesystem"
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
@@ -67,14 +68,26 @@ func main() {
go debugServer(config.HTTP.Debug.Addr)
}
- if config.HTTP.TLS.Certificate == "" {
- context.GetLogger(app).Infof("listening on %v", config.HTTP.Addr)
- if err := http.ListenAndServe(config.HTTP.Addr, handler); err != nil {
- context.GetLogger(app).Fatalln(err)
- }
- } else {
+ server := &http.Server{
+ Handler: handler,
+ }
+
+ ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr)
+ if err != nil {
+ context.GetLogger(app).Fatalln(err)
+ }
+ defer ln.Close()
+
+ if config.HTTP.TLS.Certificate != "" {
tlsConf := &tls.Config{
- ClientAuth: tls.NoClientCert,
+ ClientAuth: tls.NoClientCert,
+ NextProtos: []string{"http/1.1"},
+ Certificates: make([]tls.Certificate, 1),
+ }
+
+ tlsConf.Certificates[0], err = tls.LoadX509KeyPair(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key)
+ if err != nil {
+ context.GetLogger(app).Fatalln(err)
}
if len(config.HTTP.TLS.ClientCAs) != 0 {
@@ -99,16 +112,14 @@ func main() {
tlsConf.ClientCAs = pool
}
- context.GetLogger(app).Infof("listening on %v, tls", config.HTTP.Addr)
- server := &http.Server{
- Addr: config.HTTP.Addr,
- Handler: handler,
- TLSConfig: tlsConf,
- }
+ ln = tls.NewListener(ln, tlsConf)
+ context.GetLogger(app).Infof("listening on %v, tls", ln.Addr())
+ } else {
+ context.GetLogger(app).Infof("listening on %v", ln.Addr())
+ }
- if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil {
- context.GetLogger(app).Fatalln(err)
- }
+ if err := server.Serve(ln); err != nil {
+ context.GetLogger(app).Fatalln(err)
}
}
diff --git a/configuration/configuration.go b/configuration/configuration.go
index 074471b4..31496e6e 100644
--- a/configuration/configuration.go
+++ b/configuration/configuration.go
@@ -54,6 +54,9 @@ type Configuration struct {
// Addr specifies the bind address for the registry instance.
Addr string `yaml:"addr,omitempty"`
+ // Net specifies the net portion of the bind address. A default empty value means tcp.
+ Net string `yaml:"net,omitempty"`
+
Prefix string `yaml:"prefix,omitempty"`
// Secret specifies the secret key which HMAC tokens are created with.
diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go
index 5c5d68b3..cc7427dc 100644
--- a/configuration/configuration_test.go
+++ b/configuration/configuration_test.go
@@ -61,6 +61,7 @@ var configStruct = Configuration{
},
HTTP: struct {
Addr string `yaml:"addr,omitempty"`
+ Net string `yaml:"net,omitempty"`
Prefix string `yaml:"prefix,omitempty"`
Secret string `yaml:"secret,omitempty"`
TLS struct {
diff --git a/docs/configuration.md b/docs/configuration.md
index 29707fde..d7bd15d6 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,6 +1,6 @@
@@ -98,7 +98,7 @@ http:
debug:
addr: localhost:5001
notifications:
- endpoints:
+ endpoints:
- name: alistener
disabled: false
url: https://my.listener.com/event
@@ -158,7 +158,7 @@ directory.
>configuration.
-## version
+## version
```yaml
version: 0.1
@@ -166,7 +166,7 @@ version: 0.1
The `version` option is **required**. It specifies the configuration's version.
It is expected to remain a top-level field, to allow for a consistent version
-check before parsing the remainder of the configuration file.
+check before parsing the remainder of the configuration file.
## log
@@ -278,7 +278,7 @@ You must configure one backend; if you configure more, the registry returns an e
Use the `cache` subsection to enable caching of data accessed in the storage
backend. Currently, the only available cache provides fast access to layer
-metadata. This, if configured, uses the `layerinfo` field.
+metadata. This, if configured, uses the `layerinfo` field.
You can set `layerinfo` field to `redis` or `inmemory`. The `redis` value uses
a Redis pool to cache layer metadata. The `inmemory` value uses an in memory
@@ -296,7 +296,7 @@ here so make sure there is adequate space available.
### azure
-This storage backend uses Microsoft's Azure Storage platform.
+This storage backend uses Microsoft's Azure Storage platform.
@@ -336,7 +336,7 @@ This storage backend uses Microsoft's Azure Storage platform.
Name of the Azure container into which to store data.
|
-
+
@@ -455,7 +455,7 @@ This storage backend uses Amazon's Simple Storage Service (S3).
This is a prefix that will be applied to all S3 keys to allow you to segment data in your bucket if necessary.
|
-
+
### Maintenance
@@ -466,7 +466,7 @@ maintenance functions which are related to storage can be configured under the m
### Upload Purging
Upload purging is a background process that periodically removes orphaned files from the upload
-directories of the registry. Upload purging is enabled by default. To
+directories of the registry. Upload purging is enabled by default. To
configure upload directory purging, the following parameters
must be set.
@@ -475,10 +475,10 @@ must be set.
--------- | -------- | -----------
`enabled` | yes | Set to true to enable upload purging. Default=true. |
`age` | yes | Upload directories which are older than this age will be deleted. Default=168h (1 week)
-`interval` | yes | The interval between upload directory purging. Default=24h.
+`interval` | yes | The interval between upload directory purging. Default=24h.
`dryrun` | yes | dryrun can be set to true to obtain a summary of what directories will be deleted. Default=false.
-Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week).
+Note: `age` and `interval` are strings containing a number with optional fraction and a unit suffix: e.g. 45m, 2h10m, 168h (1 week).
## auth
@@ -505,7 +505,7 @@ The `silly` auth is only for development purposes. It simply checks for the
existence of the `Authorization` header in the HTTP request. It has no regard for
the header's value. If the header does not exist, the `silly` auth responds with a
challenge response, echoing back the realm, service, and scope that access was
-denied for.
+denied for.
The following values are used to configure the response:
@@ -545,7 +545,7 @@ The following values are used to configure the response:
Token based authentication allows the authentication system to be decoupled from
the registry. It is a well established authentication paradigm with a high
-degree of security.
+degree of security.
@@ -592,14 +592,14 @@ the token so it must match the value configured for the issuer.
rootcertbundle
- yes
+ yes
|
The absolute path to the root certificate bundle. This bundle contains the
public part of the certificates that is used to sign authentication tokens.
|
-
+
For more information about Token based authentication configuration, see the [specification.]
@@ -613,7 +613,7 @@ object they're wrapping. This means a registry middleware must implement the
`driver.StorageDriver`.
Currently only one middleware, `cloudfront`, a storage middleware, is supported
-in the registry implementation.
+in the registry implementation.
```yaml
middleware:
@@ -747,7 +747,7 @@ configuration may contain both.
production,staging, or
development.
-
+
endpoint
@@ -756,9 +756,9 @@ configuration may contain both.
no
|
- Specify the enterprise Bugsnag endpoint.
+ Specify the enterprise Bugsnag endpoint.
|
-
+
@@ -791,7 +791,7 @@ configuration may contain both.
New Relic application name.
|
-
+
verbose
@@ -802,7 +802,7 @@ configuration may contain both.
|
Enable New Relic debugging output on stdout.
|
-
+
## http
@@ -810,6 +810,7 @@ configuration may contain both.
```yaml
http:
addr: localhost:5000
+ net: tcp
prefix: /my/nested/registry/
secret: asecretforlocaldevelopment
tls:
@@ -838,7 +839,20 @@ The `http` option details the configuration for the HTTP server that hosts the r
yes
- The HOST:PORT for which the server should accept connections.
+ The address for which the server should accept connections. The form depends on a network type (see net option):
+ HOST:PORT for tcp and FILE for a unix socket.
+ |
+
+
+
+ net
+ |
+
+ no
+ |
+
+ The network which is used to create a listening socket. Known networks are unix and tcp .
+ The default empty value means tcp.
|
@@ -915,7 +929,7 @@ and proxy connections to the registry server.
An array of absolute paths to a x509 CA file
|
-
+
@@ -934,7 +948,7 @@ specifies the `HOST:PORT` on which the debug server should accept connections.
```yaml
notifications:
- endpoints:
+ endpoints:
- name: alistener
disabled: false
url: https://my.listener.com/event
@@ -965,7 +979,7 @@ Endpoints is a list of named services (URLs) that can accept event notifications
yes
-A human readable name for the service.
+A human readable name for the service.
|
@@ -989,7 +1003,7 @@ A boolean to enable/disable notifications for a service.
The URL to which events should be published.
|
-
+
headers
@@ -1000,7 +1014,7 @@ The URL to which events should be published.
|
Static headers to add to each request.
|
-
+
timeout
@@ -1021,7 +1035,7 @@ The URL to which events should be published.
If you omit the suffix, the system interprets the value as nanoseconds.
|
-
+
threshold
@@ -1032,7 +1046,7 @@ The URL to which events should be published.
|
An integer specifying how long to wait before backing off a failure.
|
-
+
backoff
@@ -1054,7 +1068,7 @@ The URL to which events should be published.
If you omit the suffix, the system interprets the value as nanoseconds.
|
-
+
@@ -1129,7 +1143,7 @@ with the [pool](#pool) subsection.
Timeout for connecting to a redis instance.
|
-
+
readtimeout
@@ -1140,7 +1154,7 @@ with the [pool](#pool) subsection.
|
Timeout for reading from redis connections.
|
-
+
writetimeout
@@ -1151,7 +1165,7 @@ with the [pool](#pool) subsection.
|
Timeout for writing to redis connections.
|
-
+
@@ -1206,7 +1220,7 @@ Configure the behavior of the Redis connection pool.
sets the amount time to wait before closing
inactive connections.
-
+
@@ -1216,7 +1230,7 @@ The following is a simple example you can use for local development:
```yaml
version: 0.1
-log:
+log:
level: debug
storage:
filesystem:
@@ -1229,7 +1243,7 @@ http:
```
The above configures the registry instance to run on port `5000`, binding to
-`localhost`, with the `debug` server enabled. Registry data storage is in the
+`localhost`, with the `debug` server enabled. Registry data storage is in the
`/tmp/registry-dev` directory. Logging is in `debug` mode, which is the most
verbose.
@@ -1242,7 +1256,7 @@ Both are generally useful for local development.
This example illustrates how to configure storage middleware in a registry.
Middleware allows the registry to serve layers via a content delivery network
-(CDN). This is useful for reducing requests to the storage layer.
+(CDN). This is useful for reducing requests to the storage layer.
Currently, the registry supports [Amazon
Cloudfront](http://aws.amazon.com/cloudfront/). You can only use Cloudfront in
@@ -1263,7 +1277,7 @@ conjunction with the S3 storage driver.
options: |
-
+ |
A set of key/value options to configure the middleware.
baseurl: The Cloudfront base URL.
@@ -1293,4 +1307,3 @@ middleware:
>**Note**: Cloudfront keys exist separately to other AWS keys. See
>[the documentation on AWS credentials](http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#KeyPairs)
>for more information.
-
diff --git a/registry/listener/listener.go b/registry/listener/listener.go
new file mode 100644
index 00000000..b93a7a63
--- /dev/null
+++ b/registry/listener/listener.go
@@ -0,0 +1,74 @@
+package listener
+
+import (
+ "fmt"
+ "net"
+ "os"
+ "time"
+)
+
+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
+// connections. It's used by ListenAndServe and ListenAndServeTLS so
+// dead TCP connections (e.g. closing laptop mid-download) eventually
+// go away.
+// it is a plain copy-paste from net/http/server.go
+type tcpKeepAliveListener struct {
+ *net.TCPListener
+}
+
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
+ tc, err := ln.AcceptTCP()
+ if err != nil {
+ return
+ }
+ tc.SetKeepAlive(true)
+ tc.SetKeepAlivePeriod(3 * time.Minute)
+ return tc, nil
+}
+
+// NewListener announces on laddr and net. Accepted values of the net are
+// 'unix' and 'tcp'
+func NewListener(net, laddr string) (net.Listener, error) {
+ switch net {
+ case "unix":
+ return newUnixListener(laddr)
+ case "tcp", "": // an empty net means tcp
+ return newTCPListener(laddr)
+ default:
+ return nil, fmt.Errorf("unknown address type %s", net)
+ }
+}
+
+func newUnixListener(laddr string) (net.Listener, error) {
+ fi, err := os.Stat(laddr)
+ if err == nil {
+ // the file exists.
+ // try to remove it if it's a socket
+ if !isSocket(fi.Mode()) {
+ return nil, fmt.Errorf("file %s exists and is not a socket", laddr)
+ }
+
+ if err := os.Remove(laddr); err != nil {
+ return nil, err
+ }
+ } else if !os.IsNotExist(err) {
+ // we can't do stat on the file.
+ // it means we can not remove it
+ return nil, err
+ }
+
+ return net.Listen("unix", laddr)
+}
+
+func isSocket(m os.FileMode) bool {
+ return m&os.ModeSocket != 0
+}
+
+func newTCPListener(laddr string) (net.Listener, error) {
+ ln, err := net.Listen("tcp", laddr)
+ if err != nil {
+ return nil, err
+ }
+
+ return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil
+}
|