Basic Static Framework
This commit is contained in:
parent
ef30703ea1
commit
3d97ae44bc
@ -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.
|
||||
|
20
contrib/dn42grcsrv.service
Normal file
20
contrib/dn42grcsrv.service
Normal 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
172
dn42grcsrv.go
Normal 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
82
util.go
Normal 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
|
Loading…
x
Reference in New Issue
Block a user