func NewHandler(cfg HandlerConfig) (*Handler, error) { name := cfg.Program if cfg.Prefix == "" { return nil, fmt.Errorf("app: could not initialize Handler for %q: empty Prefix", name) } listen, backendURL, apiHost := cfg.Listen, cfg.BackendURL, cfg.APIHost var err error if listen == "" { if cfg.ServerListen == "" { return nil, fmt.Errorf(`app: could not initialize Handler for %q: neither "Listen" or "ServerListen" defined`, name) } listen, err = randListen(cfg.ServerListen) if err != nil { return nil, err } } if backendURL == "" { if cfg.ServerBaseURL == "" { return nil, fmt.Errorf(`app: could not initialize Handler for %q: neither "BackendURL" or "ServerBaseURL" defined`, name) } backendURL, err = baseURL(cfg.ServerBaseURL, listen) if err != nil { return nil, err } } if apiHost == "" { if cfg.ServerBaseURL == "" { return nil, fmt.Errorf(`app: could not initialize Handler for %q: neither "APIHost" or "ServerBaseURL" defined`, name) } apiHost = cfg.ServerBaseURL + "/" } proxyURL, err := url.Parse(backendURL) if err != nil { return nil, fmt.Errorf("could not parse backendURL %q: %v", backendURL, err) } username, password := auth.RandToken(20), auth.RandToken(20) camliAuth := username + ":" + password basicAuth := auth.NewBasicAuth(username, password) envVars := map[string]string{ "CAMLI_API_HOST": apiHost, "CAMLI_AUTH": camliAuth, "CAMLI_APP_LISTEN": listen, } if cfg.AppConfig != nil { envVars["CAMLI_APP_CONFIG_URL"] = apiHost + strings.TrimPrefix(cfg.Prefix, "/") + "config.json" } return &Handler{ name: name, envVars: envVars, auth: basicAuth, appConfig: cfg.AppConfig, prefix: strings.TrimSuffix(cfg.Prefix, "/"), proxy: httputil.NewSingleHostReverseProxy(proxyURL), backendURL: backendURL, }, nil }
// NewHandler returns a Handler that proxies requests to an app. Start() on the // Handler starts the app. // The apiHost must end in a slash and is the camlistored API server for the app // process to hit. // The appHandlerPrefix is the URL path prefix on apiHost where the app is mounted. // It must end in a slash, and be at minimum "/". // The conf object has the following members, related to the vars described in // doc/app-environment.txt: // "program", string, required. File name of the app's program executable. Either // an absolute path, or the name of a file located in CAMLI_APP_BINDIR or in PATH. // "backendURL", string, optional. Automatic if absent. It sets CAMLI_APP_BACKEND_URL. // "appConfig", object, optional. Additional configuration that the app can request from Camlistore. func NewHandler(conf jsonconfig.Obj, apiHost, appHandlerPrefix string) (*Handler, error) { // TODO: remove the appHandlerPrefix if/when we change where the app config JSON URL is made available. name := conf.RequiredString("program") backendURL := conf.OptionalString("backendURL", "") 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 } if apiHost == "" { return nil, fmt.Errorf("app: could not initialize Handler for %q: Camlistore apiHost is unknown", name) } if appHandlerPrefix == "" { return nil, fmt.Errorf("app: could not initialize Handler for %q: empty appHandlerPrefix", name) } if backendURL == "" { var err error // If not specified in the conf, we're dynamically picking the port of the CAMLI_APP_BACKEND_URL // now (instead of letting the app itself do it), because we need to know it in advance in order // to set the app handler's proxy. backendURL, err = randPortBackendURL(apiHost, appHandlerPrefix) if 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_API_HOST": apiHost, "CAMLI_AUTH": camliAuth, "CAMLI_APP_BACKEND_URL": backendURL, } if appConfig != nil { envVars["CAMLI_APP_CONFIG_URL"] = apiHost + strings.TrimPrefix(appHandlerPrefix, "/") + "config.json" } proxyURL, err := url.Parse(backendURL) if err != nil { 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), backendURL: backendURL, }, nil }
func newCookie() *http.Cookie { expiration := cookieExpiration return &http.Cookie{ Name: "user", Value: auth.RandToken(15), Expires: time.Now().Add(expiration), } }
func newCookie() *http.Cookie { expiration := cookieExpiration if DevHandler { expiration = 2 * time.Minute } return &http.Cookie{ Name: "user", Value: auth.RandToken(15), Expires: time.Now().Add(expiration), } }
// NewDeployHandler initializes a DeployHandler that serves at https://host/prefix/ and returns it. // A Google account client ID should be set in CAMLI_GCE_CLIENTID with its corresponding client // secret in CAMLI_GCE_CLIENTSECRET. func NewDeployHandler(host, prefix string) (http.Handler, error) { clientID := os.Getenv("CAMLI_GCE_CLIENTID") if clientID == "" { return nil, errors.New("Need an oauth2 client ID defined in CAMLI_GCE_CLIENTID") } clientSecret := os.Getenv("CAMLI_GCE_CLIENTSECRET") if clientSecret == "" { return nil, errors.New("Need an oauth2 client secret defined in CAMLI_GCE_CLIENTSECRET") } tpl, err := template.New("root").Parse(noTheme + tplHTML) if err != nil { return nil, fmt.Errorf("could not parse template: %v", err) } host = strings.TrimSuffix(host, "/") prefix = strings.TrimSuffix(prefix, "/") scheme := "https://" if DevHandler { scheme = "http://" } xsrfKey := os.Getenv("CAMLI_GCE_XSRFKEY") if xsrfKey == "" { xsrfKey = auth.RandToken(20) log.Printf("xsrf key not provided as env var CAMLI_GCE_XSRFKEY, so generating one instead: %v", xsrfKey) } instConf, instState, err := dataStores() if err != nil { return nil, fmt.Errorf("could not initialize conf or state storage: %v", err) } h := &DeployHandler{ debug: DevHandler, host: host, xsrfKey: xsrfKey, instConf: instConf, instState: instState, recordStateErr: make(map[string]error), scheme: scheme, prefix: prefix, help: map[string]template.HTML{ "createProject": template.HTML(googURLPattern.ReplaceAllString(HelpCreateProject, toHyperlink)), "enableAPIs": template.HTML(HelpEnableAPIs), "genCert": template.HTML(helpGenCert), "domainName": template.HTML(helpDomainName), "machineTypes": template.HTML(helpMachineTypes), "zones": template.HTML(helpZones), "ssh": template.HTML(helpSSH), "changeCert": template.HTML(helpChangeCert), "changeSSH": template.HTML(HelpManageSSHKeys), "changeHTTPCreds": template.HTML(HelpManageHTTPCreds), }, clientID: clientID, clientSecret: clientSecret, tpl: tpl, piggyGIF: "/static/piggy.gif", } mux := http.NewServeMux() mux.HandleFunc(prefix+"/callback", func(w http.ResponseWriter, r *http.Request) { h.serveCallback(w, r) }) mux.HandleFunc(prefix+"/instance", func(w http.ResponseWriter, r *http.Request) { h.serveInstanceState(w, r) }) mux.HandleFunc(prefix+"/", func(w http.ResponseWriter, r *http.Request) { h.serveRoot(w, r) }) h.mux = mux h.Logger = log.New(os.Stderr, "", log.LstdFlags) h.zones = backupZones // TODO(mpl): use time.AfterFunc and avoid having a goroutine running all the time almost // doing nothing. refreshZonesFn := func() { for { if err := h.refreshZones(); err != nil { h.Printf("error while refreshing zones: %v", err) } time.Sleep(24 * time.Hour) } } go refreshZonesFn() return h, nil }