summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/golang/groupcache/http.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/golang/groupcache/http.go')
-rw-r--r--vendor/github.com/golang/groupcache/http.go227
1 files changed, 227 insertions, 0 deletions
diff --git a/vendor/github.com/golang/groupcache/http.go b/vendor/github.com/golang/groupcache/http.go
new file mode 100644
index 000000000..14eb345a8
--- /dev/null
+++ b/vendor/github.com/golang/groupcache/http.go
@@ -0,0 +1,227 @@
+/*
+Copyright 2013 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.
+*/
+
+package groupcache
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+ "sync"
+
+ "github.com/golang/groupcache/consistenthash"
+ pb "github.com/golang/groupcache/groupcachepb"
+ "github.com/golang/protobuf/proto"
+)
+
+const defaultBasePath = "/_groupcache/"
+
+const defaultReplicas = 50
+
+// HTTPPool implements PeerPicker for a pool of HTTP peers.
+type HTTPPool struct {
+ // Context optionally specifies a context for the server to use when it
+ // receives a request.
+ // If nil, the server uses a nil Context.
+ Context func(*http.Request) Context
+
+ // Transport optionally specifies an http.RoundTripper for the client
+ // to use when it makes a request.
+ // If nil, the client uses http.DefaultTransport.
+ Transport func(Context) http.RoundTripper
+
+ // this peer's base URL, e.g. "https://example.net:8000"
+ self string
+
+ // opts specifies the options.
+ opts HTTPPoolOptions
+
+ mu sync.Mutex // guards peers and httpGetters
+ peers *consistenthash.Map
+ httpGetters map[string]*httpGetter // keyed by e.g. "http://10.0.0.2:8008"
+}
+
+// HTTPPoolOptions are the configurations of a HTTPPool.
+type HTTPPoolOptions struct {
+ // BasePath specifies the HTTP path that will serve groupcache requests.
+ // If blank, it defaults to "/_groupcache/".
+ BasePath string
+
+ // Replicas specifies the number of key replicas on the consistent hash.
+ // If blank, it defaults to 50.
+ Replicas int
+
+ // HashFn specifies the hash function of the consistent hash.
+ // If blank, it defaults to crc32.ChecksumIEEE.
+ HashFn consistenthash.Hash
+}
+
+// NewHTTPPool initializes an HTTP pool of peers, and registers itself as a PeerPicker.
+// For convenience, it also registers itself as an http.Handler with http.DefaultServeMux.
+// The self argument be a valid base URL that points to the current server,
+// for example "http://example.net:8000".
+func NewHTTPPool(self string) *HTTPPool {
+ p := NewHTTPPoolOpts(self, nil)
+ http.Handle(p.opts.BasePath, p)
+ return p
+}
+
+var httpPoolMade bool
+
+// NewHTTPPoolOpts initializes an HTTP pool of peers with the given options.
+// Unlike NewHTTPPool, this function does not register the created pool as an HTTP handler.
+// The returned *HTTPPool implements http.Handler and must be registered using http.Handle.
+func NewHTTPPoolOpts(self string, o *HTTPPoolOptions) *HTTPPool {
+ if httpPoolMade {
+ panic("groupcache: NewHTTPPool must be called only once")
+ }
+ httpPoolMade = true
+
+ p := &HTTPPool{
+ self: self,
+ httpGetters: make(map[string]*httpGetter),
+ }
+ if o != nil {
+ p.opts = *o
+ }
+ if p.opts.BasePath == "" {
+ p.opts.BasePath = defaultBasePath
+ }
+ if p.opts.Replicas == 0 {
+ p.opts.Replicas = defaultReplicas
+ }
+ p.peers = consistenthash.New(p.opts.Replicas, p.opts.HashFn)
+
+ RegisterPeerPicker(func() PeerPicker { return p })
+ return p
+}
+
+// Set updates the pool's list of peers.
+// Each peer value should be a valid base URL,
+// for example "http://example.net:8000".
+func (p *HTTPPool) Set(peers ...string) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ p.peers = consistenthash.New(p.opts.Replicas, p.opts.HashFn)
+ p.peers.Add(peers...)
+ p.httpGetters = make(map[string]*httpGetter, len(peers))
+ for _, peer := range peers {
+ p.httpGetters[peer] = &httpGetter{transport: p.Transport, baseURL: peer + p.opts.BasePath}
+ }
+}
+
+func (p *HTTPPool) PickPeer(key string) (ProtoGetter, bool) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ if p.peers.IsEmpty() {
+ return nil, false
+ }
+ if peer := p.peers.Get(key); peer != p.self {
+ return p.httpGetters[peer], true
+ }
+ return nil, false
+}
+
+func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ // Parse request.
+ if !strings.HasPrefix(r.URL.Path, p.opts.BasePath) {
+ panic("HTTPPool serving unexpected path: " + r.URL.Path)
+ }
+ parts := strings.SplitN(r.URL.Path[len(p.opts.BasePath):], "/", 2)
+ if len(parts) != 2 {
+ http.Error(w, "bad request", http.StatusBadRequest)
+ return
+ }
+ groupName := parts[0]
+ key := parts[1]
+
+ // Fetch the value for this group/key.
+ group := GetGroup(groupName)
+ if group == nil {
+ http.Error(w, "no such group: "+groupName, http.StatusNotFound)
+ return
+ }
+ var ctx Context
+ if p.Context != nil {
+ ctx = p.Context(r)
+ }
+
+ group.Stats.ServerRequests.Add(1)
+ var value []byte
+ err := group.Get(ctx, key, AllocatingByteSliceSink(&value))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // Write the value to the response body as a proto message.
+ body, err := proto.Marshal(&pb.GetResponse{Value: value})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/x-protobuf")
+ w.Write(body)
+}
+
+type httpGetter struct {
+ transport func(Context) http.RoundTripper
+ baseURL string
+}
+
+var bufferPool = sync.Pool{
+ New: func() interface{} { return new(bytes.Buffer) },
+}
+
+func (h *httpGetter) Get(context Context, in *pb.GetRequest, out *pb.GetResponse) error {
+ u := fmt.Sprintf(
+ "%v%v/%v",
+ h.baseURL,
+ url.QueryEscape(in.GetGroup()),
+ url.QueryEscape(in.GetKey()),
+ )
+ req, err := http.NewRequest("GET", u, nil)
+ if err != nil {
+ return err
+ }
+ tr := http.DefaultTransport
+ if h.transport != nil {
+ tr = h.transport(context)
+ }
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ return err
+ }
+ defer res.Body.Close()
+ if res.StatusCode != http.StatusOK {
+ return fmt.Errorf("server returned: %v", res.Status)
+ }
+ b := bufferPool.Get().(*bytes.Buffer)
+ b.Reset()
+ defer bufferPool.Put(b)
+ _, err = io.Copy(b, res.Body)
+ if err != nil {
+ return fmt.Errorf("reading response body: %v", err)
+ }
+ err = proto.Unmarshal(b.Bytes(), out)
+ if err != nil {
+ return fmt.Errorf("decoding response body: %v", err)
+ }
+ return nil
+}