[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:
parent
ced8a0378b
commit
ad80cbe1ea
@ -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>")
|
||||||
|
@ -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.
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
74
registry/listener/listener.go
Normal file
74
registry/listener/listener.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user