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 }