HTTP: Refactor Unix socket support #2337 #3595

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2023-08-14 10:43:05 +02:00
parent 68fea5954d
commit c82a4b2287
6 changed files with 57 additions and 44 deletions

View file

@ -33,11 +33,11 @@ func statusAction(ctx *cli.Context) error {
// interrupt reading of the Response.Body.
client := &http.Client{Timeout: 10 * time.Second}
// make a dial function for unix socket
if unixSocketPath := conf.HttpHostAsSocketPath(); unixSocketPath != "" {
// Connect to unix socket?
if unixSocket := conf.HttpSocket(); unixSocket != "" {
client.Transport = &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
return net.Dial("unix", unixSocketPath)
return net.Dial("unix", unixSocket)
},
}
}

View file

@ -105,7 +105,7 @@ func (c *Config) HttpCachePublic() bool {
// HttpHost returns the built-in HTTP server host name or IP address (empty for all interfaces).
func (c *Config) HttpHost() string {
// when unix socket used as host, make host as default value. or http client will act weirdly.
if c.options.HttpHost == "" || c.HttpHostAsSocketPath() != "" {
if c.options.HttpHost == "" {
return "0.0.0.0"
}
@ -121,13 +121,17 @@ func (c *Config) HttpPort() int {
return c.options.HttpPort
}
// HttpHostAsSocketPath tries to parse the HttpHost as unix socket path. If failed, return empty string.
func (c *Config) HttpHostAsSocketPath() string {
host := c.options.HttpHost
if strings.HasPrefix(host, "unix:") && strings.Contains(host, "/") {
return strings.TrimPrefix(host, "unix:")
// HttpSocket tries to parse the HttpHost as a Unix socket path and returns an empty string otherwise.
func (c *Config) HttpSocket() string {
if c.options.HttpSocket != "" {
// Do nothing.
} else if host := c.options.HttpHost; !strings.HasPrefix(host, "unix:") {
return ""
} else if strings.Contains(host, "/") {
c.options.HttpSocket = strings.TrimPrefix(host, "unix:")
}
return ""
return c.options.HttpSocket
}
// TemplatesPath returns the server templates path.

View file

@ -8,12 +8,12 @@ import (
"github.com/photoprism/photoprism/internal/thumb"
)
func TestConfig_HttpHostAsSocketPath(t *testing.T) {
func TestConfig_HttpSocket(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, "", c.HttpHostAsSocketPath())
assert.Equal(t, "", c.HttpSocket())
c.options.HttpHost = "unix:/tmp/photoprism.sock"
assert.Equal(t, "/tmp/photoprism.sock", c.HttpHostAsSocketPath())
assert.Equal(t, "/tmp/photoprism.sock", c.HttpSocket())
}
func TestConfig_HttpServerHost2(t *testing.T) {
@ -23,7 +23,7 @@ func TestConfig_HttpServerHost2(t *testing.T) {
c.options.HttpHost = "test"
assert.Equal(t, "test", c.HttpHost())
c.options.HttpHost = "unix:/tmp/photoprism.sock"
assert.Equal(t, "0.0.0.0", c.HttpHost())
assert.Equal(t, "unix:/tmp/photoprism.sock", c.HttpHost())
}
func TestConfig_HttpServerPort2(t *testing.T) {

View file

@ -117,6 +117,7 @@ type Options struct {
HttpCachePublic bool `yaml:"HttpCachePublic" json:"HttpCachePublic" flag:"http-cache-public"`
HttpHost string `yaml:"HttpHost" json:"-" flag:"http-host"`
HttpPort int `yaml:"HttpPort" json:"-" flag:"http-port"`
HttpSocket string `yaml:"-" json:"-" flag:"-"`
DatabaseDriver string `yaml:"DatabaseDriver" json:"-" flag:"database-driver"`
DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"`
DatabaseName string `yaml:"DatabaseName" json:"-" flag:"database-name"`

View file

@ -16,8 +16,6 @@ func AutoTLS(conf *config.Config) (*autocert.Manager, error) {
// Enable automatic HTTPS via Let's Encrypt?
if !conf.SiteHttps() {
return nil, fmt.Errorf("disabled tls")
} else if conf.HttpHostAsSocketPath() != "" {
return nil, fmt.Errorf("unix socket not work with auto https")
} else if siteDomain = conf.SiteDomain(); !strings.Contains(siteDomain, ".") {
return nil, fmt.Errorf("fully qualified domain required to enable tls")
} else if tlsEmail = conf.TLSEmail(); tlsEmail == "" {

View file

@ -76,8 +76,29 @@ func Start(ctx context.Context, conf *config.Config) {
var tlsManager *autocert.Manager
var server *http.Server
// Enable TLS?
if tlsManager, tlsErr = AutoTLS(conf); tlsErr == nil {
// Start HTTP server.
if unixSocket := conf.HttpSocket(); unixSocket != "" {
var listener net.Listener
var unixAddr *net.UnixAddr
var err error
if unixAddr, err = net.ResolveUnixAddr("unix", unixSocket); err != nil {
log.Errorf("server: resolve unix address failed (%s)", err)
return
} else if listener, err = net.ListenUnix("unix", unixAddr); err != nil {
log.Errorf("server: listen unix address failed (%s)", err)
return
} else {
server = &http.Server{
Addr: unixSocket,
Handler: router,
}
log.Infof("server: listening on %s [%s]", unixSocket, time.Since(start))
go StartHttp(server, listener)
}
} else if tlsManager, tlsErr = AutoTLS(conf); tlsErr == nil {
server = &http.Server{
Addr: fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort()),
TLSConfig: tlsManager.TLSConfig(),
@ -85,11 +106,8 @@ func Start(ctx context.Context, conf *config.Config) {
}
log.Infof("server: starting in auto tls mode on %s [%s]", server.Addr, time.Since(start))
go StartAutoTLS(server, tlsManager, conf)
} else if publicCert, privateKey := conf.TLS(); publicCert != "" && privateKey != "" {
} else if publicCert, privateKey := conf.TLS(); unixSocket == "" && publicCert != "" && privateKey != "" {
log.Infof("server: starting in tls mode")
if unixSocketPath := conf.HttpHostAsSocketPath(); unixSocketPath != "" {
log.Errorf("both unix socket and tls cert provided")
}
server = &http.Server{
Addr: fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort()),
Handler: router,
@ -98,30 +116,22 @@ func Start(ctx context.Context, conf *config.Config) {
go StartTLS(server, publicCert, privateKey)
} else {
log.Infof("server: %s", tlsErr)
var listener net.Listener
var listenPath string
var err error
if unixSocketPath := conf.HttpHostAsSocketPath(); unixSocketPath != "" {
var unixAddr *net.UnixAddr
unixAddr, err = net.ResolveUnixAddr("unix", unixSocketPath)
if err != nil {
log.Errorf("server: resolve unix address failed (%s)", err)
}
listenPath = unixSocketPath
listener, err = net.ListenUnix("unix", unixAddr)
socket := fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort())
if listener, err := net.Listen("tcp", socket); err != nil {
log.Errorf("server: %s", err)
return
} else {
listenPath = fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort())
listener, err = net.Listen("tcp", listenPath)
server = &http.Server{
Addr: socket,
Handler: router,
}
log.Infof("server: listening on %s [%s]", socket, time.Since(start))
go StartHttp(server, listener)
}
if err != nil {
log.Errorf("server: listen unix address failed (%s)", err)
}
server = &http.Server{
Addr: listenPath,
Handler: router,
}
log.Infof("server: listening on %s [%s]", listenPath, time.Since(start))
go StartHttp(server, listener)
}
// Graceful HTTP server shutdown.