mirror of https://github.com/docker/docs.git
200 lines
6.3 KiB
Go
200 lines
6.3 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
ctxu "github.com/docker/distribution/context"
|
|
"github.com/docker/distribution/registry/api/errcode"
|
|
"github.com/docker/distribution/registry/api/v2"
|
|
"github.com/docker/distribution/registry/auth"
|
|
"github.com/docker/notary/tuf/signed"
|
|
"github.com/gorilla/mux"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// contextHandler defines an alterate HTTP handler interface which takes in
|
|
// a context for authorization and returns an HTTP application error.
|
|
type contextHandler func(ctx context.Context, w http.ResponseWriter, r *http.Request) error
|
|
|
|
// rootHandler is an implementation of an HTTP request handler which handles
|
|
// authorization and calling out to the defined alternate http handler.
|
|
type rootHandler struct {
|
|
handler contextHandler
|
|
auth auth.AccessController
|
|
actions []string
|
|
context context.Context
|
|
trust signed.CryptoService
|
|
//cachePool redis.Pool
|
|
}
|
|
|
|
// RootHandlerFactory creates a new rootHandler factory using the given
|
|
// Context creator and authorizer. The returned factory allows creating
|
|
// new rootHandlers from the alternate http handler contextHandler and
|
|
// a scope.
|
|
func RootHandlerFactory(auth auth.AccessController, ctx context.Context, trust signed.CryptoService) func(contextHandler, ...string) *rootHandler {
|
|
return func(handler contextHandler, actions ...string) *rootHandler {
|
|
return &rootHandler{
|
|
handler: handler,
|
|
auth: auth,
|
|
actions: actions,
|
|
context: ctx,
|
|
trust: trust,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ServeHTTP serves an HTTP request and implements the http.Handler interface.
|
|
func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
ctx := ctxu.WithRequest(root.context, r)
|
|
ctx, w = ctxu.WithResponseWriter(ctx, w)
|
|
ctx = ctxu.WithLogger(ctx, ctxu.GetRequestLogger(ctx))
|
|
ctx = context.WithValue(ctx, "repo", vars["imageName"])
|
|
ctx = context.WithValue(ctx, "cryptoService", root.trust)
|
|
|
|
defer func() {
|
|
ctxu.GetResponseLogger(ctx).Info("response completed")
|
|
}()
|
|
|
|
if root.auth != nil {
|
|
access := buildAccessRecords(vars["imageName"], root.actions...)
|
|
var authCtx context.Context
|
|
var err error
|
|
if authCtx, err = root.auth.Authorized(ctx, access...); err != nil {
|
|
if challenge, ok := err.(auth.Challenge); ok {
|
|
// Let the challenge write the response.
|
|
challenge.ServeHTTP(w, r)
|
|
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
errcode.ServeJSON(w, v2.ErrorCodeUnauthorized)
|
|
return
|
|
}
|
|
ctx = authCtx
|
|
}
|
|
if err := root.handler(ctx, w, r); err != nil {
|
|
e := errcode.ServeJSON(w, err)
|
|
if e != nil {
|
|
logrus.Error(e)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
func buildAccessRecords(repo string, actions ...string) []auth.Access {
|
|
requiredAccess := make([]auth.Access, 0, len(actions))
|
|
for _, action := range actions {
|
|
requiredAccess = append(requiredAccess, auth.Access{
|
|
Resource: auth.Resource{
|
|
Type: "repository",
|
|
Name: repo,
|
|
},
|
|
Action: action,
|
|
})
|
|
}
|
|
return requiredAccess
|
|
}
|
|
|
|
// CacheControlConfig is an interface for something that knows how to set cache
|
|
// control headers
|
|
type CacheControlConfig interface {
|
|
// SetHeaders will actually set the cache control headers on a Headers object
|
|
SetHeaders(headers http.Header)
|
|
}
|
|
|
|
// NewCacheControlConfig returns CacheControlConfig interface for either setting
|
|
// cache control or disabling cache control entirely
|
|
func NewCacheControlConfig(maxAgeInSeconds int, mustRevalidate bool) CacheControlConfig {
|
|
if maxAgeInSeconds > 0 {
|
|
return PublicCacheControl{MustReValidate: mustRevalidate, MaxAgeInSeconds: maxAgeInSeconds}
|
|
}
|
|
return NoCacheControl{}
|
|
}
|
|
|
|
// PublicCacheControl is a set of options that we will set to enable cache control
|
|
type PublicCacheControl struct {
|
|
MustReValidate bool
|
|
MaxAgeInSeconds int
|
|
}
|
|
|
|
// SetHeaders sets the public headers with an optional must-revalidate header
|
|
func (p PublicCacheControl) SetHeaders(headers http.Header) {
|
|
cacheControlValue := fmt.Sprintf("public, max-age=%v, s-maxage=%v",
|
|
p.MaxAgeInSeconds, p.MaxAgeInSeconds)
|
|
|
|
if p.MustReValidate {
|
|
cacheControlValue = fmt.Sprintf("%s, must-revalidate", cacheControlValue)
|
|
}
|
|
headers.Set("Cache-Control", cacheControlValue)
|
|
// delete the Pragma directive, because the only valid value in HTTP is
|
|
// "no-cache"
|
|
headers.Del("Pragma")
|
|
if headers.Get("Last-Modified") == "" {
|
|
SetLastModifiedHeader(headers, time.Time{})
|
|
}
|
|
}
|
|
|
|
// NoCacheControl is an object which represents a directive to cache nothing
|
|
type NoCacheControl struct{}
|
|
|
|
// SetHeaders sets the public headers cache-control headers and pragma to no-cache
|
|
func (n NoCacheControl) SetHeaders(headers http.Header) {
|
|
headers.Set("Cache-Control", "max-age=0, no-cache, no-store")
|
|
headers.Set("Pragma", "no-cache")
|
|
}
|
|
|
|
// cacheControlResponseWriter wraps an existing response writer, and if Write is
|
|
// called, will try to set the cache control headers if it can
|
|
type cacheControlResponseWriter struct {
|
|
http.ResponseWriter
|
|
config CacheControlConfig
|
|
statusCode int
|
|
}
|
|
|
|
// WriteHeader stores the header before writing it, so we can tell if it's been set
|
|
// to a non-200 status code
|
|
func (c *cacheControlResponseWriter) WriteHeader(statusCode int) {
|
|
c.statusCode = statusCode
|
|
c.ResponseWriter.WriteHeader(statusCode)
|
|
}
|
|
|
|
// Write will set the cache headers if they haven't already been set and if the status
|
|
// code has either not been set or set to 200
|
|
func (c *cacheControlResponseWriter) Write(data []byte) (int, error) {
|
|
if c.statusCode == http.StatusOK || c.statusCode == 0 {
|
|
headers := c.ResponseWriter.Header()
|
|
if headers.Get("Cache-Control") == "" {
|
|
c.config.SetHeaders(headers)
|
|
}
|
|
}
|
|
return c.ResponseWriter.Write(data)
|
|
}
|
|
|
|
type cacheControlHandler struct {
|
|
http.Handler
|
|
config CacheControlConfig
|
|
}
|
|
|
|
func (c cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
c.Handler.ServeHTTP(&cacheControlResponseWriter{ResponseWriter: w, config: c.config}, r)
|
|
}
|
|
|
|
// WrapWithCacheHandler wraps another handler in one that can add cache control headers
|
|
// given a 200 response
|
|
func WrapWithCacheHandler(ccc CacheControlConfig, handler http.Handler) http.Handler {
|
|
if ccc != nil {
|
|
return cacheControlHandler{Handler: handler, config: ccc}
|
|
}
|
|
return handler
|
|
}
|
|
|
|
// SetLastModifiedHeader takes a time and uses it to set the LastModified header using
|
|
// the right date format
|
|
func SetLastModifiedHeader(headers http.Header, lmt time.Time) {
|
|
headers.Set("Last-Modified", lmt.Format(time.RFC1123))
|
|
}
|