github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/commands/server.go (about) 1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package commands 15 16 import ( 17 "bytes" 18 "context" 19 "fmt" 20 "io" 21 "net" 22 "net/http" 23 "net/url" 24 "os" 25 "os/signal" 26 "path" 27 "path/filepath" 28 "regexp" 29 "runtime" 30 "strconv" 31 "strings" 32 "sync" 33 "syscall" 34 "time" 35 36 "github.com/gohugoio/hugo/common/htime" 37 "github.com/gohugoio/hugo/common/paths" 38 "github.com/gohugoio/hugo/hugolib" 39 "github.com/gohugoio/hugo/tpl" 40 "golang.org/x/sync/errgroup" 41 42 "github.com/gohugoio/hugo/livereload" 43 44 "github.com/gohugoio/hugo/config" 45 "github.com/gohugoio/hugo/helpers" 46 "github.com/spf13/afero" 47 "github.com/spf13/cobra" 48 jww "github.com/spf13/jwalterweatherman" 49 ) 50 51 type serverCmd struct { 52 // Can be used to stop the server. Useful in tests 53 stop chan bool 54 55 disableLiveReload bool 56 navigateToChanged bool 57 renderToDisk bool 58 renderStaticToDisk bool 59 serverAppend bool 60 serverInterface string 61 serverPort int 62 liveReloadPort int 63 serverWatch bool 64 noHTTPCache bool 65 66 disableFastRender bool 67 disableBrowserError bool 68 69 *baseBuilderCmd 70 } 71 72 func (b *commandsBuilder) newServerCmd() *serverCmd { 73 return b.newServerCmdSignaled(nil) 74 } 75 76 func (b *commandsBuilder) newServerCmdSignaled(stop chan bool) *serverCmd { 77 cc := &serverCmd{stop: stop} 78 79 cc.baseBuilderCmd = b.newBuilderCmd(&cobra.Command{ 80 Use: "server", 81 Aliases: []string{"serve"}, 82 Short: "A high performance webserver", 83 Long: `Hugo provides its own webserver which builds and serves the site. 84 While hugo server is high performance, it is a webserver with limited options. 85 Many run it in production, but the standard behavior is for people to use it 86 in development and use a more full featured server such as Nginx or Caddy. 87 88 'hugo server' will avoid writing the rendered and served content to disk, 89 preferring to store it in memory. 90 91 By default hugo will also watch your files for any changes you make and 92 automatically rebuild the site. It will then live reload any open browser pages 93 and push the latest content to them. As most Hugo sites are built in a fraction 94 of a second, you will be able to save and see your changes nearly instantly.`, 95 RunE: func(cmd *cobra.Command, args []string) error { 96 err := cc.server(cmd, args) 97 if err != nil && cc.stop != nil { 98 cc.stop <- true 99 } 100 return err 101 }, 102 }) 103 104 cc.cmd.Flags().IntVarP(&cc.serverPort, "port", "p", 1313, "port on which the server will listen") 105 cc.cmd.Flags().IntVar(&cc.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)") 106 cc.cmd.Flags().StringVarP(&cc.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind") 107 cc.cmd.Flags().BoolVarP(&cc.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed") 108 cc.cmd.Flags().BoolVar(&cc.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching") 109 cc.cmd.Flags().BoolVarP(&cc.serverAppend, "appendPort", "", true, "append port to baseURL") 110 cc.cmd.Flags().BoolVar(&cc.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild") 111 cc.cmd.Flags().BoolVar(&cc.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload") 112 cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "serve all files from disk (default is from memory)") 113 cc.cmd.Flags().BoolVar(&cc.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory") 114 cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes") 115 cc.cmd.Flags().BoolVar(&cc.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser") 116 117 cc.cmd.Flags().String("memstats", "", "log memory usage to this file") 118 cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".") 119 120 return cc 121 } 122 123 type filesOnlyFs struct { 124 fs http.FileSystem 125 } 126 127 type noDirFile struct { 128 http.File 129 } 130 131 func (fs filesOnlyFs) Open(name string) (http.File, error) { 132 f, err := fs.fs.Open(name) 133 if err != nil { 134 return nil, err 135 } 136 return noDirFile{f}, nil 137 } 138 139 func (f noDirFile) Readdir(count int) ([]os.FileInfo, error) { 140 return nil, nil 141 } 142 143 func (sc *serverCmd) server(cmd *cobra.Command, args []string) error { 144 // If a Destination is provided via flag write to disk 145 destination, _ := cmd.Flags().GetString("destination") 146 if destination != "" { 147 sc.renderToDisk = true 148 } 149 150 var serverCfgInit sync.Once 151 152 cfgInit := func(c *commandeer) (rerr error) { 153 c.Set("renderToMemory", !(sc.renderToDisk || sc.renderStaticToDisk)) 154 c.Set("renderStaticToDisk", sc.renderStaticToDisk) 155 if cmd.Flags().Changed("navigateToChanged") { 156 c.Set("navigateToChanged", sc.navigateToChanged) 157 } 158 if cmd.Flags().Changed("disableLiveReload") { 159 c.Set("disableLiveReload", sc.disableLiveReload) 160 } 161 if cmd.Flags().Changed("disableFastRender") { 162 c.Set("disableFastRender", sc.disableFastRender) 163 } 164 if cmd.Flags().Changed("disableBrowserError") { 165 c.Set("disableBrowserError", sc.disableBrowserError) 166 } 167 if sc.serverWatch { 168 c.Set("watch", true) 169 } 170 171 // TODO(bep) see issue 9901 172 // cfgInit is called twice, before and after the languages have been initialized. 173 // The servers (below) can not be initialized before we 174 // know if we're configured in a multihost setup. 175 if len(c.languages) == 0 { 176 return nil 177 } 178 179 // We can only do this once. 180 serverCfgInit.Do(func() { 181 c.serverPorts = make([]serverPortListener, 1) 182 183 if c.languages.IsMultihost() { 184 if !sc.serverAppend { 185 rerr = newSystemError("--appendPort=false not supported when in multihost mode") 186 } 187 c.serverPorts = make([]serverPortListener, len(c.languages)) 188 } 189 190 currentServerPort := sc.serverPort 191 192 for i := 0; i < len(c.serverPorts); i++ { 193 l, err := net.Listen("tcp", net.JoinHostPort(sc.serverInterface, strconv.Itoa(currentServerPort))) 194 if err == nil { 195 c.serverPorts[i] = serverPortListener{ln: l, p: currentServerPort} 196 } else { 197 if i == 0 && sc.cmd.Flags().Changed("port") { 198 // port set explicitly by user -- he/she probably meant it! 199 rerr = newSystemErrorF("Server startup failed: %s", err) 200 return 201 } 202 c.logger.Println("port", sc.serverPort, "already in use, attempting to use an available port") 203 l, sp, err := helpers.TCPListen() 204 if err != nil { 205 rerr = newSystemError("Unable to find alternative port to use:", err) 206 return 207 } 208 c.serverPorts[i] = serverPortListener{ln: l, p: sp.Port} 209 } 210 211 currentServerPort = c.serverPorts[i].p + 1 212 } 213 }) 214 215 if rerr != nil { 216 return 217 } 218 219 c.Set("port", sc.serverPort) 220 if sc.liveReloadPort != -1 { 221 c.Set("liveReloadPort", sc.liveReloadPort) 222 } else { 223 c.Set("liveReloadPort", c.serverPorts[0].p) 224 } 225 226 isMultiHost := c.languages.IsMultihost() 227 for i, language := range c.languages { 228 var serverPort int 229 if isMultiHost { 230 serverPort = c.serverPorts[i].p 231 } else { 232 serverPort = c.serverPorts[0].p 233 } 234 235 baseURL, err := sc.fixURL(language, sc.baseURL, serverPort) 236 if err != nil { 237 return nil 238 } 239 if isMultiHost { 240 language.Set("baseURL", baseURL) 241 } 242 if i == 0 { 243 c.Set("baseURL", baseURL) 244 } 245 } 246 247 return 248 } 249 250 if err := memStats(); err != nil { 251 jww.WARN.Println("memstats error:", err) 252 } 253 254 // silence errors in cobra so we can handle them here 255 cmd.SilenceErrors = true 256 257 c, err := initializeConfig(true, true, true, &sc.hugoBuilderCommon, sc, cfgInit) 258 if err != nil { 259 cmd.PrintErrln("Error:", err.Error()) 260 return err 261 } 262 263 err = func() error { 264 defer c.timeTrack(time.Now(), "Built") 265 err := c.serverBuild() 266 if err != nil { 267 cmd.PrintErrln("Error:", err.Error()) 268 } 269 return err 270 }() 271 if err != nil { 272 return err 273 } 274 275 // Watch runs its own server as part of the routine 276 if sc.serverWatch { 277 278 watchDirs, err := c.getDirList() 279 if err != nil { 280 return err 281 } 282 283 watchGroups := helpers.ExtractAndGroupRootPaths(watchDirs) 284 285 for _, group := range watchGroups { 286 jww.FEEDBACK.Printf("Watching for changes in %s\n", group) 287 } 288 watcher, err := c.newWatcher(sc.poll, watchDirs...) 289 if err != nil { 290 return err 291 } 292 293 defer watcher.Close() 294 295 } 296 297 return c.serve(sc) 298 } 299 300 func getRootWatchDirsStr(baseDir string, watchDirs []string) string { 301 relWatchDirs := make([]string, len(watchDirs)) 302 for i, dir := range watchDirs { 303 relWatchDirs[i], _ = paths.GetRelativePath(dir, baseDir) 304 } 305 306 return strings.Join(helpers.UniqueStringsSorted(helpers.ExtractRootPaths(relWatchDirs)), ",") 307 } 308 309 type fileServer struct { 310 baseURLs []string 311 roots []string 312 errorTemplate func(err any) (io.Reader, error) 313 c *commandeer 314 s *serverCmd 315 } 316 317 func (f *fileServer) rewriteRequest(r *http.Request, toPath string) *http.Request { 318 r2 := new(http.Request) 319 *r2 = *r 320 r2.URL = new(url.URL) 321 *r2.URL = *r.URL 322 r2.URL.Path = toPath 323 r2.Header.Set("X-Rewrite-Original-URI", r.URL.RequestURI()) 324 325 return r2 326 } 327 328 func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string, string, error) { 329 baseURL := f.baseURLs[i] 330 root := f.roots[i] 331 port := f.c.serverPorts[i].p 332 listener := f.c.serverPorts[i].ln 333 334 // For logging only. 335 // TODO(bep) consolidate. 336 publishDir := f.c.Cfg.GetString("publishDir") 337 publishDirStatic := f.c.Cfg.GetString("publishDirStatic") 338 workingDir := f.c.Cfg.GetString("workingDir") 339 340 if root != "" { 341 publishDir = filepath.Join(publishDir, root) 342 publishDirStatic = filepath.Join(publishDirStatic, root) 343 } 344 absPublishDir := paths.AbsPathify(workingDir, publishDir) 345 absPublishDirStatic := paths.AbsPathify(workingDir, publishDirStatic) 346 347 jww.FEEDBACK.Printf("Environment: %q", f.c.hugo().Deps.Site.Hugo().Environment) 348 349 if i == 0 { 350 if f.s.renderToDisk { 351 jww.FEEDBACK.Println("Serving pages from " + absPublishDir) 352 } else if f.s.renderStaticToDisk { 353 jww.FEEDBACK.Println("Serving pages from memory and static files from " + absPublishDirStatic) 354 } else { 355 jww.FEEDBACK.Println("Serving pages from memory") 356 } 357 } 358 359 httpFs := afero.NewHttpFs(f.c.publishDirServerFs) 360 fs := filesOnlyFs{httpFs.Dir(path.Join("/", root))} 361 362 if i == 0 && f.c.fastRenderMode { 363 jww.FEEDBACK.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender") 364 } 365 366 // We're only interested in the path 367 u, err := url.Parse(baseURL) 368 if err != nil { 369 return nil, nil, "", "", fmt.Errorf("Invalid baseURL: %w", err) 370 } 371 372 decorate := func(h http.Handler) http.Handler { 373 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 374 if f.c.showErrorInBrowser { 375 // First check the error state 376 err := f.c.getErrorWithContext() 377 if err != nil { 378 f.c.wasError = true 379 w.WriteHeader(500) 380 r, err := f.errorTemplate(err) 381 if err != nil { 382 f.c.logger.Errorln(err) 383 } 384 385 port = 1313 386 if !f.c.paused { 387 port = f.c.Cfg.GetInt("liveReloadPort") 388 } 389 lr := *u 390 lr.Host = fmt.Sprintf("%s:%d", lr.Hostname(), port) 391 fmt.Fprint(w, injectLiveReloadScript(r, lr)) 392 393 return 394 } 395 } 396 397 if f.s.noHTTPCache { 398 w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") 399 w.Header().Set("Pragma", "no-cache") 400 } 401 402 // Ignore any query params for the operations below. 403 requestURI, _ := url.PathUnescape(strings.TrimSuffix(r.RequestURI, "?"+r.URL.RawQuery)) 404 405 for _, header := range f.c.serverConfig.MatchHeaders(requestURI) { 406 w.Header().Set(header.Key, header.Value) 407 } 408 409 if redirect := f.c.serverConfig.MatchRedirect(requestURI); !redirect.IsZero() { 410 // fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))) 411 doRedirect := true 412 // This matches Netlify's behaviour and is needed for SPA behaviour. 413 // See https://docs.netlify.com/routing/redirects/rewrites-proxies/ 414 if !redirect.Force { 415 path := filepath.Clean(strings.TrimPrefix(requestURI, u.Path)) 416 if root != "" { 417 path = filepath.Join(root, path) 418 } 419 fs := f.c.publishDirServerFs 420 421 fi, err := fs.Stat(path) 422 423 if err == nil { 424 if fi.IsDir() { 425 // There will be overlapping directories, so we 426 // need to check for a file. 427 _, err = fs.Stat(filepath.Join(path, "index.html")) 428 doRedirect = err != nil 429 } else { 430 doRedirect = false 431 } 432 } 433 } 434 435 if doRedirect { 436 switch redirect.Status { 437 case 404: 438 w.WriteHeader(404) 439 file, err := fs.Open(strings.TrimPrefix(redirect.To, u.Path)) 440 if err == nil { 441 defer file.Close() 442 io.Copy(w, file) 443 } else { 444 fmt.Fprintln(w, "<h1>Page Not Found</h1>") 445 } 446 return 447 case 200: 448 if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, u.Path)); r2 != nil { 449 requestURI = redirect.To 450 r = r2 451 } 452 default: 453 w.Header().Set("Content-Type", "") 454 http.Redirect(w, r, redirect.To, redirect.Status) 455 return 456 457 } 458 } 459 460 } 461 462 if f.c.fastRenderMode && f.c.buildErr == nil { 463 if strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") { 464 if !f.c.visitedURLs.Contains(requestURI) { 465 // If not already on stack, re-render that single page. 466 if err := f.c.partialReRender(requestURI); err != nil { 467 f.c.handleBuildErr(err, fmt.Sprintf("Failed to render %q", requestURI)) 468 if f.c.showErrorInBrowser { 469 http.Redirect(w, r, requestURI, http.StatusMovedPermanently) 470 return 471 } 472 } 473 } 474 475 f.c.visitedURLs.Add(requestURI) 476 477 } 478 } 479 480 h.ServeHTTP(w, r) 481 }) 482 } 483 484 fileserver := decorate(http.FileServer(fs)) 485 mu := http.NewServeMux() 486 if u.Path == "" || u.Path == "/" { 487 mu.Handle("/", fileserver) 488 } else { 489 mu.Handle(u.Path, http.StripPrefix(u.Path, fileserver)) 490 } 491 492 endpoint := net.JoinHostPort(f.s.serverInterface, strconv.Itoa(port)) 493 494 return mu, listener, u.String(), endpoint, nil 495 } 496 497 var ( 498 logErrorRe = regexp.MustCompile(`(?s)ERROR \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `) 499 logDuplicateTemplateExecuteRe = regexp.MustCompile(`: template: .*?:\d+:\d+: executing ".*?"`) 500 logDuplicateTemplateParseRe = regexp.MustCompile(`: template: .*?:\d+:\d*`) 501 ) 502 503 func removeErrorPrefixFromLog(content string) string { 504 return logErrorRe.ReplaceAllLiteralString(content, "") 505 } 506 507 var logReplacer = strings.NewReplacer( 508 "can't", "can’t", // Chroma lexer doesn't do well with "can't" 509 "*hugolib.pageState", "page.Page", // Page is the public interface. 510 "Rebuild failed:", "", 511 ) 512 513 func cleanErrorLog(content string) string { 514 content = strings.ReplaceAll(content, "\n", " ") 515 content = logReplacer.Replace(content) 516 content = logDuplicateTemplateExecuteRe.ReplaceAllString(content, "") 517 content = logDuplicateTemplateParseRe.ReplaceAllString(content, "") 518 seen := make(map[string]bool) 519 parts := strings.Split(content, ": ") 520 keep := make([]string, 0, len(parts)) 521 for _, part := range parts { 522 if seen[part] { 523 continue 524 } 525 seen[part] = true 526 keep = append(keep, part) 527 } 528 return strings.Join(keep, ": ") 529 } 530 531 func (c *commandeer) serve(s *serverCmd) error { 532 isMultiHost := c.hugo().IsMultihost() 533 534 var ( 535 baseURLs []string 536 roots []string 537 ) 538 539 if isMultiHost { 540 for _, s := range c.hugo().Sites { 541 baseURLs = append(baseURLs, s.BaseURL.String()) 542 roots = append(roots, s.Language().Lang) 543 } 544 } else { 545 s := c.hugo().Sites[0] 546 baseURLs = []string{s.BaseURL.String()} 547 roots = []string{""} 548 } 549 550 // Cache it here. The HugoSites object may be unavailable later on due to intermittent configuration errors. 551 // To allow the en user to change the error template while the server is running, we use 552 // the freshest template we can provide. 553 var ( 554 errTempl tpl.Template 555 templHandler tpl.TemplateHandler 556 ) 557 getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (tpl.Template, tpl.TemplateHandler) { 558 if h == nil { 559 return errTempl, templHandler 560 } 561 templHandler := h.Tmpl() 562 errTempl, found := templHandler.Lookup("_server/error.html") 563 if !found { 564 panic("template server/error.html not found") 565 } 566 return errTempl, templHandler 567 } 568 errTempl, templHandler = getErrorTemplateAndHandler(c.hugo()) 569 570 srv := &fileServer{ 571 baseURLs: baseURLs, 572 roots: roots, 573 c: c, 574 s: s, 575 errorTemplate: func(ctx any) (io.Reader, error) { 576 // hugoTry does not block, getErrorTemplateAndHandler will fall back 577 // to cached values if nil. 578 templ, handler := getErrorTemplateAndHandler(c.hugoTry()) 579 b := &bytes.Buffer{} 580 err := handler.ExecuteWithContext(context.Background(), templ, b, ctx) 581 return b, err 582 }, 583 } 584 585 doLiveReload := !c.Cfg.GetBool("disableLiveReload") 586 587 if doLiveReload { 588 livereload.Initialize() 589 } 590 591 sigs := make(chan os.Signal, 1) 592 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 593 var servers []*http.Server 594 595 wg1, ctx := errgroup.WithContext(context.Background()) 596 597 for i := range baseURLs { 598 mu, listener, serverURL, endpoint, err := srv.createEndpoint(i) 599 srv := &http.Server{ 600 Addr: endpoint, 601 Handler: mu, 602 } 603 servers = append(servers, srv) 604 605 if doLiveReload { 606 u, err := url.Parse(helpers.SanitizeURL(baseURLs[i])) 607 if err != nil { 608 return err 609 } 610 611 mu.HandleFunc(u.Path+"/livereload.js", livereload.ServeJS) 612 mu.HandleFunc(u.Path+"/livereload", livereload.Handler) 613 } 614 jww.FEEDBACK.Printf("Web Server is available at %s (bind address %s)\n", serverURL, s.serverInterface) 615 wg1.Go(func() error { 616 err = srv.Serve(listener) 617 if err != nil && err != http.ErrServerClosed { 618 return err 619 } 620 return nil 621 }) 622 } 623 624 jww.FEEDBACK.Println("Press Ctrl+C to stop") 625 626 err := func() error { 627 if s.stop != nil { 628 for { 629 select { 630 case <-sigs: 631 return nil 632 case <-s.stop: 633 return nil 634 case <-ctx.Done(): 635 return ctx.Err() 636 } 637 } 638 } else { 639 for { 640 select { 641 case <-sigs: 642 return nil 643 case <-ctx.Done(): 644 return ctx.Err() 645 } 646 } 647 } 648 }() 649 650 if err != nil { 651 jww.ERROR.Println("Error:", err) 652 } 653 654 if h := c.hugoTry(); h != nil { 655 h.Close() 656 } 657 658 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 659 defer cancel() 660 wg2, ctx := errgroup.WithContext(ctx) 661 for _, srv := range servers { 662 srv := srv 663 wg2.Go(func() error { 664 return srv.Shutdown(ctx) 665 }) 666 } 667 668 err1, err2 := wg1.Wait(), wg2.Wait() 669 if err1 != nil { 670 return err1 671 } 672 return err2 673 } 674 675 // fixURL massages the baseURL into a form needed for serving 676 // all pages correctly. 677 func (sc *serverCmd) fixURL(cfg config.Provider, s string, port int) (string, error) { 678 useLocalhost := false 679 if s == "" { 680 s = cfg.GetString("baseURL") 681 useLocalhost = true 682 } 683 684 if !strings.HasSuffix(s, "/") { 685 s = s + "/" 686 } 687 688 // do an initial parse of the input string 689 u, err := url.Parse(s) 690 if err != nil { 691 return "", err 692 } 693 694 // if no Host is defined, then assume that no schema or double-slash were 695 // present in the url. Add a double-slash and make a best effort attempt. 696 if u.Host == "" && s != "/" { 697 s = "//" + s 698 699 u, err = url.Parse(s) 700 if err != nil { 701 return "", err 702 } 703 } 704 705 if useLocalhost { 706 if u.Scheme == "https" { 707 u.Scheme = "http" 708 } 709 u.Host = "localhost" 710 } 711 712 if sc.serverAppend { 713 if strings.Contains(u.Host, ":") { 714 u.Host, _, err = net.SplitHostPort(u.Host) 715 if err != nil { 716 return "", fmt.Errorf("Failed to split baseURL hostpost: %w", err) 717 } 718 } 719 u.Host += fmt.Sprintf(":%d", port) 720 } 721 722 return u.String(), nil 723 } 724 725 func memStats() error { 726 b := newCommandsBuilder() 727 sc := b.newServerCmd().getCommand() 728 memstats := sc.Flags().Lookup("memstats").Value.String() 729 if memstats != "" { 730 interval, err := time.ParseDuration(sc.Flags().Lookup("meminterval").Value.String()) 731 if err != nil { 732 interval, _ = time.ParseDuration("100ms") 733 } 734 735 fileMemStats, err := os.Create(memstats) 736 if err != nil { 737 return err 738 } 739 740 fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n") 741 742 go func() { 743 var stats runtime.MemStats 744 745 start := htime.Now().UnixNano() 746 747 for { 748 runtime.ReadMemStats(&stats) 749 if fileMemStats != nil { 750 fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n", 751 (htime.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased)) 752 time.Sleep(interval) 753 } else { 754 break 755 } 756 } 757 }() 758 } 759 return nil 760 }