github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/api/v2/urls.go (about)

     1  package v2
     2  
     3  import (
     4  	"net/http"
     5  	"net/url"
     6  	"strings"
     7  
     8  	"github.com/docker/distribution/digest"
     9  	"github.com/gorilla/mux"
    10  )
    11  
    12  // URLBuilder creates registry API urls from a single base endpoint. It can be
    13  // used to create urls for use in a registry client or server.
    14  //
    15  // All urls will be created from the given base, including the api version.
    16  // For example, if a root of "/foo/" is provided, urls generated will be fall
    17  // under "/foo/v2/...". Most application will only provide a schema, host and
    18  // port, such as "https://localhost:5000/".
    19  type URLBuilder struct {
    20  	root   *url.URL // url root (ie http://localhost/)
    21  	router *mux.Router
    22  }
    23  
    24  // NewURLBuilder creates a URLBuilder with provided root url object.
    25  func NewURLBuilder(root *url.URL) *URLBuilder {
    26  	return &URLBuilder{
    27  		root:   root,
    28  		router: Router(),
    29  	}
    30  }
    31  
    32  // NewURLBuilderFromString workes identically to NewURLBuilder except it takes
    33  // a string argument for the root, returning an error if it is not a valid
    34  // url.
    35  func NewURLBuilderFromString(root string) (*URLBuilder, error) {
    36  	u, err := url.Parse(root)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	return NewURLBuilder(u), nil
    42  }
    43  
    44  // NewURLBuilderFromRequest uses information from an *http.Request to
    45  // construct the root url.
    46  func NewURLBuilderFromRequest(r *http.Request) *URLBuilder {
    47  	var scheme string
    48  
    49  	forwardedProto := r.Header.Get("X-Forwarded-Proto")
    50  
    51  	switch {
    52  	case len(forwardedProto) > 0:
    53  		scheme = forwardedProto
    54  	case r.TLS != nil:
    55  		scheme = "https"
    56  	case len(r.URL.Scheme) > 0:
    57  		scheme = r.URL.Scheme
    58  	default:
    59  		scheme = "http"
    60  	}
    61  
    62  	host := r.Host
    63  	forwardedHost := r.Header.Get("X-Forwarded-Host")
    64  	if len(forwardedHost) > 0 {
    65  		// According to the Apache mod_proxy docs, X-Forwarded-Host can be a
    66  		// comma-separated list of hosts, to which each proxy appends the
    67  		// requested host. We want to grab the first from this comma-separated
    68  		// list.
    69  		hosts := strings.SplitN(forwardedHost, ",", 2)
    70  		host = strings.TrimSpace(hosts[0])
    71  	}
    72  
    73  	basePath := routeDescriptorsMap[RouteNameBase].Path
    74  
    75  	requestPath := r.URL.Path
    76  	index := strings.Index(requestPath, basePath)
    77  
    78  	u := &url.URL{
    79  		Scheme: scheme,
    80  		Host:   host,
    81  	}
    82  
    83  	if index > 0 {
    84  		// N.B. index+1 is important because we want to include the trailing /
    85  		u.Path = requestPath[0 : index+1]
    86  	}
    87  
    88  	return NewURLBuilder(u)
    89  }
    90  
    91  // BuildBaseURL constructs a base url for the API, typically just "/v2/".
    92  func (ub *URLBuilder) BuildBaseURL() (string, error) {
    93  	route := ub.cloneRoute(RouteNameBase)
    94  
    95  	baseURL, err := route.URL()
    96  	if err != nil {
    97  		return "", err
    98  	}
    99  
   100  	return baseURL.String(), nil
   101  }
   102  
   103  // BuildCatalogURL constructs a url get a catalog of repositories
   104  func (ub *URLBuilder) BuildCatalogURL(values ...url.Values) (string, error) {
   105  	route := ub.cloneRoute(RouteNameCatalog)
   106  
   107  	catalogURL, err := route.URL()
   108  	if err != nil {
   109  		return "", err
   110  	}
   111  
   112  	return appendValuesURL(catalogURL, values...).String(), nil
   113  }
   114  
   115  // BuildTagsURL constructs a url to list the tags in the named repository.
   116  func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
   117  	route := ub.cloneRoute(RouteNameTags)
   118  
   119  	tagsURL, err := route.URL("name", name)
   120  	if err != nil {
   121  		return "", err
   122  	}
   123  
   124  	return tagsURL.String(), nil
   125  }
   126  
   127  // BuildManifestURL constructs a url for the manifest identified by name and
   128  // reference. The argument reference may be either a tag or digest.
   129  func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
   130  	route := ub.cloneRoute(RouteNameManifest)
   131  
   132  	manifestURL, err := route.URL("name", name, "reference", reference)
   133  	if err != nil {
   134  		return "", err
   135  	}
   136  
   137  	return manifestURL.String(), nil
   138  }
   139  
   140  // BuildBlobURL constructs the url for the blob identified by name and dgst.
   141  func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, error) {
   142  	route := ub.cloneRoute(RouteNameBlob)
   143  
   144  	layerURL, err := route.URL("name", name, "digest", dgst.String())
   145  	if err != nil {
   146  		return "", err
   147  	}
   148  
   149  	return layerURL.String(), nil
   150  }
   151  
   152  // BuildBlobUploadURL constructs a url to begin a blob upload in the
   153  // repository identified by name.
   154  func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) {
   155  	route := ub.cloneRoute(RouteNameBlobUpload)
   156  
   157  	uploadURL, err := route.URL("name", name)
   158  	if err != nil {
   159  		return "", err
   160  	}
   161  
   162  	return appendValuesURL(uploadURL, values...).String(), nil
   163  }
   164  
   165  // BuildBlobUploadChunkURL constructs a url for the upload identified by uuid,
   166  // including any url values. This should generally not be used by clients, as
   167  // this url is provided by server implementations during the blob upload
   168  // process.
   169  func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) {
   170  	route := ub.cloneRoute(RouteNameBlobUploadChunk)
   171  
   172  	uploadURL, err := route.URL("name", name, "uuid", uuid)
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  
   177  	return appendValuesURL(uploadURL, values...).String(), nil
   178  }
   179  
   180  // clondedRoute returns a clone of the named route from the router. Routes
   181  // must be cloned to avoid modifying them during url generation.
   182  func (ub *URLBuilder) cloneRoute(name string) clonedRoute {
   183  	route := new(mux.Route)
   184  	root := new(url.URL)
   185  
   186  	*route = *ub.router.GetRoute(name) // clone the route
   187  	*root = *ub.root
   188  
   189  	return clonedRoute{Route: route, root: root}
   190  }
   191  
   192  type clonedRoute struct {
   193  	*mux.Route
   194  	root *url.URL
   195  }
   196  
   197  func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) {
   198  	routeURL, err := cr.Route.URL(pairs...)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" {
   204  		routeURL.Path = routeURL.Path[1:]
   205  	}
   206  
   207  	url := cr.root.ResolveReference(routeURL)
   208  	url.Scheme = cr.root.Scheme
   209  	return url, nil
   210  }
   211  
   212  // appendValuesURL appends the parameters to the url.
   213  func appendValuesURL(u *url.URL, values ...url.Values) *url.URL {
   214  	merged := u.Query()
   215  
   216  	for _, v := range values {
   217  		for k, vv := range v {
   218  			merged[k] = append(merged[k], vv...)
   219  		}
   220  	}
   221  
   222  	u.RawQuery = merged.Encode()
   223  	return u
   224  }
   225  
   226  // appendValues appends the parameters to the url. Panics if the string is not
   227  // a url.
   228  func appendValues(u string, values ...url.Values) string {
   229  	up, err := url.Parse(u)
   230  
   231  	if err != nil {
   232  		panic(err) // should never happen
   233  	}
   234  
   235  	return appendValuesURL(up, values...).String()
   236  }