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