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