[Server] Listen and serve on a unix socket

Allow to use a unix socket as a listener.
To specify an endpoint type we use an optional configuration
field 'net', as there's no way to distinguish a relative
socket path from a hostname.

Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>
This commit is contained in:
Anton Tiurin 2015-05-05 11:25:42 +03:00
parent ced8a0378b
commit ad80cbe1ea
5 changed files with 157 additions and 55 deletions

View File

@ -21,6 +21,7 @@ import (
_ "github.com/docker/distribution/registry/auth/silly" _ "github.com/docker/distribution/registry/auth/silly"
_ "github.com/docker/distribution/registry/auth/token" _ "github.com/docker/distribution/registry/auth/token"
"github.com/docker/distribution/registry/handlers" "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/azure"
_ "github.com/docker/distribution/registry/storage/driver/filesystem" _ "github.com/docker/distribution/registry/storage/driver/filesystem"
_ "github.com/docker/distribution/registry/storage/driver/inmemory" _ "github.com/docker/distribution/registry/storage/driver/inmemory"
@ -67,14 +68,26 @@ func main() {
go debugServer(config.HTTP.Debug.Addr) go debugServer(config.HTTP.Debug.Addr)
} }
if config.HTTP.TLS.Certificate == "" { server := &http.Server{
context.GetLogger(app).Infof("listening on %v", config.HTTP.Addr) Handler: handler,
if err := http.ListenAndServe(config.HTTP.Addr, handler); err != nil { }
ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr)
if err != nil {
context.GetLogger(app).Fatalln(err) context.GetLogger(app).Fatalln(err)
} }
} else { defer ln.Close()
if config.HTTP.TLS.Certificate != "" {
tlsConf := &tls.Config{ 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 { if len(config.HTTP.TLS.ClientCAs) != 0 {
@ -99,18 +112,16 @@ func main() {
tlsConf.ClientCAs = pool tlsConf.ClientCAs = pool
} }
context.GetLogger(app).Infof("listening on %v, tls", config.HTTP.Addr) ln = tls.NewListener(ln, tlsConf)
server := &http.Server{ context.GetLogger(app).Infof("listening on %v, tls", ln.Addr())
Addr: config.HTTP.Addr, } else {
Handler: handler, context.GetLogger(app).Infof("listening on %v", ln.Addr())
TLSConfig: tlsConf,
} }
if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil { if err := server.Serve(ln); err != nil {
context.GetLogger(app).Fatalln(err) context.GetLogger(app).Fatalln(err)
} }
} }
}
func usage() { func usage() {
fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "<config>") fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "<config>")

View File

@ -54,6 +54,9 @@ type Configuration struct {
// Addr specifies the bind address for the registry instance. // Addr specifies the bind address for the registry instance.
Addr string `yaml:"addr,omitempty"` 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"` Prefix string `yaml:"prefix,omitempty"`
// Secret specifies the secret key which HMAC tokens are created with. // Secret specifies the secret key which HMAC tokens are created with.

View File

@ -61,6 +61,7 @@ var configStruct = Configuration{
}, },
HTTP: struct { HTTP: struct {
Addr string `yaml:"addr,omitempty"` Addr string `yaml:"addr,omitempty"`
Net string `yaml:"net,omitempty"`
Prefix string `yaml:"prefix,omitempty"` Prefix string `yaml:"prefix,omitempty"`
Secret string `yaml:"secret,omitempty"` Secret string `yaml:"secret,omitempty"`
TLS struct { TLS struct {

View File

@ -810,6 +810,7 @@ configuration may contain both.
```yaml ```yaml
http: http:
addr: localhost:5000 addr: localhost:5000
net: tcp
prefix: /my/nested/registry/ prefix: /my/nested/registry/
secret: asecretforlocaldevelopment secret: asecretforlocaldevelopment
tls: tls:
@ -838,7 +839,20 @@ The `http` option details the configuration for the HTTP server that hosts the r
yes yes
</td> </td>
<td> <td>
The <code>HOST:PORT</code> for which the server should accept connections. The address for which the server should accept connections. The form depends on a network type (see <code>net</code> option):
<code>HOST:PORT</code> for tcp and <code>FILE</code> for a unix socket.
</td>
</tr>
<tr>
<td>
<code>net</code>
</td>
<td>
no
</td>
<td>
The network which is used to create a listening socket. Known networks are <code>unix</code> and <code>tcp</code>.
The default empty value means tcp.
</td> </td>
</tr> </tr>
<tr> <tr>
@ -1293,4 +1307,3 @@ middleware:
>**Note**: Cloudfront keys exist separately to other AWS keys. See >**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) >[the documentation on AWS credentials](http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#KeyPairs)
>for more information. >for more information.

View File

@ -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
}