func (s *Storage) CacheKey(bucket, name string) string {
	return fmt.Sprintf("%s/%s", s.Base, path.Join(bucket, name))
}
CacheKey returns a key to cache an object under, computed from
s.Base, bucket and then name.
package weasel
import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"path"
	"path/filepath"
	"strings"
	"time"
	"github.com/google/weasel/internal"
	"golang.org/x/oauth2"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/memcache"
	"google.golang.org/appengine/urlfetch"
)
const scopeStorageRead = "https://www.googleapis.com/auth/devstorage.read_only"
var DefaultStorage = &Storage{
	Base:  "https://storage.googleapis.com",
	Index: "index.html",
	CORS: CORS{
		Origin: []string{"*"},
		MaxAge: "86400",
	},
}
type CORS struct {
	Origin []string  
	MaxAge string    
}
type Storage struct {
	Base  string  
	Index string  
	CORS  CORS
}
func (s *Storage) OpenFile(ctx context.Context, bucket, name string) (*Object, error) {
	if name == "" || strings.HasSuffix(name, "/") {
		name += s.Index
	}
	checkStat := !strings.HasSuffix(name, s.Index) && filepath.Ext(name) == ""
	type stat struct {
		o   *Object
		err error
	}
	var ch chan *stat
	if checkStat {
		ch = make(chan *stat, 1)
		go func() {
			o, err := s.Stat(ctx, bucket, path.Join(name, s.Index))
			ch <- &stat{o, err}
			close(ch)
		}()
	}
	o, err := s.Open(ctx, bucket, name)
	if err == nil || !checkStat {
		return o, err
	}
	if ferr, ok := err.(*FetchError); ok && ferr.Code != 404 && ferr.Code != 403 {
		return nil, err
	}
	select {
	case <-time.After(5 * time.Second):
		log.Errorf(ctx, "s.Stat(bucket=%q) timeout", bucket)
		return nil, err
	case res := <-ch:
		if res.err != nil {
			return nil, err
		}
		o = res.o
	}
	if o.Redirect() == "" {
		o = &Object{
			Body: ioutil.NopCloser(bytes.NewReader(nil)),
			Meta: map[string]string{
				metaRedirect: path.Join("/", name) + "/",
			},
		}
	}
	return o, nil
}
func (s *Storage) Open(ctx context.Context, bucket, name string) (*Object, error) {
	key := s.CacheKey(bucket, name)
	o, err := getCache(ctx, key)
	if err != nil {
		u := fmt.Sprintf("%s/%s", s.Base, path.Join(bucket, name))
		o, err = fetch(ctx, u, key)
	}
	return o, err
}
func (s *Storage) Stat(ctx context.Context, bucket, name string) (*Object, error) {
	if o, err := getCache(ctx, s.CacheKey(bucket, name)); err == nil {
		return o, nil
	}
	u := fmt.Sprintf("%s/%s", s.Base, path.Join(bucket, name))
	req, err := http.NewRequest("HEAD", u, nil)
	if err != nil {
		return nil, err
	}
	res, err := httpClient(ctx, scopeStorageRead).Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		b, _ := ioutil.ReadAll(res.Body)
		return nil, &FetchError{
			Msg:  fmt.Sprintf("%s: %s", res.Status, b),
			Code: res.StatusCode,
		}
	}
	meta := make(map[string]string)
	for _, k := range objectHeaders {
		if v := res.Header.Get(k); v != "" {
			meta[k] = v
		}
	}
	return &Object{Meta: meta}, nil
}
func (s *Storage) PurgeCache(ctx context.Context, bucket, name string) error {
	return purgeCache(ctx, s.CacheKey(bucket, name))
}