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