Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

998 linhas
26 KiB

  1. // Copyright 2017 The Go Authors. All rights reserved.
  2. //
  3. // Use of this source code is governed by a BSD-style
  4. // license that can be found in the LICENSE file or at
  5. // https://developers.google.com/open-source/licenses/bsd.
  6. // Command gddo-server is the GoPkgDoc server.
  7. package main
  8. import (
  9. "bytes"
  10. "crypto/md5"
  11. "encoding/json"
  12. "errors"
  13. "fmt"
  14. "go/build"
  15. "html/template"
  16. "io"
  17. "log"
  18. "net/http"
  19. "os"
  20. "path"
  21. "regexp"
  22. "runtime/debug"
  23. "sort"
  24. "strconv"
  25. "strings"
  26. "time"
  27. "cloud.google.com/go/compute/metadata"
  28. "cloud.google.com/go/logging"
  29. "github.com/spf13/viper"
  30. "golang.org/x/net/context"
  31. "google.golang.org/appengine"
  32. "github.com/golang/gddo/database"
  33. "github.com/golang/gddo/doc"
  34. "github.com/golang/gddo/gosrc"
  35. "github.com/golang/gddo/httputil"
  36. )
  37. const (
  38. jsonMIMEType = "application/json; charset=utf-8"
  39. textMIMEType = "text/plain; charset=utf-8"
  40. htmlMIMEType = "text/html; charset=utf-8"
  41. )
  42. var errUpdateTimeout = errors.New("refresh timeout")
  43. type httpError struct {
  44. status int // HTTP status code.
  45. err error // Optional reason for the HTTP error.
  46. }
  47. func (err *httpError) Error() string {
  48. if err.err != nil {
  49. return fmt.Sprintf("status %d, reason %s", err.status, err.err.Error())
  50. }
  51. return fmt.Sprintf("Status %d", err.status)
  52. }
  53. const (
  54. humanRequest = iota
  55. robotRequest
  56. queryRequest
  57. refreshRequest
  58. apiRequest
  59. )
  60. type crawlResult struct {
  61. pdoc *doc.Package
  62. err error
  63. }
  64. // getDoc gets the package documentation from the database or from the version
  65. // control system as needed.
  66. func getDoc(path string, requestType int) (*doc.Package, []database.Package, error) {
  67. if path == "-" {
  68. // A hack in the database package uses the path "-" to represent the
  69. // next document to crawl. Block "-" here so that requests to /- always
  70. // return not found.
  71. return nil, nil, &httpError{status: http.StatusNotFound}
  72. }
  73. pdoc, pkgs, nextCrawl, err := db.Get(path)
  74. if err != nil {
  75. return nil, nil, err
  76. }
  77. needsCrawl := false
  78. switch requestType {
  79. case queryRequest, apiRequest:
  80. needsCrawl = nextCrawl.IsZero() && len(pkgs) == 0
  81. case humanRequest:
  82. needsCrawl = nextCrawl.Before(time.Now())
  83. case robotRequest:
  84. needsCrawl = nextCrawl.IsZero() && len(pkgs) > 0
  85. }
  86. if !needsCrawl {
  87. return pdoc, pkgs, nil
  88. }
  89. c := make(chan crawlResult, 1)
  90. go func() {
  91. pdoc, err := crawlDoc("web ", path, pdoc, len(pkgs) > 0, nextCrawl)
  92. c <- crawlResult{pdoc, err}
  93. }()
  94. timeout := viper.GetDuration(ConfigGetTimeout)
  95. if pdoc == nil {
  96. timeout = viper.GetDuration(ConfigFirstGetTimeout)
  97. }
  98. select {
  99. case cr := <-c:
  100. err = cr.err
  101. if err == nil {
  102. pdoc = cr.pdoc
  103. }
  104. case <-time.After(timeout):
  105. err = errUpdateTimeout
  106. }
  107. switch {
  108. case err == nil:
  109. return pdoc, pkgs, nil
  110. case gosrc.IsNotFound(err):
  111. return nil, nil, err
  112. case pdoc != nil:
  113. log.Printf("Serving %q from database after error getting doc: %v", path, err)
  114. return pdoc, pkgs, nil
  115. case err == errUpdateTimeout:
  116. log.Printf("Serving %q as not found after timeout getting doc", path)
  117. return nil, nil, &httpError{status: http.StatusNotFound}
  118. default:
  119. return nil, nil, err
  120. }
  121. }
  122. func templateExt(req *http.Request) string {
  123. if httputil.NegotiateContentType(req, []string{"text/html", "text/plain"}, "text/html") == "text/plain" {
  124. return ".txt"
  125. }
  126. return ".html"
  127. }
  128. var (
  129. robotPat = regexp.MustCompile(`(:?\+https?://)|(?:\Wbot\W)|(?:^Python-urllib)|(?:^Go )|(?:^Java/)`)
  130. )
  131. func isRobot(req *http.Request) bool {
  132. if robotPat.MatchString(req.Header.Get("User-Agent")) {
  133. return true
  134. }
  135. host := httputil.StripPort(req.RemoteAddr)
  136. n, err := db.IncrementCounter(host, 1)
  137. if err != nil {
  138. log.Printf("error incrementing counter for %s, %v", host, err)
  139. return false
  140. }
  141. if n > viper.GetFloat64(ConfigRobotThreshold) {
  142. log.Printf("robot %.2f %s %s", n, host, req.Header.Get("User-Agent"))
  143. return true
  144. }
  145. return false
  146. }
  147. func popularLinkReferral(req *http.Request) bool {
  148. return strings.HasSuffix(req.Header.Get("Referer"), "//"+req.Host+"/")
  149. }
  150. func isView(req *http.Request, key string) bool {
  151. rq := req.URL.RawQuery
  152. return strings.HasPrefix(rq, key) &&
  153. (len(rq) == len(key) || rq[len(key)] == '=' || rq[len(key)] == '&')
  154. }
  155. // httpEtag returns the package entity tag used in HTTP transactions.
  156. func httpEtag(pdoc *doc.Package, pkgs []database.Package, importerCount int, flashMessages []flashMessage) string {
  157. b := make([]byte, 0, 128)
  158. b = strconv.AppendInt(b, pdoc.Updated.Unix(), 16)
  159. b = append(b, 0)
  160. b = append(b, pdoc.Etag...)
  161. if importerCount >= 8 {
  162. importerCount = 8
  163. }
  164. b = append(b, 0)
  165. b = strconv.AppendInt(b, int64(importerCount), 16)
  166. for _, pkg := range pkgs {
  167. b = append(b, 0)
  168. b = append(b, pkg.Path...)
  169. b = append(b, 0)
  170. b = append(b, pkg.Synopsis...)
  171. }
  172. if viper.GetBool(ConfigSidebar) {
  173. b = append(b, "\000xsb"...)
  174. }
  175. for _, m := range flashMessages {
  176. b = append(b, 0)
  177. b = append(b, m.ID...)
  178. for _, a := range m.Args {
  179. b = append(b, 1)
  180. b = append(b, a...)
  181. }
  182. }
  183. h := md5.New()
  184. h.Write(b)
  185. b = h.Sum(b[:0])
  186. return fmt.Sprintf("\"%x\"", b)
  187. }
  188. func servePackage(resp http.ResponseWriter, req *http.Request) error {
  189. p := path.Clean(req.URL.Path)
  190. if strings.HasPrefix(p, "/pkg/") {
  191. p = p[len("/pkg"):]
  192. }
  193. if p != req.URL.Path {
  194. http.Redirect(resp, req, p, http.StatusMovedPermanently)
  195. return nil
  196. }
  197. if isView(req, "status.svg") {
  198. statusImageHandlerSVG.ServeHTTP(resp, req)
  199. return nil
  200. }
  201. if isView(req, "status.png") {
  202. statusImageHandlerPNG.ServeHTTP(resp, req)
  203. return nil
  204. }
  205. requestType := humanRequest
  206. if isRobot(req) {
  207. requestType = robotRequest
  208. }
  209. importPath := strings.TrimPrefix(req.URL.Path, "/")
  210. pdoc, pkgs, err := getDoc(importPath, requestType)
  211. if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
  212. // To prevent dumb clients from following redirect loops, respond with
  213. // status 404 if the target document is not found.
  214. if _, _, err := getDoc(e.Redirect, requestType); gosrc.IsNotFound(err) {
  215. return &httpError{status: http.StatusNotFound}
  216. }
  217. u := "/" + e.Redirect
  218. if req.URL.RawQuery != "" {
  219. u += "?" + req.URL.RawQuery
  220. }
  221. setFlashMessages(resp, []flashMessage{{ID: "redir", Args: []string{importPath}}})
  222. http.Redirect(resp, req, u, http.StatusFound)
  223. return nil
  224. }
  225. if err != nil {
  226. return err
  227. }
  228. flashMessages := getFlashMessages(resp, req)
  229. if pdoc == nil {
  230. if len(pkgs) == 0 {
  231. return &httpError{status: http.StatusNotFound}
  232. }
  233. pdocChild, _, _, err := db.Get(pkgs[0].Path)
  234. if err != nil {
  235. return err
  236. }
  237. pdoc = &doc.Package{
  238. ProjectName: pdocChild.ProjectName,
  239. ProjectRoot: pdocChild.ProjectRoot,
  240. ProjectURL: pdocChild.ProjectURL,
  241. ImportPath: importPath,
  242. }
  243. }
  244. switch {
  245. case len(req.Form) == 0:
  246. importerCount := 0
  247. if pdoc.Name != "" {
  248. importerCount, err = db.ImporterCount(importPath)
  249. if err != nil {
  250. return err
  251. }
  252. }
  253. etag := httpEtag(pdoc, pkgs, importerCount, flashMessages)
  254. status := http.StatusOK
  255. if req.Header.Get("If-None-Match") == etag {
  256. status = http.StatusNotModified
  257. }
  258. if requestType == humanRequest &&
  259. pdoc.Name != "" && // not a directory
  260. pdoc.ProjectRoot != "" && // not a standard package
  261. !pdoc.IsCmd &&
  262. len(pdoc.Errors) == 0 &&
  263. !popularLinkReferral(req) {
  264. if err := db.IncrementPopularScore(pdoc.ImportPath); err != nil {
  265. log.Printf("ERROR db.IncrementPopularScore(%s): %v", pdoc.ImportPath, err)
  266. }
  267. }
  268. if gceLogger != nil {
  269. gceLogger.LogEvent(resp, req, nil)
  270. }
  271. template := "dir"
  272. switch {
  273. case pdoc.IsCmd:
  274. template = "cmd"
  275. case pdoc.Name != "":
  276. template = "pkg"
  277. }
  278. template += templateExt(req)
  279. return executeTemplate(resp, template, status, http.Header{"Etag": {etag}}, map[string]interface{}{
  280. "flashMessages": flashMessages,
  281. "pkgs": pkgs,
  282. "pdoc": newTDoc(pdoc),
  283. "importerCount": importerCount,
  284. })
  285. case isView(req, "imports"):
  286. if pdoc.Name == "" {
  287. break
  288. }
  289. pkgs, err = db.Packages(pdoc.Imports)
  290. if err != nil {
  291. return err
  292. }
  293. return executeTemplate(resp, "imports.html", http.StatusOK, nil, map[string]interface{}{
  294. "flashMessages": flashMessages,
  295. "pkgs": pkgs,
  296. "pdoc": newTDoc(pdoc),
  297. })
  298. case isView(req, "tools"):
  299. proto := "http"
  300. if req.Host == "godoc.org" {
  301. proto = "https"
  302. }
  303. return executeTemplate(resp, "tools.html", http.StatusOK, nil, map[string]interface{}{
  304. "flashMessages": flashMessages,
  305. "uri": fmt.Sprintf("%s://%s/%s", proto, req.Host, importPath),
  306. "pdoc": newTDoc(pdoc),
  307. })
  308. case isView(req, "importers"):
  309. if pdoc.Name == "" {
  310. break
  311. }
  312. pkgs, err = db.Importers(importPath)
  313. if err != nil {
  314. return err
  315. }
  316. template := "importers.html"
  317. if requestType == robotRequest {
  318. // Hide back links from robots.
  319. template = "importers_robot.html"
  320. }
  321. return executeTemplate(resp, template, http.StatusOK, nil, map[string]interface{}{
  322. "flashMessages": flashMessages,
  323. "pkgs": pkgs,
  324. "pdoc": newTDoc(pdoc),
  325. })
  326. case isView(req, "import-graph"):
  327. if requestType == robotRequest {
  328. return &httpError{status: http.StatusForbidden}
  329. }
  330. if pdoc.Name == "" {
  331. break
  332. }
  333. hide := database.ShowAllDeps
  334. switch req.Form.Get("hide") {
  335. case "1":
  336. hide = database.HideStandardDeps
  337. case "2":
  338. hide = database.HideStandardAll
  339. }
  340. pkgs, edges, err := db.ImportGraph(pdoc, hide)
  341. if err != nil {
  342. return err
  343. }
  344. b, err := renderGraph(pdoc, pkgs, edges)
  345. if err != nil {
  346. return err
  347. }
  348. return executeTemplate(resp, "graph.html", http.StatusOK, nil, map[string]interface{}{
  349. "flashMessages": flashMessages,
  350. "svg": template.HTML(b),
  351. "pdoc": newTDoc(pdoc),
  352. "hide": hide,
  353. })
  354. case isView(req, "play"):
  355. u, err := playURL(pdoc, req.Form.Get("play"), req.Header.Get("X-AppEngine-Country"))
  356. if err != nil {
  357. return err
  358. }
  359. http.Redirect(resp, req, u, http.StatusMovedPermanently)
  360. return nil
  361. case req.Form.Get("view") != "":
  362. // Redirect deprecated view= queries.
  363. var q string
  364. switch view := req.Form.Get("view"); view {
  365. case "imports", "importers":
  366. q = view
  367. case "import-graph":
  368. if req.Form.Get("hide") == "1" {
  369. q = "import-graph&hide=1"
  370. } else {
  371. q = "import-graph"
  372. }
  373. }
  374. if q != "" {
  375. u := *req.URL
  376. u.RawQuery = q
  377. http.Redirect(resp, req, u.String(), http.StatusMovedPermanently)
  378. return nil
  379. }
  380. }
  381. return &httpError{status: http.StatusNotFound}
  382. }
  383. func serveRefresh(resp http.ResponseWriter, req *http.Request) error {
  384. importPath := req.Form.Get("path")
  385. _, pkgs, _, err := db.Get(importPath)
  386. if err != nil {
  387. return err
  388. }
  389. c := make(chan error, 1)
  390. go func() {
  391. _, err := crawlDoc("rfrsh", importPath, nil, len(pkgs) > 0, time.Time{})
  392. c <- err
  393. }()
  394. select {
  395. case err = <-c:
  396. case <-time.After(viper.GetDuration(ConfigGetTimeout)):
  397. err = errUpdateTimeout
  398. }
  399. if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
  400. setFlashMessages(resp, []flashMessage{{ID: "redir", Args: []string{importPath}}})
  401. importPath = e.Redirect
  402. err = nil
  403. } else if err != nil {
  404. setFlashMessages(resp, []flashMessage{{ID: "refresh", Args: []string{errorText(err)}}})
  405. }
  406. http.Redirect(resp, req, "/"+importPath, http.StatusFound)
  407. return nil
  408. }
  409. func serveGoIndex(resp http.ResponseWriter, req *http.Request) error {
  410. pkgs, err := db.GoIndex()
  411. if err != nil {
  412. return err
  413. }
  414. return executeTemplate(resp, "std.html", http.StatusOK, nil, map[string]interface{}{
  415. "pkgs": pkgs,
  416. })
  417. }
  418. func serveGoSubrepoIndex(resp http.ResponseWriter, req *http.Request) error {
  419. pkgs, err := db.GoSubrepoIndex()
  420. if err != nil {
  421. return err
  422. }
  423. return executeTemplate(resp, "subrepo.html", http.StatusOK, nil, map[string]interface{}{
  424. "pkgs": pkgs,
  425. })
  426. }
  427. func runReindex(resp http.ResponseWriter, req *http.Request) {
  428. fmt.Fprintln(resp, "Reindexing...")
  429. go reindex()
  430. }
  431. func runPurgeIndex(resp http.ResponseWriter, req *http.Request) {
  432. fmt.Fprintln(resp, "Purging the search index...")
  433. go purgeIndex()
  434. }
  435. type byPath struct {
  436. pkgs []database.Package
  437. rank []int
  438. }
  439. func (bp *byPath) Len() int { return len(bp.pkgs) }
  440. func (bp *byPath) Less(i, j int) bool { return bp.pkgs[i].Path < bp.pkgs[j].Path }
  441. func (bp *byPath) Swap(i, j int) {
  442. bp.pkgs[i], bp.pkgs[j] = bp.pkgs[j], bp.pkgs[i]
  443. bp.rank[i], bp.rank[j] = bp.rank[j], bp.rank[i]
  444. }
  445. type byRank struct {
  446. pkgs []database.Package
  447. rank []int
  448. }
  449. func (br *byRank) Len() int { return len(br.pkgs) }
  450. func (br *byRank) Less(i, j int) bool { return br.rank[i] < br.rank[j] }
  451. func (br *byRank) Swap(i, j int) {
  452. br.pkgs[i], br.pkgs[j] = br.pkgs[j], br.pkgs[i]
  453. br.rank[i], br.rank[j] = br.rank[j], br.rank[i]
  454. }
  455. func popular() ([]database.Package, error) {
  456. const n = 25
  457. pkgs, err := db.Popular(2 * n)
  458. if err != nil {
  459. return nil, err
  460. }
  461. rank := make([]int, len(pkgs))
  462. for i := range pkgs {
  463. rank[i] = i
  464. }
  465. sort.Sort(&byPath{pkgs, rank})
  466. j := 0
  467. prev := "."
  468. for i, pkg := range pkgs {
  469. if strings.HasPrefix(pkg.Path, prev) {
  470. if rank[j-1] < rank[i] {
  471. rank[j-1] = rank[i]
  472. }
  473. continue
  474. }
  475. prev = pkg.Path + "/"
  476. pkgs[j] = pkg
  477. rank[j] = rank[i]
  478. j++
  479. }
  480. pkgs = pkgs[:j]
  481. sort.Sort(&byRank{pkgs, rank})
  482. if len(pkgs) > n {
  483. pkgs = pkgs[:n]
  484. }
  485. sort.Sort(&byPath{pkgs, rank})
  486. return pkgs, nil
  487. }
  488. func serveHome(resp http.ResponseWriter, req *http.Request) error {
  489. if req.URL.Path != "/" {
  490. return servePackage(resp, req)
  491. }
  492. q := strings.TrimSpace(req.Form.Get("q"))
  493. if q == "" {
  494. pkgs, err := popular()
  495. if err != nil {
  496. return err
  497. }
  498. return executeTemplate(resp, "home"+templateExt(req), http.StatusOK, nil,
  499. map[string]interface{}{"Popular": pkgs})
  500. }
  501. if path, ok := isBrowseURL(q); ok {
  502. q = path
  503. }
  504. if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
  505. pdoc, pkgs, err := getDoc(q, queryRequest)
  506. if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
  507. http.Redirect(resp, req, "/"+e.Redirect, http.StatusFound)
  508. return nil
  509. }
  510. if err == nil && (pdoc != nil || len(pkgs) > 0) {
  511. http.Redirect(resp, req, "/"+q, http.StatusFound)
  512. return nil
  513. }
  514. }
  515. ctx := appengine.NewContext(req)
  516. pkgs, err := database.Search(ctx, q)
  517. if err != nil {
  518. return err
  519. }
  520. if gceLogger != nil {
  521. // Log up to top 10 packages we served upon a search.
  522. logPkgs := pkgs
  523. if len(pkgs) > 10 {
  524. logPkgs = pkgs[:10]
  525. }
  526. gceLogger.LogEvent(resp, req, logPkgs)
  527. }
  528. return executeTemplate(resp, "results"+templateExt(req), http.StatusOK, nil,
  529. map[string]interface{}{"q": q, "pkgs": pkgs})
  530. }
  531. func serveAbout(resp http.ResponseWriter, req *http.Request) error {
  532. return executeTemplate(resp, "about.html", http.StatusOK, nil,
  533. map[string]interface{}{"Host": req.Host})
  534. }
  535. func serveBot(resp http.ResponseWriter, req *http.Request) error {
  536. return executeTemplate(resp, "bot.html", http.StatusOK, nil, nil)
  537. }
  538. func serveHealthCheck(resp http.ResponseWriter, req *http.Request) {
  539. resp.Write([]byte("Health check: ok\n"))
  540. }
  541. func logError(req *http.Request, err error, rv interface{}) {
  542. if err != nil {
  543. var buf bytes.Buffer
  544. fmt.Fprintf(&buf, "Error serving %s: %v\n", req.URL, err)
  545. if rv != nil {
  546. fmt.Fprintln(&buf, rv)
  547. buf.Write(debug.Stack())
  548. }
  549. log.Print(buf.String())
  550. }
  551. }
  552. func serveAPISearch(resp http.ResponseWriter, req *http.Request) error {
  553. q := strings.TrimSpace(req.Form.Get("q"))
  554. var pkgs []database.Package
  555. if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
  556. pdoc, _, err := getDoc(q, apiRequest)
  557. if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
  558. pdoc, _, err = getDoc(e.Redirect, robotRequest)
  559. }
  560. if err == nil && pdoc != nil {
  561. pkgs = []database.Package{{Path: pdoc.ImportPath, Synopsis: pdoc.Synopsis}}
  562. }
  563. }
  564. if pkgs == nil {
  565. var err error
  566. ctx := appengine.NewContext(req)
  567. pkgs, err = database.Search(ctx, q)
  568. if err != nil {
  569. return err
  570. }
  571. }
  572. var data = struct {
  573. Results []database.Package `json:"results"`
  574. }{
  575. pkgs,
  576. }
  577. resp.Header().Set("Content-Type", jsonMIMEType)
  578. return json.NewEncoder(resp).Encode(&data)
  579. }
  580. func serveAPIPackages(resp http.ResponseWriter, req *http.Request) error {
  581. pkgs, err := db.AllPackages()
  582. if err != nil {
  583. return err
  584. }
  585. data := struct {
  586. Results []database.Package `json:"results"`
  587. }{
  588. pkgs,
  589. }
  590. resp.Header().Set("Content-Type", jsonMIMEType)
  591. return json.NewEncoder(resp).Encode(&data)
  592. }
  593. func serveAPIImporters(resp http.ResponseWriter, req *http.Request) error {
  594. importPath := strings.TrimPrefix(req.URL.Path, "/importers/")
  595. pkgs, err := db.Importers(importPath)
  596. if err != nil {
  597. return err
  598. }
  599. data := struct {
  600. Results []database.Package `json:"results"`
  601. }{
  602. pkgs,
  603. }
  604. resp.Header().Set("Content-Type", jsonMIMEType)
  605. return json.NewEncoder(resp).Encode(&data)
  606. }
  607. func serveAPIImports(resp http.ResponseWriter, req *http.Request) error {
  608. importPath := strings.TrimPrefix(req.URL.Path, "/imports/")
  609. pdoc, _, err := getDoc(importPath, robotRequest)
  610. if err != nil {
  611. return err
  612. }
  613. if pdoc == nil || pdoc.Name == "" {
  614. return &httpError{status: http.StatusNotFound}
  615. }
  616. imports, err := db.Packages(pdoc.Imports)
  617. if err != nil {
  618. return err
  619. }
  620. testImports, err := db.Packages(pdoc.TestImports)
  621. if err != nil {
  622. return err
  623. }
  624. data := struct {
  625. Imports []database.Package `json:"imports"`
  626. TestImports []database.Package `json:"testImports"`
  627. }{
  628. imports,
  629. testImports,
  630. }
  631. resp.Header().Set("Content-Type", jsonMIMEType)
  632. return json.NewEncoder(resp).Encode(&data)
  633. }
  634. func serveAPIHome(resp http.ResponseWriter, req *http.Request) error {
  635. return &httpError{status: http.StatusNotFound}
  636. }
  637. func runHandler(resp http.ResponseWriter, req *http.Request,
  638. fn func(resp http.ResponseWriter, req *http.Request) error, errfn httputil.Error) {
  639. defer func() {
  640. if rv := recover(); rv != nil {
  641. err := errors.New("handler panic")
  642. logError(req, err, rv)
  643. errfn(resp, req, http.StatusInternalServerError, err)
  644. }
  645. }()
  646. // TODO(stephenmw): choose headers based on if we are on App Engine
  647. if viper.GetBool(ConfigTrustProxyHeaders) {
  648. // If running on GAE, use X-Appengine-User-Ip to identify real ip of requests.
  649. if s := req.Header.Get("X-Appengine-User-Ip"); s != "" {
  650. req.RemoteAddr = s
  651. } else if s := req.Header.Get("X-Real-Ip"); s != "" {
  652. req.RemoteAddr = s
  653. }
  654. }
  655. req.Body = http.MaxBytesReader(resp, req.Body, 2048)
  656. req.ParseForm()
  657. var rb httputil.ResponseBuffer
  658. err := fn(&rb, req)
  659. if err == nil {
  660. rb.WriteTo(resp)
  661. } else if e, ok := err.(*httpError); ok {
  662. if e.status >= 500 {
  663. logError(req, err, nil)
  664. }
  665. errfn(resp, req, e.status, e.err)
  666. } else if gosrc.IsNotFound(err) {
  667. errfn(resp, req, http.StatusNotFound, nil)
  668. } else {
  669. logError(req, err, nil)
  670. errfn(resp, req, http.StatusInternalServerError, err)
  671. }
  672. }
  673. type handler func(resp http.ResponseWriter, req *http.Request) error
  674. func (h handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
  675. runHandler(resp, req, h, handleError)
  676. }
  677. type apiHandler func(resp http.ResponseWriter, req *http.Request) error
  678. func (h apiHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
  679. runHandler(resp, req, h, handleAPIError)
  680. }
  681. func errorText(err error) string {
  682. if err == errUpdateTimeout {
  683. return "Timeout getting package files from the version control system."
  684. }
  685. if e, ok := err.(*gosrc.RemoteError); ok {
  686. return "Error getting package files from " + e.Host + "."
  687. }
  688. return "Internal server error."
  689. }
  690. func handleError(resp http.ResponseWriter, req *http.Request, status int, err error) {
  691. switch status {
  692. case http.StatusNotFound:
  693. executeTemplate(resp, "notfound"+templateExt(req), status, nil, map[string]interface{}{
  694. "flashMessages": getFlashMessages(resp, req),
  695. })
  696. default:
  697. resp.Header().Set("Content-Type", textMIMEType)
  698. resp.WriteHeader(http.StatusInternalServerError)
  699. io.WriteString(resp, errorText(err))
  700. }
  701. }
  702. func handleAPIError(resp http.ResponseWriter, req *http.Request, status int, err error) {
  703. var data struct {
  704. Error struct {
  705. Message string `json:"message"`
  706. } `json:"error"`
  707. }
  708. data.Error.Message = http.StatusText(status)
  709. resp.Header().Set("Content-Type", jsonMIMEType)
  710. resp.WriteHeader(status)
  711. json.NewEncoder(resp).Encode(&data)
  712. }
  713. type rootHandler []struct {
  714. prefix string
  715. h http.Handler
  716. }
  717. func (m rootHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
  718. var h http.Handler
  719. for _, ph := range m {
  720. if strings.HasPrefix(req.Host, ph.prefix) {
  721. h = ph.h
  722. break
  723. }
  724. }
  725. h.ServeHTTP(resp, req)
  726. }
  727. // otherDomainHandler redirects to another domain keeping the rest of the URL.
  728. type otherDomainHandler struct {
  729. scheme string
  730. targetDomain string
  731. }
  732. func (h otherDomainHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  733. u := *req.URL
  734. u.Scheme = h.scheme
  735. u.Host = h.targetDomain
  736. http.Redirect(w, req, u.String(), http.StatusFound)
  737. }
  738. func defaultBase(path string) string {
  739. p, err := build.Default.Import(path, "", build.FindOnly)
  740. if err != nil {
  741. return "."
  742. }
  743. return p.Dir
  744. }
  745. var (
  746. db *database.Database
  747. httpClient *http.Client
  748. statusImageHandlerPNG http.Handler
  749. statusImageHandlerSVG http.Handler
  750. gceLogger *GCELogger
  751. )
  752. func main() {
  753. doc.SetDefaultGOOS(viper.GetString(ConfigDefaultGOOS))
  754. httpClient = newHTTPClient()
  755. var (
  756. gceLogName string
  757. projID string
  758. )
  759. // TODO(stephenmw): merge into viper config infrastructure.
  760. if metadata.OnGCE() {
  761. acct, err := metadata.ProjectAttributeValue("ga-account")
  762. if err != nil {
  763. log.Printf("querying metadata for ga-account: %v", err)
  764. } else {
  765. gaAccount = acct
  766. }
  767. // Get the log name on GCE and setup context for creating a GCE log client.
  768. if name, err := metadata.ProjectAttributeValue("gce-log-name"); err != nil {
  769. log.Printf("querying metadata for gce-log-name: %v", err)
  770. } else {
  771. gceLogName = name
  772. if id, err := metadata.ProjectID(); err != nil {
  773. log.Printf("querying metadata for project ID: %v", err)
  774. } else {
  775. projID = id
  776. }
  777. }
  778. } else {
  779. gaAccount = os.Getenv("GA_ACCOUNT")
  780. }
  781. if err := parseHTMLTemplates([][]string{
  782. {"about.html", "common.html", "layout.html"},
  783. {"bot.html", "common.html", "layout.html"},
  784. {"cmd.html", "common.html", "layout.html"},
  785. {"dir.html", "common.html", "layout.html"},
  786. {"home.html", "common.html", "layout.html"},
  787. {"importers.html", "common.html", "layout.html"},
  788. {"importers_robot.html", "common.html", "layout.html"},
  789. {"imports.html", "common.html", "layout.html"},
  790. {"notfound.html", "common.html", "layout.html"},
  791. {"pkg.html", "common.html", "layout.html"},
  792. {"results.html", "common.html", "layout.html"},
  793. {"tools.html", "common.html", "layout.html"},
  794. {"std.html", "common.html", "layout.html"},
  795. {"subrepo.html", "common.html", "layout.html"},
  796. {"graph.html", "common.html"},
  797. }); err != nil {
  798. log.Fatal(err)
  799. }
  800. if err := parseTextTemplates([][]string{
  801. {"cmd.txt", "common.txt"},
  802. {"dir.txt", "common.txt"},
  803. {"home.txt", "common.txt"},
  804. {"notfound.txt", "common.txt"},
  805. {"pkg.txt", "common.txt"},
  806. {"results.txt", "common.txt"},
  807. }); err != nil {
  808. log.Fatal(err)
  809. }
  810. var err error
  811. db, err = database.New()
  812. if err != nil {
  813. log.Fatalf("Error opening database: %v", err)
  814. }
  815. go runBackgroundTasks()
  816. staticServer := httputil.StaticServer{
  817. Dir: viper.GetString(ConfigAssetsDir),
  818. MaxAge: time.Hour,
  819. MIMETypes: map[string]string{
  820. ".css": "text/css; charset=utf-8",
  821. ".js": "text/javascript; charset=utf-8",
  822. },
  823. }
  824. statusImageHandlerPNG = staticServer.FileHandler("status.png")
  825. statusImageHandlerSVG = staticServer.FileHandler("status.svg")
  826. apiMux := http.NewServeMux()
  827. apiMux.Handle("/favicon.ico", staticServer.FileHandler("favicon.ico"))
  828. apiMux.Handle("/google3d2f3cd4cc2bb44b.html", staticServer.FileHandler("google3d2f3cd4cc2bb44b.html"))
  829. apiMux.Handle("/humans.txt", staticServer.FileHandler("humans.txt"))
  830. apiMux.Handle("/robots.txt", staticServer.FileHandler("apiRobots.txt"))
  831. apiMux.Handle("/search", apiHandler(serveAPISearch))
  832. apiMux.Handle("/packages", apiHandler(serveAPIPackages))
  833. apiMux.Handle("/importers/", apiHandler(serveAPIImporters))
  834. apiMux.Handle("/imports/", apiHandler(serveAPIImports))
  835. apiMux.Handle("/", apiHandler(serveAPIHome))
  836. mux := http.NewServeMux()
  837. mux.Handle("/-/site.js", staticServer.FilesHandler(
  838. "third_party/jquery.timeago.js",
  839. "site.js"))
  840. mux.Handle("/-/site.css", staticServer.FilesHandler("site.css"))
  841. mux.Handle("/-/bootstrap.min.css", staticServer.FilesHandler("bootstrap.min.css"))
  842. mux.Handle("/-/bootstrap.min.js", staticServer.FilesHandler("bootstrap.min.js"))
  843. mux.Handle("/-/jquery-2.0.3.min.js", staticServer.FilesHandler("jquery-2.0.3.min.js"))
  844. if viper.GetBool(ConfigSidebar) {
  845. mux.Handle("/-/sidebar.css", staticServer.FilesHandler("sidebar.css"))
  846. }
  847. mux.Handle("/-/", http.NotFoundHandler())
  848. mux.Handle("/-/about", handler(serveAbout))
  849. mux.Handle("/-/bot", handler(serveBot))
  850. mux.Handle("/-/go", handler(serveGoIndex))
  851. mux.Handle("/-/subrepo", handler(serveGoSubrepoIndex))
  852. mux.Handle("/-/refresh", handler(serveRefresh))
  853. mux.Handle("/-/admin/reindex", http.HandlerFunc(runReindex))
  854. mux.Handle("/-/admin/purgeindex", http.HandlerFunc(runPurgeIndex))
  855. mux.Handle("/about", http.RedirectHandler("/-/about", http.StatusMovedPermanently))
  856. mux.Handle("/favicon.ico", staticServer.FileHandler("favicon.ico"))
  857. mux.Handle("/google3d2f3cd4cc2bb44b.html", staticServer.FileHandler("google3d2f3cd4cc2bb44b.html"))
  858. mux.Handle("/humans.txt", staticServer.FileHandler("humans.txt"))
  859. mux.Handle("/robots.txt", staticServer.FileHandler("robots.txt"))
  860. mux.Handle("/BingSiteAuth.xml", staticServer.FileHandler("BingSiteAuth.xml"))
  861. mux.Handle("/C", http.RedirectHandler("http://golang.org/doc/articles/c_go_cgo.html", http.StatusMovedPermanently))
  862. mux.Handle("/code.jquery.com/", http.NotFoundHandler())
  863. mux.Handle("/_ah/health", http.HandlerFunc(serveHealthCheck))
  864. mux.Handle("/_ah/", http.NotFoundHandler())
  865. mux.Handle("/", handler(serveHome))
  866. cacheBusters.Handler = mux
  867. var root http.Handler = rootHandler{
  868. {"api.", apiMux},
  869. {"talks.godoc.org", otherDomainHandler{"https", "go-talks.appspot.com"}},
  870. {"www.godoc.org", otherDomainHandler{"https", "godoc.org"}},
  871. {"", mux},
  872. }
  873. if gceLogName != "" {
  874. ctx := context.Background()
  875. logc, err := logging.NewClient(ctx, projID)
  876. if err != nil {
  877. log.Fatalf("Failed to create cloud logging client: %v", err)
  878. }
  879. logger := logc.Logger(gceLogName)
  880. if err := logc.Ping(ctx); err != nil {
  881. log.Fatalf("Failed to ping Google Cloud Logging: %v", err)
  882. }
  883. gceLogger = newGCELogger(logger)
  884. }
  885. http.Handle("/", root)
  886. appengine.Main()
  887. }