...

Source file src/pkg/cmd/go/internal/sumweb/server.go

     1	// Copyright 2019 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	// Package sumweb implements the HTTP protocols for serving or accessing a go.sum database.
     6	package sumweb
     7	
     8	import (
     9		"context"
    10		"internal/lazyregexp"
    11		"net/http"
    12		"os"
    13		"strings"
    14	
    15		"cmd/go/internal/tlog"
    16	)
    17	
    18	// A Server provides the external operations
    19	// (underlying database access and so on)
    20	// needed to implement the HTTP server Handler.
    21	type Server interface {
    22		// NewContext returns the context to use for the request r.
    23		NewContext(r *http.Request) (context.Context, error)
    24	
    25		// Signed returns the signed hash of the latest tree.
    26		Signed(ctx context.Context) ([]byte, error)
    27	
    28		// ReadRecords returns the content for the n records id through id+n-1.
    29		ReadRecords(ctx context.Context, id, n int64) ([][]byte, error)
    30	
    31		// Lookup looks up a record by its associated key ("module@version"),
    32		// returning the record ID.
    33		Lookup(ctx context.Context, key string) (int64, error)
    34	
    35		// ReadTileData reads the content of tile t.
    36		// It is only invoked for hash tiles (t.L ≥ 0).
    37		ReadTileData(ctx context.Context, t tlog.Tile) ([]byte, error)
    38	}
    39	
    40	// A Handler is the go.sum database server handler,
    41	// which should be invoked to serve the paths listed in Paths.
    42	// The calling code is responsible for initializing Server.
    43	type Handler struct {
    44		Server Server
    45	}
    46	
    47	// Paths are the URL paths for which Handler should be invoked.
    48	//
    49	// Typically a server will do:
    50	//
    51	//	handler := &sumweb.Handler{Server: srv}
    52	//	for _, path := range sumweb.Paths {
    53	//		http.HandleFunc(path, handler)
    54	//	}
    55	//
    56	var Paths = []string{
    57		"/lookup/",
    58		"/latest",
    59		"/tile/",
    60	}
    61	
    62	var modVerRE = lazyregexp.New(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
    63	
    64	func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    65		ctx, err := h.Server.NewContext(r)
    66		if err != nil {
    67			http.Error(w, err.Error(), 500)
    68			return
    69		}
    70	
    71		switch {
    72		default:
    73			http.NotFound(w, r)
    74	
    75		case strings.HasPrefix(r.URL.Path, "/lookup/"):
    76			mod := strings.TrimPrefix(r.URL.Path, "/lookup/")
    77			if !modVerRE.MatchString(mod) {
    78				http.Error(w, "invalid module@version syntax", http.StatusBadRequest)
    79				return
    80			}
    81			i := strings.Index(mod, "@")
    82			encPath, encVers := mod[:i], mod[i+1:]
    83			path, err := decodePath(encPath)
    84			if err != nil {
    85				reportError(w, r, err)
    86				return
    87			}
    88			vers, err := decodeVersion(encVers)
    89			if err != nil {
    90				reportError(w, r, err)
    91				return
    92			}
    93			id, err := h.Server.Lookup(ctx, path+"@"+vers)
    94			if err != nil {
    95				reportError(w, r, err)
    96				return
    97			}
    98			records, err := h.Server.ReadRecords(ctx, id, 1)
    99			if err != nil {
   100				// This should never happen - the lookup says the record exists.
   101				http.Error(w, err.Error(), http.StatusInternalServerError)
   102				return
   103			}
   104			if len(records) != 1 {
   105				http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
   106				return
   107			}
   108			msg, err := tlog.FormatRecord(id, records[0])
   109			if err != nil {
   110				http.Error(w, err.Error(), http.StatusInternalServerError)
   111				return
   112			}
   113			signed, err := h.Server.Signed(ctx)
   114			if err != nil {
   115				http.Error(w, err.Error(), http.StatusInternalServerError)
   116				return
   117			}
   118			w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
   119			w.Write(msg)
   120			w.Write(signed)
   121	
   122		case r.URL.Path == "/latest":
   123			data, err := h.Server.Signed(ctx)
   124			if err != nil {
   125				http.Error(w, err.Error(), http.StatusInternalServerError)
   126				return
   127			}
   128			w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
   129			w.Write(data)
   130	
   131		case strings.HasPrefix(r.URL.Path, "/tile/"):
   132			t, err := tlog.ParseTilePath(r.URL.Path[1:])
   133			if err != nil {
   134				http.Error(w, "invalid tile syntax", http.StatusBadRequest)
   135				return
   136			}
   137			if t.L == -1 {
   138				// Record data.
   139				start := t.N << uint(t.H)
   140				records, err := h.Server.ReadRecords(ctx, start, int64(t.W))
   141				if err != nil {
   142					reportError(w, r, err)
   143					return
   144				}
   145				if len(records) != t.W {
   146					http.Error(w, "invalid record count returned by ReadRecords", http.StatusInternalServerError)
   147					return
   148				}
   149				var data []byte
   150				for i, text := range records {
   151					msg, err := tlog.FormatRecord(start+int64(i), text)
   152					if err != nil {
   153						http.Error(w, err.Error(), http.StatusInternalServerError)
   154					}
   155					data = append(data, msg...)
   156				}
   157				w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
   158				w.Write(data)
   159				return
   160			}
   161	
   162			data, err := h.Server.ReadTileData(ctx, t)
   163			if err != nil {
   164				reportError(w, r, err)
   165				return
   166			}
   167			w.Header().Set("Content-Type", "application/octet-stream")
   168			w.Write(data)
   169		}
   170	}
   171	
   172	// reportError reports err to w.
   173	// If it's a not-found, the reported error is 404.
   174	// Otherwise it is an internal server error.
   175	// The caller must only call reportError in contexts where
   176	// a not-found err should be reported as 404.
   177	func reportError(w http.ResponseWriter, r *http.Request, err error) {
   178		if os.IsNotExist(err) {
   179			http.Error(w, err.Error(), http.StatusNotFound)
   180			return
   181		}
   182		http.Error(w, err.Error(), http.StatusInternalServerError)
   183	}
   184	

View as plain text