github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/serverconfig/serverconfig.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package serverconfig is responsible for mapping from a Camlistore
    18  // configuration file and instantiating HTTP Handlers for all the
    19  // necessary endpoints.
    20  package serverconfig
    21  
    22  import (
    23  	"encoding/json"
    24  	"errors"
    25  	"expvar"
    26  	"fmt"
    27  	"io"
    28  	"log"
    29  	"net/http"
    30  	"net/http/pprof"
    31  	"os"
    32  	"runtime"
    33  	rpprof "runtime/pprof"
    34  	"strconv"
    35  	"strings"
    36  
    37  	"camlistore.org/pkg/auth"
    38  	"camlistore.org/pkg/blobserver"
    39  	"camlistore.org/pkg/blobserver/handlers"
    40  	"camlistore.org/pkg/httputil"
    41  	"camlistore.org/pkg/importer"
    42  	"camlistore.org/pkg/index"
    43  	"camlistore.org/pkg/jsonconfig"
    44  )
    45  
    46  const camliPrefix = "/camli/"
    47  
    48  var ErrCamliPath = errors.New("Invalid Camlistore request path")
    49  
    50  type handlerConfig struct {
    51  	prefix   string         // "/foo/"
    52  	htype    string         // "localdisk", etc
    53  	conf     jsonconfig.Obj // never nil
    54  	internal bool           // if true, not accessible over HTTP
    55  
    56  	settingUp, setupDone bool
    57  }
    58  
    59  type handlerLoader struct {
    60  	installer   HandlerInstaller
    61  	baseURL     string
    62  	config      map[string]*handlerConfig // prefix -> config
    63  	handler     map[string]interface{}    // prefix -> http.Handler / func / blobserver.Storage
    64  	curPrefix   string
    65  	closers     []io.Closer
    66  	prefixStack []string
    67  	reindex     bool
    68  
    69  	// optional context (for App Engine, the first request that
    70  	// started up the process).  we may need this if setting up
    71  	// handlers involves doing datastore/memcache/blobstore
    72  	// lookups.
    73  	context *http.Request
    74  }
    75  
    76  // A HandlerInstaller is anything that can register an HTTP Handler at
    77  // a prefix path.  Both *http.ServeMux and camlistore.org/pkg/webserver.Server
    78  // implement HandlerInstaller.
    79  type HandlerInstaller interface {
    80  	Handle(path string, h http.Handler)
    81  }
    82  
    83  type storageAndConfig struct {
    84  	blobserver.Storage
    85  	config *blobserver.Config
    86  }
    87  
    88  // parseCamliPath looks for "/camli/" in the path and returns
    89  // what follows it (the action).
    90  func parseCamliPath(path string) (action string, err error) {
    91  	camIdx := strings.Index(path, camliPrefix)
    92  	if camIdx == -1 {
    93  		return "", ErrCamliPath
    94  	}
    95  	action = path[camIdx+len(camliPrefix):]
    96  	return
    97  }
    98  
    99  func unsupportedHandler(conn http.ResponseWriter, req *http.Request) {
   100  	httputil.BadRequestError(conn, "Unsupported camlistore path or method.")
   101  }
   102  
   103  func (s *storageAndConfig) Config() *blobserver.Config {
   104  	return s.config
   105  }
   106  
   107  // GetStorage returns the unwrapped blobserver.Storage interface value for
   108  // callers to type-assert optional interface implementations on. (e.g. EnumeratorConfig)
   109  func (s *storageAndConfig) GetStorage() blobserver.Storage {
   110  	return s.Storage
   111  }
   112  
   113  // action is the part following "/camli/" in the URL. It's either a
   114  // string like "enumerate-blobs", "stat", "upload", or a blobref.
   115  func camliHandlerUsingStorage(req *http.Request, action string, storage blobserver.StorageConfiger) (http.Handler, auth.Operation) {
   116  	var handler http.Handler
   117  	op := auth.OpAll
   118  	switch req.Method {
   119  	case "GET", "HEAD":
   120  		switch action {
   121  		case "enumerate-blobs":
   122  			handler = handlers.CreateEnumerateHandler(storage)
   123  			op = auth.OpGet
   124  		case "stat":
   125  			handler = handlers.CreateStatHandler(storage)
   126  		default:
   127  			handler = handlers.CreateGetHandler(storage)
   128  			op = auth.OpGet
   129  		}
   130  	case "POST":
   131  		switch action {
   132  		case "stat":
   133  			handler = handlers.CreateStatHandler(storage)
   134  			op = auth.OpStat
   135  		case "upload":
   136  			handler = handlers.CreateBatchUploadHandler(storage)
   137  			op = auth.OpUpload
   138  		case "remove":
   139  			handler = handlers.CreateRemoveHandler(storage)
   140  		}
   141  	case "PUT":
   142  		handler = handlers.CreatePutUploadHandler(storage)
   143  		op = auth.OpUpload
   144  	}
   145  	if handler == nil {
   146  		handler = http.HandlerFunc(unsupportedHandler)
   147  	}
   148  	return handler, op
   149  }
   150  
   151  // where prefix is like "/" or "/s3/" for e.g. "/camli/" or "/s3/camli/*"
   152  func makeCamliHandler(prefix, baseURL string, storage blobserver.Storage, hf blobserver.FindHandlerByTyper) http.Handler {
   153  	if !strings.HasSuffix(prefix, "/") {
   154  		panic("expected prefix to end in slash")
   155  	}
   156  	baseURL = strings.TrimRight(baseURL, "/")
   157  
   158  	canLongPoll := true
   159  	// TODO(bradfitz): set to false if this is App Engine, or provide some way to disable
   160  
   161  	storageConfig := &storageAndConfig{
   162  		storage,
   163  		&blobserver.Config{
   164  			Writable:      true,
   165  			Readable:      true,
   166  			Deletable:     false,
   167  			URLBase:       baseURL + prefix[:len(prefix)-1],
   168  			CanLongPoll:   canLongPoll,
   169  			HandlerFinder: hf,
   170  		},
   171  	}
   172  	return http.HandlerFunc(func(conn http.ResponseWriter, req *http.Request) {
   173  		action, err := parseCamliPath(req.URL.Path[len(prefix)-1:])
   174  		if err != nil {
   175  			log.Printf("Invalid request for method %q, path %q",
   176  				req.Method, req.URL.Path)
   177  			unsupportedHandler(conn, req)
   178  			return
   179  		}
   180  		handler := auth.RequireAuth(camliHandlerUsingStorage(req, action, storageConfig))
   181  		handler.ServeHTTP(conn, req)
   182  	})
   183  }
   184  
   185  func (hl *handlerLoader) FindHandlerByType(htype string) (prefix string, handler interface{}, err error) {
   186  	nFound := 0
   187  	for pfx, config := range hl.config {
   188  		if config.htype == htype {
   189  			nFound++
   190  			prefix, handler = pfx, hl.handler[pfx]
   191  		}
   192  	}
   193  	if nFound == 0 {
   194  		return "", nil, blobserver.ErrHandlerTypeNotFound
   195  	}
   196  	if htype == "jsonsign" && nFound > 1 {
   197  		// TODO: do this for all handler types later? audit
   198  		// callers of FindHandlerByType and see if that's
   199  		// feasible. For now I'm only paranoid about jsonsign.
   200  		return "", nil, fmt.Errorf("%d handlers found of type %q; ambiguous", nFound, htype)
   201  	}
   202  	return
   203  }
   204  
   205  func (hl *handlerLoader) setupAll() {
   206  	for prefix := range hl.config {
   207  		hl.setupHandler(prefix)
   208  	}
   209  }
   210  
   211  func (hl *handlerLoader) configType(prefix string) string {
   212  	if h, ok := hl.config[prefix]; ok {
   213  		return h.htype
   214  	}
   215  	return ""
   216  }
   217  
   218  func (hl *handlerLoader) getOrSetup(prefix string) interface{} {
   219  	hl.setupHandler(prefix)
   220  	return hl.handler[prefix]
   221  }
   222  
   223  func (hl *handlerLoader) MyPrefix() string {
   224  	return hl.curPrefix
   225  }
   226  
   227  func (hl *handlerLoader) GetStorage(prefix string) (blobserver.Storage, error) {
   228  	hl.setupHandler(prefix)
   229  	if s, ok := hl.handler[prefix].(blobserver.Storage); ok {
   230  		return s, nil
   231  	}
   232  	return nil, fmt.Errorf("bogus storage handler referenced as %q", prefix)
   233  }
   234  
   235  func (hl *handlerLoader) GetHandler(prefix string) (interface{}, error) {
   236  	hl.setupHandler(prefix)
   237  	if s, ok := hl.handler[prefix].(blobserver.Storage); ok {
   238  		return s, nil
   239  	}
   240  	if h, ok := hl.handler[prefix].(http.Handler); ok {
   241  		return h, nil
   242  	}
   243  	return nil, fmt.Errorf("bogus http or storage handler referenced as %q", prefix)
   244  }
   245  
   246  func (hl *handlerLoader) GetHandlerType(prefix string) string {
   247  	return hl.configType(prefix)
   248  }
   249  
   250  func exitFailure(pattern string, args ...interface{}) {
   251  	if !strings.HasSuffix(pattern, "\n") {
   252  		pattern = pattern + "\n"
   253  	}
   254  	panic(fmt.Sprintf(pattern, args...))
   255  }
   256  
   257  func (hl *handlerLoader) setupHandler(prefix string) {
   258  	h, ok := hl.config[prefix]
   259  	if !ok {
   260  		exitFailure("invalid reference to undefined handler %q", prefix)
   261  	}
   262  	if h.setupDone {
   263  		// Already setup by something else reference it and forcing it to be
   264  		// setup before the bottom loop got to it.
   265  		return
   266  	}
   267  	hl.prefixStack = append(hl.prefixStack, prefix)
   268  	if h.settingUp {
   269  		buf := make([]byte, 1024)
   270  		buf = buf[:runtime.Stack(buf, false)]
   271  		exitFailure("loop in configuration graph; %q tried to load itself indirectly: %q\nStack:\n%s",
   272  			prefix, hl.prefixStack, buf)
   273  	}
   274  	h.settingUp = true
   275  	defer func() {
   276  		// log.Printf("Configured handler %q", prefix)
   277  		h.setupDone = true
   278  		hl.prefixStack = hl.prefixStack[:len(hl.prefixStack)-1]
   279  		r := recover()
   280  		if r == nil {
   281  			if hl.handler[prefix] == nil {
   282  				panic(fmt.Sprintf("setupHandler for %q didn't install a handler", prefix))
   283  			}
   284  		} else {
   285  			panic(r)
   286  		}
   287  	}()
   288  
   289  	hl.curPrefix = prefix
   290  
   291  	if strings.HasPrefix(h.htype, "storage-") {
   292  		stype := strings.TrimPrefix(h.htype, "storage-")
   293  		// Assume a storage interface
   294  		pstorage, err := blobserver.CreateStorage(stype, hl, h.conf)
   295  		if err != nil {
   296  			exitFailure("error instantiating storage for prefix %q, type %q: %v",
   297  				h.prefix, stype, err)
   298  		}
   299  		if ix, ok := pstorage.(*index.Index); ok && hl.reindex {
   300  			log.Printf("Reindexing %s ...", h.prefix)
   301  			if err := ix.Reindex(); err != nil {
   302  				exitFailure("Error reindexing %s: %v", h.prefix, err)
   303  			}
   304  		}
   305  		hl.handler[h.prefix] = pstorage
   306  		if h.internal {
   307  			hl.installer.Handle(prefix, unauthorizedHandler{})
   308  		} else {
   309  			hl.installer.Handle(prefix+"camli/", makeCamliHandler(prefix, hl.baseURL, pstorage, hl))
   310  		}
   311  		if cl, ok := pstorage.(blobserver.ShutdownStorage); ok {
   312  			hl.closers = append(hl.closers, cl)
   313  		}
   314  		return
   315  	}
   316  
   317  	var hh http.Handler
   318  
   319  	if strings.HasPrefix(h.htype, "importer-") {
   320  		itype := strings.TrimPrefix(h.htype, "importer-")
   321  		imp, err := importer.Create(itype, hl, hl.baseURL+h.prefix, h.conf)
   322  		if err != nil {
   323  			exitFailure("error instantiating importer for prefix %q, type %q: %v",
   324  				h.prefix, itype, err)
   325  		}
   326  		hh = imp
   327  	} else {
   328  		var err error
   329  		hh, err = blobserver.CreateHandler(h.htype, hl, h.conf)
   330  		if err != nil {
   331  			exitFailure("error instantiating handler for prefix %q, type %q: %v",
   332  				h.prefix, h.htype, err)
   333  		}
   334  	}
   335  
   336  	hl.handler[prefix] = hh
   337  	var wrappedHandler http.Handler
   338  	if h.internal {
   339  		wrappedHandler = unauthorizedHandler{}
   340  	} else {
   341  		wrappedHandler = &httputil.PrefixHandler{prefix, hh}
   342  		if handerTypeWantsAuth(h.htype) {
   343  			wrappedHandler = auth.Handler{wrappedHandler}
   344  		}
   345  	}
   346  	hl.installer.Handle(prefix, wrappedHandler)
   347  }
   348  
   349  type unauthorizedHandler struct{}
   350  
   351  func (unauthorizedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   352  	http.Error(w, "Unauthorized", http.StatusUnauthorized)
   353  }
   354  
   355  func handerTypeWantsAuth(handlerType string) bool {
   356  	// TODO(bradfitz): ask the handler instead? This is a bit of a
   357  	// weird spot for this policy maybe?
   358  	switch handlerType {
   359  	case "ui", "search", "jsonsign", "sync", "status":
   360  		return true
   361  	}
   362  	return false
   363  }
   364  
   365  // A Config is the wrapper around a Camlistore JSON configuration file.
   366  // Files on disk can be in either high-level or low-level format, but
   367  // the Load function always returns the Config in its low-level format.
   368  type Config struct {
   369  	jsonconfig.Obj
   370  	UIPath     string // Not valid until after InstallHandlers
   371  	configPath string // Filesystem path
   372  }
   373  
   374  // Load returns a low-level "handler config" from the provided filename.
   375  // If the config file doesn't contain a top-level JSON key of "handlerConfig"
   376  // with boolean value true, the configuration is assumed to be a high-level
   377  // "user config" file, and transformed into a low-level config.
   378  func Load(filename string) (*Config, error) {
   379  	obj, err := jsonconfig.ReadFile(filename)
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	conf := &Config{
   384  		Obj:        obj,
   385  		configPath: filename,
   386  	}
   387  
   388  	if lowLevel := obj.OptionalBool("handlerConfig", false); !lowLevel {
   389  		conf, err = genLowLevelConfig(conf)
   390  		if err != nil {
   391  			return nil, fmt.Errorf(
   392  				"Failed to transform user config file %q into internal handler configuration: %v",
   393  				filename, err)
   394  		}
   395  		if v, _ := strconv.ParseBool(os.Getenv("CAMLI_DEBUG_CONFIG")); v {
   396  			jsconf, _ := json.MarshalIndent(conf.Obj, "", "  ")
   397  			log.Printf("From high-level config, generated low-level config: %s", jsconf)
   398  		}
   399  	}
   400  
   401  	return conf, nil
   402  }
   403  
   404  func (config *Config) checkValidAuth() error {
   405  	authConfig := config.OptionalString("auth", "")
   406  	mode, err := auth.FromConfig(authConfig)
   407  	if err == nil {
   408  		auth.SetMode(mode)
   409  	}
   410  	return err
   411  }
   412  
   413  // InstallHandlers creates and registers all the HTTP Handlers needed by config
   414  // into the provided HandlerInstaller.
   415  //
   416  // baseURL is required and specifies the root of this webserver, without trailing slash.
   417  // context may be nil (used and required by App Engine only)
   418  //
   419  // The returned shutdown value can be used to cleanly shut down the
   420  // handlers.
   421  func (config *Config) InstallHandlers(hi HandlerInstaller, baseURL string, reindex bool, context *http.Request) (shutdown io.Closer, err error) {
   422  	defer func() {
   423  		if e := recover(); e != nil {
   424  			log.Printf("Caught panic installer handlers: %v", e)
   425  			err = fmt.Errorf("Caught panic: %v", e)
   426  		}
   427  	}()
   428  
   429  	if err := config.checkValidAuth(); err != nil {
   430  		return nil, fmt.Errorf("error while configuring auth: %v", err)
   431  	}
   432  	prefixes := config.RequiredObject("prefixes")
   433  	if err := config.Validate(); err != nil {
   434  		return nil, fmt.Errorf("configuration error in root object's keys: %v", err)
   435  	}
   436  
   437  	if v := os.Getenv("CAMLI_PPROF_START"); v != "" {
   438  		cpuf := mustCreate(v + ".cpu")
   439  		defer cpuf.Close()
   440  		memf := mustCreate(v + ".mem")
   441  		defer memf.Close()
   442  		rpprof.StartCPUProfile(cpuf)
   443  		defer rpprof.StopCPUProfile()
   444  		defer rpprof.WriteHeapProfile(memf)
   445  	}
   446  
   447  	hl := &handlerLoader{
   448  		installer: hi,
   449  		baseURL:   baseURL,
   450  		config:    make(map[string]*handlerConfig),
   451  		handler:   make(map[string]interface{}),
   452  		context:   context,
   453  		reindex:   reindex,
   454  	}
   455  
   456  	for prefix, vei := range prefixes {
   457  		if !strings.HasPrefix(prefix, "/") {
   458  			exitFailure("prefix %q doesn't start with /", prefix)
   459  		}
   460  		if !strings.HasSuffix(prefix, "/") {
   461  			exitFailure("prefix %q doesn't end with /", prefix)
   462  		}
   463  		pmap, ok := vei.(map[string]interface{})
   464  		if !ok {
   465  			exitFailure("prefix %q value is a %T, not an object", prefix, vei)
   466  		}
   467  		pconf := jsonconfig.Obj(pmap)
   468  		enabled := pconf.OptionalBool("enabled", true)
   469  		if !enabled {
   470  			continue
   471  		}
   472  		handlerType := pconf.RequiredString("handler")
   473  		handlerArgs := pconf.OptionalObject("handlerArgs")
   474  		internal := pconf.OptionalBool("internal", false)
   475  		if err := pconf.Validate(); err != nil {
   476  			exitFailure("configuration error in prefix %s: %v", prefix, err)
   477  		}
   478  		h := &handlerConfig{
   479  			prefix:   prefix,
   480  			htype:    handlerType,
   481  			conf:     handlerArgs,
   482  			internal: internal,
   483  		}
   484  		hl.config[prefix] = h
   485  
   486  		if handlerType == "ui" {
   487  			config.UIPath = prefix
   488  		}
   489  	}
   490  	hl.setupAll()
   491  
   492  	// Now that everything is setup, run any handlers' InitHandler
   493  	// methods.
   494  	for pfx, handler := range hl.handler {
   495  		if in, ok := handler.(blobserver.HandlerIniter); ok {
   496  			if err := in.InitHandler(hl); err != nil {
   497  				return nil, fmt.Errorf("Error calling InitHandler on %s: %v", pfx, err)
   498  			}
   499  		}
   500  	}
   501  
   502  	if v, _ := strconv.ParseBool(os.Getenv("CAMLI_HTTP_EXPVAR")); v {
   503  		hi.Handle("/debug/vars", expvarHandler{})
   504  	}
   505  	if v, _ := strconv.ParseBool(os.Getenv("CAMLI_HTTP_PPROF")); v {
   506  		hi.Handle("/debug/pprof/", profileHandler{})
   507  	}
   508  	return multiCloser(hl.closers), nil
   509  }
   510  
   511  func mustCreate(path string) *os.File {
   512  	f, err := os.Create(path)
   513  	if err != nil {
   514  		log.Fatalf("Failed to create %s: %v", path, err)
   515  	}
   516  	return f
   517  }
   518  
   519  type multiCloser []io.Closer
   520  
   521  func (s multiCloser) Close() (err error) {
   522  	for _, cl := range s {
   523  		if err1 := cl.Close(); err == nil && err1 != nil {
   524  			err = err1
   525  		}
   526  	}
   527  	return
   528  }
   529  
   530  // expvarHandler publishes expvar stats.
   531  type expvarHandler struct{}
   532  
   533  func (expvarHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   534  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
   535  	fmt.Fprintf(w, "{\n")
   536  	first := true
   537  	expvar.Do(func(kv expvar.KeyValue) {
   538  		if !first {
   539  			fmt.Fprintf(w, ",\n")
   540  		}
   541  		first = false
   542  		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
   543  	})
   544  	fmt.Fprintf(w, "\n}\n")
   545  }
   546  
   547  // profileHandler publishes server profile information.
   548  type profileHandler struct{}
   549  
   550  func (profileHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
   551  	switch req.URL.Path {
   552  	case "/debug/pprof/cmdline":
   553  		pprof.Cmdline(rw, req)
   554  	case "/debug/pprof/profile":
   555  		pprof.Profile(rw, req)
   556  	case "/debug/pprof/symbol":
   557  		pprof.Symbol(rw, req)
   558  	default:
   559  		pprof.Index(rw, req)
   560  	}
   561  }