2016-03-21 19:28:42 -04:00
// Copyright 2016 The Hugo Authors. All rights reserved.
2013-09-29 02:09:03 -04:00
//
2015-11-23 22:16:36 -05:00
// Licensed under the Apache License, Version 2.0 (the "License");
2013-09-29 02:09:03 -04:00
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2015-11-23 22:16:36 -05:00
// http://www.apache.org/licenses/LICENSE-2.0
2013-09-29 02:09:03 -04:00
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"fmt"
2014-05-15 15:07:46 -04:00
"net"
2013-09-29 02:09:03 -04:00
"net/http"
2014-08-22 07:59:59 -04:00
"net/url"
2014-01-26 04:48:00 -05:00
"os"
2014-09-22 09:45:05 -04:00
"runtime"
2013-09-29 02:09:03 -04:00
"strconv"
2013-11-22 00:28:05 -05:00
"strings"
2014-09-22 09:45:05 -04:00
"time"
2014-03-31 13:23:34 -04:00
2016-04-27 15:39:57 -04:00
"mime"
2014-11-01 11:57:29 -04:00
"github.com/spf13/afero"
2014-03-31 13:23:34 -04:00
"github.com/spf13/cobra"
2017-02-04 22:20:06 -05:00
"github.com/spf13/hugo/config"
2014-04-05 01:26:43 -04:00
"github.com/spf13/hugo/helpers"
2014-03-31 13:23:34 -04:00
jww "github.com/spf13/jwalterweatherman"
2013-09-29 02:09:03 -04:00
)
2016-02-05 12:41:40 -05:00
var (
disableLiveReload bool
renderToDisk bool
serverAppend bool
serverInterface string
serverPort int
serverWatch bool
)
2013-09-29 02:09:03 -04:00
var serverCmd = & cobra . Command {
2015-11-16 21:55:18 -05:00
Use : "server" ,
Aliases : [ ] string { "serve" } ,
Short : "A high performance webserver" ,
2015-11-21 08:31:10 -05:00
Long : ` Hugo provides its own webserver which builds and serves the site .
2015-11-12 10:23:41 -05:00
While hugo server is high performance , it is a webserver with limited options .
2015-11-21 08:31:10 -05:00
Many run it in production , but the standard behavior is for people to use it
in development and use a more full featured server such as Nginx or Caddy .
2015-11-12 10:23:41 -05:00
2015-11-16 21:55:18 -05:00
' hugo server ' will avoid writing the rendered and served content to disk ,
preferring to store it in memory .
2015-11-20 10:13:03 -05:00
By default hugo will also watch your files for any changes you make and
automatically rebuild the site . It will then live reload any open browser pages
2015-11-21 08:31:10 -05:00
and push the latest content to them . As most Hugo sites are built in a fraction
of a second , you will be able to save and see your changes nearly instantly . ` ,
2015-12-02 05:42:53 -05:00
//RunE: server,
2014-05-16 17:49:27 -04:00
}
2015-10-23 12:21:37 -04:00
type filesOnlyFs struct {
fs http . FileSystem
}
type noDirFile struct {
http . File
}
func ( fs filesOnlyFs ) Open ( name string ) ( http . File , error ) {
f , err := fs . fs . Open ( name )
if err != nil {
return nil , err
}
return noDirFile { f } , nil
}
func ( f noDirFile ) Readdir ( count int ) ( [ ] os . FileInfo , error ) {
return nil , nil
}
2014-05-16 17:49:27 -04:00
func init ( ) {
2016-02-05 12:41:40 -05:00
initHugoBuilderFlags ( serverCmd )
2015-05-01 22:28:21 -04:00
serverCmd . Flags ( ) . IntVarP ( & serverPort , "port" , "p" , 1313 , "port on which the server will listen" )
serverCmd . Flags ( ) . StringVarP ( & serverInterface , "bind" , "" , "127.0.0.1" , "interface to which the server will bind" )
2015-11-20 10:13:03 -05:00
serverCmd . Flags ( ) . BoolVarP ( & serverWatch , "watch" , "w" , true , "watch filesystem for changes and recreate as needed" )
2016-10-24 14:56:00 -04:00
serverCmd . Flags ( ) . BoolVarP ( & serverAppend , "appendPort" , "" , true , "append port to baseURL" )
2014-05-16 17:49:27 -04:00
serverCmd . Flags ( ) . BoolVar ( & disableLiveReload , "disableLiveReload" , false , "watch without enabling live browser reload on rebuild" )
2015-11-16 21:55:18 -05:00
serverCmd . Flags ( ) . BoolVar ( & renderToDisk , "renderToDisk" , false , "render to Destination path (default is render to memory & serve from there)" )
2014-09-22 09:45:05 -04:00
serverCmd . Flags ( ) . String ( "memstats" , "" , "log memory usage to this file" )
2016-08-02 13:48:07 -04:00
serverCmd . Flags ( ) . String ( "meminterval" , "100ms" , "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\"." )
2015-12-02 05:42:53 -05:00
serverCmd . RunE = server
2016-03-06 08:10:06 -05:00
2016-06-15 13:34:21 -04:00
mime . AddExtensionType ( ".json" , "application/json; charset=utf-8" )
mime . AddExtensionType ( ".css" , "text/css; charset=utf-8" )
2016-04-27 15:39:57 -04:00
2013-09-29 02:09:03 -04:00
}
2015-12-02 05:42:53 -05:00
func server ( cmd * cobra . Command , args [ ] string ) error {
2017-01-11 12:53:51 -05:00
cfg , err := InitializeConfig ( serverCmd )
2017-01-03 11:28:51 -05:00
if err != nil {
2015-12-02 13:56:36 -05:00
return err
}
2013-09-29 02:09:03 -04:00
2017-03-25 09:37:04 -04:00
c , err := newCommandeer ( cfg )
if err != nil {
return err
}
2017-01-10 04:55:03 -05:00
2017-02-20 03:50:34 -05:00
if flagChanged ( cmd . Flags ( ) , "disableLiveReload" ) {
2017-02-04 22:20:06 -05:00
c . Set ( "disableLiveReload" , disableLiveReload )
2014-05-16 17:49:27 -04:00
}
if serverWatch {
2017-02-04 22:20:06 -05:00
c . Set ( "watch" , true )
2014-05-16 17:49:27 -04:00
}
2017-02-04 22:20:06 -05:00
if c . Cfg . GetBool ( "watch" ) {
2015-07-17 20:42:09 -04:00
serverWatch = true
2017-01-10 04:55:03 -05:00
c . watchConfig ( )
2015-07-17 20:42:09 -04:00
}
2015-05-01 22:28:21 -04:00
l , err := net . Listen ( "tcp" , net . JoinHostPort ( serverInterface , strconv . Itoa ( serverPort ) ) )
2014-05-15 15:07:46 -04:00
if err == nil {
l . Close ( )
} else {
2017-02-20 03:50:34 -05:00
if flagChanged ( serverCmd . Flags ( ) , "port" ) {
2016-02-27 10:58:42 -05:00
// port set explicitly by user -- he/she probably meant it!
2016-06-14 11:48:27 -04:00
return newSystemErrorF ( "Server startup failed: %s" , err )
2016-02-27 10:58:42 -05:00
}
2014-05-15 15:07:46 -04:00
jww . ERROR . Println ( "port" , serverPort , "already in use, attempting to use an available port" )
sp , err := helpers . FindAvailablePort ( )
if err != nil {
2015-12-02 05:42:53 -05:00
return newSystemError ( "Unable to find alternative port to use:" , err )
2014-05-15 15:07:46 -04:00
}
serverPort = sp . Port
}
2017-02-04 22:20:06 -05:00
c . Set ( "port" , serverPort )
2014-05-28 19:01:24 -04:00
2017-02-04 22:20:06 -05:00
baseURL , err = fixURL ( c . Cfg , baseURL )
2014-08-22 07:59:59 -04:00
if err != nil {
2015-12-02 05:42:53 -05:00
return err
2014-01-29 17:50:31 -05:00
}
2017-02-04 22:20:06 -05:00
c . Set ( "baseURL" , baseURL )
2013-11-22 00:28:05 -05:00
2014-09-22 09:45:05 -04:00
if err := memStats ( ) ; err != nil {
jww . ERROR . Println ( "memstats error:" , err )
}
2015-11-16 21:55:18 -05:00
// If a Destination is provided via flag write to disk
2016-02-05 16:58:17 -05:00
if destination != "" {
2015-11-16 21:55:18 -05:00
renderToDisk = true
}
// Hugo writes the output to memory instead of the disk
if ! renderToDisk {
2017-01-10 04:55:03 -05:00
cfg . Fs . Destination = new ( afero . MemMapFs )
2015-11-16 21:55:18 -05:00
// Rendering to memoryFS, publish to Root regardless of publishDir.
2017-02-04 22:20:06 -05:00
c . Set ( "publishDir" , "/" )
2015-11-16 21:55:18 -05:00
}
2017-01-10 04:55:03 -05:00
if err := c . build ( serverWatch ) ; err != nil {
2015-12-02 05:42:53 -05:00
return err
}
2013-10-09 18:24:40 -04:00
2013-09-29 02:09:03 -04:00
// Watch runs its own server as part of the routine
if serverWatch {
2017-01-10 04:55:03 -05:00
watchDirs := c . getDirList ( )
2017-02-04 22:20:06 -05:00
baseWatchDir := c . Cfg . GetString ( "workingDir" )
2015-11-23 10:32:06 -05:00
for i , dir := range watchDirs {
watchDirs [ i ] , _ = helpers . GetRelativePath ( dir , baseWatchDir )
2015-04-02 00:40:29 -04:00
}
2015-11-23 10:32:06 -05:00
rootWatchDirs := strings . Join ( helpers . UniqueStrings ( helpers . ExtractRootPaths ( watchDirs ) ) , "," )
2015-12-16 22:41:33 -05:00
jww . FEEDBACK . Printf ( "Watching for changes in %s%s{%s}\n" , baseWatchDir , helpers . FilePathSeparator , rootWatchDirs )
2017-01-10 04:55:03 -05:00
err := c . newWatcher ( serverPort )
2015-12-02 05:42:53 -05:00
2013-09-29 02:09:03 -04:00
if err != nil {
2015-12-02 05:42:53 -05:00
return err
2013-09-29 02:09:03 -04:00
}
}
2017-01-10 04:55:03 -05:00
c . serve ( serverPort )
2015-12-02 05:42:53 -05:00
return nil
2013-09-29 02:09:03 -04:00
}
2017-02-04 22:20:06 -05:00
func ( c * commandeer ) serve ( port int ) {
2015-11-16 21:55:18 -05:00
if renderToDisk {
2017-02-04 22:20:06 -05:00
jww . FEEDBACK . Println ( "Serving pages from " + c . PathSpec ( ) . AbsPathify ( c . Cfg . GetString ( "publishDir" ) ) )
2015-11-16 21:55:18 -05:00
} else {
jww . FEEDBACK . Println ( "Serving pages from memory" )
}
2014-01-26 04:48:00 -05:00
2017-01-10 04:55:03 -05:00
httpFs := afero . NewHttpFs ( c . Fs . Destination )
2017-02-04 22:20:06 -05:00
fs := filesOnlyFs { httpFs . Dir ( c . PathSpec ( ) . AbsPathify ( c . Cfg . GetString ( "publishDir" ) ) ) }
2015-10-23 12:21:37 -04:00
fileserver := http . FileServer ( fs )
2014-08-22 07:59:59 -04:00
2015-05-01 22:28:21 -04:00
// We're only interested in the path
2017-02-04 22:20:06 -05:00
u , err := url . Parse ( c . Cfg . GetString ( "baseURL" ) )
2014-08-22 07:59:59 -04:00
if err != nil {
2016-10-24 14:56:00 -04:00
jww . ERROR . Fatalf ( "Invalid baseURL: %s" , err )
2014-08-22 07:59:59 -04:00
}
if u . Path == "" || u . Path == "/" {
http . Handle ( "/" , fileserver )
} else {
2014-10-19 08:41:02 -04:00
http . Handle ( u . Path , http . StripPrefix ( u . Path , fileserver ) )
2014-08-22 07:59:59 -04:00
}
2015-09-16 01:12:01 -04:00
jww . FEEDBACK . Printf ( "Web Server is available at %s (bind address %s)\n" , u . String ( ) , serverInterface )
2016-11-22 12:47:20 -05:00
jww . FEEDBACK . Println ( "Press Ctrl+C to stop" )
2014-10-17 12:32:16 -04:00
2015-05-01 22:28:21 -04:00
endpoint := net . JoinHostPort ( serverInterface , strconv . Itoa ( port ) )
err = http . ListenAndServe ( endpoint , nil )
2014-01-26 04:48:00 -05:00
if err != nil {
2014-03-31 13:23:34 -04:00
jww . ERROR . Printf ( "Error: %s\n" , err . Error ( ) )
2014-01-26 04:48:00 -05:00
os . Exit ( 1 )
}
2013-09-29 02:09:03 -04:00
}
2014-08-22 07:59:59 -04:00
2016-10-24 14:56:00 -04:00
// fixURL massages the baseURL into a form needed for serving
2015-01-15 20:02:19 -05:00
// all pages correctly.
2017-02-04 22:20:06 -05:00
func fixURL ( cfg config . Provider , s string ) ( string , error ) {
2014-08-22 07:59:59 -04:00
useLocalhost := false
if s == "" {
2017-02-04 22:20:06 -05:00
s = cfg . GetString ( "baseURL" )
2014-08-22 07:59:59 -04:00
useLocalhost = true
}
2016-05-12 19:06:56 -04:00
2015-01-22 19:46:29 -05:00
if ! strings . HasSuffix ( s , "/" ) {
2015-01-15 20:02:19 -05:00
s = s + "/"
}
2016-05-12 19:06:56 -04:00
// do an initial parse of the input string
2014-08-22 07:59:59 -04:00
u , err := url . Parse ( s )
if err != nil {
return "" , err
}
2016-05-12 19:06:56 -04:00
// if no Host is defined, then assume that no schema or double-slash were
// present in the url. Add a double-slash and make a best effort attempt.
if u . Host == "" && s != "/" {
s = "//" + s
u , err = url . Parse ( s )
if err != nil {
return "" , err
}
}
if useLocalhost {
if u . Scheme == "https" {
2015-01-01 10:32:56 -05:00
u . Scheme = "http"
2014-08-22 07:59:59 -04:00
}
2016-05-12 19:06:56 -04:00
u . Host = "localhost"
}
if serverAppend {
if strings . Contains ( u . Host , ":" ) {
u . Host , _ , err = net . SplitHostPort ( u . Host )
2014-08-22 07:59:59 -04:00
if err != nil {
2016-10-24 14:56:00 -04:00
return "" , fmt . Errorf ( "Failed to split baseURL hostpost: %s" , err )
2014-08-22 07:59:59 -04:00
}
}
2016-05-12 19:06:56 -04:00
u . Host += fmt . Sprintf ( ":%d" , serverPort )
2014-08-22 07:59:59 -04:00
}
return u . String ( ) , nil
}
2014-09-22 09:45:05 -04:00
func memStats ( ) error {
memstats := serverCmd . Flags ( ) . Lookup ( "memstats" ) . Value . String ( )
if memstats != "" {
interval , err := time . ParseDuration ( serverCmd . Flags ( ) . Lookup ( "meminterval" ) . Value . String ( ) )
if err != nil {
interval , _ = time . ParseDuration ( "100ms" )
}
fileMemStats , err := os . Create ( memstats )
if err != nil {
return err
}
fileMemStats . WriteString ( "# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n" )
go func ( ) {
var stats runtime . MemStats
start := time . Now ( ) . UnixNano ( )
for {
runtime . ReadMemStats ( & stats )
if fileMemStats != nil {
fileMemStats . WriteString ( fmt . Sprintf ( "%d\t%d\t%d\t%d\t%d\n" ,
( time . Now ( ) . UnixNano ( ) - start ) / 1000000 , stats . HeapSys , stats . HeapAlloc , stats . HeapIdle , stats . HeapReleased ) )
time . Sleep ( interval )
} else {
break
}
}
} ( )
}
return nil
}