distribution/app.go
Stephen J Day 22c9f45598 Carve out initial application structure
This changeset defines the application structure to be used for the http side
of the new registry. The main components are the App and Context structs. The
App context is instance global and manages global configuration and resources.
Context contains request-specific resources that may be created as a by-product
of an in-flight request.

To latently construct per-request handlers and leverage gorilla/mux, a dispatch
structure has been propped up next to the main handler flow. Without this, a
router and all handlers need to be constructed on every request. By
constructing handlers on each request, we ensure thread isolation and can
carefully control the security context of in-flight requests. There are unit
tests covering this functionality.
2014-11-10 19:03:49 -08:00

95 lines
3.0 KiB
Go

package registry
import (
"net/http"
"github.com/docker/docker-registry/configuration"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
)
// App is a global registry application object. Shared resources can be placed
// on this object that will be accessible from all requests. Any writable
// fields should be protected.
type App struct {
Config configuration.Configuration
router *mux.Router
}
// NewApp takes a configuration and returns a configured app, ready to serve
// requests. The app only implements ServeHTTP and can be wrapped in other
// handlers accordingly.
func NewApp(configuration configuration.Configuration) *App {
app := &App{
Config: configuration,
router: v2APIRouter(),
}
// Register the handler dispatchers.
app.register(routeNameImageManifest, imageManifestDispatcher)
app.register(routeNameLayer, layerDispatcher)
app.register(routeNameTags, tagsDispatcher)
app.register(routeNameLayerUpload, layerUploadDispatcher)
app.register(routeNameLayerUploadResume, layerUploadDispatcher)
return app
}
func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
app.router.ServeHTTP(w, r)
}
// register a handler with the application, by route name. The handler will be
// passed through the application filters and context will be constructed at
// request time.
func (app *App) register(routeName string, dispatch dispatchFunc) {
// TODO(stevvooe): This odd dispatcher/route registration is by-product of
// some limitations in the gorilla/mux router. We are using it to keep
// routing consistent between the client and server, but we may want to
// replace it with manual routing and structure-based dispatch for better
// control over the request execution.
app.router.GetRoute(routeName).Handler(app.dispatcher(dispatch))
}
// dispatchFunc takes a context and request and returns a constructed handler
// for the route. The dispatcher will use this to dynamically create request
// specific handlers for each endpoint without creating a new router for each
// request.
type dispatchFunc func(ctx *Context, r *http.Request) http.Handler
// TODO(stevvooe): dispatchers should probably have some validation error
// chain with proper error reporting.
// dispatcher returns a handler that constructs a request specific context and
// handler, using the dispatch factory function.
func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
context := &Context{
App: app,
Name: vars["name"],
}
// Store vars for underlying handlers.
context.vars = vars
context.log = log.WithField("name", context.Name)
handler := dispatch(context, r)
context.log.Infoln("handler", resolveHandlerName(r.Method, handler))
handler.ServeHTTP(w, r)
// Automated error response handling here. Handlers may return their
// own errors if they need different behavior (such as range errors
// for layer upload).
if len(context.Errors.Errors) > 0 {
w.WriteHeader(http.StatusBadRequest)
serveJSON(w, context.Errors)
}
})
}