mirror of https://github.com/perkeep/perkeep.git
netutil: add HostPort, serverinit: return app baseURL
Context: http://camlistore.org/issue/479 This patch allows camlistored to wait for all the apps to be serving, before printing its own listening address. Change-Id: I4035b115a03ef6a2a43177b83b5b65ebc50a2188
This commit is contained in:
parent
1763efbefe
commit
0a869ad067
|
@ -59,6 +59,8 @@ func ListenAddress() (string, error) {
|
|||
if baseURL == "" {
|
||||
return "", errors.New("CAMLI_APP_BACKEND_URL is undefined")
|
||||
}
|
||||
|
||||
// TODO(mpl): see if can use netutil.TCPAddress (and get IP6 for free).
|
||||
defaultPort := "80"
|
||||
noScheme := strings.TrimPrefix(baseURL, "http://")
|
||||
if strings.HasPrefix(baseURL, "https://") {
|
||||
|
|
|
@ -19,6 +19,8 @@ package netutil
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -36,3 +38,32 @@ func AwaitReachable(addr string, maxWait time.Duration) error {
|
|||
}
|
||||
return fmt.Errorf("%v unreachable for %v", addr, maxWait)
|
||||
}
|
||||
|
||||
// HostPort takes a urlStr string URL, and returns a host:port string suitable
|
||||
// to passing to net.Dial, with the port set as the scheme's default port if
|
||||
// absent.
|
||||
func HostPort(urlStr string) (string, error) {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse %q as a url: %v", urlStr, err)
|
||||
}
|
||||
if u.Scheme == "" {
|
||||
return "", fmt.Errorf("url %q has no scheme", urlStr)
|
||||
}
|
||||
hostPort := u.Host
|
||||
if hostPort == "" || strings.HasPrefix(hostPort, ":") {
|
||||
return "", fmt.Errorf("url %q has no host", urlStr)
|
||||
}
|
||||
idx := strings.Index(hostPort, "]")
|
||||
if idx == -1 {
|
||||
idx = 0
|
||||
}
|
||||
if !strings.Contains(hostPort[idx:], ":") {
|
||||
if u.Scheme == "https" {
|
||||
hostPort += ":443"
|
||||
} else {
|
||||
hostPort += ":80"
|
||||
}
|
||||
}
|
||||
return hostPort, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
Copyright 2014 The Camlistore Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHostPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
baseURL string
|
||||
wantNetAddr string
|
||||
}{
|
||||
// IPv4, no prefix
|
||||
{
|
||||
baseURL: "http://foo.com/",
|
||||
wantNetAddr: "foo.com:80",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com/",
|
||||
wantNetAddr: "foo.com:443",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://foo.com:8080/",
|
||||
wantNetAddr: "foo.com:8080",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com:8080/",
|
||||
wantNetAddr: "foo.com:8080",
|
||||
},
|
||||
|
||||
// IPv4, with prefix
|
||||
{
|
||||
baseURL: "http://foo.com/pics/",
|
||||
wantNetAddr: "foo.com:80",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com/pics/",
|
||||
wantNetAddr: "foo.com:443",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://foo.com:8080/pics/",
|
||||
wantNetAddr: "foo.com:8080",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com:8080/pics/",
|
||||
wantNetAddr: "foo.com:8080",
|
||||
},
|
||||
|
||||
// IPv6, no prefix
|
||||
{
|
||||
baseURL: "http://[::1]/",
|
||||
wantNetAddr: "[::1]:80",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://[::1]/",
|
||||
wantNetAddr: "[::1]:443",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://[::1]:8080/",
|
||||
wantNetAddr: "[::1]:8080",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://[::1]:8080/",
|
||||
wantNetAddr: "[::1]:8080",
|
||||
},
|
||||
|
||||
// IPv6, with prefix
|
||||
{
|
||||
baseURL: "http://[::1]/pics/",
|
||||
wantNetAddr: "[::1]:80",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://[::1]/pics/",
|
||||
wantNetAddr: "[::1]:443",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://[::1]:8080/pics/",
|
||||
wantNetAddr: "[::1]:8080",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://[::1]:8080/pics/",
|
||||
wantNetAddr: "[::1]:8080",
|
||||
},
|
||||
}
|
||||
for _, v := range tests {
|
||||
got, err := HostPort(v.baseURL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
if got != v.wantNetAddr {
|
||||
t.Errorf("got: %v for %v, want: %v", got, v.baseURL, v.wantNetAddr)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,7 +44,8 @@ type Handler struct {
|
|||
auth auth.AuthMode // Used for basic HTTP authenticating against the app requests.
|
||||
appConfig jsonconfig.Obj // Additional parameters the app can request, or nil.
|
||||
|
||||
proxy *httputil.ReverseProxy // For redirecting requests to the app.
|
||||
proxy *httputil.ReverseProxy // For redirecting requests to the app.
|
||||
backendURL string // URL that we proxy to (i.e. base URL of the app).
|
||||
}
|
||||
|
||||
func (a *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -80,6 +81,7 @@ func randPortBackendURL(apiHost, appHandlerPrefix string) (string, error) {
|
|||
return "", fmt.Errorf("could not close random listener: %v", err)
|
||||
}
|
||||
|
||||
// TODO(mpl): see if can use netutil.TCPAddress.
|
||||
scheme := "https://"
|
||||
noScheme := strings.TrimPrefix(apiHost, scheme)
|
||||
if strings.HasPrefix(noScheme, "http://") {
|
||||
|
@ -161,11 +163,12 @@ func NewHandler(conf jsonconfig.Obj, apiHost, appHandlerPrefix string) (*Handler
|
|||
return nil, fmt.Errorf("could not parse backendURL %q: %v", backendURL, err)
|
||||
}
|
||||
return &Handler{
|
||||
name: name,
|
||||
envVars: envVars,
|
||||
auth: basicAuth,
|
||||
appConfig: appConfig,
|
||||
proxy: httputil.NewSingleHostReverseProxy(proxyURL),
|
||||
name: name,
|
||||
envVars: envVars,
|
||||
auth: basicAuth,
|
||||
appConfig: appConfig,
|
||||
proxy: httputil.NewSingleHostReverseProxy(proxyURL),
|
||||
backendURL: backendURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -235,3 +238,8 @@ func (a *Handler) AuthMode() auth.AuthMode {
|
|||
func (a *Handler) AppConfig() map[string]interface{} {
|
||||
return a.appConfig
|
||||
}
|
||||
|
||||
// BackendURL returns the appBackendURL that the app handler will proxy to.
|
||||
func (a *Handler) BackendURL() string {
|
||||
return a.backendURL
|
||||
}
|
||||
|
|
|
@ -582,6 +582,16 @@ func (config *Config) StartApps() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// AppURL returns a map of app name to app base URL for all the configured
|
||||
// server apps.
|
||||
func (config *Config) AppURL() map[string]string {
|
||||
appURL := make(map[string]string, len(config.apps))
|
||||
for _, ap := range config.apps {
|
||||
appURL[ap.ProgramName()] = ap.BackendURL()
|
||||
}
|
||||
return appURL
|
||||
}
|
||||
|
||||
func mustCreate(path string) *os.File {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
|
|
|
@ -42,6 +42,7 @@ import (
|
|||
"camlistore.org/pkg/buildinfo"
|
||||
"camlistore.org/pkg/legal/legalprint"
|
||||
"camlistore.org/pkg/misc"
|
||||
"camlistore.org/pkg/netutil"
|
||||
"camlistore.org/pkg/osutil"
|
||||
"camlistore.org/pkg/serverinit"
|
||||
"camlistore.org/pkg/webserver"
|
||||
|
@ -399,8 +400,16 @@ func Main(up chan<- struct{}, down <-chan struct{}) {
|
|||
exitf("StartApps: %v", err)
|
||||
}
|
||||
|
||||
// TODO(mpl): wait for all the apps to somehow signal they have started (or failed to)
|
||||
// before printing the one below?
|
||||
for appName, appURL := range config.AppURL() {
|
||||
addr, err := netutil.HostPort(appURL)
|
||||
if err != nil {
|
||||
log.Printf("Could not get app %v address: %v", appName, err)
|
||||
continue
|
||||
}
|
||||
if err := netutil.AwaitReachable(addr, 5*time.Second); err != nil {
|
||||
log.Printf("Could not reach app %v: %v", appName, err)
|
||||
}
|
||||
}
|
||||
log.Printf("Available on %s", urlToOpen)
|
||||
|
||||
// Block forever, except during tests.
|
||||
|
|
Loading…
Reference in New Issue