engine/static.go
Rama main · réplica de lectura del repositorio original.
package engine
// This source file is for the special case of serving a single file.
import (
"errors"
"net"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/parser"
"github.com/sirupsen/logrus"
"github.com/xyproto/algernon/utils"
"github.com/xyproto/datablock"
)
const (
defaultStaticCacheSize = 128 * utils.MiB
maxAttemptsAtIncreasingPortNumber = 128
delayBeforeLaunchingBrowser = time.Millisecond * 200
)
// nextPort increases the port number by 1
func nextPort(colonPort string) (string, error) {
_, port, err := net.SplitHostPort(colonPort)
if err != nil {
return colonPort, errors.New("invalid port format: \"" + colonPort + "\"")
}
num, err := strconv.Atoi(port)
if err != nil {
return colonPort, errors.New("could not convert port number: \"" + port + "\"")
}
return net.JoinHostPort("", strconv.Itoa(num+1)), nil
}
// This is a bit hacky, but it's only used when serving a single static file
func (ac *Config) openAfter(wait time.Duration, hostname, colonPort string, https bool, cancelChannel chan bool) {
// Wait a bit
time.Sleep(wait)
select {
case <-cancelChannel:
// Got a message on the cancelChannel:
// don't open the URL with an external application.
return
case <-time.After(delayBeforeLaunchingBrowser):
// Got timeout, assume the port was not busy
ac.OpenURL(hostname, colonPort, https)
}
}
// shortInfo outputs a short string about which file is served where
func (ac *Config) shortInfoAndOpen(filename, colonPort string, cancelChannel chan bool) {
hostname := "localhost"
if ac.serverHost != "" {
hostname = ac.serverHost
}
logrus.Infof("Serving %s on http://%s", filename, utils.HostPortToURL(utils.JoinHostPort(hostname, colonPort)))
if ac.openURLAfterServing {
go ac.openAfter(delayBeforeLaunchingBrowser, hostname, colonPort, false, cancelChannel)
}
}
// ServeStaticFile is a convenience function for serving only a single file.
// It can be used as a quick and easy way to view a README.md file.
// Will also serve local images if the resulting HTML contains them.
func (ac *Config) ServeStaticFile(filename, colonPort string) error {
logrus.Info("Single file mode. Not using the regular parameters.")
if filepath.Base(filename) == "README.md" {
logrus.Info("Try using the -m flag for displaying Markdown files in a browser.")
}
cancelChannel := make(chan bool, 1)
ac.shortInfoAndOpen(filename, colonPort, cancelChannel)
mux := http.NewServeMux()
// 64 MiB cache, use cache compression, no per-file size limit, use best gzip compression, compress for size not for speed
ac.cache = datablock.NewFileCache(defaultStaticCacheSize, true, 0, false, 0)
if ac.markdownMode {
// Discover all local images mentioned in the Markdown document
var localImages []string
if markdownData, err := ac.cache.Read(filename, true); err == nil { // success
// Create a Markdown parser with the desired extensions
mdParser := parser.NewWithExtensions(enabledMarkdownExtensions)
// Convert from Markdown to HTML
tempHTMLData := string(markdown.ToHTML(markdownData.Bytes(), mdParser, nil))
localImages = utils.ExtractLocalImagePaths(tempHTMLData)
}
// Serve all local images mentioned in the Markdown document.
// If a file is not found, then the FilePage function will handle it.
for _, localImage := range localImages {
mux.HandleFunc("/"+localImage, func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Server", ac.versionString)
ac.FilePage(w, req, localImage, defaultLuaDataFilename)
})
}
}
// Prepare to serve the given filename
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Server", ac.versionString)
ac.FilePage(w, req, filename, defaultLuaDataFilename)
})
HTTPserver := ac.NewGracefulServer(mux, false, utils.JoinHostPort(ac.serverHost, colonPort))
// Attempt to serve the handler functions above
if errServe := HTTPserver.ListenAndServe(); errServe != nil {
// If it fails, try several times, increasing the port by 1 each time
for range maxAttemptsAtIncreasingPortNumber {
if errServe = HTTPserver.ListenAndServe(); errServe != nil {
cancelChannel <- true
if !strings.HasSuffix(errServe.Error(), "already in use") {
// Not a problem with address already being in use
ac.fatalExit(errServe)
}
logrus.Warn("Address already in use. Using next port number.")
if newPort, errNext := nextPort(colonPort); errNext != nil {
ac.fatalExit(errNext)
} else {
colonPort = newPort
}
// Make a new cancel channel, and use the new URL
cancelChannel = make(chan bool, 1)
ac.shortInfoAndOpen(filename, colonPort, cancelChannel)
HTTPserver = ac.NewGracefulServer(mux, false, utils.JoinHostPort(ac.serverHost, colonPort))
}
}
// Several attempts failed
return errServe
}
return nil
}