2016-03-21 23:28:42 +00:00
// Copyright 2016 The Hugo Authors. All rights reserved.
2013-09-29 06:09:03 +00:00
//
2015-11-24 03:16:36 +00:00
// Licensed under the Apache License, Version 2.0 (the "License");
2013-09-29 06:09:03 +00:00
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2015-11-24 03:16:36 +00:00
// http://www.apache.org/licenses/LICENSE-2.0
2013-09-29 06:09:03 +00: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 19:07:46 +00:00
"net"
2013-09-29 06:09:03 +00:00
"net/http"
2014-08-22 11:59:59 +00:00
"net/url"
2014-01-26 09:48:00 +00:00
"os"
2014-09-22 13:45:05 +00:00
"runtime"
2013-09-29 06:09:03 +00:00
"strconv"
2013-11-22 05:28:05 +00:00
"strings"
2014-09-22 13:45:05 +00:00
"time"
2014-03-31 17:23:34 +00:00
2016-04-27 19:39:57 +00:00
"mime"
2014-11-01 15:57:29 +00:00
"github.com/spf13/afero"
2014-03-31 17:23:34 +00:00
"github.com/spf13/cobra"
2014-04-05 05:26:43 +00:00
"github.com/spf13/hugo/helpers"
2014-11-01 15:57:29 +00:00
"github.com/spf13/hugo/hugofs"
2014-03-31 17:23:34 +00:00
jww "github.com/spf13/jwalterweatherman"
2014-04-05 05:26:43 +00:00
"github.com/spf13/viper"
2013-09-29 06:09:03 +00:00
)
2016-02-05 17:41:40 +00:00
var (
disableLiveReload bool
renderToDisk bool
serverAppend bool
serverInterface string
serverPort int
serverWatch bool
)
2013-09-29 06:09:03 +00:00
2014-05-16 21:49:27 +00:00
//var serverCmdV *cobra.Command
2013-09-29 06:09:03 +00:00
var serverCmd = & cobra . Command {
2015-11-17 02:55:18 +00:00
Use : "server" ,
Aliases : [ ] string { "serve" } ,
Short : "A high performance webserver" ,
2015-11-21 13:31:10 +00:00
Long : ` Hugo provides its own webserver which builds and serves the site .
2015-11-12 15:23:41 +00:00
While hugo server is high performance , it is a webserver with limited options .
2015-11-21 13:31:10 +00: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 15:23:41 +00:00
2015-11-17 02:55:18 +00:00
' hugo server ' will avoid writing the rendered and served content to disk ,
preferring to store it in memory .
2015-11-20 15:13:03 +00: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 13:31:10 +00: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 10:42:53 +00:00
//RunE: server,
2014-05-16 21:49:27 +00:00
}
2015-10-23 16:21:37 +00: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 21:49:27 +00:00
func init ( ) {
2016-02-05 17:41:40 +00:00
initHugoBuilderFlags ( serverCmd )
2015-05-02 02:28:21 +00: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 15:13:03 +00:00
serverCmd . Flags ( ) . BoolVarP ( & serverWatch , "watch" , "w" , true , "watch filesystem for changes and recreate as needed" )
2016-10-24 18:56:00 +00:00
serverCmd . Flags ( ) . BoolVarP ( & serverAppend , "appendPort" , "" , true , "append port to baseURL" )
2014-05-16 21:49:27 +00:00
serverCmd . Flags ( ) . BoolVar ( & disableLiveReload , "disableLiveReload" , false , "watch without enabling live browser reload on rebuild" )
2015-11-17 02:55:18 +00:00
serverCmd . Flags ( ) . BoolVar ( & renderToDisk , "renderToDisk" , false , "render to Destination path (default is render to memory & serve from there)" )
2014-09-22 13:45:05 +00:00
serverCmd . Flags ( ) . String ( "memstats" , "" , "log memory usage to this file" )
2016-08-02 17:48:07 +00: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 10:42:53 +00:00
serverCmd . RunE = server
2016-03-06 13:10:06 +00:00
2016-06-15 17:34:21 +00:00
mime . AddExtensionType ( ".json" , "application/json; charset=utf-8" )
mime . AddExtensionType ( ".css" , "text/css; charset=utf-8" )
2016-04-27 19:39:57 +00:00
2013-09-29 06:09:03 +00:00
}
2015-12-02 10:42:53 +00:00
func server ( cmd * cobra . Command , args [ ] string ) error {
2015-12-02 19:00:47 +00:00
if err := InitializeConfig ( serverCmd ) ; err != nil {
2015-12-02 18:56:36 +00:00
return err
}
2013-09-29 06:09:03 +00:00
2016-02-27 17:01:01 +00:00
if flagChanged ( cmd . Flags ( ) , "disableLiveReload" ) {
2016-10-24 18:56:00 +00:00
viper . Set ( "disableLiveReload" , disableLiveReload )
2014-05-16 21:49:27 +00:00
}
if serverWatch {
2016-10-24 18:56:00 +00:00
viper . Set ( "watch" , true )
2014-05-16 21:49:27 +00:00
}
2015-07-18 00:42:09 +00:00
if viper . GetBool ( "watch" ) {
serverWatch = true
2015-11-10 04:31:52 +00:00
watchConfig ( )
2015-07-18 00:42:09 +00:00
}
2015-05-02 02:28:21 +00:00
l , err := net . Listen ( "tcp" , net . JoinHostPort ( serverInterface , strconv . Itoa ( serverPort ) ) )
2014-05-15 19:07:46 +00:00
if err == nil {
l . Close ( )
} else {
2016-02-27 15:58:42 +00:00
if flagChanged ( serverCmd . Flags ( ) , "port" ) {
// port set explicitly by user -- he/she probably meant it!
2016-06-14 15:48:27 +00:00
return newSystemErrorF ( "Server startup failed: %s" , err )
2016-02-27 15:58:42 +00:00
}
2014-05-15 19:07:46 +00: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 10:42:53 +00:00
return newSystemError ( "Unable to find alternative port to use:" , err )
2014-05-15 19:07:46 +00:00
}
serverPort = sp . Port
}
2014-05-28 23:01:24 +00:00
viper . Set ( "port" , serverPort )
2016-10-24 18:56:00 +00:00
baseURL , err = fixURL ( baseURL )
2014-08-22 11:59:59 +00:00
if err != nil {
2015-12-02 10:42:53 +00:00
return err
2014-01-29 22:50:31 +00:00
}
2016-10-24 18:56:00 +00:00
viper . Set ( "baseURL" , baseURL )
2013-11-22 05:28:05 +00:00
2014-09-22 13:45:05 +00:00
if err := memStats ( ) ; err != nil {
jww . ERROR . Println ( "memstats error:" , err )
}
2015-11-17 02:55:18 +00:00
// If a Destination is provided via flag write to disk
2016-02-05 21:58:17 +00:00
if destination != "" {
2015-11-17 02:55:18 +00:00
renderToDisk = true
}
// Hugo writes the output to memory instead of the disk
if ! renderToDisk {
2016-03-21 23:28:42 +00:00
hugofs . SetDestination ( new ( afero . MemMapFs ) )
2015-11-17 02:55:18 +00:00
// Rendering to memoryFS, publish to Root regardless of publishDir.
2016-10-24 18:56:00 +00:00
viper . Set ( "publishDir" , "/" )
2015-11-17 02:55:18 +00:00
}
2015-12-02 10:42:53 +00:00
if err := build ( serverWatch ) ; err != nil {
return err
}
2013-10-09 22:24:40 +00:00
2013-09-29 06:09:03 +00:00
// Watch runs its own server as part of the routine
if serverWatch {
2015-11-23 15:32:06 +00:00
watchDirs := getDirList ( )
2016-10-24 18:56:00 +00:00
baseWatchDir := viper . GetString ( "workingDir" )
2015-11-23 15:32:06 +00:00
for i , dir := range watchDirs {
watchDirs [ i ] , _ = helpers . GetRelativePath ( dir , baseWatchDir )
2015-04-02 04:40:29 +00:00
}
2015-11-23 15:32:06 +00:00
rootWatchDirs := strings . Join ( helpers . UniqueStrings ( helpers . ExtractRootPaths ( watchDirs ) ) , "," )
2015-12-17 03:41:33 +00:00
jww . FEEDBACK . Printf ( "Watching for changes in %s%s{%s}\n" , baseWatchDir , helpers . FilePathSeparator , rootWatchDirs )
2013-10-01 02:38:32 +00:00
err := NewWatcher ( serverPort )
2015-12-02 10:42:53 +00:00
2013-09-29 06:09:03 +00:00
if err != nil {
2015-12-02 10:42:53 +00:00
return err
2013-09-29 06:09:03 +00:00
}
}
serve ( serverPort )
2015-12-02 10:42:53 +00:00
return nil
2013-09-29 06:09:03 +00:00
}
func serve ( port int ) {
2015-11-17 02:55:18 +00:00
if renderToDisk {
2016-10-24 18:56:00 +00:00
jww . FEEDBACK . Println ( "Serving pages from " + helpers . AbsPathify ( viper . GetString ( "publishDir" ) ) )
2015-11-17 02:55:18 +00:00
} else {
jww . FEEDBACK . Println ( "Serving pages from memory" )
}
2014-01-26 09:48:00 +00:00
2016-03-21 23:28:42 +00:00
httpFs := afero . NewHttpFs ( hugofs . Destination ( ) )
2016-10-24 18:56:00 +00:00
fs := filesOnlyFs { httpFs . Dir ( helpers . AbsPathify ( viper . GetString ( "publishDir" ) ) ) }
2015-10-23 16:21:37 +00:00
fileserver := http . FileServer ( fs )
2014-08-22 11:59:59 +00:00
2015-05-02 02:28:21 +00:00
// We're only interested in the path
2016-10-24 18:56:00 +00:00
u , err := url . Parse ( viper . GetString ( "baseURL" ) )
2014-08-22 11:59:59 +00:00
if err != nil {
2016-10-24 18:56:00 +00:00
jww . ERROR . Fatalf ( "Invalid baseURL: %s" , err )
2014-08-22 11:59:59 +00:00
}
if u . Path == "" || u . Path == "/" {
http . Handle ( "/" , fileserver )
} else {
2014-10-19 12:41:02 +00:00
http . Handle ( u . Path , http . StripPrefix ( u . Path , fileserver ) )
2014-08-22 11:59:59 +00:00
}
2015-09-16 05:12:01 +00:00
jww . FEEDBACK . Printf ( "Web Server is available at %s (bind address %s)\n" , u . String ( ) , serverInterface )
2015-01-29 21:19:12 +00:00
fmt . Println ( "Press Ctrl+C to stop" )
2014-10-17 16:32:16 +00:00
2015-05-02 02:28:21 +00:00
endpoint := net . JoinHostPort ( serverInterface , strconv . Itoa ( port ) )
err = http . ListenAndServe ( endpoint , nil )
2014-01-26 09:48:00 +00:00
if err != nil {
2014-03-31 17:23:34 +00:00
jww . ERROR . Printf ( "Error: %s\n" , err . Error ( ) )
2014-01-26 09:48:00 +00:00
os . Exit ( 1 )
}
2013-09-29 06:09:03 +00:00
}
2014-08-22 11:59:59 +00:00
2016-10-24 18:56:00 +00:00
// fixURL massages the baseURL into a form needed for serving
2015-01-16 01:02:19 +00:00
// all pages correctly.
2015-03-11 17:34:57 +00:00
func fixURL ( s string ) ( string , error ) {
2014-08-22 11:59:59 +00:00
useLocalhost := false
if s == "" {
2016-10-24 18:56:00 +00:00
s = viper . GetString ( "baseURL" )
2014-08-22 11:59:59 +00:00
useLocalhost = true
}
2016-05-12 23:06:56 +00:00
2015-01-23 00:46:29 +00:00
if ! strings . HasSuffix ( s , "/" ) {
2015-01-16 01:02:19 +00:00
s = s + "/"
}
2016-05-12 23:06:56 +00:00
// do an initial parse of the input string
2014-08-22 11:59:59 +00:00
u , err := url . Parse ( s )
if err != nil {
return "" , err
}
2016-05-12 23:06:56 +00: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 15:32:56 +00:00
u . Scheme = "http"
2014-08-22 11:59:59 +00:00
}
2016-05-12 23:06:56 +00:00
u . Host = "localhost"
}
if serverAppend {
if strings . Contains ( u . Host , ":" ) {
u . Host , _ , err = net . SplitHostPort ( u . Host )
2014-08-22 11:59:59 +00:00
if err != nil {
2016-10-24 18:56:00 +00:00
return "" , fmt . Errorf ( "Failed to split baseURL hostpost: %s" , err )
2014-08-22 11:59:59 +00:00
}
}
2016-05-12 23:06:56 +00:00
u . Host += fmt . Sprintf ( ":%d" , serverPort )
2014-08-22 11:59:59 +00:00
}
return u . String ( ) , nil
}
2014-09-22 13:45:05 +00: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
}