mirror of https://github.com/perkeep/perkeep.git
app/hello: dummy server application (hello world)
Change-Id: I6690b9459325af5a76d1de679d56701eefdd195e
This commit is contained in:
parent
28ac303dc7
commit
21dda2b4ef
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// The hello application serves as an example on how to make stand-alone
|
||||
// server applications, interacting with a Camlistore server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"camlistore.org/pkg/app"
|
||||
"camlistore.org/pkg/buildinfo"
|
||||
"camlistore.org/pkg/webserver"
|
||||
)
|
||||
|
||||
var (
|
||||
flagVersion = flag.Bool("version", false, "show version")
|
||||
)
|
||||
|
||||
// config is used to unmarshal the application configuration JSON
|
||||
// that we get from Camlistore when we request it at $CAMLI_APP_CONFIG_URL.
|
||||
type config struct {
|
||||
Word string `json:"word,omitempty"` // Argument printed after "Hello " in the helloHandler response.
|
||||
}
|
||||
|
||||
func appConfig() *config {
|
||||
configURL := os.Getenv("CAMLI_APP_CONFIG_URL")
|
||||
if configURL == "" {
|
||||
log.Fatalf("Hello application needs a CAMLI_APP_CONFIG_URL env var")
|
||||
}
|
||||
cl, err := app.Client()
|
||||
if err != nil {
|
||||
log.Fatalf("could not get a client to fetch extra config: %v", err)
|
||||
}
|
||||
conf := &config{}
|
||||
if err := cl.GetJSON(configURL, conf); err != nil {
|
||||
log.Fatalf("could not get app config at %v: %v", configURL, err)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
type helloHandler struct {
|
||||
who string // who to say hello to.
|
||||
}
|
||||
|
||||
func (h *helloHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
rw.WriteHeader(200)
|
||||
fmt.Fprintf(rw, "Hello %s\n", h.who)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *flagVersion {
|
||||
fmt.Fprintf(os.Stderr, "hello version: %s\nGo version: %s (%s/%s)\n",
|
||||
buildinfo.Version(), runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Starting hello version %s; Go %s (%s/%s)", buildinfo.Version(), runtime.Version(),
|
||||
runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
listenAddr, err := app.ListenAddress()
|
||||
if err != nil {
|
||||
log.Fatalf("Listen address: %v", err)
|
||||
}
|
||||
conf := appConfig()
|
||||
ws := webserver.New()
|
||||
ws.Handle("/", &helloHandler{who: conf.Word})
|
||||
// TODO(mpl): handle status requests too. Camlistore will send an auth
|
||||
// token in the extra config that should be used as the "password" for
|
||||
// subsequent status requests.
|
||||
if err := ws.Listen(listenAddr); err != nil {
|
||||
log.Fatalf("Listen: %v", err)
|
||||
}
|
||||
|
||||
ws.Serve()
|
||||
}
|
|
@ -17,6 +17,17 @@
|
|||
}
|
||||
},
|
||||
|
||||
"/hello/": {
|
||||
"handler": "app",
|
||||
"handlerArgs": {
|
||||
"program": "hello",
|
||||
"baseURL": "http://localhost:3178/",
|
||||
"appConfig": {
|
||||
"word": "world"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"/blog/": {
|
||||
"enabled": ["_env", "${CAMLI_PUBLISH_ENABLED}"],
|
||||
"handler": "publish",
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
Camlistore applications run with the following environment variables set:
|
||||
|
||||
CAMLI_APP_BASEURL (string):
|
||||
URL prefix of the application's root, always ending in a trailing slash. Examples:
|
||||
https://foo.org:3178/pub/
|
||||
https://foo.org/pub/
|
||||
http://192.168.0.1/
|
||||
http://192.168.0.1:1234/
|
||||
|
||||
CAMLI_APP_CONFIG_URL (string):
|
||||
URL containing JSON configuration for the app. The body of this URL comes from the
|
||||
"appConfig" part of the config file.
|
||||
|
||||
CAMLI_AUTH (string):
|
||||
Username and password (username:password) that the app should use to authenticate
|
||||
over HTTP basic auth with the Camlistore server. Basic auth is unencrypted, hence
|
||||
it should only be used with HTTPS or in a secure (local loopback) environment.
|
||||
|
||||
CAMLI_SERVER (string):
|
||||
URL prefix of Camlistore's root, always ending in a trailing slash. Examples:
|
||||
https://foo.org:3178/pub/
|
||||
https://foo.org/pub/
|
||||
http://192.168.0.1/
|
||||
http://192.168.0.1:1234/
|
4
make.go
4
make.go
|
@ -133,7 +133,7 @@ func main() {
|
|||
// TODO(mpl): main is getting long. We could probably move all the mirroring
|
||||
// dance to its own func.
|
||||
// We copy all *.go files from camRoot's goDirs to buildSrcDir.
|
||||
goDirs := []string{"cmd", "pkg", "dev", "server/camlistored", "third_party"}
|
||||
goDirs := []string{"app", "cmd", "pkg", "dev", "server/camlistored", "third_party"}
|
||||
if *onlysync {
|
||||
goDirs = append(goDirs, "server/appengine", "config")
|
||||
}
|
||||
|
@ -168,6 +168,7 @@ func main() {
|
|||
"camlistore.org/cmd/camput",
|
||||
"camlistore.org/cmd/camtool",
|
||||
"camlistore.org/server/camlistored",
|
||||
"camlistore.org/app/hello",
|
||||
}
|
||||
switch *targets {
|
||||
case "*":
|
||||
|
@ -227,6 +228,7 @@ func main() {
|
|||
|
||||
if buildAll {
|
||||
args = append(args,
|
||||
"camlistore.org/app/...",
|
||||
"camlistore.org/pkg/...",
|
||||
"camlistore.org/server/...",
|
||||
"camlistore.org/third_party/...",
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
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 app provides helpers for server applications interacting
|
||||
// with Camlistore.
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"camlistore.org/pkg/auth"
|
||||
"camlistore.org/pkg/client"
|
||||
)
|
||||
|
||||
// Client returns a client from pkg/client, configured by environment variables
|
||||
// for applications, and ready to be used to connect to the Camlistore server.
|
||||
func Client() (*client.Client, error) {
|
||||
server := os.Getenv("CAMLI_SERVER")
|
||||
if server == "" {
|
||||
return nil, errors.New("CAMLI_SERVER var not set")
|
||||
}
|
||||
authString := os.Getenv("CAMLI_AUTH")
|
||||
if authString == "" {
|
||||
return nil, errors.New("CAMLI_AUTH var not set")
|
||||
}
|
||||
userpass := strings.Split(authString, ":")
|
||||
if len(userpass) != 2 {
|
||||
return nil, fmt.Errorf("invalid auth string syntax. got %q, want \"username:password\"", authString)
|
||||
}
|
||||
cl := client.NewFromParams(server, auth.NewBasicAuth(userpass[0], userpass[1]))
|
||||
cl.SetHTTPClient(&http.Client{
|
||||
Transport: cl.TransportForConfig(nil),
|
||||
})
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
// ListenAddress returns the host:[port] network address, derived from the environment,
|
||||
// that the application should listen on.
|
||||
func ListenAddress() (string, error) {
|
||||
baseURL := os.Getenv("CAMLI_APP_BASEURL")
|
||||
if baseURL == "" {
|
||||
return "", errors.New("CAMLI_APP_BASEURL is undefined")
|
||||
}
|
||||
defaultPort := "80"
|
||||
noScheme := strings.TrimPrefix(baseURL, "http://")
|
||||
if strings.HasPrefix(baseURL, "https://") {
|
||||
noScheme = strings.TrimPrefix(baseURL, "https://")
|
||||
defaultPort = "443"
|
||||
}
|
||||
hostPortPrefix := strings.SplitN(noScheme, "/", 2)
|
||||
if len(hostPortPrefix) != 2 {
|
||||
return "", fmt.Errorf("invalid CAMLI_APP_BASEURL: %q (no trailing slash?)", baseURL)
|
||||
}
|
||||
if !strings.Contains(hostPortPrefix[0], ":") {
|
||||
return fmt.Sprintf("%s:%s", hostPortPrefix[0], defaultPort), nil
|
||||
}
|
||||
return hostPortPrefix[0], nil
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
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 app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListenAddress(t *testing.T) {
|
||||
tests := []struct {
|
||||
baseURL string
|
||||
wantAddr string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
baseURL: "http://foo.com/",
|
||||
wantAddr: "foo.com:80",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com/",
|
||||
wantAddr: "foo.com:443",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://foo.com:8080/",
|
||||
wantAddr: "foo.com:8080",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com:8080/",
|
||||
wantAddr: "foo.com:8080",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://foo.com:/",
|
||||
wantAddr: "foo.com:",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com:/",
|
||||
wantAddr: "foo.com:",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://foo.com/bar/",
|
||||
wantAddr: "foo.com:80",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com/bar/",
|
||||
wantAddr: "foo.com:443",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://foo.com:8080/bar/",
|
||||
wantAddr: "foo.com:8080",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com:8080/bar/",
|
||||
wantAddr: "foo.com:8080",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://foo.com:/bar/",
|
||||
wantAddr: "foo.com:",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "https://foo.com:/bar/",
|
||||
wantAddr: "foo.com:",
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "",
|
||||
wantErr: true,
|
||||
},
|
||||
|
||||
{
|
||||
baseURL: "http://foo.com",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, v := range tests {
|
||||
os.Setenv("CAMLI_APP_BASEURL", v.baseURL)
|
||||
got, err := ListenAddress()
|
||||
if v.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("Wanted error for %v", v.baseURL)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
if got != v.wantAddr {
|
||||
t.Errorf("got: %v, want: %v", got, v.wantAddr)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -82,6 +82,7 @@ var authConstructor = map[string]AuthConfigParser{
|
|||
"localhost": newLocalhostAuth,
|
||||
"userpass": newUserPassAuth,
|
||||
"devauth": newDevAuth,
|
||||
"basic": newBasicAuth,
|
||||
}
|
||||
|
||||
// RegisterAuth registers a new authentication scheme.
|
||||
|
@ -111,7 +112,7 @@ func newDevAuth(pw string) (AuthMode, error) {
|
|||
func newUserPassAuth(arg string) (AuthMode, error) {
|
||||
pieces := strings.Split(arg, ":")
|
||||
if len(pieces) < 2 {
|
||||
return nil, fmt.Errorf("Wrong userpass auth string; needs to be \"userpass:user:password\"")
|
||||
return nil, fmt.Errorf("Wrong userpass auth string; needs to be \"user:password\"")
|
||||
}
|
||||
username := pieces[0]
|
||||
password := pieces[1]
|
||||
|
@ -130,6 +131,23 @@ func newUserPassAuth(arg string) (AuthMode, error) {
|
|||
return mode, nil
|
||||
}
|
||||
|
||||
func newBasicAuth(arg string) (AuthMode, error) {
|
||||
pieces := strings.Split(arg, ":")
|
||||
if len(pieces) != 2 {
|
||||
return nil, fmt.Errorf("invalid basic auth syntax. got %q, want \"username:password\"", arg)
|
||||
}
|
||||
return NewBasicAuth(pieces[0], pieces[1]), nil
|
||||
}
|
||||
|
||||
// NewBasicAuth returns a UserPass Authmode, adequate to support HTTP
|
||||
// basic authentication.
|
||||
func NewBasicAuth(username, password string) AuthMode {
|
||||
return &UserPass{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNoAuth is returned when there is no configured authentication.
|
||||
var ErrNoAuth = errors.New("auth: no configured authentication")
|
||||
|
||||
|
@ -347,9 +365,15 @@ func ProcessRandom() string {
|
|||
}
|
||||
|
||||
func genProcessRand() {
|
||||
buf := make([]byte, 20)
|
||||
processRand = RandToken(20)
|
||||
}
|
||||
|
||||
// RandToken genererates (with crypto/rand.Read) and returns a token
|
||||
// that is the hex version (2x size) of size bytes of randomness.
|
||||
func RandToken(size int) string {
|
||||
buf := make([]byte, size)
|
||||
if n, err := rand.Read(buf); err != nil || n != len(buf) {
|
||||
panic("failed to get random: " + err.Error())
|
||||
}
|
||||
processRand = fmt.Sprintf("%x", buf)
|
||||
return fmt.Sprintf("%x", buf)
|
||||
}
|
||||
|
|
|
@ -37,6 +37,9 @@ func TestFromConfig(t *testing.T) {
|
|||
{in: "userpass:alice:secret:+localhost", want: &UserPass{Username: "alice", Password: "secret", OrLocalhost: true, VivifyPass: ""}},
|
||||
{in: "userpass:alice:secret:+localhost:vivify=foo", want: &UserPass{Username: "alice", Password: "secret", OrLocalhost: true, VivifyPass: "foo"}},
|
||||
{in: "devauth:port3179", want: &DevAuth{Password: "port3179", VivifyPass: "viviport3179"}},
|
||||
{in: "basic:alice:secret", want: &Basic{Username: "alice", Password: "secret", OrLocalhost: true, VivifyPass: ""}},
|
||||
{in: "basic:alice:secret:+localhost", wanterr: `invalid basic auth syntax. got "alice:secret:+localhost", want "username:password"`},
|
||||
{in: "basic:alice:secret:+vivify=foo", wanterr: `invalid basic auth syntax. got "alice:secret:+vivify=foo", want "username:password"`},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
am, err := FromConfig(tt.in)
|
||||
|
|
|
@ -770,6 +770,21 @@ func (c *Client) doDiscovery() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetJSON sends a GET request to url, and unmarshals the returned
|
||||
// JSON response into data. The URL's host must match the client's
|
||||
// configured server.
|
||||
func (c *Client) GetJSON(url string, data interface{}) error {
|
||||
if !strings.HasPrefix(url, c.discoRoot()) {
|
||||
return fmt.Errorf("wrong URL (%q) for this server", url)
|
||||
}
|
||||
hreq := c.newRequest("GET", url)
|
||||
resp, err := c.expect2XX(hreq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return httputil.DecodeJSON(resp, data)
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(method, url string, body ...io.Reader) *http.Request {
|
||||
var bodyR io.Reader
|
||||
if len(body) > 0 {
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
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 app helps with configuring and starting server applications
|
||||
// from Camlistore.
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"camlistore.org/pkg/auth"
|
||||
camhttputil "camlistore.org/pkg/httputil"
|
||||
"camlistore.org/pkg/jsonconfig"
|
||||
"camlistore.org/pkg/osutil"
|
||||
)
|
||||
|
||||
// AppHandler acts as a reverse proxy for a server application started by
|
||||
// Camlistore. It can also serve some extra JSON configuration to the app.
|
||||
type AppHandler struct {
|
||||
name string // Name of the app's program.
|
||||
envVars map[string]string // Variables set in the app's process environment. See pkg/app/vars.txt.
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
func (a *AppHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if camhttputil.PathSuffix(req) == "config.json" {
|
||||
if a.auth.AllowedAccess(req)&auth.OpGet == auth.OpGet {
|
||||
camhttputil.ReturnJSON(rw, a.appConfig)
|
||||
} else {
|
||||
auth.SendUnauthorized(rw, req)
|
||||
}
|
||||
return
|
||||
}
|
||||
if a.proxy == nil {
|
||||
http.Error(rw, "no proxy for the app", 500)
|
||||
return
|
||||
}
|
||||
a.proxy.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// New returns a configured AppHandler that Camlistore can use during server initialization
|
||||
// as a handler that proxies request to an app. It is also used to start the app.
|
||||
// The conf object has the following members, related to the vars described in doc/app-environment.text:
|
||||
// "program", string, required. Name of the app's program.
|
||||
// "baseURL", string, required. See CAMLI_APP_BASEURL.
|
||||
// "server", string, optional, overrides the camliBaseURL argument. See CAMLI_SERVER.
|
||||
// "appConfig", object, optional. Additional configuration that the app can request from Camlistore.
|
||||
func New(conf jsonconfig.Obj, serverBaseURL string) (*AppHandler, error) {
|
||||
name := conf.RequiredString("program")
|
||||
server := conf.OptionalString("server", serverBaseURL)
|
||||
if server == "" {
|
||||
return nil, fmt.Errorf("could not initialize AppHandler for %q: Camlistore baseURL is unknown", name)
|
||||
}
|
||||
baseURL := conf.RequiredString("baseURL")
|
||||
appConfig := conf.OptionalObject("appConfig")
|
||||
// TODO(mpl): add an auth token in the extra config of the dev server config,
|
||||
// that the hello app can use to setup a status handler than only responds
|
||||
// to requests with that token.
|
||||
if err := conf.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
username, password := auth.RandToken(20), auth.RandToken(20)
|
||||
camliAuth := username + ":" + password
|
||||
basicAuth := auth.NewBasicAuth(username, password)
|
||||
envVars := map[string]string{
|
||||
"CAMLI_SERVER": server,
|
||||
"CAMLI_AUTH": camliAuth,
|
||||
"CAMLI_APP_BASEURL": baseURL,
|
||||
}
|
||||
if appConfig != nil {
|
||||
appConfigURL := fmt.Sprintf("%s/%s/%s", strings.TrimSuffix(server, "/"), name, "config.json")
|
||||
envVars["CAMLI_APP_CONFIG_URL"] = appConfigURL
|
||||
}
|
||||
|
||||
proxyURL, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse baseURL %q: %v", baseURL, err)
|
||||
}
|
||||
return &AppHandler{
|
||||
name: name,
|
||||
envVars: envVars,
|
||||
auth: basicAuth,
|
||||
appConfig: appConfig,
|
||||
proxy: httputil.NewSingleHostReverseProxy(proxyURL),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AppHandler) Start() error {
|
||||
name := a.name
|
||||
if name == "" {
|
||||
return fmt.Errorf("invalid app name: %q", name)
|
||||
}
|
||||
// first look for it in PATH
|
||||
binPath, err := exec.LookPath(name)
|
||||
if err != nil {
|
||||
log.Printf("%q binary not found in PATH. now trying in the camlistore tree.", name)
|
||||
// else try in the camlistore tree
|
||||
binDir, err := osutil.GoPackagePath("camlistore.org/bin")
|
||||
if err != nil {
|
||||
return fmt.Errorf("bin dir in camlistore tree was not found: %v", err)
|
||||
}
|
||||
binPath = filepath.Join(binDir, name)
|
||||
if _, err = os.Stat(binPath); err != nil {
|
||||
return fmt.Errorf("could not find %v binary at %v: %v", name, binPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(binPath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
// TODO(mpl): extract Env methods from dev/devcam/env.go to a util pkg and use them here.
|
||||
newVars := make(map[string]string, len(a.envVars))
|
||||
for k, v := range a.envVars {
|
||||
newVars[k+"="] = v
|
||||
}
|
||||
env := os.Environ()
|
||||
for pos, oldkv := range env {
|
||||
for k, newVal := range newVars {
|
||||
if strings.HasPrefix(oldkv, k) {
|
||||
env[pos] = k + newVal
|
||||
delete(newVars, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range newVars {
|
||||
env = append(env, k+v)
|
||||
}
|
||||
cmd.Env = env
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("could not start app %v: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AppHandler) Name() string {
|
||||
return a.name
|
||||
}
|
|
@ -42,6 +42,7 @@ import (
|
|||
"camlistore.org/pkg/httputil"
|
||||
"camlistore.org/pkg/index"
|
||||
"camlistore.org/pkg/jsonconfig"
|
||||
"camlistore.org/pkg/server/app"
|
||||
"camlistore.org/pkg/types/serverconfig"
|
||||
)
|
||||
|
||||
|
@ -333,10 +334,20 @@ func (hl *handlerLoader) setupHandler(prefix string) {
|
|||
return
|
||||
}
|
||||
|
||||
hh, err := blobserver.CreateHandler(h.htype, hl, h.conf)
|
||||
if err != nil {
|
||||
exitFailure("error instantiating handler for prefix %q, type %q: %v",
|
||||
h.prefix, h.htype, err)
|
||||
var hh http.Handler
|
||||
if h.htype == "app" {
|
||||
ap, err := app.New(h.conf, hl.baseURL+"/")
|
||||
if err != nil {
|
||||
exitFailure("error setting up app for prefix %q: %v", h.prefix, err)
|
||||
}
|
||||
hh = ap
|
||||
} else {
|
||||
var err error
|
||||
hh, err = blobserver.CreateHandler(h.htype, hl, h.conf)
|
||||
if err != nil {
|
||||
exitFailure("error instantiating handler for prefix %q, type %q: %v",
|
||||
h.prefix, h.htype, err)
|
||||
}
|
||||
}
|
||||
|
||||
hl.handler[prefix] = hh
|
||||
|
@ -345,7 +356,7 @@ func (hl *handlerLoader) setupHandler(prefix string) {
|
|||
wrappedHandler = unauthorizedHandler{}
|
||||
} else {
|
||||
wrappedHandler = &httputil.PrefixHandler{prefix, hh}
|
||||
if handerTypeWantsAuth(h.htype) {
|
||||
if handlerTypeWantsAuth(h.htype) {
|
||||
wrappedHandler = auth.Handler{wrappedHandler}
|
||||
}
|
||||
}
|
||||
|
@ -358,7 +369,7 @@ func (unauthorizedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func handerTypeWantsAuth(handlerType string) bool {
|
||||
func handlerTypeWantsAuth(handlerType string) bool {
|
||||
// TODO(bradfitz): ask the handler instead? This is a bit of a
|
||||
// weird spot for this policy maybe?
|
||||
switch handlerType {
|
||||
|
@ -375,6 +386,10 @@ type Config struct {
|
|||
jsonconfig.Obj
|
||||
UIPath string // Not valid until after InstallHandlers
|
||||
configPath string // Filesystem path
|
||||
|
||||
// apps is the list of server apps configured during InstallHandlers,
|
||||
// and that should be started after camlistored has started serving.
|
||||
apps []*app.AppHandler
|
||||
}
|
||||
|
||||
// detectConfigChange returns an informative error if conf contains obsolete keys.
|
||||
|
@ -527,7 +542,11 @@ func (config *Config) InstallHandlers(hi HandlerInstaller, baseURL string, reind
|
|||
|
||||
// Now that everything is setup, run any handlers' InitHandler
|
||||
// methods.
|
||||
// And register apps that will be started later.
|
||||
for pfx, handler := range hl.handler {
|
||||
if starter, ok := handler.(*app.AppHandler); ok {
|
||||
config.apps = append(config.apps, starter)
|
||||
}
|
||||
if in, ok := handler.(blobserver.HandlerIniter); ok {
|
||||
if err := in.InitHandler(hl); err != nil {
|
||||
return nil, fmt.Errorf("Error calling InitHandler on %s: %v", pfx, err)
|
||||
|
@ -544,6 +563,19 @@ func (config *Config) InstallHandlers(hi HandlerInstaller, baseURL string, reind
|
|||
return multiCloser(hl.closers), nil
|
||||
}
|
||||
|
||||
// StartApps starts all the server applications that were configured
|
||||
// during InstallHandlers. It should only be called after camlistored
|
||||
// has started serving, since these apps might request some configuration
|
||||
// from Camlistore to finish initializing.
|
||||
func (config *Config) StartApps() error {
|
||||
for _, ap := range config.apps {
|
||||
if err := ap.Start(); err != nil {
|
||||
return fmt.Errorf("error starting app %v: %v", ap.Name(), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustCreate(path string) *os.File {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
|
|
|
@ -395,6 +395,10 @@ func Main(up chan<- struct{}, down <-chan struct{}) {
|
|||
osutil.DieOnParentDeath()
|
||||
}
|
||||
|
||||
if err := config.StartApps(); err != nil {
|
||||
exitf("StartApps: %v", err)
|
||||
}
|
||||
|
||||
// Block forever, except during tests.
|
||||
up <- struct{}{}
|
||||
<-down
|
||||
|
|
Loading…
Reference in New Issue