2011-03-30 00:42:49 +00:00
|
|
|
/*
|
|
|
|
Copyright 2011 Google Inc.
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2010-06-12 21:45:58 +00:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
2010-07-11 04:18:16 +00:00
|
|
|
import (
|
2011-04-02 05:14:23 +00:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"http"
|
|
|
|
"log"
|
2011-05-01 23:10:53 +00:00
|
|
|
"path/filepath"
|
2011-04-02 05:14:23 +00:00
|
|
|
"strings"
|
|
|
|
"os"
|
|
|
|
|
2010-11-29 04:06:22 +00:00
|
|
|
"camli/auth"
|
2011-02-03 23:45:35 +00:00
|
|
|
"camli/blobserver"
|
2011-04-02 05:14:23 +00:00
|
|
|
"camli/blobserver/handlers"
|
2011-04-16 05:25:45 +00:00
|
|
|
"camli/httputil"
|
|
|
|
"camli/jsonconfig"
|
2011-04-03 15:07:40 +00:00
|
|
|
"camli/osutil"
|
2011-04-16 05:25:45 +00:00
|
|
|
"camli/webserver"
|
2011-04-02 05:14:23 +00:00
|
|
|
|
|
|
|
// Storage options:
|
2011-06-04 04:52:56 +00:00
|
|
|
_ "camli/blobserver/cond"
|
2011-05-09 19:07:56 +00:00
|
|
|
_ "camli/blobserver/localdisk"
|
2011-05-21 20:40:17 +00:00
|
|
|
_ "camli/blobserver/remote"
|
2011-05-23 04:22:21 +00:00
|
|
|
_ "camli/blobserver/replica"
|
2011-04-02 03:45:40 +00:00
|
|
|
_ "camli/blobserver/s3"
|
2011-05-21 16:26:20 +00:00
|
|
|
_ "camli/blobserver/shard"
|
2011-05-01 23:10:53 +00:00
|
|
|
_ "camli/mysqlindexer" // indexer, but uses storage interface
|
2011-05-30 05:52:31 +00:00
|
|
|
// Handlers:
|
|
|
|
_ "camli/search"
|
2010-07-11 04:18:16 +00:00
|
|
|
)
|
2010-07-07 04:57:53 +00:00
|
|
|
|
2011-05-01 23:10:53 +00:00
|
|
|
var flagConfigFile = flag.String("configfile", "serverconfig",
|
|
|
|
"Config file to use, relative to camli config dir root, or blank to not use config files.")
|
2011-03-05 23:09:36 +00:00
|
|
|
|
2011-02-03 06:42:31 +00:00
|
|
|
const camliPrefix = "/camli/"
|
|
|
|
|
2011-04-16 05:25:45 +00:00
|
|
|
var ErrCamliPath = os.NewError("Invalid Camlistore request path")
|
2011-03-07 04:11:36 +00:00
|
|
|
|
2011-04-16 05:25:45 +00:00
|
|
|
func parseCamliPath(path string) (action string, err os.Error) {
|
2011-02-03 06:42:31 +00:00
|
|
|
camIdx := strings.Index(path, camliPrefix)
|
|
|
|
if camIdx == -1 {
|
2011-04-16 05:25:45 +00:00
|
|
|
return "", ErrCamliPath
|
2011-02-03 06:42:31 +00:00
|
|
|
}
|
|
|
|
action = path[camIdx+len(camliPrefix):]
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func unsupportedHandler(conn http.ResponseWriter, req *http.Request) {
|
2011-03-05 23:32:08 +00:00
|
|
|
httputil.BadRequestError(conn, "Unsupported camlistore path or method.")
|
2011-02-03 06:42:31 +00:00
|
|
|
}
|
2010-12-14 02:20:31 +00:00
|
|
|
|
2011-05-10 21:55:12 +00:00
|
|
|
type storageAndConfig struct {
|
|
|
|
blobserver.Storage
|
|
|
|
config *blobserver.Config
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *storageAndConfig) Config() *blobserver.Config {
|
|
|
|
return s.config
|
|
|
|
}
|
|
|
|
|
2011-04-03 15:07:40 +00:00
|
|
|
// where prefix is like "/" or "/s3/" for e.g. "/camli/" or "/s3/camli/*"
|
2011-04-16 05:25:45 +00:00
|
|
|
func makeCamliHandler(prefix, baseURL string, storage blobserver.Storage) http.Handler {
|
2011-05-10 21:55:12 +00:00
|
|
|
if !strings.HasSuffix(prefix, "/") {
|
|
|
|
panic("expected prefix to end in slash")
|
|
|
|
}
|
|
|
|
baseURL = strings.TrimRight(baseURL, "/")
|
|
|
|
|
|
|
|
storageConfig := &storageAndConfig{
|
|
|
|
storage,
|
|
|
|
&blobserver.Config{
|
|
|
|
Writable: true,
|
|
|
|
Readable: true,
|
|
|
|
IsQueue: false,
|
|
|
|
URLBase: baseURL + prefix[:len(prefix)-1],
|
|
|
|
},
|
|
|
|
}
|
2011-04-16 05:25:45 +00:00
|
|
|
return http.HandlerFunc(func(conn http.ResponseWriter, req *http.Request) {
|
|
|
|
action, err := parseCamliPath(req.URL.Path[len(prefix)-1:])
|
2011-04-03 15:07:40 +00:00
|
|
|
if err != nil {
|
2011-04-16 05:25:45 +00:00
|
|
|
log.Printf("Invalid request for method %q, path %q",
|
|
|
|
req.Method, req.URL.Path)
|
2011-04-03 15:07:40 +00:00
|
|
|
unsupportedHandler(conn, req)
|
|
|
|
return
|
|
|
|
}
|
2011-05-10 21:55:12 +00:00
|
|
|
handleCamliUsingStorage(conn, req, action, storageConfig)
|
2011-04-16 05:25:45 +00:00
|
|
|
})
|
2011-03-05 22:25:08 +00:00
|
|
|
}
|
2011-02-03 06:42:31 +00:00
|
|
|
|
2011-05-10 21:55:12 +00:00
|
|
|
func handleCamliUsingStorage(conn http.ResponseWriter, req *http.Request, action string, storage blobserver.StorageConfiger) {
|
2011-02-03 06:42:31 +00:00
|
|
|
handler := unsupportedHandler
|
2010-07-11 04:18:16 +00:00
|
|
|
switch req.Method {
|
|
|
|
case "GET":
|
2011-02-03 06:42:31 +00:00
|
|
|
switch action {
|
|
|
|
case "enumerate-blobs":
|
2011-05-09 16:11:18 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateEnumerateHandler(storage))
|
2011-02-08 16:24:16 +00:00
|
|
|
case "stat":
|
2011-05-09 16:11:18 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateStatHandler(storage))
|
2010-07-26 05:18:21 +00:00
|
|
|
default:
|
2011-02-04 01:28:05 +00:00
|
|
|
handler = handlers.CreateGetHandler(storage)
|
2010-07-26 05:18:21 +00:00
|
|
|
}
|
2010-07-11 04:18:16 +00:00
|
|
|
case "POST":
|
2011-02-03 06:42:31 +00:00
|
|
|
switch action {
|
2011-02-08 16:24:16 +00:00
|
|
|
case "stat":
|
2011-05-09 16:11:18 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateStatHandler(storage))
|
2011-02-03 06:42:31 +00:00
|
|
|
case "upload":
|
2011-05-09 16:11:18 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateUploadHandler(storage))
|
2011-02-03 06:42:31 +00:00
|
|
|
case "remove":
|
2011-05-09 16:11:18 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateRemoveHandler(storage))
|
2010-07-11 04:18:16 +00:00
|
|
|
}
|
|
|
|
case "PUT": // no longer part of spec
|
2011-05-09 16:11:18 +00:00
|
|
|
handler = auth.RequireAuth(handlers.CreateNonStandardPutHandler(storage))
|
2010-06-13 00:15:49 +00:00
|
|
|
}
|
2010-07-18 18:08:45 +00:00
|
|
|
handler(conn, req)
|
2010-06-12 21:45:58 +00:00
|
|
|
}
|
|
|
|
|
2011-02-04 22:31:23 +00:00
|
|
|
func exitFailure(pattern string, args ...interface{}) {
|
|
|
|
if !strings.HasSuffix(pattern, "\n") {
|
|
|
|
pattern = pattern + "\n"
|
|
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, pattern, args...)
|
|
|
|
os.Exit(1)
|
2010-06-12 21:45:58 +00:00
|
|
|
}
|
|
|
|
|
2011-05-09 21:20:19 +00:00
|
|
|
type handlerConfig struct {
|
|
|
|
prefix string // "/foo/"
|
|
|
|
htype string // "localdisk", etc
|
|
|
|
conf jsonconfig.Obj // never nil
|
|
|
|
|
|
|
|
settingUp, setupDone bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type handlerLoader struct {
|
|
|
|
ws *webserver.Server
|
|
|
|
baseURL string
|
|
|
|
config map[string]*handlerConfig // prefix -> config
|
2011-05-21 20:40:17 +00:00
|
|
|
handler map[string]interface{} // prefix -> http.Handler / func / blobserver.Storage
|
2011-05-09 21:20:19 +00:00
|
|
|
}
|
|
|
|
|
2010-06-12 21:45:58 +00:00
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
|
2011-05-01 23:10:53 +00:00
|
|
|
configPath := *flagConfigFile
|
|
|
|
if !filepath.IsAbs(configPath) {
|
|
|
|
configPath = filepath.Join(osutil.CamliConfigDir(), configPath)
|
|
|
|
}
|
2011-06-24 20:02:51 +00:00
|
|
|
|
|
|
|
config, err := jsonconfig.ReadFile(configPath)
|
2011-04-03 15:07:40 +00:00
|
|
|
if err != nil {
|
2011-06-24 20:02:51 +00:00
|
|
|
exitFailure("%v", err)
|
2011-05-01 23:10:53 +00:00
|
|
|
}
|
2011-04-03 15:07:40 +00:00
|
|
|
|
2011-04-04 02:58:20 +00:00
|
|
|
ws := webserver.New()
|
|
|
|
baseURL := ws.BaseURL()
|
|
|
|
|
2011-05-09 18:49:02 +00:00
|
|
|
{
|
|
|
|
cert, key := config.OptionalString("TLSCertFile", ""), config.OptionalString("TLSKeyFile", "")
|
|
|
|
if (cert != "") != (key != "") {
|
|
|
|
exitFailure("TLSCertFile and TLSKeyFile must both be either present or absent")
|
|
|
|
}
|
|
|
|
if cert != "" {
|
|
|
|
ws.SetTLS(cert, key)
|
|
|
|
}
|
2011-04-04 02:38:22 +00:00
|
|
|
}
|
|
|
|
|
2011-05-09 18:49:02 +00:00
|
|
|
auth.AccessPassword = config.OptionalString("password", "")
|
|
|
|
if url := config.OptionalString("baseURL", ""); url != "" {
|
2011-04-04 02:58:20 +00:00
|
|
|
baseURL = url
|
|
|
|
}
|
2011-05-09 18:49:02 +00:00
|
|
|
prefixes := config.RequiredObject("prefixes")
|
2011-06-24 20:02:51 +00:00
|
|
|
if err = config.Validate(); err != nil {
|
2011-05-09 18:49:02 +00:00
|
|
|
exitFailure("configuration error in root object's keys in %s: %v", configPath, err)
|
2011-04-03 15:07:40 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
|
|
|
|
hl := &handlerLoader{
|
|
|
|
ws: ws,
|
|
|
|
baseURL: baseURL,
|
|
|
|
config: make(map[string]*handlerConfig),
|
|
|
|
handler: make(map[string]interface{}),
|
|
|
|
}
|
2011-04-16 05:25:45 +00:00
|
|
|
|
2011-04-03 15:07:40 +00:00
|
|
|
for prefix, vei := range prefixes {
|
|
|
|
if !strings.HasPrefix(prefix, "/") {
|
|
|
|
exitFailure("prefix %q doesn't start with /", prefix)
|
|
|
|
}
|
|
|
|
if !strings.HasSuffix(prefix, "/") {
|
|
|
|
exitFailure("prefix %q doesn't end with /", prefix)
|
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
pmap, ok := vei.(map[string]interface{})
|
2011-04-03 15:07:40 +00:00
|
|
|
if !ok {
|
2011-07-02 16:09:50 +00:00
|
|
|
exitFailure("prefix %q value is a %T, not an object", prefix, vei)
|
2011-04-03 15:07:40 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
pconf := jsonconfig.Obj(pmap)
|
2011-07-08 16:08:41 +00:00
|
|
|
enabled := pconf.OptionalBool("enabled", true)
|
|
|
|
if !enabled {
|
|
|
|
continue
|
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
handlerType := pconf.RequiredString("handler")
|
|
|
|
handlerArgs := pconf.OptionalObject("handlerArgs")
|
|
|
|
if err := pconf.Validate(); err != nil {
|
|
|
|
exitFailure("configuration error in prefix %s: %v", prefix, err)
|
2011-04-03 15:07:40 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
h := &handlerConfig{
|
|
|
|
prefix: prefix,
|
|
|
|
htype: handlerType,
|
|
|
|
conf: handlerArgs,
|
2011-04-03 15:07:40 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
hl.config[prefix] = h
|
|
|
|
}
|
|
|
|
hl.setupAll()
|
|
|
|
ws.Serve()
|
|
|
|
}
|
|
|
|
|
2011-07-08 22:05:46 +00:00
|
|
|
func (hl *handlerLoader) FindHandlerByTypeIfLoaded(htype string) (prefix string, handler interface{}, err os.Error) {
|
|
|
|
for prefix, config := range hl.config {
|
|
|
|
if config.htype == htype {
|
|
|
|
return prefix, hl.handler[prefix], nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", nil, os.ENOENT
|
|
|
|
}
|
|
|
|
|
2011-05-09 21:20:19 +00:00
|
|
|
func (hl *handlerLoader) setupAll() {
|
|
|
|
for prefix := range hl.config {
|
|
|
|
hl.setupHandler(prefix)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hl *handlerLoader) configType(prefix string) string {
|
|
|
|
if h, ok := hl.config[prefix]; ok {
|
|
|
|
return h.htype
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hl *handlerLoader) getOrSetup(prefix string) interface{} {
|
|
|
|
hl.setupHandler(prefix)
|
|
|
|
return hl.handler[prefix]
|
|
|
|
}
|
|
|
|
|
2011-05-21 20:40:17 +00:00
|
|
|
func (hl *handlerLoader) GetStorage(prefix string) (blobserver.Storage, os.Error) {
|
|
|
|
hl.setupHandler(prefix)
|
|
|
|
if s, ok := hl.handler[prefix].(blobserver.Storage); ok {
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("bogus storage handler referenced as %q", prefix)
|
|
|
|
}
|
|
|
|
|
2011-05-30 05:52:31 +00:00
|
|
|
func (hl *handlerLoader) GetHandler(prefix string) (interface{}, os.Error) {
|
2011-05-26 14:34:39 +00:00
|
|
|
hl.setupHandler(prefix)
|
2011-05-30 05:52:31 +00:00
|
|
|
if s, ok := hl.handler[prefix].(blobserver.Storage); ok {
|
|
|
|
return s, nil
|
|
|
|
}
|
2011-05-26 14:34:39 +00:00
|
|
|
if h, ok := hl.handler[prefix].(http.Handler); ok {
|
|
|
|
return h, nil
|
|
|
|
}
|
2011-05-30 05:52:31 +00:00
|
|
|
return nil, fmt.Errorf("bogus http or storage handler referenced as %q", prefix)
|
2011-05-26 14:34:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (hl *handlerLoader) GetHandlerType(prefix string) string {
|
|
|
|
hl.setupHandler(prefix)
|
|
|
|
return hl.configType(prefix)
|
|
|
|
}
|
|
|
|
|
2011-05-09 21:20:19 +00:00
|
|
|
func (hl *handlerLoader) setupHandler(prefix string) {
|
|
|
|
h, ok := hl.config[prefix]
|
|
|
|
if !ok {
|
2011-05-30 06:01:29 +00:00
|
|
|
exitFailure("invalid reference to undefined handler %q", prefix)
|
2011-05-09 21:20:19 +00:00
|
|
|
}
|
|
|
|
if h.setupDone {
|
|
|
|
// Already setup by something else reference it and forcing it to be
|
|
|
|
// setup before the bottom loop got to it.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if h.settingUp {
|
|
|
|
exitFailure("loop in configuration graph; %q tried to load itself indirectly", prefix)
|
|
|
|
}
|
|
|
|
h.settingUp = true
|
|
|
|
defer func() {
|
|
|
|
h.setupDone = true
|
|
|
|
if hl.handler[prefix] == nil {
|
|
|
|
panic(fmt.Sprintf("setupHandler for %q didn't install a handler", prefix))
|
2011-04-16 22:44:22 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
}()
|
2011-05-30 05:28:17 +00:00
|
|
|
|
|
|
|
if strings.HasPrefix(h.htype, "storage-") {
|
|
|
|
stype := h.htype[len("storage-"):]
|
|
|
|
// Assume a storage interface
|
|
|
|
pstorage, err := blobserver.CreateStorage(stype, hl, h.conf)
|
|
|
|
if err != nil {
|
|
|
|
exitFailure("error instantiating storage for prefix %q, type %q: %v",
|
|
|
|
h.prefix, stype, err)
|
|
|
|
}
|
|
|
|
hl.handler[h.prefix] = pstorage
|
|
|
|
hl.ws.Handle(prefix+"camli/", makeCamliHandler(prefix, hl.baseURL, pstorage))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-05-30 06:01:29 +00:00
|
|
|
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)
|
2011-04-03 15:07:40 +00:00
|
|
|
}
|
2011-05-30 06:01:29 +00:00
|
|
|
hl.handler[prefix] = hh
|
|
|
|
hl.ws.Handle(prefix, &httputil.PrefixHandler{prefix, hh})
|
2011-04-02 05:14:23 +00:00
|
|
|
}
|