Skip to content

Commit d3f7cf0

Browse files
committed
internal/frontend: move urlinfo to its own package
These are also being moved so that the code in fetch.go can be moved to a new package without depending on internal/frontend. A couple of functions that were only used by details.go are moved to that file. For #61399 Change-Id: Ic299069ea0b3aaeb80dbbf7f1ed4fedf7d1787df Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/518816 Reviewed-by: Robert Findley <[email protected]> kokoro-CI: kokoro <[email protected]> Run-TryBot: Michael Matloob <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 712da68 commit d3f7cf0

File tree

10 files changed

+265
-237
lines changed

10 files changed

+265
-237
lines changed

internal/frontend/404.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"golang.org/x/pkgsite/internal/experiment"
2424
"golang.org/x/pkgsite/internal/frontend/page"
2525
"golang.org/x/pkgsite/internal/frontend/serrors"
26+
"golang.org/x/pkgsite/internal/frontend/urlinfo"
2627
"golang.org/x/pkgsite/internal/log"
2728
"golang.org/x/pkgsite/internal/stdlib"
2829
"golang.org/x/pkgsite/internal/version"
@@ -187,7 +188,7 @@ func githubPathRedirect(fullPath string) string {
187188
// pathNotFoundError returns a page with an option on how to
188189
// add a package or module to the site.
189190
func pathNotFoundError(ctx context.Context, fullPath, requestedVersion string) error {
190-
if !isSupportedVersion(fullPath, requestedVersion) {
191+
if !urlinfo.IsSupportedVersion(fullPath, requestedVersion) {
191192
return invalidVersionError(fullPath, requestedVersion)
192193
}
193194
if stdlib.Contains(fullPath) {

internal/frontend/details.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"golang.org/x/pkgsite/internal/frontend/page"
1414
"golang.org/x/pkgsite/internal/frontend/serrors"
15+
"golang.org/x/pkgsite/internal/frontend/urlinfo"
1516
mstats "golang.org/x/pkgsite/internal/middleware/stats"
1617

1718
"github.com/google/safehtml/template"
@@ -51,26 +52,26 @@ func (s *Server) serveDetails(w http.ResponseWriter, r *http.Request, ds interna
5152
ctx = setExperimentsFromQueryParam(ctx, r)
5253
}
5354

54-
urlInfo, err := extractURLPathInfo(r.URL.Path)
55+
urlInfo, err := urlinfo.ExtractURLPathInfo(r.URL.Path)
5556
if err != nil {
5657
var epage *page.ErrorPage
57-
if uerr := new(userError); errors.As(err, &uerr) {
58-
epage = &page.ErrorPage{MessageData: uerr.userMessage}
58+
if uerr := new(urlinfo.UserError); errors.As(err, &uerr) {
59+
epage = &page.ErrorPage{MessageData: uerr.UserMessage}
5960
}
6061
return &serrors.ServerError{
6162
Status: http.StatusBadRequest,
6263
Err: err,
6364
Epage: epage,
6465
}
6566
}
66-
if !isSupportedVersion(urlInfo.fullPath, urlInfo.requestedVersion) {
67-
return invalidVersionError(urlInfo.fullPath, urlInfo.requestedVersion)
67+
if !urlinfo.IsSupportedVersion(urlInfo.FullPath, urlInfo.RequestedVersion) {
68+
return invalidVersionError(urlInfo.FullPath, urlInfo.RequestedVersion)
6869
}
69-
if urlPath := stdlibRedirectURL(urlInfo.fullPath); urlPath != "" {
70+
if urlPath := stdlibRedirectURL(urlInfo.FullPath); urlPath != "" {
7071
http.Redirect(w, r, urlPath, http.StatusMovedPermanently)
7172
return
7273
}
73-
if err := checkExcluded(ctx, ds, urlInfo.fullPath); err != nil {
74+
if err := checkExcluded(ctx, ds, urlInfo.FullPath); err != nil {
7475
return err
7576
}
7677
return s.serveUnitPage(ctx, w, r, ds, urlInfo)
@@ -140,3 +141,19 @@ func recordVersionTypeMetric(ctx context.Context, requestedVersion string) {
140141
tag.Upsert(keyVersionType, v),
141142
}, versionTypeResults.M(1))
142143
}
144+
145+
func checkExcluded(ctx context.Context, ds internal.DataSource, fullPath string) error {
146+
db, ok := ds.(internal.PostgresDB)
147+
if !ok {
148+
return nil
149+
}
150+
excluded, err := db.IsExcluded(ctx, fullPath)
151+
if err != nil {
152+
return err
153+
}
154+
if excluded {
155+
// Return NotFound; don't let the user know that the package was excluded.
156+
return &serrors.ServerError{Status: http.StatusNotFound}
157+
}
158+
return nil
159+
}

internal/frontend/experiments.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2023 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 frontend
6+
7+
import (
8+
"context"
9+
"net/http"
10+
"strings"
11+
12+
"golang.org/x/pkgsite/internal/experiment"
13+
"golang.org/x/pkgsite/internal/log"
14+
)
15+
16+
func setExperimentsFromQueryParam(ctx context.Context, r *http.Request) context.Context {
17+
if err := r.ParseForm(); err != nil {
18+
log.Errorf(ctx, "ParseForm: %v", err)
19+
return ctx
20+
}
21+
return newContextFromExps(ctx, r.Form["exp"])
22+
}
23+
24+
// newContextFromExps adds and removes experiments from the context's experiment
25+
// set, creates a new set with the changes, and returns a context with the new
26+
// set. Each string in expMods can be either an experiment name, which means
27+
// that the experiment should be added, or "!" followed by an experiment name,
28+
// meaning that it should be removed.
29+
func newContextFromExps(ctx context.Context, expMods []string) context.Context {
30+
var (
31+
exps []string
32+
remove = map[string]bool{}
33+
)
34+
set := experiment.FromContext(ctx)
35+
for _, exp := range expMods {
36+
if strings.HasPrefix(exp, "!") {
37+
exp = exp[1:]
38+
if set.IsActive(exp) {
39+
remove[exp] = true
40+
}
41+
} else if !set.IsActive(exp) {
42+
exps = append(exps, exp)
43+
}
44+
}
45+
if len(exps) == 0 && len(remove) == 0 {
46+
return ctx
47+
}
48+
for _, a := range set.Active() {
49+
if !remove[a] {
50+
exps = append(exps, a)
51+
}
52+
}
53+
return experiment.NewContext(ctx, exps...)
54+
}

internal/frontend/experments_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2023 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 frontend
6+
7+
import (
8+
"context"
9+
"sort"
10+
"testing"
11+
12+
"github.com/google/go-cmp/cmp"
13+
"golang.org/x/pkgsite/internal/experiment"
14+
)
15+
16+
func TestNewContextFromExps(t *testing.T) {
17+
for _, test := range []struct {
18+
mods []string
19+
want []string
20+
}{
21+
{
22+
mods: []string{"c", "a", "b"},
23+
want: []string{"a", "b", "c"},
24+
},
25+
{
26+
mods: []string{"d", "a"},
27+
want: []string{"a", "b", "c", "d"},
28+
},
29+
{
30+
mods: []string{"d", "!b", "!a", "c"},
31+
want: []string{"c", "d"},
32+
},
33+
} {
34+
ctx := experiment.NewContext(context.Background(), "a", "b", "c")
35+
ctx = newContextFromExps(ctx, test.mods)
36+
got := experiment.FromContext(ctx).Active()
37+
sort.Strings(got)
38+
if !cmp.Equal(got, test.want) {
39+
t.Errorf("mods=%v:\ngot %v\nwant %v", test.mods, got, test.want)
40+
}
41+
}
42+
}

internal/frontend/fetch.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"golang.org/x/pkgsite/internal/experiment"
2626
"golang.org/x/pkgsite/internal/fetch"
2727
"golang.org/x/pkgsite/internal/frontend/serrors"
28+
"golang.org/x/pkgsite/internal/frontend/urlinfo"
2829
"golang.org/x/pkgsite/internal/log"
2930
"golang.org/x/pkgsite/internal/proxy"
3031
"golang.org/x/pkgsite/internal/queue"
@@ -39,7 +40,7 @@ var (
3940
// this module version in version_map.
4041
errModuleDoesNotExist = errors.New("module does not exist")
4142
// errPathDoesNotExistInModule indicates that a module for the path prefix
42-
// exists, but within that module version, this fullPath could not be found.
43+
// exists, but within that module version, this FullPath could not be found.
4344
errPathDoesNotExistInModule = errors.New("path does not exist in module")
4445
fetchTimeout = 30 * time.Second
4546
pollEvery = 1 * time.Second
@@ -103,11 +104,11 @@ func (s *Server) serveFetch(w http.ResponseWriter, r *http.Request, ds internal.
103104
return &serrors.ServerError{Status: http.StatusNotFound}
104105
}
105106

106-
urlInfo, err := extractURLPathInfo(strings.TrimPrefix(r.URL.Path, "/fetch"))
107+
urlInfo, err := urlinfo.ExtractURLPathInfo(strings.TrimPrefix(r.URL.Path, "/fetch"))
107108
if err != nil {
108109
return &serrors.ServerError{Status: http.StatusBadRequest}
109110
}
110-
status, responseText := s.fetchAndPoll(r.Context(), ds, urlInfo.modulePath, urlInfo.fullPath, urlInfo.requestedVersion)
111+
status, responseText := s.fetchAndPoll(r.Context(), ds, urlInfo.ModulePath, urlInfo.FullPath, urlInfo.RequestedVersion)
111112
if status != http.StatusOK {
112113
return &serrors.ServerError{Status: status, ResponseText: responseText}
113114
}
@@ -134,14 +135,14 @@ func (s *Server) fetchAndPoll(ctx context.Context, ds internal.DataSource, modul
134135
recordFrontendFetchMetric(ctx, status, time.Since(start))
135136
}()
136137

137-
if !isSupportedVersion(fullPath, requestedVersion) {
138+
if !urlinfo.IsSupportedVersion(fullPath, requestedVersion) {
138139
return http.StatusBadRequest, http.StatusText(http.StatusBadRequest)
139140
}
140141
if !experiment.IsActive(ctx, internal.ExperimentEnableStdFrontendFetch) && stdlib.Contains(fullPath) {
141142
return http.StatusBadRequest, http.StatusText(http.StatusBadRequest)
142143
}
143144

144-
// Generate all possible module paths for the fullPath.
145+
// Generate all possible module paths for the FullPath.
145146
db := ds.(internal.PostgresDB)
146147
modulePaths, err := modulePathsToFetch(ctx, db, fullPath, modulePath)
147148
if err != nil {
@@ -165,7 +166,7 @@ func (s *Server) fetchAndPoll(ctx context.Context, ds internal.DataSource, modul
165166
}
166167

167168
// checkPossibleModulePaths checks all modulePaths at the requestedVersion, to see
168-
// if the fullPath exists. For each module path, it first checks version_map to
169+
// if the FullPath exists. For each module path, it first checks version_map to
169170
// see if we already attempted to fetch the module. If not, and shouldQueue is
170171
// true, it will enqueue the module to the frontend task queue to be fetched.
171172
// checkPossibleModulePaths will then poll the database for each module path,
@@ -391,7 +392,7 @@ func checkForPath(ctx context.Context, db internal.PostgresDB,
391392
vm, err := db.GetVersionMap(ctx, modulePath, requestedVersion)
392393
if err != nil {
393394
// If an error is returned, there are two possibilities:
394-
// (1) A row for this modulePath and version does not exist.
395+
// (1) A row for this ModulePath and version does not exist.
395396
// This means that the fetch request is not done yet, so return
396397
// statusNotFoundInVersionMap so the fetchHandler will call checkForPath
397398
// again in a few seconds.
@@ -520,10 +521,10 @@ func candidateModulePaths(fullPath string) (_ []string, err error) {
520521
if fullPath == stdlib.ModulePath {
521522
return []string{stdlib.ModulePath}, nil
522523
}
523-
if !isValidPath(fullPath) {
524+
if !urlinfo.IsValidPath(fullPath) {
524525
return nil, &serrors.ServerError{
525526
Status: http.StatusBadRequest,
526-
Err: fmt.Errorf("isValidPath(%q): false", fullPath),
527+
Err: fmt.Errorf("urlinfo.IsValidPath(%q): false", fullPath),
527528
}
528529
}
529530
paths := internal.CandidateModulePaths(fullPath)

internal/frontend/server.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"golang.org/x/pkgsite/internal/experiment"
2929
pagepkg "golang.org/x/pkgsite/internal/frontend/page"
3030
"golang.org/x/pkgsite/internal/frontend/serrors"
31+
"golang.org/x/pkgsite/internal/frontend/urlinfo"
3132
"golang.org/x/pkgsite/internal/godoc/dochtml"
3233
"golang.org/x/pkgsite/internal/licenses"
3334
"golang.org/x/pkgsite/internal/log"
@@ -329,12 +330,12 @@ func detailsTTLForPath(ctx context.Context, urlPath, tab string) time.Duration {
329330
if urlPath == "/" {
330331
return defaultTTL
331332
}
332-
info, err := parseDetailsURLPath(urlPath)
333+
info, err := urlinfo.ParseDetailsURLPath(urlPath)
333334
if err != nil {
334335
log.Errorf(ctx, "falling back to default TTL: %v", err)
335336
return defaultTTL
336337
}
337-
if info.requestedVersion == version.Latest {
338+
if info.RequestedVersion == version.Latest {
338339
return shortTTL
339340
}
340341
if tab == "importedby" || tab == "versions" {

internal/frontend/unit.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"golang.org/x/pkgsite/internal/derrors"
2020
"golang.org/x/pkgsite/internal/frontend/page"
2121
"golang.org/x/pkgsite/internal/frontend/serrors"
22+
"golang.org/x/pkgsite/internal/frontend/urlinfo"
2223
"golang.org/x/pkgsite/internal/log"
2324
"golang.org/x/pkgsite/internal/middleware/stats"
2425
"golang.org/x/pkgsite/internal/stdlib"
@@ -106,7 +107,7 @@ type UnitPage struct {
106107

107108
// serveUnitPage serves a unit page for a path.
108109
func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *http.Request,
109-
ds internal.DataSource, info *urlPathInfo) (err error) {
110+
ds internal.DataSource, info *urlinfo.URLPathInfo) (err error) {
110111
defer derrors.Wrap(&err, "serveUnitPage(ctx, w, r, ds, %v)", info)
111112
defer stats.Elapsed(ctx, "serveUnitPage")()
112113

@@ -121,12 +122,12 @@ func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *ht
121122
return nil
122123
}
123124

124-
um, err := ds.GetUnitMeta(ctx, info.fullPath, info.modulePath, info.requestedVersion)
125+
um, err := ds.GetUnitMeta(ctx, info.FullPath, info.ModulePath, info.RequestedVersion)
125126
if err != nil {
126127
if !errors.Is(err, derrors.NotFound) {
127128
return err
128129
}
129-
return s.servePathNotFoundPage(w, r, ds, info.fullPath, info.modulePath, info.requestedVersion)
130+
return s.servePathNotFoundPage(w, r, ds, info.FullPath, info.ModulePath, info.RequestedVersion)
130131
}
131132

132133
makeDepsDevURL := depsDevURLGenerator(ctx, um)
@@ -137,16 +138,16 @@ func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *ht
137138
// It's also okay to provide just one (e.g. GOOS=windows), which will select
138139
// the first doc with that value, ignoring the other one.
139140
bc := internal.BuildContext{GOOS: r.FormValue("GOOS"), GOARCH: r.FormValue("GOARCH")}
140-
d, err := fetchDetailsForUnit(ctx, r, tab, ds, um, info.requestedVersion, bc, s.vulnClient)
141+
d, err := fetchDetailsForUnit(ctx, r, tab, ds, um, info.RequestedVersion, bc, s.vulnClient)
141142
if err != nil {
142143
return err
143144
}
144145
if s.shouldServeJSON(r) {
145146
return s.serveJSONPage(w, r, d)
146147
}
147148

148-
recordVersionTypeMetric(ctx, info.requestedVersion)
149-
if _, ok := internal.DefaultBranches[info.requestedVersion]; ok {
149+
recordVersionTypeMetric(ctx, info.RequestedVersion)
150+
if _, ok := internal.DefaultBranches[info.RequestedVersion]; ok {
150151
// Since path@master is a moving target, we don't want it to be stale.
151152
// As a result, we enqueue every request of path@master to the frontend
152153
// task queue, which will initiate a fetch request depending on the
@@ -160,17 +161,17 @@ func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *ht
160161
Err: err,
161162
Epage: &page.ErrorPage{
162163
MessageData: fmt.Sprintf(`Default branches like "@%s" are not supported. Omit to get the current version.`,
163-
info.requestedVersion),
164+
info.RequestedVersion),
164165
},
165166
}
166167
}
167168
go func() {
168169
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
169170
defer cancel()
170-
log.Infof(ctx, "serveUnitPage: Scheduling %q@%q to be fetched", um.ModulePath, info.requestedVersion)
171-
if _, err := s.queue.ScheduleFetch(ctx, um.ModulePath, info.requestedVersion, nil); err != nil {
171+
log.Infof(ctx, "serveUnitPage: Scheduling %q@%q to be fetched", um.ModulePath, info.RequestedVersion)
172+
if _, err := s.queue.ScheduleFetch(ctx, um.ModulePath, info.RequestedVersion, nil); err != nil {
172173
log.Errorf(ctx, "serveUnitPage(%q): scheduling fetch for %q@%q: %v",
173-
r.URL.Path, um.ModulePath, info.requestedVersion, err)
174+
r.URL.Path, um.ModulePath, info.RequestedVersion, err)
174175
}
175176
}()
176177
}
@@ -185,7 +186,7 @@ func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *ht
185186
// If we've already called GetUnitMeta for an unknown module path and the latest version, pass
186187
// it to GetLatestInfo to avoid a redundant call.
187188
var latestUnitMeta *internal.UnitMeta
188-
if info.modulePath == internal.UnknownModulePath && info.requestedVersion == version.Latest {
189+
if info.ModulePath == internal.UnknownModulePath && info.RequestedVersion == version.Latest {
189190
latestUnitMeta = um
190191
}
191192
latestInfo := s.GetLatestInfo(ctx, um.Path, um.ModulePath, latestUnitMeta)
@@ -202,16 +203,16 @@ func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *ht
202203
if tabSettings.Name == "" {
203204
basePage.UseResponsiveLayout = true
204205
}
205-
lv := linkVersion(um.ModulePath, info.requestedVersion, um.Version)
206+
lv := linkVersion(um.ModulePath, info.RequestedVersion, um.Version)
206207
page := UnitPage{
207208
BasePage: basePage,
208209
Unit: um,
209-
Breadcrumb: displayBreadcrumb(um, info.requestedVersion),
210+
Breadcrumb: displayBreadcrumb(um, info.RequestedVersion),
210211
Title: title,
211212
SelectedTab: tabSettings,
212-
URLPath: constructUnitURL(um.Path, um.ModulePath, info.requestedVersion),
213-
CanonicalURLPath: canonicalURLPath(um.Path, um.ModulePath, info.requestedVersion, um.Version),
214-
DisplayVersion: displayVersion(um.ModulePath, info.requestedVersion, um.Version),
213+
URLPath: constructUnitURL(um.Path, um.ModulePath, info.RequestedVersion),
214+
CanonicalURLPath: canonicalURLPath(um.Path, um.ModulePath, info.RequestedVersion, um.Version),
215+
DisplayVersion: displayVersion(um.ModulePath, info.RequestedVersion, um.Version),
215216
LinkVersion: lv,
216217
LatestURL: constructUnitURL(um.Path, um.ModulePath, version.Latest),
217218
LatestMinorClass: latestMinorClass(lv, latestInfo),

0 commit comments

Comments
 (0)