Basic Static Framework

This commit is contained in:
Simon Marsh 2019-02-24 09:39:36 +00:00
parent ef30703ea1
commit 3d97ae44bc
Signed by: burble
GPG Key ID: 7B9FE8780CFB6593
5 changed files with 281 additions and 1 deletions

4
API.md Normal file
View File

@ -0,0 +1,4 @@
# dn42grcsrv API Description
tbc

View File

@ -9,6 +9,8 @@ A public instance of the API and explorer web app can be accessed via:
## Features
* tbc
* Includes simple webserver for delivering static content
## Using
See the [API.md](API.md) file for a detailed description of the API.

View File

@ -0,0 +1,20 @@
##########################################################################
# dn42regsrv example systemd service file
##########################################################################
[Unit]
Description=DN42 GRC API Server
After=network.target
[Install]
WantedBy=multi-user.target
[Service]
User=regsrv
Group=registry
Type=simple
Restart=on-failure
ExecStart=/home/grcsrv/go/bin/dn42grcsrv
#########################################################################
# end of file

172
dn42grcsrv.go Normal file
View File

@ -0,0 +1,172 @@
//////////////////////////////////////////////////////////////////////////
// DN42 GRC API Server
//////////////////////////////////////////////////////////////////////////
package main
//////////////////////////////////////////////////////////////////////////
import (
"context"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
"net/http"
"os"
"os/signal"
"time"
)
//////////////////////////////////////////////////////////////////////////
var EventBus = make(SimpleEventBus)
//////////////////////////////////////////////////////////////////////////
// http request logger
func requestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.WithFields(log.Fields{
"method": r.Method,
"URL": r.URL.String(),
"Remote": r.RemoteAddr,
}).Debug("HTTP Request")
next.ServeHTTP(w, r)
})
}
//////////////////////////////////////////////////////////////////////////
// Install static routes
func installStaticRoutes(router *mux.Router, staticPath string) {
// an empty path disables static route serving
if staticPath == "" {
log.Info("Disabling static route serving")
return
}
// validate that the staticPath exists
stat, err := os.Stat(staticPath)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"path": staticPath,
}).Fatal("Unable to find static page directory")
}
// and it is a directory
if !stat.IsDir() {
log.WithFields(log.Fields{
"error": err,
"path": staticPath,
}).Fatal("Static path is not a directory")
}
// install a file server for the static route
router.PathPrefix("/").Handler(http.StripPrefix("/",
http.FileServer(http.Dir(staticPath)))).Methods("GET")
log.WithFields(log.Fields{
"path": staticPath,
}).Info("Static route installed")
}
//////////////////////////////////////////////////////////////////////////
// everything starts here
func main() {
// set a default log level, so that logging can be used immediately
// the level will be overidden later on once the command line
// options are loaded
log.SetLevel(log.InfoLevel)
log.Info("DN42 GRC API Server Starting")
// declare cmd line options
var (
logLevel = flag.StringP("LogLevel", "l", "Info", "Log level")
bindAddress = flag.StringP("BindAddress", "b", ":80", "Server bind address")
staticRoot = flag.StringP("StaticRoot", "s", "/home/grcsrv/webapp", "Static page directory")
refreshInterval = flag.StringP("Refresh", "i", "60m", "Refresh interval")
)
flag.Parse()
// now initialise logging properly based on the cmd line options
setLogLevel(*logLevel)
// parse the refreshInterval and start data collection
interval, err := time.ParseDuration(*refreshInterval)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"interval": *refreshInterval,
}).Fatal("Unable to parse registry refresh interval")
}
interval = interval
// start data collection here
// initialise router
router := mux.NewRouter()
// log all access
router.Use(requestLogger)
// add API routes
subr := router.PathPrefix("/api").Subrouter()
EventBus.Fire("APIEndpoint", subr)
// initialise static routes
installStaticRoutes(router, *staticRoot)
// initialise http server
server := &http.Server{
Addr: *bindAddress,
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: router,
}
// run the server in a non-blocking goroutine
log.WithFields(log.Fields{
"BindAddress": *bindAddress,
}).Info("Starting server")
go func() {
if err := server.ListenAndServe(); err != nil {
log.WithFields(log.Fields{
"error": err,
"BindAddress": *bindAddress,
}).Fatal("Unable to start server")
}
}()
// graceful shutdown via SIGINT (^C)
csig := make(chan os.Signal, 1)
signal.Notify(csig, os.Interrupt)
// and block
<-csig
log.Info("Server shutting down")
// deadline for server to shutdown
ctx, cancel := context.WithTimeout(context.Background(), 10)
defer cancel()
// shutdown the server
server.Shutdown(ctx)
// nothing left to do
log.Info("Shutdown complete, all done")
os.Exit(0)
}
//////////////////////////////////////////////////////////////////////////
// end of code

82
util.go Normal file
View File

@ -0,0 +1,82 @@
//////////////////////////////////////////////////////////////////////////
// DN42 GRC API Server
//////////////////////////////////////////////////////////////////////////
package main
//////////////////////////////////////////////////////////////////////////
import (
"encoding/json"
log "github.com/sirupsen/logrus"
"net/http"
)
//////////////////////////////////////////////////////////////////////////
// Simple event bus
type NotifyFunc func(...interface{})
type SimpleEventBus map[string][]NotifyFunc
// add a listener to an event
func (bus SimpleEventBus) Listen(event string, nfunc NotifyFunc) {
bus[event] = append(bus[event], nfunc)
}
// fire notifications for an event
func (bus SimpleEventBus) Fire(event string, params ...interface{}) {
funcs := bus[event]
if funcs != nil {
for _, nfunc := range funcs {
nfunc(params...)
}
}
}
//////////////////////////////////////////////////////////////////////////
// Return JSON from an API endpoint
func ResponseJSON(w http.ResponseWriter, v interface{}) {
// for response time testing
//time.Sleep(time.Second)
// marshal the JSON string
data, err := json.Marshal(v)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("Failed to marshal JSON")
}
// write back to http handler
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write(data)
}
//////////////////////////////////////////////////////////////////////////
// Set the log level
func setLogLevel(levelStr string) {
if level, err := log.ParseLevel(levelStr); err != nil {
// failed to set the level
// set a sensible default and, of course, log the error
log.SetLevel(log.InfoLevel)
log.WithFields(log.Fields{
"loglevel": levelStr,
"error": err,
}).Error("Failed to set requested log level")
} else {
// set the requested level
log.SetLevel(level)
}
}
//////////////////////////////////////////////////////////////////////////
// end of code