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"
|
2011-04-03 15:07:40 +00:00
|
|
|
"json"
|
2011-04-02 05:14:23 +00:00
|
|
|
"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-03-16 06:16:24 +00:00
|
|
|
"camli/blobref"
|
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/errorutil"
|
|
|
|
"camli/httputil"
|
|
|
|
"camli/jsonconfig"
|
2011-04-03 15:07:40 +00:00
|
|
|
"camli/osutil"
|
2011-04-02 05:14:23 +00:00
|
|
|
"camli/search"
|
2011-04-16 05:25:45 +00:00
|
|
|
"camli/webserver"
|
2011-04-02 05:14:23 +00:00
|
|
|
|
|
|
|
// Storage options:
|
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
|
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)
|
|
|
|
}
|
|
|
|
f, err := os.Open(configPath)
|
2011-04-03 15:07:40 +00:00
|
|
|
if err != nil {
|
2011-05-01 23:10:53 +00:00
|
|
|
exitFailure("error opening %s: %v", configPath, err)
|
2011-04-03 15:07:40 +00:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
dj := json.NewDecoder(f)
|
2011-05-09 18:49:02 +00:00
|
|
|
rootjson := make(map[string]interface{})
|
|
|
|
if err = dj.Decode(&rootjson); err != nil {
|
2011-04-16 00:17:33 +00:00
|
|
|
extra := ""
|
|
|
|
if serr, ok := err.(*json.SyntaxError); ok {
|
|
|
|
if _, serr := f.Seek(0, os.SEEK_SET); serr != nil {
|
|
|
|
log.Fatalf("seek error: %v", serr)
|
|
|
|
}
|
|
|
|
line, col, highlight := errorutil.HighlightBytePosition(f, serr.Offset)
|
|
|
|
extra = fmt.Sprintf(":\nError at line %d, column %d (file offset %d):\n%s",
|
|
|
|
line, col, serr.Offset, highlight)
|
|
|
|
}
|
|
|
|
exitFailure("error parsing JSON object in config file %s%s\n%v",
|
|
|
|
osutil.UserServerConfigPath(), extra, err)
|
2011-04-03 15:07:40 +00:00
|
|
|
}
|
2011-05-09 18:49:02 +00:00
|
|
|
if err := jsonconfig.EvaluateExpressions(rootjson); err != nil {
|
2011-05-01 23:10:53 +00:00
|
|
|
exitFailure("error expanding JSON config expressions in %s: %v", configPath, err)
|
|
|
|
}
|
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
|
|
|
// Root configuration
|
|
|
|
config := jsonconfig.Obj(rootjson)
|
|
|
|
|
|
|
|
{
|
|
|
|
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")
|
|
|
|
if err := config.Validate(); err != nil {
|
|
|
|
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 {
|
|
|
|
exitFailure("prefix %q value isn't an object", prefix)
|
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
pconf := jsonconfig.Obj(pmap)
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
|
|
|
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-26 14:34:39 +00:00
|
|
|
func (hl *handlerLoader) GetHandler(prefix string) (http.Handler, os.Error) {
|
|
|
|
hl.setupHandler(prefix)
|
|
|
|
if h, ok := hl.handler[prefix].(http.Handler); ok {
|
|
|
|
return h, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("bogus http handler referenced as %q", prefix)
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
exitFailure("invalid reference to non-existant handler %q", prefix)
|
|
|
|
}
|
|
|
|
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-09 21:20:19 +00:00
|
|
|
checkConfig := func() {
|
|
|
|
if err := h.conf.Validate(); err != nil {
|
|
|
|
exitFailure("configuration error in \"handlerArgs\" for prefix %s: %v", prefix, err)
|
2011-04-16 22:44:22 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
}
|
|
|
|
switch h.htype {
|
2011-05-26 14:34:39 +00:00
|
|
|
case "search": // TODO: use blobserver registry
|
2011-05-09 21:20:19 +00:00
|
|
|
indexPrefix := h.conf.RequiredString("index") // TODO: add optional help tips here?
|
|
|
|
ownerBlobStr := h.conf.RequiredString("owner")
|
|
|
|
checkConfig()
|
|
|
|
indexer, ok := hl.getOrSetup(indexPrefix).(search.Index)
|
|
|
|
if !ok {
|
|
|
|
exitFailure("prefix %q references invalid indexer %q", prefix, indexPrefix)
|
|
|
|
}
|
|
|
|
ownerBlobRef := blobref.Parse(ownerBlobStr)
|
|
|
|
if ownerBlobRef == nil {
|
|
|
|
exitFailure("prefix %q references has malformed blobref %q; expecting e.g. sha1-xxxxxxxxxxxx",
|
|
|
|
prefix, ownerBlobStr)
|
2011-04-16 05:25:45 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
searchh := auth.RequireAuth(search.CreateHandler(indexer, ownerBlobRef))
|
|
|
|
hl.handler[h.prefix] = searchh
|
|
|
|
hl.ws.HandleFunc(prefix+"camli/", searchh)
|
2011-05-26 14:34:39 +00:00
|
|
|
case "sync": // TODO: use blobserver registry
|
2011-05-09 21:20:19 +00:00
|
|
|
from := h.conf.RequiredString("from")
|
|
|
|
to := h.conf.RequiredString("to")
|
|
|
|
checkConfig()
|
|
|
|
getBlobServer := func(bsPrefix string) blobserver.Storage {
|
|
|
|
bs, ok := hl.getOrSetup(bsPrefix).(blobserver.Storage)
|
2011-04-16 05:25:45 +00:00
|
|
|
if !ok {
|
2011-05-09 21:20:19 +00:00
|
|
|
exitFailure("sync prefix %q references %q, of type %T, but expected a blob server",
|
|
|
|
prefix, bsPrefix, h)
|
2011-04-16 05:25:45 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
return bs
|
2011-04-16 05:25:45 +00:00
|
|
|
}
|
2011-05-09 21:20:19 +00:00
|
|
|
fromBs, toBs := getBlobServer(from), getBlobServer(to)
|
|
|
|
synch, err := createSyncHandler(from, to, fromBs, toBs)
|
|
|
|
if err != nil {
|
|
|
|
exitFailure(err.String())
|
|
|
|
}
|
|
|
|
hl.handler[h.prefix] = synch
|
|
|
|
hl.ws.Handle(prefix, synch)
|
|
|
|
default:
|
2011-05-30 05:28:17 +00:00
|
|
|
hh, err := blobserver.CreateHandler(h.htype, hl, h.conf)
|
2011-05-09 21:20:19 +00:00
|
|
|
if err != nil {
|
2011-05-30 05:28:17 +00:00
|
|
|
exitFailure("error instantiating handler for prefix %q, type %q: %v",
|
2011-05-09 21:20:19 +00:00
|
|
|
h.prefix, h.htype, err)
|
|
|
|
}
|
2011-05-30 05:28:17 +00:00
|
|
|
hl.handler[prefix] = hh
|
|
|
|
hl.ws.Handle(prefix, &httputil.PrefixHandler{prefix, hh})
|
2011-04-03 15:07:40 +00:00
|
|
|
}
|
2011-04-02 05:14:23 +00:00
|
|
|
}
|