@@ -16,6 +16,7 @@ import (
16
16
"fmt"
17
17
"go/format"
18
18
"html/template"
19
+ "io"
19
20
"io/fs"
20
21
"io/ioutil"
21
22
"log"
@@ -27,6 +28,7 @@ import (
27
28
"runtime"
28
29
"runtime/debug"
29
30
"strings"
31
+ "sync"
30
32
"sync/atomic"
31
33
"time"
32
34
@@ -242,6 +244,8 @@ func NewHandler(contentDir, goroot string) http.Handler {
242
244
return h
243
245
}
244
246
247
+ var gorebuild = NewCachedURL ("https://gorebuild.storage.googleapis.com/gorebuild.json" , 5 * time .Minute )
248
+
245
249
// newSite creates a new site for a given content and goroot file system pair
246
250
// and registers it in mux to handle requests for host.
247
251
// If host is the empty string, the registrations are for the wildcard host.
@@ -251,11 +255,14 @@ func newSite(mux *http.ServeMux, host string, content, goroot fs.FS) (*web.Site,
251
255
site .Funcs (template.FuncMap {
252
256
"googleAnalytics" : func () string { return googleAnalytics },
253
257
"googleCN" : func () bool { return host == "golang.google.cn" },
258
+ "gorebuild" : gorebuild .Get ,
259
+ "json" : jsonUnmarshal ,
254
260
"newest" : newest ,
261
+ "now" : func () time.Time { return time .Now () },
255
262
"releases" : func () []* history.Major { return history .Majors },
263
+ "rfc3339" : parseRFC3339 ,
256
264
"section" : section ,
257
265
"version" : func () string { return runtime .Version () },
258
- "now" : func () time.Time { return time .Now () },
259
266
})
260
267
docs , err := pkgdoc .NewServer (fsys , site , googleCN )
261
268
if err != nil {
@@ -269,6 +276,10 @@ func newSite(mux *http.ServeMux, host string, content, goroot fs.FS) (*web.Site,
269
276
return site , nil
270
277
}
271
278
279
+ func parseRFC3339 (s string ) (time.Time , error ) {
280
+ return time .Parse (time .RFC3339 , s )
281
+ }
282
+
272
283
// watchTip is a background goroutine that watches the main Go repo for updates.
273
284
// When a new commit is available, watchTip downloads the new tree and calls
274
285
// tipGoroot.Set to install the new file system.
@@ -803,3 +814,66 @@ func redirectPrefix(prefix string) http.Handler {
803
814
http .Redirect (w , r , url , http .StatusMovedPermanently )
804
815
})
805
816
}
817
+
818
+ type CachedURL struct {
819
+ url string
820
+ timeout time.Duration
821
+
822
+ mu sync.Mutex
823
+ data []byte
824
+ err error
825
+ etag string
826
+ updated time.Time
827
+ }
828
+
829
+ func NewCachedURL (url string , timeout time.Duration ) * CachedURL {
830
+ return & CachedURL {url : url , timeout : timeout }
831
+ }
832
+
833
+ func (c * CachedURL ) Get () (data []byte , err error ) {
834
+ c .mu .Lock ()
835
+ defer c .mu .Unlock ()
836
+
837
+ if time .Since (c .updated ) < c .timeout {
838
+ return c .data , c .err
839
+ }
840
+ defer func () {
841
+ c .updated = time .Now ()
842
+ c .data , c .err = data , err
843
+ }()
844
+
845
+ cli := & http.Client {Timeout : 60 * time .Second }
846
+ req , err := http .NewRequest ("GET" , c .url , nil )
847
+ if err != nil {
848
+ return nil , err
849
+ }
850
+ if c .etag != "" {
851
+ req .Header .Set ("If-None-Match" , c .etag )
852
+ }
853
+ resp , err := cli .Do (req )
854
+ if err != nil {
855
+ return nil , fmt .Errorf ("loading rebuild report JSON: %v" , err )
856
+ }
857
+ defer resp .Body .Close ()
858
+ if resp .StatusCode == 206 {
859
+ // Unmodified.
860
+ log .Printf ("checked %s - unmodified" , c .url )
861
+ return c .data , c .err
862
+ }
863
+ log .Printf ("reloading %s" , c .url )
864
+ if resp .StatusCode != 200 {
865
+ return nil , fmt .Errorf ("loading rebuild report JSON: %v" , resp .Status )
866
+ }
867
+ c .etag = resp .Header .Get ("Etag" )
868
+ data , err = io .ReadAll (resp .Body )
869
+ if err != nil {
870
+ return nil , fmt .Errorf ("loading rebuild report JSON: %v" , err )
871
+ }
872
+ return data , nil
873
+ }
874
+
875
+ func jsonUnmarshal (data []byte ) (any , error ) {
876
+ var x any
877
+ err := json .Unmarshal (data , & x )
878
+ return x , err
879
+ }
0 commit comments