Initial static file router
This commit is contained in:
parent
3d30a58ccb
commit
2966cb2c7f
23
.editorconfig
Normal file
23
.editorconfig
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Common settings for all files. I don't specify indent type here, because
|
||||||
|
# some EditorConfig implementations (notably Atom) will cause default settings
|
||||||
|
# and behaviors (like tab type auto-detection) to be overridden.
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -0,0 +1,2 @@
|
||||||
|
bin/
|
||||||
|
dist/
|
34
Makefile
Normal file
34
Makefile
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
SHELL := /bin/bash
|
||||||
|
|
||||||
|
VERSION=$(shell grep -e 'Version' blog.go | head -n 1 | cut -d '"' -f 2)
|
||||||
|
BUILD=$(shell git describe --always)
|
||||||
|
CURDIR=$(shell curdir)
|
||||||
|
|
||||||
|
# Inject the build version (commit hash) into the executable.
|
||||||
|
LDFLAGS := -ldflags "-X main.Build=$(BUILD)"
|
||||||
|
|
||||||
|
# `make setup` to set up a new environment, pull dependencies, etc.
|
||||||
|
.PHONY: setup
|
||||||
|
setup: clean
|
||||||
|
go get -u ./...
|
||||||
|
|
||||||
|
# `make build` to build the binary.
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
gofmt -w .
|
||||||
|
go build $(LDFLAGS) -i -o bin/blog main.go
|
||||||
|
|
||||||
|
# `make run` to run it in debug mode.
|
||||||
|
.PHONY: run
|
||||||
|
run:
|
||||||
|
./go-reload main.go -debug
|
||||||
|
|
||||||
|
# `make test` to run unit tests.
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
# `make clean` cleans everything up.
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf bin dist
|
47
core/app.go
Normal file
47
core/app.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Blog is the root application object that maintains the app configuration
|
||||||
|
// and helper objects.
|
||||||
|
type Blog struct {
|
||||||
|
// DocumentRoot is the core static files root; UserRoot masks over it.
|
||||||
|
DocumentRoot string
|
||||||
|
UserRoot string
|
||||||
|
|
||||||
|
// Web app objects.
|
||||||
|
n *negroni.Negroni // Negroni middleware manager
|
||||||
|
r *mux.Router // Router
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes the Blog application.
|
||||||
|
func New(documentRoot, userRoot string) *Blog {
|
||||||
|
blog := &Blog{
|
||||||
|
DocumentRoot: documentRoot,
|
||||||
|
UserRoot: userRoot,
|
||||||
|
}
|
||||||
|
r := mux.NewRouter()
|
||||||
|
blog.r = r
|
||||||
|
r.HandleFunc("/", blog.PageHandler)
|
||||||
|
r.NotFoundHandler = http.HandlerFunc(blog.PageHandler)
|
||||||
|
|
||||||
|
n := negroni.New(
|
||||||
|
negroni.NewRecovery(),
|
||||||
|
negroni.NewLogger(),
|
||||||
|
)
|
||||||
|
blog.n = n
|
||||||
|
n.UseHandler(r)
|
||||||
|
|
||||||
|
return blog
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe begins listening on the given bind address.
|
||||||
|
func (b *Blog) ListenAndServe(address string) {
|
||||||
|
log.Info("Listening on %s", address)
|
||||||
|
http.ListenAndServe(address, b.n)
|
||||||
|
}
|
13
core/log.go
Normal file
13
core/log.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import "github.com/kirsle/golog"
|
||||||
|
|
||||||
|
var log *golog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = golog.GetLogger("blog")
|
||||||
|
log.Configure(&golog.Config{
|
||||||
|
Colors: golog.ExtendedColor,
|
||||||
|
Theme: golog.DarkTheme,
|
||||||
|
})
|
||||||
|
}
|
56
core/pages.go
Normal file
56
core/pages.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageHandler is the catch-all route handler, for serving static web pages.
|
||||||
|
func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := r.URL.Path
|
||||||
|
|
||||||
|
// Remove trailing slashes by redirecting them away.
|
||||||
|
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||||
|
Redirect(w, strings.TrimRight(path, "/"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for a file that matches their URL.
|
||||||
|
log.Debug("Resolving filepath for URI: %s", path)
|
||||||
|
for _, root := range []string{b.DocumentRoot, b.UserRoot} {
|
||||||
|
relPath := filepath.Join(root, path)
|
||||||
|
absPath, err := filepath.Abs(relPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Expected filepath: %s", absPath)
|
||||||
|
|
||||||
|
// Found an exact hit?
|
||||||
|
if stat, err := os.Stat(absPath); !os.IsNotExist(err) && !stat.IsDir() {
|
||||||
|
log.Debug("Exact filepath found: %s", absPath)
|
||||||
|
http.ServeFile(w, r, absPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try some supported suffixes.
|
||||||
|
suffixes := []string{
|
||||||
|
".html",
|
||||||
|
"/index.html",
|
||||||
|
".md",
|
||||||
|
"/index.md",
|
||||||
|
}
|
||||||
|
for _, suffix := range suffixes {
|
||||||
|
if stat, err := os.Stat(absPath + suffix); !os.IsNotExist(err) && !stat.IsDir() {
|
||||||
|
log.Debug("Filepath found via suffix %s: %s", suffix, absPath+suffix)
|
||||||
|
http.ServeFile(w, r, absPath+suffix)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No file, must be a 404.
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
9
core/responses.go
Normal file
9
core/responses.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Redirect sends an HTTP redirect response.
|
||||||
|
func Redirect(w http.ResponseWriter, location string) {
|
||||||
|
w.Header().Set("Location", location)
|
||||||
|
w.WriteHeader(http.StatusFound)
|
||||||
|
}
|
95
go-reload
Executable file
95
go-reload
Executable file
|
@ -0,0 +1,95 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Credit from: https://github.com/alexedwards/go-reload/tree/aabe19d0a9935d1238763a4a35e71787854cd5f5
|
||||||
|
|
||||||
|
####################################################
|
||||||
|
# @kirsle's custom changes from upstream are below #
|
||||||
|
####################################################
|
||||||
|
|
||||||
|
function die() {
|
||||||
|
echo >&2 $1
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Before we crash and burn, make sure necessary programs are installed!
|
||||||
|
command -v inotifywait || die "I need the inotifywait command (apt install inotify-tools)"
|
||||||
|
|
||||||
|
################################
|
||||||
|
# end @kirsle's custom changes #
|
||||||
|
################################
|
||||||
|
|
||||||
|
function monitor() {
|
||||||
|
if [ "$2" = "true" ]; then
|
||||||
|
# Watch all files in the specified directory
|
||||||
|
# Call the restart function when they are saved
|
||||||
|
inotifywait -q -m -r -e close_write -e moved_to $1 |
|
||||||
|
while read line; do
|
||||||
|
restart
|
||||||
|
done
|
||||||
|
else
|
||||||
|
# Watch all *.go files in the specified directory
|
||||||
|
# Call the restart function when they are saved
|
||||||
|
inotifywait -q -m -r -e close_write -e moved_to --exclude '[^g][^o]$' $1 |
|
||||||
|
while read line; do
|
||||||
|
restart
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Terminate and rerun the main Go program
|
||||||
|
function restart {
|
||||||
|
if [ "$(pidof $PROCESS_NAME)" ]; then
|
||||||
|
killall -q -w -9 $PROCESS_NAME
|
||||||
|
fi
|
||||||
|
echo ">> Reloading..."
|
||||||
|
eval "go run $ARGS &"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure all background processes get terminated
|
||||||
|
function close {
|
||||||
|
killall -q -w -9 inotifywait
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
trap close INT
|
||||||
|
echo "== Go-reload"
|
||||||
|
|
||||||
|
WATCH_ALL=false
|
||||||
|
while getopts ":a" opt; do
|
||||||
|
case $opt in
|
||||||
|
a)
|
||||||
|
WATCH_ALL=true
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo "Invalid option: -$OPTARG" >&2
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
shift "$((OPTIND - 1))"
|
||||||
|
|
||||||
|
FILE_NAME=$(basename $1)
|
||||||
|
PROCESS_NAME=${FILE_NAME%%.*}
|
||||||
|
|
||||||
|
ARGS=$@
|
||||||
|
|
||||||
|
# Start the main Go program
|
||||||
|
echo ">> Watching directories, CTRL+C to stop"
|
||||||
|
eval "go run $ARGS &"
|
||||||
|
|
||||||
|
# Monitor all /src directories on the GOPATH
|
||||||
|
OIFS="$IFS"
|
||||||
|
IFS=':'
|
||||||
|
for path in $GOPATH
|
||||||
|
do
|
||||||
|
monitor $path/src $WATCH_ALL &
|
||||||
|
done
|
||||||
|
IFS="$OIFS"
|
||||||
|
|
||||||
|
# If the current working directory isn't on the GOPATH, monitor it too
|
||||||
|
if [[ $PWD != "$GOPATH/"* ]]
|
||||||
|
then
|
||||||
|
monitor $PWD $WATCH_ALL
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait
|
38
main.go
Normal file
38
main.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Package blog is a web application which lets you host your own web blog,
|
||||||
|
// photo albums, wiki, etc.
|
||||||
|
//
|
||||||
|
// It is currently under early development and is not yet stable.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"github.com/kirsle/blog/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build-time config constants.
|
||||||
|
var (
|
||||||
|
Version = "0.0.1"
|
||||||
|
Build = "live"
|
||||||
|
DocumentRoot = "root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command line args.
|
||||||
|
var (
|
||||||
|
fDebug bool
|
||||||
|
fAddress string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&fDebug, "debug", false, "Debug mode")
|
||||||
|
flag.BoolVar(&fDebug, "d", false, "Debug mode (alias)")
|
||||||
|
flag.StringVar(&fAddress, "address", ":8000", "Bind address")
|
||||||
|
flag.StringVar(&fAddress, "a", ":8000", "Bind address (alias)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
app := core.New(DocumentRoot, "")
|
||||||
|
app.ListenAndServe(fAddress)
|
||||||
|
}
|
11
root/index.html
Normal file
11
root/index.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Blog</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Index</h1>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
11
root/test.html
Normal file
11
root/test.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Blog</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Test</h1>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user