Permalink
Browse files

Add multilingual multihost support

This commit adds multihost support when more than one language is configured and `baseURL` is set per language.

Updates #4027
  • Loading branch information...
bep committed Nov 2, 2017
1 parent 6233ddf commit 2e0465764b5dacc511b977b1c9aa07324ad0ee9c
View
@@ -41,6 +41,10 @@ func (c *commandeer) PathSpec() *helpers.PathSpec {
return c.pathSpec
}
func (c *commandeer) languages() helpers.Languages {
return c.Cfg.Get("languagesSorted").(helpers.Languages)
}
func (c *commandeer) initFs(fs *hugofs.Fs) error {
c.DepsCfg.Fs = fs
ps, err := helpers.NewPathSpec(fs, c.Cfg)
View
@@ -526,6 +526,7 @@ func (c *commandeer) watchConfig() {
func (c *commandeer) build(watches ...bool) error {
if err := c.copyStatic(); err != nil {
// TODO(bep) multihost
return fmt.Errorf("Error copying static files to %s: %s", c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")), err)
}
watch := false
@@ -593,6 +594,24 @@ func (c *commandeer) getStaticSourceFs() afero.Fs {
func (c *commandeer) copyStatic() error {
publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator
roots := c.roots()
if len(roots) == 0 {
return c.copyStaticTo(publishDir)
}
for _, root := range roots {
dir := filepath.Join(publishDir, root)
if err := c.copyStaticTo(dir); err != nil {
return err
}
}
return nil
}
func (c *commandeer) copyStaticTo(publishDir string) error {
// If root, remove the second '/'
if publishDir == "//" {
@@ -893,6 +912,7 @@ func (c *commandeer) newWatcher(port int) error {
if c.Cfg.GetBool("forceSyncStatic") {
c.Logger.FEEDBACK.Printf("Syncing all static files\n")
// TODO(bep) multihost
err := c.copyStatic()
if err != nil {
utils.StopOnErr(c.Logger, err, fmt.Sprintf("Error copying static files to %s", publishDir))
View
@@ -19,12 +19,14 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
"github.com/spf13/cobra"
@@ -137,34 +139,58 @@ func server(cmd *cobra.Command, args []string) error {
c.watchConfig()
}
l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(serverPort)))
if err == nil {
l.Close()
} else {
if serverCmd.Flags().Changed("port") {
// port set explicitly by user -- he/she probably meant it!
return newSystemErrorF("Server startup failed: %s", err)
}
jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port")
sp, err := helpers.FindAvailablePort()
if err != nil {
return newSystemError("Unable to find alternative port to use:", err)
languages := c.languages()
serverPorts := make([]int, 1)
if languages.IsMultihost() {
serverPorts = make([]int, len(languages))
}
currentServerPort := serverPort
for i := 0; i < len(serverPorts); i++ {
l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(currentServerPort)))
if err == nil {
l.Close()
serverPorts[i] = currentServerPort
} else {
if i == 0 && serverCmd.Flags().Changed("port") {
// port set explicitly by user -- he/she probably meant it!
return newSystemErrorF("Server startup failed: %s", err)
}
jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port")
sp, err := helpers.FindAvailablePort()
if err != nil {
return newSystemError("Unable to find alternative port to use:", err)
}
serverPorts[i] = sp.Port
}
serverPort = sp.Port
currentServerPort = serverPorts[i] + 1
}
c.Set("port", serverPort)
if liveReloadPort != -1 {
c.Set("liveReloadPort", liveReloadPort)
} else {
c.Set("liveReloadPort", serverPort)
c.Set("liveReloadPort", serverPorts[0])
}
baseURL, err = fixURL(c.Cfg, baseURL)
if err != nil {
return err
if languages.IsMultihost() {
for i, language := range languages {
baseURL, err = fixURL(language, baseURL, serverPorts[i])
if err != nil {
return err
}
language.Set("baseURL", baseURL)
}
} else {
baseURL, err = fixURL(c.Cfg, baseURL, serverPorts[0])
if err != nil {
return err
}
c.Cfg.Set("baseURL", baseURL)
}
c.Set("baseURL", baseURL)
if err := memStats(); err != nil {
jww.ERROR.Println("memstats error:", err)
@@ -208,28 +234,52 @@ func server(cmd *cobra.Command, args []string) error {
}
}
c.serve(serverPort)
return nil
}
func (c *commandeer) serve(port int) {
type fileServer struct {
basePort int
baseURLs []string
roots []string
c *commandeer
}
func (f *fileServer) createEndpoint(i int) (*http.ServeMux, string, error) {
baseURL := f.baseURLs[i]
root := f.roots[i]
port := f.basePort + i
publishDir := f.c.Cfg.GetString("publishDir")
if root != "" {
publishDir = filepath.Join(publishDir, root)
}
absPublishDir := f.c.PathSpec().AbsPathify(publishDir)
// TODO(bep) multihost unify feedback
if renderToDisk {
jww.FEEDBACK.Println("Serving pages from " + c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")))
jww.FEEDBACK.Println("Serving pages from " + absPublishDir)
} else {
jww.FEEDBACK.Println("Serving pages from memory")
}
httpFs := afero.NewHttpFs(c.Fs.Destination)
fs := filesOnlyFs{httpFs.Dir(c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")))}
httpFs := afero.NewHttpFs(f.c.Fs.Destination)
fs := filesOnlyFs{httpFs.Dir(absPublishDir)}
doLiveReload := !buildWatch && !c.Cfg.GetBool("disableLiveReload")
fastRenderMode := doLiveReload && !c.Cfg.GetBool("disableFastRender")
doLiveReload := !buildWatch && !f.c.Cfg.GetBool("disableLiveReload")
fastRenderMode := doLiveReload && !f.c.Cfg.GetBool("disableFastRender")
if fastRenderMode {
jww.FEEDBACK.Println("Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender")
}
// We're only interested in the path
u, err := url.Parse(baseURL)
if err != nil {
return nil, "", fmt.Errorf("Invalid baseURL: %s", err)
}
decorate := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if noHTTPCache {
@@ -240,40 +290,86 @@ func (c *commandeer) serve(port int) {
if fastRenderMode {
p := r.RequestURI
if strings.HasSuffix(p, "/") || strings.HasSuffix(p, "html") || strings.HasSuffix(p, "htm") {
c.visitedURLs.Add(p)
f.c.visitedURLs.Add(p)
}
}
h.ServeHTTP(w, r)
})
}
fileserver := decorate(http.FileServer(fs))
mu := http.NewServeMux()
// We're only interested in the path
u, err := url.Parse(c.Cfg.GetString("baseURL"))
if err != nil {
jww.ERROR.Fatalf("Invalid baseURL: %s", err)
}
if u.Path == "" || u.Path == "/" {
http.Handle("/", fileserver)
mu.Handle("/", fileserver)
} else {
http.Handle(u.Path, http.StripPrefix(u.Path, fileserver))
mu.Handle(u.Path, http.StripPrefix(u.Path, fileserver))
}
jww.FEEDBACK.Printf("Web Server is available at %s (bind address %s)\n", u.String(), serverInterface)
jww.FEEDBACK.Println("Press Ctrl+C to stop")
endpoint := net.JoinHostPort(serverInterface, strconv.Itoa(port))
err = http.ListenAndServe(endpoint, nil)
if err != nil {
jww.ERROR.Printf("Error: %s\n", err.Error())
os.Exit(1)
return mu, endpoint, nil
}
func (c *commandeer) roots() []string {
var roots []string
languages := c.languages()
isMultiHost := languages.IsMultihost()
if !isMultiHost {
return roots
}
for _, l := range languages {
roots = append(roots, l.Lang)
}
return roots
}
func (c *commandeer) serve(port int) {
// TODO(bep) multihost
isMultiHost := Hugo.IsMultihost()
var (
baseURLs []string
roots []string
)
if isMultiHost {
for _, s := range Hugo.Sites {
baseURLs = append(baseURLs, s.BaseURL.String())
roots = append(roots, s.Language.Lang)
}
} else {
baseURLs = []string{Hugo.Sites[0].BaseURL.String()}
roots = []string{""}
}
srv := &fileServer{
basePort: port,
baseURLs: baseURLs,
roots: roots,
c: c,
}
for i, _ := range baseURLs {
mu, endpoint, err := srv.createEndpoint(i)
go func() {
err = http.ListenAndServe(endpoint, mu)
if err != nil {
jww.ERROR.Printf("Error: %s\n", err.Error())
os.Exit(1)
}
}()
}
// TODO(bep) multihost jww.FEEDBACK.Printf("Web Server is available at %s (bind address %s)\n", u.String(), serverInterface)
jww.FEEDBACK.Println("Press Ctrl+C to stop")
}
// fixURL massages the baseURL into a form needed for serving
// all pages correctly.
func fixURL(cfg config.Provider, s string) (string, error) {
func fixURL(cfg config.Provider, s string, port int) (string, error) {
useLocalhost := false
if s == "" {
s = cfg.GetString("baseURL")
@@ -315,7 +411,7 @@ func fixURL(cfg config.Provider, s string) (string, error) {
return "", fmt.Errorf("Failed to split baseURL hostpost: %s", err)
}
}
u.Host += fmt.Sprintf(":%d", serverPort)
u.Host += fmt.Sprintf(":%d", port)
}
return u.String(), nil
View
@@ -47,7 +47,7 @@ func TestFixURL(t *testing.T) {
v.Set("baseURL", test.CfgBaseURL)
serverAppend = test.AppendPort
serverPort = test.Port
result, err := fixURL(v, baseURL)
result, err := fixURL(v, baseURL, serverPort)
if err != nil {
t.Errorf("Test #%d %s: unexpected error %s", i, test.TestName, err)
}
View
@@ -102,6 +102,17 @@ func (l *Language) Params() map[string]interface{} {
return l.params
}
// IsMultihost returns whether the languages has baseURL specificed on the
// language level.
func (l Languages) IsMultihost() bool {
for _, lang := range l {
if lang.GetLocal("baseURL") != nil {
return true
}
}
return false
}
// SetParam sets param with the given key and value.
// SetParam is case-insensitive.
func (l *Language) SetParam(k string, v interface{}) {
@@ -132,6 +143,17 @@ func (l *Language) GetStringMapString(key string) map[string]string {
//
// Get returns an interface. For a specific value use one of the Get____ methods.
func (l *Language) Get(key string) interface{} {
local := l.GetLocal(key)
if local != nil {
return local
}
return l.Cfg.Get(key)
}
// GetLocal gets a configuration value set on language level. It will
// not fall back to any global value.
// It will return nil if a value with the given key cannot be found.
func (l *Language) GetLocal(key string) interface{} {
if l == nil {
panic("language not set")
}
@@ -141,7 +163,7 @@ func (l *Language) Get(key string) interface{} {
return v
}
}
return l.Cfg.Get(key)
return nil
}
// Set sets the value for the key in the language's params.
View
@@ -158,7 +158,6 @@ func (p *PathSpec) AbsPathify(inPath string) string {
return filepath.Clean(inPath)
}
// TODO(bep): Consider moving workingDir to argument list
return filepath.Join(p.workingDir, inPath)
}
Oops, something went wrong.

0 comments on commit 2e04657

Please sign in to comment.