godoc/dl, godoc/proxy, godoc/short, internal/memcache: delete
These packages existed only to power cmd/godoc for the purpose of serving the golang.org website. That functionality has moved into x/website as part of golang/go#29206. x/website has become the canonical source of golang.org in CL 162157, the golang.org-serving code was removed from cmd/godoc in CL 162400, and these packages can be deleted too now. This removes the last dependency on the cloud.google.com/go module, which results in a significant reduction of the number of indirect dependencies in x/tools (this is due to issue golang/go#29935, which affects the current version of the cloud.google.com/go module). Run go mod tidy (using Go 1.12 RC 1). Updates golang/go#29206 Updates golang/go#29981 Change-Id: If07e3ccae8538b3ebd51af64b6af5be5463f4906 Reviewed-on: https://go-review.googlesource.com/c/162401 Reviewed-by: Bryan C. Mills <bcmills@google.com> Reviewed-by: Channing Kimble-Brown <channing@golang.org> Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
b4f3f03986
commit
97f80cd550
3
go.mod
3
go.mod
|
@ -1,8 +1,7 @@
|
||||||
module golang.org/x/tools
|
module golang.org/x/tools
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.36.0
|
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||||
google.golang.org/appengine v1.4.0
|
google.golang.org/appengine v1.4.0
|
||||||
)
|
)
|
||||||
|
|
140
go.sum
140
go.sum
|
@ -1,150 +1,10 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.36.0 h1:+aCSj7tOo2LODWVEuZDZeGCckdt6MlSF+X/rB3wUiS8=
|
|
||||||
cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40=
|
|
||||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
|
||||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
|
||||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
|
||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
|
||||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
|
||||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
|
||||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
|
||||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
|
||||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
|
||||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
|
||||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
|
||||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
|
||||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
|
||||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
|
||||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
|
||||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
|
||||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
|
||||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
|
||||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
|
||||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
|
||||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
|
||||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
|
||||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
|
||||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
|
||||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
|
||||||
go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
|
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497 h1:GXMDsk4xWZCVzkAWCabrabzCCVmfiYSw72f1K/S9QIY=
|
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
|
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
|
||||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8=
|
|
||||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
|
||||||
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
|
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
|
||||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
|
||||||
|
|
353
godoc/dl/dl.go
353
godoc/dl/dl.go
|
@ -1,353 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package dl implements a simple downloads frontend server.
|
|
||||||
//
|
|
||||||
// It accepts HTTP POST requests to create a new download metadata entity, and
|
|
||||||
// lists entities with sorting and filtering.
|
|
||||||
// It is designed to run only on the instance of godoc that serves golang.org.
|
|
||||||
package dl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
downloadBaseURL = "https://dl.google.com/go/"
|
|
||||||
cacheKey = "download_list_3" // increment if listTemplateData changes
|
|
||||||
cacheDuration = time.Hour
|
|
||||||
)
|
|
||||||
|
|
||||||
// File represents a file on the golang.org downloads page.
|
|
||||||
// It should be kept in sync with the upload code in x/build/cmd/release.
|
|
||||||
type File struct {
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
OS string `json:"os"`
|
|
||||||
Arch string `json:"arch"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Checksum string `json:"-" datastore:",noindex"` // SHA1; deprecated
|
|
||||||
ChecksumSHA256 string `json:"sha256" datastore:",noindex"`
|
|
||||||
Size int64 `json:"size" datastore:",noindex"`
|
|
||||||
Kind string `json:"kind"` // "archive", "installer", "source"
|
|
||||||
Uploaded time.Time `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) ChecksumType() string {
|
|
||||||
if f.ChecksumSHA256 != "" {
|
|
||||||
return "SHA256"
|
|
||||||
}
|
|
||||||
return "SHA1"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) PrettyChecksum() string {
|
|
||||||
if f.ChecksumSHA256 != "" {
|
|
||||||
return f.ChecksumSHA256
|
|
||||||
}
|
|
||||||
return f.Checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) PrettyOS() string {
|
|
||||||
if f.OS == "darwin" {
|
|
||||||
switch {
|
|
||||||
case strings.Contains(f.Filename, "osx10.8"):
|
|
||||||
return "OS X 10.8+"
|
|
||||||
case strings.Contains(f.Filename, "osx10.6"):
|
|
||||||
return "OS X 10.6+"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pretty(f.OS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) PrettySize() string {
|
|
||||||
const mb = 1 << 20
|
|
||||||
if f.Size == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if f.Size < mb {
|
|
||||||
// All Go releases are >1mb, but handle this case anyway.
|
|
||||||
return fmt.Sprintf("%v bytes", f.Size)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%.0fMB", float64(f.Size)/mb)
|
|
||||||
}
|
|
||||||
|
|
||||||
var primaryPorts = map[string]bool{
|
|
||||||
"darwin/amd64": true,
|
|
||||||
"linux/386": true,
|
|
||||||
"linux/amd64": true,
|
|
||||||
"linux/armv6l": true,
|
|
||||||
"windows/386": true,
|
|
||||||
"windows/amd64": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) PrimaryPort() bool {
|
|
||||||
if f.Kind == "source" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return primaryPorts[f.OS+"/"+f.Arch]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) Highlight() bool {
|
|
||||||
switch {
|
|
||||||
case f.Kind == "source":
|
|
||||||
return true
|
|
||||||
case f.Arch == "amd64" && f.OS == "linux":
|
|
||||||
return true
|
|
||||||
case f.Arch == "amd64" && f.Kind == "installer":
|
|
||||||
switch f.OS {
|
|
||||||
case "windows":
|
|
||||||
return true
|
|
||||||
case "darwin":
|
|
||||||
if !strings.Contains(f.Filename, "osx10.6") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) URL() string {
|
|
||||||
return downloadBaseURL + f.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
type Release struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Stable bool `json:"stable"`
|
|
||||||
Files []File `json:"files"`
|
|
||||||
Visible bool `json:"-"` // show files on page load
|
|
||||||
SplitPortTable bool `json:"-"` // whether files should be split by primary/other ports.
|
|
||||||
}
|
|
||||||
|
|
||||||
type Feature struct {
|
|
||||||
// The File field will be filled in by the first stable File
|
|
||||||
// whose name matches the given fileRE.
|
|
||||||
File
|
|
||||||
fileRE *regexp.Regexp
|
|
||||||
|
|
||||||
Platform string // "Microsoft Windows", "Apple macOS", "Linux"
|
|
||||||
Requirements string // "Windows XP and above, 64-bit Intel Processor"
|
|
||||||
}
|
|
||||||
|
|
||||||
// featuredFiles lists the platforms and files to be featured
|
|
||||||
// at the top of the downloads page.
|
|
||||||
var featuredFiles = []Feature{
|
|
||||||
{
|
|
||||||
Platform: "Microsoft Windows",
|
|
||||||
Requirements: "Windows 7 or later, Intel 64-bit processor",
|
|
||||||
fileRE: regexp.MustCompile(`\.windows-amd64\.msi$`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Platform: "Apple macOS",
|
|
||||||
Requirements: "macOS 10.10 or later, Intel 64-bit processor",
|
|
||||||
fileRE: regexp.MustCompile(`\.darwin-amd64(-osx10\.8)?\.pkg$`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Platform: "Linux",
|
|
||||||
Requirements: "Linux 2.6.23 or later, Intel 64-bit processor",
|
|
||||||
fileRE: regexp.MustCompile(`\.linux-amd64\.tar\.gz$`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Platform: "Source",
|
|
||||||
fileRE: regexp.MustCompile(`\.src\.tar\.gz$`),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// data to send to the template; increment cacheKey if you change this.
|
|
||||||
type listTemplateData struct {
|
|
||||||
Featured []Feature
|
|
||||||
Stable, Unstable, Archive []Release
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
listTemplate = template.Must(template.New("").Funcs(templateFuncs).Parse(templateHTML))
|
|
||||||
templateFuncs = template.FuncMap{"pretty": pretty}
|
|
||||||
)
|
|
||||||
|
|
||||||
func filesToFeatured(fs []File) (featured []Feature) {
|
|
||||||
for _, feature := range featuredFiles {
|
|
||||||
for _, file := range fs {
|
|
||||||
if feature.fileRE.MatchString(file.Filename) {
|
|
||||||
feature.File = file
|
|
||||||
featured = append(featured, feature)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func filesToReleases(fs []File) (stable, unstable, archive []Release) {
|
|
||||||
sort.Sort(fileOrder(fs))
|
|
||||||
|
|
||||||
var r *Release
|
|
||||||
var stableMaj, stableMin int
|
|
||||||
add := func() {
|
|
||||||
if r == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !r.Stable {
|
|
||||||
if len(unstable) != 0 {
|
|
||||||
// Only show one (latest) unstable version.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
maj, min, _ := parseVersion(r.Version)
|
|
||||||
if maj < stableMaj || maj == stableMaj && min <= stableMin {
|
|
||||||
// Display unstable version only if newer than the
|
|
||||||
// latest stable release.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
unstable = append(unstable, *r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reports whether the release is the most recent minor version of the
|
|
||||||
// two most recent major versions.
|
|
||||||
shouldAddStable := func() bool {
|
|
||||||
if len(stable) >= 2 {
|
|
||||||
// Show up to two stable versions.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(stable) == 0 {
|
|
||||||
// Most recent stable version.
|
|
||||||
stableMaj, stableMin, _ = parseVersion(r.Version)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if maj, _, _ := parseVersion(r.Version); maj == stableMaj {
|
|
||||||
// Older minor version of most recent major version.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Second most recent stable version.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !shouldAddStable() {
|
|
||||||
archive = append(archive, *r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split the file list into primary/other ports for the stable releases.
|
|
||||||
// NOTE(cbro): This is only done for stable releases because maintaining the historical
|
|
||||||
// nature of primary/other ports for older versions is infeasible.
|
|
||||||
// If freebsd is considered primary some time in the future, we'd not want to
|
|
||||||
// mark all of the older freebsd binaries as "primary".
|
|
||||||
// It might be better if we set that as a flag when uploading.
|
|
||||||
r.SplitPortTable = true
|
|
||||||
r.Visible = true // Toggle open all stable releases.
|
|
||||||
stable = append(stable, *r)
|
|
||||||
}
|
|
||||||
for _, f := range fs {
|
|
||||||
if r == nil || f.Version != r.Version {
|
|
||||||
add()
|
|
||||||
r = &Release{
|
|
||||||
Version: f.Version,
|
|
||||||
Stable: isStable(f.Version),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.Files = append(r.Files, f)
|
|
||||||
}
|
|
||||||
add()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// isStable reports whether the version string v is a stable version.
|
|
||||||
func isStable(v string) bool {
|
|
||||||
return !strings.Contains(v, "beta") && !strings.Contains(v, "rc")
|
|
||||||
}
|
|
||||||
|
|
||||||
type fileOrder []File
|
|
||||||
|
|
||||||
func (s fileOrder) Len() int { return len(s) }
|
|
||||||
func (s fileOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s fileOrder) Less(i, j int) bool {
|
|
||||||
a, b := s[i], s[j]
|
|
||||||
if av, bv := a.Version, b.Version; av != bv {
|
|
||||||
return versionLess(av, bv)
|
|
||||||
}
|
|
||||||
if a.OS != b.OS {
|
|
||||||
return a.OS < b.OS
|
|
||||||
}
|
|
||||||
if a.Arch != b.Arch {
|
|
||||||
return a.Arch < b.Arch
|
|
||||||
}
|
|
||||||
if a.Kind != b.Kind {
|
|
||||||
return a.Kind < b.Kind
|
|
||||||
}
|
|
||||||
return a.Filename < b.Filename
|
|
||||||
}
|
|
||||||
|
|
||||||
func versionLess(a, b string) bool {
|
|
||||||
// Put stable releases first.
|
|
||||||
if isStable(a) != isStable(b) {
|
|
||||||
return isStable(a)
|
|
||||||
}
|
|
||||||
maja, mina, ta := parseVersion(a)
|
|
||||||
majb, minb, tb := parseVersion(b)
|
|
||||||
if maja == majb {
|
|
||||||
if mina == minb {
|
|
||||||
return ta >= tb
|
|
||||||
}
|
|
||||||
return mina >= minb
|
|
||||||
}
|
|
||||||
return maja >= majb
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseVersion(v string) (maj, min int, tail string) {
|
|
||||||
if i := strings.Index(v, "beta"); i > 0 {
|
|
||||||
tail = v[i:]
|
|
||||||
v = v[:i]
|
|
||||||
}
|
|
||||||
if i := strings.Index(v, "rc"); i > 0 {
|
|
||||||
tail = v[i:]
|
|
||||||
v = v[:i]
|
|
||||||
}
|
|
||||||
p := strings.Split(strings.TrimPrefix(v, "go1."), ".")
|
|
||||||
maj, _ = strconv.Atoi(p[0])
|
|
||||||
if len(p) < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
min, _ = strconv.Atoi(p[1])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func validUser(user string) bool {
|
|
||||||
switch user {
|
|
||||||
case "adg", "bradfitz", "cbro", "andybons", "valsorda", "dmitshur", "katiehockman", "julieqiu":
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
fileRe = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+\.(tar\.gz|pkg|msi|zip)$`)
|
|
||||||
goGetRe = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// pretty returns a human-readable version of the given OS, Arch, or Kind.
|
|
||||||
func pretty(s string) string {
|
|
||||||
t, ok := prettyStrings[s]
|
|
||||||
if !ok {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
var prettyStrings = map[string]string{
|
|
||||||
"darwin": "macOS",
|
|
||||||
"freebsd": "FreeBSD",
|
|
||||||
"linux": "Linux",
|
|
||||||
"windows": "Windows",
|
|
||||||
|
|
||||||
"386": "x86",
|
|
||||||
"amd64": "x86-64",
|
|
||||||
"armv6l": "ARMv6",
|
|
||||||
"arm64": "ARMv8",
|
|
||||||
|
|
||||||
"archive": "Archive",
|
|
||||||
"installer": "Installer",
|
|
||||||
"source": "Source",
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package dl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseVersion(t *testing.T) {
|
|
||||||
for _, c := range []struct {
|
|
||||||
in string
|
|
||||||
maj, min int
|
|
||||||
tail string
|
|
||||||
}{
|
|
||||||
{"go1.5", 5, 0, ""},
|
|
||||||
{"go1.5beta1", 5, 0, "beta1"},
|
|
||||||
{"go1.5.1", 5, 1, ""},
|
|
||||||
{"go1.5.1rc1", 5, 1, "rc1"},
|
|
||||||
} {
|
|
||||||
maj, min, tail := parseVersion(c.in)
|
|
||||||
if maj != c.maj || min != c.min || tail != c.tail {
|
|
||||||
t.Errorf("parseVersion(%q) = %v, %v, %q; want %v, %v, %q",
|
|
||||||
c.in, maj, min, tail, c.maj, c.min, c.tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileOrder(t *testing.T) {
|
|
||||||
fs := []File{
|
|
||||||
{Filename: "go1.3.src.tar.gz", Version: "go1.3", OS: "", Arch: "", Kind: "source"},
|
|
||||||
{Filename: "go1.3.1.src.tar.gz", Version: "go1.3.1", OS: "", Arch: "", Kind: "source"},
|
|
||||||
{Filename: "go1.3.linux-amd64.tar.gz", Version: "go1.3", OS: "linux", Arch: "amd64", Kind: "archive"},
|
|
||||||
{Filename: "go1.3.1.linux-amd64.tar.gz", Version: "go1.3.1", OS: "linux", Arch: "amd64", Kind: "archive"},
|
|
||||||
{Filename: "go1.3.darwin-amd64.tar.gz", Version: "go1.3", OS: "darwin", Arch: "amd64", Kind: "archive"},
|
|
||||||
{Filename: "go1.3.darwin-amd64.pkg", Version: "go1.3", OS: "darwin", Arch: "amd64", Kind: "installer"},
|
|
||||||
{Filename: "go1.3.darwin-386.tar.gz", Version: "go1.3", OS: "darwin", Arch: "386", Kind: "archive"},
|
|
||||||
{Filename: "go1.3beta1.linux-amd64.tar.gz", Version: "go1.3beta1", OS: "linux", Arch: "amd64", Kind: "archive"},
|
|
||||||
{Filename: "go1.3beta2.linux-amd64.tar.gz", Version: "go1.3beta2", OS: "linux", Arch: "amd64", Kind: "archive"},
|
|
||||||
{Filename: "go1.3rc1.linux-amd64.tar.gz", Version: "go1.3rc1", OS: "linux", Arch: "amd64", Kind: "archive"},
|
|
||||||
{Filename: "go1.2.linux-amd64.tar.gz", Version: "go1.2", OS: "linux", Arch: "amd64", Kind: "archive"},
|
|
||||||
{Filename: "go1.2.2.linux-amd64.tar.gz", Version: "go1.2.2", OS: "linux", Arch: "amd64", Kind: "archive"},
|
|
||||||
}
|
|
||||||
sort.Sort(fileOrder(fs))
|
|
||||||
var s []string
|
|
||||||
for _, f := range fs {
|
|
||||||
s = append(s, f.Filename)
|
|
||||||
}
|
|
||||||
got := strings.Join(s, "\n")
|
|
||||||
want := strings.Join([]string{
|
|
||||||
"go1.3.1.src.tar.gz",
|
|
||||||
"go1.3.1.linux-amd64.tar.gz",
|
|
||||||
"go1.3.src.tar.gz",
|
|
||||||
"go1.3.darwin-386.tar.gz",
|
|
||||||
"go1.3.darwin-amd64.tar.gz",
|
|
||||||
"go1.3.darwin-amd64.pkg",
|
|
||||||
"go1.3.linux-amd64.tar.gz",
|
|
||||||
"go1.2.2.linux-amd64.tar.gz",
|
|
||||||
"go1.2.linux-amd64.tar.gz",
|
|
||||||
"go1.3rc1.linux-amd64.tar.gz",
|
|
||||||
"go1.3beta2.linux-amd64.tar.gz",
|
|
||||||
"go1.3beta1.linux-amd64.tar.gz",
|
|
||||||
}, "\n")
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("sort order is\n%s\nwant:\n%s", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilesToReleases(t *testing.T) {
|
|
||||||
fs := []File{
|
|
||||||
{Version: "go1.7.4", OS: "darwin"},
|
|
||||||
{Version: "go1.7.4", OS: "windows"},
|
|
||||||
{Version: "go1.7", OS: "darwin"},
|
|
||||||
{Version: "go1.7", OS: "windows"},
|
|
||||||
{Version: "go1.6.2", OS: "darwin"},
|
|
||||||
{Version: "go1.6.2", OS: "windows"},
|
|
||||||
{Version: "go1.6", OS: "darwin"},
|
|
||||||
{Version: "go1.6", OS: "windows"},
|
|
||||||
{Version: "go1.5.2", OS: "darwin"},
|
|
||||||
{Version: "go1.5.2", OS: "windows"},
|
|
||||||
{Version: "go1.5", OS: "darwin"},
|
|
||||||
{Version: "go1.5", OS: "windows"},
|
|
||||||
{Version: "go1.5beta1", OS: "windows"},
|
|
||||||
}
|
|
||||||
stable, unstable, archive := filesToReleases(fs)
|
|
||||||
if got, want := list(stable), "go1.7.4, go1.6.2"; got != want {
|
|
||||||
t.Errorf("stable = %q; want %q", got, want)
|
|
||||||
}
|
|
||||||
if got, want := list(unstable), ""; got != want {
|
|
||||||
t.Errorf("unstable = %q; want %q", got, want)
|
|
||||||
}
|
|
||||||
if got, want := list(archive), "go1.7, go1.6, go1.5.2, go1.5"; got != want {
|
|
||||||
t.Errorf("archive = %q; want %q", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOldUnstableNotShown(t *testing.T) {
|
|
||||||
fs := []File{
|
|
||||||
{Version: "go1.7.4"},
|
|
||||||
{Version: "go1.7"},
|
|
||||||
{Version: "go1.7beta1"},
|
|
||||||
}
|
|
||||||
_, unstable, _ := filesToReleases(fs)
|
|
||||||
if len(unstable) != 0 {
|
|
||||||
t.Errorf("got unstable, want none")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A new beta should show up under unstable, but not show up under archive. See golang.org/issue/29669.
|
|
||||||
func TestNewUnstableShownOnce(t *testing.T) {
|
|
||||||
fs := []File{
|
|
||||||
{Version: "go1.12beta2"},
|
|
||||||
{Version: "go1.11.4"},
|
|
||||||
{Version: "go1.11"},
|
|
||||||
{Version: "go1.10.7"},
|
|
||||||
{Version: "go1.10"},
|
|
||||||
{Version: "go1.9"},
|
|
||||||
}
|
|
||||||
stable, unstable, archive := filesToReleases(fs)
|
|
||||||
if got, want := list(stable), "go1.11.4, go1.10.7"; got != want {
|
|
||||||
t.Errorf("stable = %q; want %q", got, want)
|
|
||||||
}
|
|
||||||
if got, want := list(unstable), "go1.12beta2"; got != want {
|
|
||||||
t.Errorf("unstable = %q; want %q", got, want)
|
|
||||||
}
|
|
||||||
if got, want := list(archive), "go1.11, go1.10, go1.9"; got != want {
|
|
||||||
t.Errorf("archive = %q; want %q", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnstableShown(t *testing.T) {
|
|
||||||
fs := []File{
|
|
||||||
{Version: "go1.8beta2"},
|
|
||||||
{Version: "go1.8rc1"},
|
|
||||||
{Version: "go1.7.4"},
|
|
||||||
{Version: "go1.7"},
|
|
||||||
{Version: "go1.7beta1"},
|
|
||||||
}
|
|
||||||
_, unstable, _ := filesToReleases(fs)
|
|
||||||
// Show RCs ahead of betas.
|
|
||||||
if got, want := list(unstable), "go1.8rc1"; got != want {
|
|
||||||
t.Errorf("unstable = %q; want %q", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list returns a version list string for the given releases.
|
|
||||||
func list(rs []Release) string {
|
|
||||||
var s string
|
|
||||||
for i, r := range rs {
|
|
||||||
if i > 0 {
|
|
||||||
s += ", "
|
|
||||||
}
|
|
||||||
s += r.Version
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
|
@ -1,266 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build golangorg
|
|
||||||
|
|
||||||
package dl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"html"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"cloud.google.com/go/datastore"
|
|
||||||
"golang.org/x/tools/godoc/env"
|
|
||||||
"golang.org/x/tools/internal/memcache"
|
|
||||||
)
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
datastore *datastore.Client
|
|
||||||
memcache *memcache.CodecClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterHandlers(mux *http.ServeMux, dc *datastore.Client, mc *memcache.Client) {
|
|
||||||
s := server{dc, mc.WithCodec(memcache.Gob)}
|
|
||||||
mux.HandleFunc("/dl", s.getHandler)
|
|
||||||
mux.HandleFunc("/dl/", s.getHandler) // also serves listHandler
|
|
||||||
mux.HandleFunc("/dl/upload", s.uploadHandler)
|
|
||||||
|
|
||||||
// NOTE(cbro): this only needs to be run once per project,
|
|
||||||
// and should be behind an admin login.
|
|
||||||
// TODO(cbro): move into a locally-run program? or remove?
|
|
||||||
// mux.HandleFunc("/dl/init", initHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// rootKey is the ancestor of all File entities.
|
|
||||||
var rootKey = datastore.NameKey("FileRoot", "root", nil)
|
|
||||||
|
|
||||||
func (h server) listHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "GET" {
|
|
||||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := r.Context()
|
|
||||||
var d listTemplateData
|
|
||||||
|
|
||||||
if err := h.memcache.Get(ctx, cacheKey, &d); err != nil {
|
|
||||||
if err != memcache.ErrCacheMiss {
|
|
||||||
log.Printf("ERROR cache get error: %v", err)
|
|
||||||
// NOTE(cbro): continue to hit datastore if the memcache is down.
|
|
||||||
}
|
|
||||||
|
|
||||||
var fs []File
|
|
||||||
q := datastore.NewQuery("File").Ancestor(rootKey)
|
|
||||||
if _, err := h.datastore.GetAll(ctx, q, &fs); err != nil {
|
|
||||||
log.Printf("ERROR error listing: %v", err)
|
|
||||||
http.Error(w, "Could not get download page. Try again in a few minutes.", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.Stable, d.Unstable, d.Archive = filesToReleases(fs)
|
|
||||||
if len(d.Stable) > 0 {
|
|
||||||
d.Featured = filesToFeatured(d.Stable[0].Files)
|
|
||||||
}
|
|
||||||
|
|
||||||
item := &memcache.Item{Key: cacheKey, Object: &d, Expiration: cacheDuration}
|
|
||||||
if err := h.memcache.Set(ctx, item); err != nil {
|
|
||||||
log.Printf("ERROR cache set error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Query().Get("mode") == "json" {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
enc := json.NewEncoder(w)
|
|
||||||
enc.SetIndent("", " ")
|
|
||||||
if err := enc.Encode(d.Stable); err != nil {
|
|
||||||
log.Printf("ERROR rendering JSON for releases: %v", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := listTemplate.ExecuteTemplate(w, "root", d); err != nil {
|
|
||||||
log.Printf("ERROR executing template: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h server) uploadHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "POST" {
|
|
||||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
// Authenticate using a user token (same as gomote).
|
|
||||||
user := r.FormValue("user")
|
|
||||||
if !validUser(user) {
|
|
||||||
http.Error(w, "bad user", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.FormValue("key") != h.userKey(ctx, user) {
|
|
||||||
http.Error(w, "bad key", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var f File
|
|
||||||
defer r.Body.Close()
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&f); err != nil {
|
|
||||||
log.Printf("ERROR decoding upload JSON: %v", err)
|
|
||||||
http.Error(w, "Something broke", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if f.Filename == "" {
|
|
||||||
http.Error(w, "Must provide Filename", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if f.Uploaded.IsZero() {
|
|
||||||
f.Uploaded = time.Now()
|
|
||||||
}
|
|
||||||
k := datastore.NameKey("File", f.Filename, rootKey)
|
|
||||||
if _, err := h.datastore.Put(ctx, k, &f); err != nil {
|
|
||||||
log.Printf("ERROR File entity: %v", err)
|
|
||||||
http.Error(w, "could not put File entity", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := h.memcache.Delete(ctx, cacheKey); err != nil {
|
|
||||||
log.Printf("ERROR delete error: %v", err)
|
|
||||||
}
|
|
||||||
io.WriteString(w, "OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h server) getHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
isGoGet := (r.Method == "GET" || r.Method == "HEAD") && r.FormValue("go-get") == "1"
|
|
||||||
// For go get, we need to serve the same meta tags at /dl for cmd/go to
|
|
||||||
// validate against the import path.
|
|
||||||
if r.URL.Path == "/dl" && isGoGet {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
fmt.Fprintf(w, `<!DOCTYPE html><html><head>
|
|
||||||
<meta name="go-import" content="golang.org/dl git https://go.googlesource.com/dl">
|
|
||||||
</head></html>`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.URL.Path == "/dl" {
|
|
||||||
http.Redirect(w, r, "/dl/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
name := strings.TrimPrefix(r.URL.Path, "/dl/")
|
|
||||||
var redirectURL string
|
|
||||||
switch {
|
|
||||||
case name == "":
|
|
||||||
h.listHandler(w, r)
|
|
||||||
return
|
|
||||||
case fileRe.MatchString(name):
|
|
||||||
http.Redirect(w, r, downloadBaseURL+name, http.StatusFound)
|
|
||||||
return
|
|
||||||
case name == "gotip":
|
|
||||||
redirectURL = "https://godoc.org/golang.org/dl/gotip"
|
|
||||||
case goGetRe.MatchString(name):
|
|
||||||
redirectURL = "https://golang.org/dl/#" + name
|
|
||||||
default:
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
if !isGoGet {
|
|
||||||
w.Header().Set("Location", redirectURL)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, `<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="go-import" content="golang.org/dl git https://go.googlesource.com/dl">
|
|
||||||
<meta http-equiv="refresh" content="0; url=%s">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
Nothing to see here; <a href="%s">move along</a>.
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`, html.EscapeString(redirectURL), html.EscapeString(redirectURL))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h server) initHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var fileRoot struct {
|
|
||||||
Root string
|
|
||||||
}
|
|
||||||
ctx := r.Context()
|
|
||||||
k := rootKey
|
|
||||||
_, err := h.datastore.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
|
|
||||||
err := tx.Get(k, &fileRoot)
|
|
||||||
if err != nil && err != datastore.ErrNoSuchEntity {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tx.Put(k, &fileRoot)
|
|
||||||
return err
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
io.WriteString(w, "OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h server) userKey(c context.Context, user string) string {
|
|
||||||
hash := hmac.New(md5.New, []byte(h.secret(c)))
|
|
||||||
hash.Write([]byte("user-" + user))
|
|
||||||
return fmt.Sprintf("%x", hash.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code below copied from x/build/app/key
|
|
||||||
|
|
||||||
var theKey struct {
|
|
||||||
sync.RWMutex
|
|
||||||
builderKey
|
|
||||||
}
|
|
||||||
|
|
||||||
type builderKey struct {
|
|
||||||
Secret string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *builderKey) Key() *datastore.Key {
|
|
||||||
return datastore.NameKey("BuilderKey", "root", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h server) secret(ctx context.Context) string {
|
|
||||||
// check with rlock
|
|
||||||
theKey.RLock()
|
|
||||||
k := theKey.Secret
|
|
||||||
theKey.RUnlock()
|
|
||||||
if k != "" {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare to fill; check with lock and keep lock
|
|
||||||
theKey.Lock()
|
|
||||||
defer theKey.Unlock()
|
|
||||||
if theKey.Secret != "" {
|
|
||||||
return theKey.Secret
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill
|
|
||||||
if err := h.datastore.Get(ctx, theKey.Key(), &theKey.builderKey); err != nil {
|
|
||||||
if err == datastore.ErrNoSuchEntity {
|
|
||||||
// If the key is not stored in datastore, write it.
|
|
||||||
// This only happens at the beginning of a new deployment.
|
|
||||||
// The code is left here for SDK use and in case a fresh
|
|
||||||
// deployment is ever needed. "gophers rule" is not the
|
|
||||||
// real key.
|
|
||||||
if env.IsProd() {
|
|
||||||
panic("lost key from datastore")
|
|
||||||
}
|
|
||||||
theKey.Secret = "gophers rule"
|
|
||||||
h.datastore.Put(ctx, theKey.Key(), &theKey.builderKey)
|
|
||||||
return theKey.Secret
|
|
||||||
}
|
|
||||||
panic("cannot load builder key: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return theKey.Secret
|
|
||||||
}
|
|
277
godoc/dl/tmpl.go
277
godoc/dl/tmpl.go
|
@ -1,277 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package dl
|
|
||||||
|
|
||||||
// TODO(adg): refactor this to use the tools/godoc/static template.
|
|
||||||
|
|
||||||
const templateHTML = `
|
|
||||||
{{define "root"}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
||||||
<title>Downloads - The Go Programming Language</title>
|
|
||||||
<link type="text/css" rel="stylesheet" href="/lib/godoc/style.css">
|
|
||||||
<script type="text/javascript">window.initFuncs = [];</script>
|
|
||||||
<style>
|
|
||||||
table.codetable {
|
|
||||||
margin-left: 20px; margin-right: 20px;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
table.codetable tr {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
table.codetable tr:nth-child(2n), table.codetable tr.first {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
table.codetable td, table.codetable th {
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 6px 10px;
|
|
||||||
}
|
|
||||||
table.codetable tt {
|
|
||||||
font-size: xx-small;
|
|
||||||
}
|
|
||||||
table.codetable tr.highlight td {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
a.downloadBox {
|
|
||||||
display: block;
|
|
||||||
color: #222;
|
|
||||||
border: 1px solid #375EAB;
|
|
||||||
border-radius: 5px;
|
|
||||||
background: #E0EBF5;
|
|
||||||
width: 280px;
|
|
||||||
float: left;
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
a.downloadBox:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.downloadBox .platform {
|
|
||||||
font-size: large;
|
|
||||||
}
|
|
||||||
.downloadBox .filename {
|
|
||||||
color: #375EAB;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
|
||||||
a.downloadBox:hover .filename {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.downloadBox .size {
|
|
||||||
font-size: small;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.downloadBox .reqs {
|
|
||||||
font-size: small;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.downloadBox .checksum {
|
|
||||||
font-size: 5pt;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="topbar"><div class="container">
|
|
||||||
|
|
||||||
<div class="top-heading"><a href="/">The Go Programming Language</a></div>
|
|
||||||
<form method="GET" action="/search">
|
|
||||||
<div id="menu">
|
|
||||||
<a href="/doc/">Documents</a>
|
|
||||||
<a href="/pkg/">Packages</a>
|
|
||||||
<a href="/project/">The Project</a>
|
|
||||||
<a href="/help/">Help</a>
|
|
||||||
<a href="/blog/">Blog</a>
|
|
||||||
<span class="search-box"><input type="search" id="search" name="q" placeholder="Search" aria-label="Search" required><button type="submit"><span><!-- magnifying glass: --><svg width="24" height="24" viewBox="0 0 24 24"><title>submit search</title><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/><path d="M0 0h24v24H0z" fill="none"/></svg></span></button></span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</div></div>
|
|
||||||
|
|
||||||
<div id="page">
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<h1>Downloads</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
After downloading a binary release suitable for your system,
|
|
||||||
please follow the <a href="/doc/install">installation instructions</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
If you are building from source,
|
|
||||||
follow the <a href="/doc/install/source">source installation instructions</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
See the <a href="/doc/devel/release.html">release history</a> for more
|
|
||||||
information about Go releases.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{{with .Featured}}
|
|
||||||
<h3 id="featured">Featured downloads</h3>
|
|
||||||
{{range .}}
|
|
||||||
{{template "download" .}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<div style="clear: both;"></div>
|
|
||||||
|
|
||||||
{{with .Stable}}
|
|
||||||
<h3 id="stable">Stable versions</h3>
|
|
||||||
{{template "releases" .}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{with .Unstable}}
|
|
||||||
<h3 id="unstable">Unstable version</h3>
|
|
||||||
{{template "releases" .}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{with .Archive}}
|
|
||||||
<div class="toggle" id="archive">
|
|
||||||
<div class="collapsed">
|
|
||||||
<h3 class="toggleButton" title="Click to show versions">Archived versions▹</h3>
|
|
||||||
</div>
|
|
||||||
<div class="expanded">
|
|
||||||
<h3 class="toggleButton" title="Click to hide versions">Archived versions▾</h3>
|
|
||||||
{{template "releases" .}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<div id="footer">
|
|
||||||
<p>
|
|
||||||
Except as
|
|
||||||
<a href="https://developers.google.com/site-policies#restrictions">noted</a>,
|
|
||||||
the content of this page is licensed under the Creative Commons
|
|
||||||
Attribution 3.0 License,<br>
|
|
||||||
and code is licensed under a <a href="http://golang.org/LICENSE">BSD license</a>.<br>
|
|
||||||
<a href="http://golang.org/doc/tos.html">Terms of Service</a> |
|
|
||||||
<a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
|
|
||||||
</p>
|
|
||||||
</div><!-- #footer -->
|
|
||||||
|
|
||||||
</div><!-- .container -->
|
|
||||||
</div><!-- #page -->
|
|
||||||
<script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-11222381-2', 'auto');
|
|
||||||
ga('send', 'pageview');
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
<script src="/lib/godoc/jquery.js"></script>
|
|
||||||
<script src="/lib/godoc/godocs.js"></script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('a.download').click(function(e) {
|
|
||||||
// Try using the link text as the file name,
|
|
||||||
// unless there's a child element of class 'filename'.
|
|
||||||
var filename = $(this).text();
|
|
||||||
var child = $(this).find('.filename');
|
|
||||||
if (child.length > 0) {
|
|
||||||
filename = child.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This must be kept in sync with the filenameRE in godocs.js.
|
|
||||||
var filenameRE = /^go1\.\d+(\.\d+)?([a-z0-9]+)?\.([a-z0-9]+)(-[a-z0-9]+)?(-osx10\.[68])?\.([a-z.]+)$/;
|
|
||||||
var m = filenameRE.exec(filename);
|
|
||||||
if (!m) {
|
|
||||||
// Don't redirect to the download page if it won't recognize this file.
|
|
||||||
// (Should not happen.)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dest = "/doc/install";
|
|
||||||
if (filename.indexOf(".src.") != -1) {
|
|
||||||
dest += "/source";
|
|
||||||
}
|
|
||||||
dest += "?download=" + filename;
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
window.location = dest;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "releases"}}
|
|
||||||
{{range .}}
|
|
||||||
<div class="toggle{{if .Visible}}Visible{{end}}" id="{{.Version}}">
|
|
||||||
<div class="collapsed">
|
|
||||||
<h2 class="toggleButton" title="Click to show downloads for this version">{{.Version}} ▹</h2>
|
|
||||||
</div>
|
|
||||||
<div class="expanded">
|
|
||||||
<h2 class="toggleButton" title="Click to hide downloads for this version">{{.Version}} ▾</h2>
|
|
||||||
{{if .Stable}}{{else}}
|
|
||||||
<p>This is an <b>unstable</b> version of Go. Use with caution.</p>
|
|
||||||
<p>If you already have Go installed, you can install this version by running:</p>
|
|
||||||
<pre>
|
|
||||||
go get golang.org/dl/{{.Version}}
|
|
||||||
</pre>
|
|
||||||
<p>Then, use the <code>{{.Version}}</code> command instead of the <code>go</code> command to use {{.Version}}.</p>
|
|
||||||
{{end}}
|
|
||||||
{{template "files" .}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "files"}}
|
|
||||||
<table class="codetable">
|
|
||||||
<thead>
|
|
||||||
<tr class="first">
|
|
||||||
<th>File name</th>
|
|
||||||
<th>Kind</th>
|
|
||||||
<th>OS</th>
|
|
||||||
<th>Arch</th>
|
|
||||||
<th>Size</th>
|
|
||||||
{{/* Use the checksum type of the first file for the column heading. */}}
|
|
||||||
<th>{{(index .Files 0).ChecksumType}} Checksum</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
{{if .SplitPortTable}}
|
|
||||||
{{range .Files}}{{if .PrimaryPort}}{{template "file" .}}{{end}}{{end}}
|
|
||||||
|
|
||||||
{{/* TODO(cbro): add a link to an explanatory doc page */}}
|
|
||||||
<tr class="first"><th colspan="6" class="first">Other Ports</th></tr>
|
|
||||||
{{range .Files}}{{if not .PrimaryPort}}{{template "file" .}}{{end}}{{end}}
|
|
||||||
{{else}}
|
|
||||||
{{range .Files}}{{template "file" .}}{{end}}
|
|
||||||
{{end}}
|
|
||||||
</table>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "file"}}
|
|
||||||
<tr{{if .Highlight}} class="highlight"{{end}}>
|
|
||||||
<td class="filename"><a class="download" href="{{.URL}}">{{.Filename}}</a></td>
|
|
||||||
<td>{{pretty .Kind}}</td>
|
|
||||||
<td>{{.PrettyOS}}</td>
|
|
||||||
<td>{{pretty .Arch}}</td>
|
|
||||||
<td>{{.PrettySize}}</td>
|
|
||||||
<td><tt>{{.PrettyChecksum}}</tt></td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "download"}}
|
|
||||||
<a class="download downloadBox" href="{{.URL}}">
|
|
||||||
<div class="platform">{{.Platform}}</div>
|
|
||||||
{{with .Requirements}}<div class="reqs">{{.}}</div>{{end}}
|
|
||||||
<div>
|
|
||||||
<span class="filename">{{.Filename}}</span>
|
|
||||||
{{if .Size}}<span class="size">({{.PrettySize}})</span>{{end}}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
`
|
|
|
@ -1,170 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package proxy proxies requests to the playground's compile and share handlers.
|
|
||||||
// It is designed to run only on the instance of godoc that serves golang.org.
|
|
||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/tools/godoc/env"
|
|
||||||
)
|
|
||||||
|
|
||||||
const playgroundURL = "https://play.golang.org"
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
Errors string
|
|
||||||
Events []Event
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
Message string
|
|
||||||
Kind string // "stdout" or "stderr"
|
|
||||||
Delay time.Duration // time to wait before printing Message
|
|
||||||
}
|
|
||||||
|
|
||||||
const expires = 7 * 24 * time.Hour // 1 week
|
|
||||||
var cacheControlHeader = fmt.Sprintf("public, max-age=%d", int(expires.Seconds()))
|
|
||||||
|
|
||||||
func RegisterHandlers(mux *http.ServeMux) {
|
|
||||||
mux.HandleFunc("/compile", compile)
|
|
||||||
mux.HandleFunc("/share", share)
|
|
||||||
}
|
|
||||||
|
|
||||||
func compile(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "POST" {
|
|
||||||
http.Error(w, "I only answer to POST requests.", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
body := r.FormValue("body")
|
|
||||||
res := &Response{}
|
|
||||||
req := &Request{Body: body}
|
|
||||||
if err := makeCompileRequest(ctx, req, res); err != nil {
|
|
||||||
log.Printf("ERROR compile error: %v", err)
|
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var out interface{}
|
|
||||||
switch r.FormValue("version") {
|
|
||||||
case "2":
|
|
||||||
out = res
|
|
||||||
default: // "1"
|
|
||||||
out = struct {
|
|
||||||
CompileErrors string `json:"compile_errors"`
|
|
||||||
Output string `json:"output"`
|
|
||||||
}{res.Errors, flatten(res.Events)}
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(out)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ERROR encoding response: %v", err)
|
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expiresTime := time.Now().Add(expires).UTC()
|
|
||||||
w.Header().Set("Expires", expiresTime.Format(time.RFC1123))
|
|
||||||
w.Header().Set("Cache-Control", cacheControlHeader)
|
|
||||||
w.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// makePlaygroundRequest sends the given Request to the playground compile
|
|
||||||
// endpoint and stores the response in the given Response.
|
|
||||||
func makeCompileRequest(ctx context.Context, req *Request, res *Response) error {
|
|
||||||
reqJ, err := json.Marshal(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("marshalling request: %v", err)
|
|
||||||
}
|
|
||||||
hReq, _ := http.NewRequest("POST", playgroundURL+"/compile", bytes.NewReader(reqJ))
|
|
||||||
hReq.Header.Set("Content-Type", "application/json")
|
|
||||||
hReq = hReq.WithContext(ctx)
|
|
||||||
|
|
||||||
r, err := http.DefaultClient.Do(hReq)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("making request: %v", err)
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
if r.StatusCode != http.StatusOK {
|
|
||||||
b, _ := ioutil.ReadAll(r.Body)
|
|
||||||
return fmt.Errorf("bad status: %v body:\n%s", r.Status, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(res); err != nil {
|
|
||||||
return fmt.Errorf("unmarshalling response: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// flatten takes a sequence of Events and returns their contents, concatenated.
|
|
||||||
func flatten(seq []Event) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for _, e := range seq {
|
|
||||||
buf.WriteString(e.Message)
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func share(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if googleCN(r) {
|
|
||||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK(cbro): use a simple proxy rather than httputil.ReverseProxy because of Issue #28168.
|
|
||||||
// TODO: investigate using ReverseProxy with a Director, unsetting whatever's necessary to make that work.
|
|
||||||
req, _ := http.NewRequest("POST", playgroundURL+"/share", r.Body)
|
|
||||||
req.Header.Set("Content-Type", r.Header.Get("Content-Type"))
|
|
||||||
req = req.WithContext(r.Context())
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ERROR share error: %v", err)
|
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
copyHeader := func(k string) {
|
|
||||||
if v := resp.Header.Get(k); v != "" {
|
|
||||||
w.Header().Set(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
copyHeader("Content-Type")
|
|
||||||
copyHeader("Content-Length")
|
|
||||||
defer resp.Body.Close()
|
|
||||||
w.WriteHeader(resp.StatusCode)
|
|
||||||
io.Copy(w, resp.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func googleCN(r *http.Request) bool {
|
|
||||||
if r.FormValue("googlecn") != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !env.IsProd() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(r.Host, ".cn") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
switch r.Header.Get("X-AppEngine-Country") {
|
|
||||||
case "", "ZZ", "CN":
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build golangorg
|
|
||||||
|
|
||||||
// Package short implements a simple URL shortener, serving an administrative
|
|
||||||
// interface at /s and shortened urls from /s/key.
|
|
||||||
// It is designed to run only on the instance of godoc that serves golang.org.
|
|
||||||
package short
|
|
||||||
|
|
||||||
// TODO(adg): collect statistics on URL visits
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"cloud.google.com/go/datastore"
|
|
||||||
"golang.org/x/tools/internal/memcache"
|
|
||||||
"google.golang.org/appengine/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
prefix = "/s"
|
|
||||||
kind = "Link"
|
|
||||||
baseURL = "https://golang.org" + prefix
|
|
||||||
)
|
|
||||||
|
|
||||||
// Link represents a short link.
|
|
||||||
type Link struct {
|
|
||||||
Key, Target string
|
|
||||||
}
|
|
||||||
|
|
||||||
var validKey = regexp.MustCompile(`^[a-zA-Z0-9-_.]+$`)
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
datastore *datastore.Client
|
|
||||||
memcache *memcache.CodecClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterHandlers(mux *http.ServeMux, dc *datastore.Client, mc *memcache.Client) {
|
|
||||||
s := server{dc, mc.WithCodec(memcache.JSON)}
|
|
||||||
mux.HandleFunc(prefix+"/", s.linkHandler)
|
|
||||||
|
|
||||||
// TODO(cbro): move storage of the links to a text file in Gerrit.
|
|
||||||
// Disable the admin handler until that happens, since GAE Flex doesn't support
|
|
||||||
// the "google.golang.org/appengine/user" package.
|
|
||||||
// See golang.org/issue/29988 and golang.org/issue/27205#issuecomment-418673218.
|
|
||||||
// mux.HandleFunc(prefix, adminHandler)
|
|
||||||
mux.HandleFunc(prefix, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
io.WriteString(w, "Link creation temporarily unavailable. See golang.org/issue/29988.")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// linkHandler services requests to short URLs.
|
|
||||||
// http://golang.org/s/key
|
|
||||||
// It consults memcache and datastore for the Link for key.
|
|
||||||
// It then sends a redirects or an error message.
|
|
||||||
func (h server) linkHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
key := r.URL.Path[len(prefix)+1:]
|
|
||||||
if !validKey.MatchString(key) {
|
|
||||||
http.Error(w, "not found", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var link Link
|
|
||||||
if err := h.memcache.Get(ctx, cacheKey(key), &link); err != nil {
|
|
||||||
k := datastore.NameKey(kind, key, nil)
|
|
||||||
err = h.datastore.Get(ctx, k, &link)
|
|
||||||
switch err {
|
|
||||||
case datastore.ErrNoSuchEntity:
|
|
||||||
http.Error(w, "not found", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
default: // != nil
|
|
||||||
log.Printf("ERROR %q: %v", key, err)
|
|
||||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
case nil:
|
|
||||||
item := &memcache.Item{
|
|
||||||
Key: cacheKey(key),
|
|
||||||
Object: &link,
|
|
||||||
}
|
|
||||||
if err := h.memcache.Set(ctx, item); err != nil {
|
|
||||||
log.Printf("WARNING %q: %v", key, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, link.Target, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
var adminTemplate = template.Must(template.New("admin").Parse(templateHTML))
|
|
||||||
|
|
||||||
// adminHandler serves an administrative interface.
|
|
||||||
func (h server) adminHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := r.Context()
|
|
||||||
|
|
||||||
if !user.IsAdmin(ctx) {
|
|
||||||
http.Error(w, "forbidden", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var newLink *Link
|
|
||||||
var doErr error
|
|
||||||
if r.Method == "POST" {
|
|
||||||
key := r.FormValue("key")
|
|
||||||
switch r.FormValue("do") {
|
|
||||||
case "Add":
|
|
||||||
newLink = &Link{key, r.FormValue("target")}
|
|
||||||
doErr = h.putLink(ctx, newLink)
|
|
||||||
case "Delete":
|
|
||||||
k := datastore.NameKey(kind, key, nil)
|
|
||||||
doErr = h.datastore.Delete(ctx, k)
|
|
||||||
default:
|
|
||||||
http.Error(w, "unknown action", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
err := h.memcache.Delete(ctx, cacheKey(key))
|
|
||||||
if err != nil && err != memcache.ErrCacheMiss {
|
|
||||||
log.Printf("WARNING %q: %v", key, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var links []*Link
|
|
||||||
q := datastore.NewQuery(kind).Order("Key")
|
|
||||||
if _, err := h.datastore.GetAll(ctx, q, &links); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
log.Printf("ERROR %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put the new link in the list if it's not there already.
|
|
||||||
// (Eventual consistency means that it might not show up
|
|
||||||
// immediately, which might be confusing for the user.)
|
|
||||||
if newLink != nil && doErr == nil {
|
|
||||||
found := false
|
|
||||||
for i := range links {
|
|
||||||
if links[i].Key == newLink.Key {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
links = append([]*Link{newLink}, links...)
|
|
||||||
}
|
|
||||||
newLink = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = struct {
|
|
||||||
BaseURL string
|
|
||||||
Prefix string
|
|
||||||
Links []*Link
|
|
||||||
New *Link
|
|
||||||
Error error
|
|
||||||
}{baseURL, prefix, links, newLink, doErr}
|
|
||||||
if err := adminTemplate.Execute(w, &data); err != nil {
|
|
||||||
log.Printf("ERROR adminTemplate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// putLink validates the provided link and puts it into the datastore.
|
|
||||||
func (h server) putLink(ctx context.Context, link *Link) error {
|
|
||||||
if !validKey.MatchString(link.Key) {
|
|
||||||
return errors.New("invalid key; must match " + validKey.String())
|
|
||||||
}
|
|
||||||
if _, err := url.Parse(link.Target); err != nil {
|
|
||||||
return fmt.Errorf("bad target: %v", err)
|
|
||||||
}
|
|
||||||
k := datastore.NameKey(kind, link.Key, nil)
|
|
||||||
_, err := h.datastore.Put(ctx, k, link)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// cacheKey returns a short URL key as a memcache key.
|
|
||||||
func cacheKey(key string) string {
|
|
||||||
return "link-" + key
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package short
|
|
||||||
|
|
||||||
const templateHTML = `
|
|
||||||
<!doctype HTML>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>golang.org URL shortener</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
input[type=text] {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
input, td, th {
|
|
||||||
color: #333;
|
|
||||||
font-family: Georgia, Times New Roman, serif;
|
|
||||||
}
|
|
||||||
input, td {
|
|
||||||
font-size: 14pt;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
font-size: 16pt;
|
|
||||||
text-align: left;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
.autoselect {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: #900;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
|
|
||||||
{{with .Error}}
|
|
||||||
<tr>
|
|
||||||
<th colspan="3">Error</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="error" colspan="3">{{.}}</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th>Key</th>
|
|
||||||
<th>Target</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<form method="POST" action="{{.Prefix}}">
|
|
||||||
<tr>
|
|
||||||
<td><input type="text" name="key"{{with .New}} value="{{.Key}}"{{end}}></td>
|
|
||||||
<td><input type="text" name="target"{{with .New}} value="{{.Target}}"{{end}}></td>
|
|
||||||
<td><input type="submit" name="do" value="Add">
|
|
||||||
</tr>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{{with .Links}}
|
|
||||||
<tr>
|
|
||||||
<th>Short Link</th>
|
|
||||||
<th> </th>
|
|
||||||
<th> </th>
|
|
||||||
</tr>
|
|
||||||
{{range .}}
|
|
||||||
<tr>
|
|
||||||
<td><input class="autoselect" type="text" orig="{{$.BaseURL}}/{{.Key}}" value="{{$.BaseURL}}/{{.Key}}"></td>
|
|
||||||
<td><input class="autoselect" type="text" orig="{{.Target}}" value="{{.Target}}"></td>
|
|
||||||
<td>
|
|
||||||
<form method="POST" action="{{$.Prefix}}">
|
|
||||||
<input type="hidden" name="key" value="{{.Key}}">
|
|
||||||
<input type="submit" name="do" value="Delete" class="delete">
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
|
|
||||||
<script type="text/javascript">window.jQuery || document.write(unescape("%3Cscript src='/doc/jquery.js' type='text/javascript'%3E%3C/script%3E"));</script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('.autoselect').each(function() {
|
|
||||||
$(this).click(function() {
|
|
||||||
$(this).select();
|
|
||||||
});
|
|
||||||
$(this).change(function() {
|
|
||||||
$(this).val($(this).attr('orig'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$('.delete').click(function(e) {
|
|
||||||
var link = $(this).closest('tr').find('input').first().val();
|
|
||||||
var ok = confirm('Delete this link?\n' + link);
|
|
||||||
if (!ok) {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||
`
|
|
|
@ -1,159 +0,0 @@
|
||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build golangorg
|
|
||||||
|
|
||||||
// Package memcache provides a minimally compatible interface for
|
|
||||||
// google.golang.org/appengine/memcache
|
|
||||||
// and stores the data in Redis (e.g., via Cloud Memorystore).
|
|
||||||
package memcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gomodule/redigo/redis"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrCacheMiss = errors.New("memcache: cache miss")
|
|
||||||
|
|
||||||
func New(addr string) *Client {
|
|
||||||
const maxConns = 20
|
|
||||||
|
|
||||||
pool := redis.NewPool(func() (redis.Conn, error) {
|
|
||||||
return redis.Dial("tcp", addr)
|
|
||||||
}, maxConns)
|
|
||||||
|
|
||||||
return &Client{
|
|
||||||
pool: pool,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
pool *redis.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
type CodecClient struct {
|
|
||||||
client *Client
|
|
||||||
codec Codec
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item struct {
|
|
||||||
Key string
|
|
||||||
Value []byte
|
|
||||||
Object interface{} // Used with Codec.
|
|
||||||
Expiration time.Duration // Read-only.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) WithCodec(codec Codec) *CodecClient {
|
|
||||||
return &CodecClient{
|
|
||||||
c, codec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Delete(ctx context.Context, key string) error {
|
|
||||||
conn, err := c.pool.GetContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
_, err = conn.Do("DEL", key)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CodecClient) Delete(ctx context.Context, key string) error {
|
|
||||||
return c.client.Delete(ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Set(ctx context.Context, item *Item) error {
|
|
||||||
if item.Value == nil {
|
|
||||||
return errors.New("nil item value")
|
|
||||||
}
|
|
||||||
return c.set(ctx, item.Key, item.Value, item.Expiration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CodecClient) Set(ctx context.Context, item *Item) error {
|
|
||||||
if item.Object == nil {
|
|
||||||
return errors.New("nil object value")
|
|
||||||
}
|
|
||||||
b, err := c.codec.Marshal(item.Object)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.client.set(ctx, item.Key, b, item.Expiration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) set(ctx context.Context, key string, value []byte, expiration time.Duration) error {
|
|
||||||
conn, err := c.pool.GetContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
if expiration == 0 {
|
|
||||||
_, err := conn.Do("SET", key, value)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(cbro): redis does not support expiry in units more granular than a second.
|
|
||||||
exp := int64(expiration.Seconds())
|
|
||||||
if exp == 0 {
|
|
||||||
// Redis doesn't allow a zero expiration, delete the key instead.
|
|
||||||
_, err := conn.Do("DEL", key)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.Do("SETEX", key, exp, value)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get gets the item.
|
|
||||||
func (c *Client) Get(ctx context.Context, key string) ([]byte, error) {
|
|
||||||
conn, err := c.pool.GetContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
b, err := redis.Bytes(conn.Do("GET", key))
|
|
||||||
if err == redis.ErrNil {
|
|
||||||
err = ErrCacheMiss
|
|
||||||
}
|
|
||||||
return b, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CodecClient) Get(ctx context.Context, key string, v interface{}) error {
|
|
||||||
b, err := c.client.Get(ctx, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.codec.Unmarshal(b, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
Gob = Codec{gobMarshal, gobUnmarshal}
|
|
||||||
JSON = Codec{json.Marshal, json.Unmarshal}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Codec struct {
|
|
||||||
Marshal func(interface{}) ([]byte, error)
|
|
||||||
Unmarshal func([]byte, interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func gobMarshal(v interface{}) ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := gob.NewEncoder(&buf).Encode(v); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func gobUnmarshal(data []byte, v interface{}) error {
|
|
||||||
return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build golangorg
|
|
||||||
|
|
||||||
package memcache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getClient(t *testing.T) *Client {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
addr := os.Getenv("GOLANG_REDIS_ADDR")
|
|
||||||
if addr == "" {
|
|
||||||
t.Skip("skipping because GOLANG_REDIS_ADDR is unset")
|
|
||||||
}
|
|
||||||
|
|
||||||
return New(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCacheMiss(t *testing.T) {
|
|
||||||
c := getClient(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
if _, err := c.Get(ctx, "doesnotexist"); err != ErrCacheMiss {
|
|
||||||
t.Errorf("got %v; want ErrCacheMiss", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExpiry(t *testing.T) {
|
|
||||||
c := getClient(t).WithCodec(Gob)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
key := "testexpiry"
|
|
||||||
|
|
||||||
firstTime := time.Now()
|
|
||||||
err := c.Set(ctx, &Item{
|
|
||||||
Key: key,
|
|
||||||
Object: firstTime,
|
|
||||||
Expiration: 3500 * time.Millisecond, // NOTE: check that non-rounded expiries work.
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Set: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var newTime time.Time
|
|
||||||
if err := c.Get(ctx, key, &newTime); err != nil {
|
|
||||||
t.Fatalf("Get: %v", err)
|
|
||||||
}
|
|
||||||
if !firstTime.Equal(newTime) {
|
|
||||||
t.Errorf("Get: got value %v, want %v", newTime, firstTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(4 * time.Second)
|
|
||||||
|
|
||||||
if err := c.Get(ctx, key, &newTime); err != ErrCacheMiss {
|
|
||||||
t.Errorf("Get: got %v, want ErrCacheMiss", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShortExpiry(t *testing.T) {
|
|
||||||
c := getClient(t).WithCodec(Gob)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
key := "testshortexpiry"
|
|
||||||
|
|
||||||
err := c.Set(ctx, &Item{
|
|
||||||
Key: key,
|
|
||||||
Value: []byte("ok"),
|
|
||||||
Expiration: time.Millisecond,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Set: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Get(ctx, key, nil); err != ErrCacheMiss {
|
|
||||||
t.Errorf("GetBytes: got %v, want ErrCacheMiss", err)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue