You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

345 lines
9.6 KiB

  1. // Copyright 2009 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // godoc: Go Documentation Server
  5. // Web server tree:
  6. //
  7. // http://godoc/ main landing page
  8. // http://godoc/doc/ serve from $GOROOT/doc - spec, mem, etc.
  9. // http://godoc/src/ serve files from $GOROOT/src; .go gets pretty-printed
  10. // http://godoc/cmd/ serve documentation about commands
  11. // http://godoc/pkg/ serve documentation about packages
  12. // (idea is if you say import "compress/zlib", you go to
  13. // http://godoc/pkg/compress/zlib)
  14. //
  15. // Command-line interface:
  16. //
  17. // godoc packagepath [name ...]
  18. //
  19. // godoc compress/zlib
  20. // - prints doc for package compress/zlib
  21. // godoc crypto/block Cipher NewCMAC
  22. // - prints doc for Cipher and NewCMAC in package crypto/block
  23. // +build !appengine
  24. package main
  25. import (
  26. "archive/zip"
  27. _ "expvar" // to serve /debug/vars
  28. "flag"
  29. "fmt"
  30. "go/build"
  31. "log"
  32. "net/http"
  33. "net/http/httptest"
  34. _ "net/http/pprof" // to serve /debug/pprof/*
  35. "net/url"
  36. "os"
  37. "path/filepath"
  38. "regexp"
  39. "runtime"
  40. "strings"
  41. "golang.org/x/tools/godoc"
  42. "golang.org/x/tools/godoc/analysis"
  43. "golang.org/x/tools/godoc/static"
  44. "golang.org/x/tools/godoc/vfs"
  45. "golang.org/x/tools/godoc/vfs/gatefs"
  46. "golang.org/x/tools/godoc/vfs/mapfs"
  47. "golang.org/x/tools/godoc/vfs/zipfs"
  48. )
  49. const defaultAddr = ":6060" // default webserver address
  50. var (
  51. // file system to serve
  52. // (with e.g.: zip -r go.zip $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh -i favicon.ico)
  53. zipfile = flag.String("zip", "", "zip file providing the file system to serve; disabled if empty")
  54. // file-based index
  55. writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files")
  56. analysisFlag = flag.String("analysis", "", `comma-separated list of analyses to perform (supported: type, pointer). See http://golang.org/lib/godoc/analysis/help.html`)
  57. // network
  58. httpAddr = flag.String("http", "", "HTTP service address (e.g., '"+defaultAddr+"')")
  59. serverAddr = flag.String("server", "", "webserver address for command line searches")
  60. // layout control
  61. html = flag.Bool("html", false, "print HTML in command-line mode")
  62. srcMode = flag.Bool("src", false, "print (exported) source in command-line mode")
  63. urlFlag = flag.String("url", "", "print HTML for named URL")
  64. // command-line searches
  65. query = flag.Bool("q", false, "arguments are considered search queries")
  66. verbose = flag.Bool("v", false, "verbose mode")
  67. // file system roots
  68. // TODO(gri) consider the invariant that goroot always end in '/'
  69. goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
  70. // layout control
  71. tabWidth = flag.Int("tabwidth", 4, "tab width")
  72. showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
  73. templateDir = flag.String("templates", "", "load templates/JS/CSS from disk in this directory")
  74. showPlayground = flag.Bool("play", false, "enable playground in web interface")
  75. showExamples = flag.Bool("ex", false, "show examples in command line mode")
  76. declLinks = flag.Bool("links", true, "link identifiers to their declarations")
  77. // search index
  78. indexEnabled = flag.Bool("index", false, "enable search index")
  79. indexFiles = flag.String("index_files", "", "glob pattern specifying index files; if not empty, the index is read from these files in sorted order")
  80. indexInterval = flag.Duration("index_interval", 0, "interval of indexing; 0 for default (5m), negative to only index once at startup")
  81. maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
  82. indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
  83. // source code notes
  84. notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show")
  85. )
  86. func usage() {
  87. fmt.Fprintf(os.Stderr,
  88. "usage: godoc package [name ...]\n"+
  89. " godoc -http="+defaultAddr+"\n")
  90. flag.PrintDefaults()
  91. os.Exit(2)
  92. }
  93. func loggingHandler(h http.Handler) http.Handler {
  94. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  95. log.Printf("%s\t%s", req.RemoteAddr, req.URL)
  96. h.ServeHTTP(w, req)
  97. })
  98. }
  99. func handleURLFlag() {
  100. // Try up to 10 fetches, following redirects.
  101. urlstr := *urlFlag
  102. for i := 0; i < 10; i++ {
  103. // Prepare request.
  104. u, err := url.Parse(urlstr)
  105. if err != nil {
  106. log.Fatal(err)
  107. }
  108. req := &http.Request{
  109. URL: u,
  110. }
  111. // Invoke default HTTP handler to serve request
  112. // to our buffering httpWriter.
  113. w := httptest.NewRecorder()
  114. http.DefaultServeMux.ServeHTTP(w, req)
  115. // Return data, error, or follow redirect.
  116. switch w.Code {
  117. case 200: // ok
  118. os.Stdout.Write(w.Body.Bytes())
  119. return
  120. case 301, 302, 303, 307: // redirect
  121. redirect := w.HeaderMap.Get("Location")
  122. if redirect == "" {
  123. log.Fatalf("HTTP %d without Location header", w.Code)
  124. }
  125. urlstr = redirect
  126. default:
  127. log.Fatalf("HTTP error %d", w.Code)
  128. }
  129. }
  130. log.Fatalf("too many redirects")
  131. }
  132. func main() {
  133. flag.Usage = usage
  134. flag.Parse()
  135. playEnabled = *showPlayground
  136. // Check usage: server and no args.
  137. if (*httpAddr != "" || *urlFlag != "") && (flag.NArg() > 0) {
  138. fmt.Fprintln(os.Stderr, "can't use -http with args.")
  139. usage()
  140. }
  141. // Check usage: command line args or index creation mode.
  142. if (*httpAddr != "" || *urlFlag != "") != (flag.NArg() == 0) && !*writeIndex {
  143. fmt.Fprintln(os.Stderr, "missing args.")
  144. usage()
  145. }
  146. var fsGate chan bool
  147. fsGate = make(chan bool, 20)
  148. // Determine file system to use.
  149. if *zipfile == "" {
  150. // use file system of underlying OS
  151. rootfs := gatefs.New(vfs.OS(*goroot), fsGate)
  152. fs.Bind("/", rootfs, "/", vfs.BindReplace)
  153. } else {
  154. // use file system specified via .zip file (path separator must be '/')
  155. rc, err := zip.OpenReader(*zipfile)
  156. if err != nil {
  157. log.Fatalf("%s: %s\n", *zipfile, err)
  158. }
  159. defer rc.Close() // be nice (e.g., -writeIndex mode)
  160. fs.Bind("/", zipfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
  161. }
  162. if *templateDir != "" {
  163. fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
  164. } else {
  165. fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
  166. }
  167. // Bind $GOPATH trees into Go root.
  168. for _, p := range filepath.SplitList(build.Default.GOPATH) {
  169. fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
  170. }
  171. httpMode := *httpAddr != ""
  172. var typeAnalysis, pointerAnalysis bool
  173. if *analysisFlag != "" {
  174. for _, a := range strings.Split(*analysisFlag, ",") {
  175. switch a {
  176. case "type":
  177. typeAnalysis = true
  178. case "pointer":
  179. pointerAnalysis = true
  180. default:
  181. log.Fatalf("unknown analysis: %s", a)
  182. }
  183. }
  184. }
  185. corpus := godoc.NewCorpus(fs)
  186. corpus.Verbose = *verbose
  187. corpus.MaxResults = *maxResults
  188. corpus.IndexEnabled = *indexEnabled && httpMode
  189. if *maxResults == 0 {
  190. corpus.IndexFullText = false
  191. }
  192. corpus.IndexFiles = *indexFiles
  193. corpus.IndexDirectory = indexDirectoryDefault
  194. corpus.IndexThrottle = *indexThrottle
  195. corpus.IndexInterval = *indexInterval
  196. if *writeIndex {
  197. corpus.IndexThrottle = 1.0
  198. corpus.IndexEnabled = true
  199. }
  200. if *writeIndex || httpMode || *urlFlag != "" {
  201. if err := corpus.Init(); err != nil {
  202. log.Fatal(err)
  203. }
  204. }
  205. pres = godoc.NewPresentation(corpus)
  206. pres.TabWidth = *tabWidth
  207. pres.ShowTimestamps = *showTimestamps
  208. pres.ShowPlayground = *showPlayground
  209. pres.ShowExamples = *showExamples
  210. pres.DeclLinks = *declLinks
  211. pres.SrcMode = *srcMode
  212. pres.HTMLMode = *html
  213. if *notesRx != "" {
  214. pres.NotesRx = regexp.MustCompile(*notesRx)
  215. }
  216. readTemplates(pres, httpMode || *urlFlag != "")
  217. registerHandlers(pres)
  218. if *writeIndex {
  219. // Write search index and exit.
  220. if *indexFiles == "" {
  221. log.Fatal("no index file specified")
  222. }
  223. log.Println("initialize file systems")
  224. *verbose = true // want to see what happens
  225. corpus.UpdateIndex()
  226. log.Println("writing index file", *indexFiles)
  227. f, err := os.Create(*indexFiles)
  228. if err != nil {
  229. log.Fatal(err)
  230. }
  231. index, _ := corpus.CurrentIndex()
  232. _, err = index.WriteTo(f)
  233. if err != nil {
  234. log.Fatal(err)
  235. }
  236. log.Println("done")
  237. return
  238. }
  239. // Print content that would be served at the URL *urlFlag.
  240. if *urlFlag != "" {
  241. handleURLFlag()
  242. return
  243. }
  244. if httpMode {
  245. // HTTP server mode.
  246. var handler http.Handler = http.DefaultServeMux
  247. if *verbose {
  248. log.Printf("Go Documentation Server")
  249. log.Printf("version = %s", runtime.Version())
  250. log.Printf("address = %s", *httpAddr)
  251. log.Printf("goroot = %s", *goroot)
  252. log.Printf("tabwidth = %d", *tabWidth)
  253. switch {
  254. case !*indexEnabled:
  255. log.Print("search index disabled")
  256. case *maxResults > 0:
  257. log.Printf("full text index enabled (maxresults = %d)", *maxResults)
  258. default:
  259. log.Print("identifier search index enabled")
  260. }
  261. fs.Fprint(os.Stderr)
  262. handler = loggingHandler(handler)
  263. }
  264. // Initialize search index.
  265. if *indexEnabled {
  266. go corpus.RunIndexer()
  267. }
  268. // Start type/pointer analysis.
  269. if typeAnalysis || pointerAnalysis {
  270. go analysis.Run(pointerAnalysis, &corpus.Analysis)
  271. }
  272. if serveAutoCertHook != nil {
  273. go func() {
  274. if err := serveAutoCertHook(handler); err != nil {
  275. log.Fatalf("ListenAndServe TLS: %v", err)
  276. }
  277. }()
  278. }
  279. // Start http server.
  280. if err := http.ListenAndServe(*httpAddr, handler); err != nil {
  281. log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
  282. }
  283. return
  284. }
  285. if *query {
  286. handleRemoteSearch()
  287. return
  288. }
  289. if err := godoc.CommandLine(os.Stdout, fs, pres, flag.Args()); err != nil {
  290. log.Print(err)
  291. }
  292. }
  293. // serveAutoCertHook if non-nil specifies a function to listen on port 443.
  294. // See autocert.go.
  295. var serveAutoCertHook func(http.Handler) error