Remove API and simplify webserver

This commit is contained in:
Julian Kornberger 2017-01-29 20:06:56 +01:00
parent c66e1120d3
commit 4cc93891ee
11 changed files with 39 additions and 335 deletions

View File

@ -1,6 +1,5 @@
language: go language: go
go: go:
- 1.7
- tip - tip
install: install:
- go get -t github.com/FreifunkBremen/respond-collector/... - go get -t github.com/FreifunkBremen/respond-collector/...
@ -8,3 +7,5 @@ install:
- go get golang.org/x/tools/cmd/cover - go get golang.org/x/tools/cmd/cover
script: script:
- ./.test-coverage - ./.test-coverage
- go install github.com/FreifunkBremen/respond-collector/cmd/respond-collector
- go install github.com/FreifunkBremen/respond-collector/cmd/respond-query

View File

@ -1,75 +0,0 @@
package api
import (
"encoding/json"
"fmt"
"github.com/FreifunkBremen/respond-collector/models"
"github.com/julienschmidt/httprouter"
"net/http"
)
// GEOROUND : 7 nachkommerstellen sollten genug sein (7cm genau)
// http://blog.3960.org/post/7309573249/genauigkeit-bei-geo-koordinaten
const GEOROUND = 0.0000001
func geoEqual(a, b float64) bool {
if (a-b) < GEOROUND && (b-a) < GEOROUND {
return true
}
return false
}
// AliasesAPI struct for API
type AliasesAPI struct {
aliases *models.Aliases
config *models.Config
nodes *models.Nodes
}
// NewAliases Bind to API
func NewAliases(config *models.Config, router *httprouter.Router, prefix string, nodes *models.Nodes) {
api := &AliasesAPI{
aliases: models.NewAliases(config),
nodes: nodes,
config: config,
}
router.GET(prefix, api.GetAll)
router.GET(prefix+"/ansible", api.Ansible)
router.GET(prefix+"/alias/:nodeid", api.GetOne)
router.POST(prefix+"/alias/:nodeid", BasicAuth(api.SaveOne, []byte(config.Webserver.API.Passphrase)))
}
// GetAll request for get all aliases
func (api *AliasesAPI) GetAll(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
jsonOutput(w, r, api.aliases.List)
}
// GetOne request for get one alias
func (api *AliasesAPI) GetOne(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if alias := api.aliases.List[ps.ByName("nodeid")]; alias != nil {
jsonOutput(w, r, alias)
return
}
fmt.Fprint(w, "Not found: ", ps.ByName("nodeid"), "\n")
}
// SaveOne request for save a alias
func (api *AliasesAPI) SaveOne(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var alias models.Alias
err := json.NewDecoder(r.Body).Decode(&alias)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
fmt.Fprint(w, "Decode: ", ps.ByName("nodeid"), "\n")
return
}
api.aliases.Update(ps.ByName("nodeid"), &alias)
fmt.Print("[api] node updated '", ps.ByName("nodeid"), "'\n")
jsonOutput(w, r, alias)
}
// Ansible json output
func (api *AliasesAPI) Ansible(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Print("[api] ansible\n")
jsonOutput(w, r, models.GenerateAnsible(api.nodes, api.aliases.List))
}

View File

@ -1,63 +0,0 @@
package api
import (
"bytes"
"encoding/base64"
"encoding/json"
"net/http"
"strings"
"github.com/julienschmidt/httprouter"
)
func jsonOutput(w http.ResponseWriter, r *http.Request, data interface{}) {
js, err := json.Marshal(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if origin := r.Header.Get("Origin"); origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Write(js)
}
// BasicAuth for API request
func BasicAuth(h httprouter.Handle, pass []byte) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if origin := r.Header.Get("Origin"); origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
const basicAuthPrefix string = "Basic "
// Get the Basic Authentication credentials
auth := r.Header.Get("Authorization")
if strings.HasPrefix(auth, basicAuthPrefix) {
// Check credentials
payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):])
if err == nil {
pair := bytes.SplitN(payload, []byte(":"), 2)
if len(pair) == 2 &&
bytes.Equal(pair[1], pass) {
// Delegate request to the given handle
h(w, r, ps)
return
}
}
}
// Request Basic Authentication otherwise
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}

View File

@ -1,27 +0,0 @@
package api
import (
"github.com/FreifunkBremen/respond-collector/models"
"github.com/julienschmidt/httprouter"
"net/http"
)
// NodesAPI struct for API
type NodesAPI struct {
config *models.Config
nodes *models.Nodes
}
// NewNodes Bind to API
func NewNodes(config *models.Config, router *httprouter.Router, prefix string, nodes *models.Nodes) {
api := &NodesAPI{
nodes: nodes,
config: config,
}
router.GET(prefix, api.GetAll)
}
// GetAll request for get all nodes
func (api *NodesAPI) GetAll(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
jsonOutput(w, r, api.nodes.List)
}

View File

@ -3,20 +3,15 @@ package main
import ( import (
"flag" "flag"
"log" "log"
"net"
"net/http"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/NYTimes/gziphandler"
"github.com/julienschmidt/httprouter"
"github.com/FreifunkBremen/respond-collector/api"
"github.com/FreifunkBremen/respond-collector/database" "github.com/FreifunkBremen/respond-collector/database"
"github.com/FreifunkBremen/respond-collector/models" "github.com/FreifunkBremen/respond-collector/models"
"github.com/FreifunkBremen/respond-collector/respond" "github.com/FreifunkBremen/respond-collector/respond"
"github.com/FreifunkBremen/respond-collector/rrd" "github.com/FreifunkBremen/respond-collector/rrd"
"github.com/FreifunkBremen/respond-collector/webserver"
) )
var ( var (
@ -54,21 +49,9 @@ func main() {
} }
if config.Webserver.Enable { if config.Webserver.Enable {
router := httprouter.New() log.Println("starting webserver on", config.Webserver.Bind)
if config.Webserver.API.NewNodes { srv := webserver.New(config.Webserver.Bind, config.Webserver.Webroot)
api.NewNodes(config, router, "/api/nodes", nodes) go srv.Close()
log.Println("api nodes started")
}
if config.Webserver.API.Aliases {
api.NewAliases(config, router, "/api/aliases", nodes)
log.Println("api aliases started")
}
router.NotFound = gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webserver.Webroot)))
address := net.JoinHostPort(config.Webserver.Address, config.Webserver.Port)
log.Println("starting webserver on", address)
// TODO bad
log.Fatal(http.ListenAndServe(address, router))
} }
// Wait for INT/TERM // Wait for INT/TERM

View File

@ -3,18 +3,18 @@ enable = true
interface = "eth0" interface = "eth0"
collect_interval = "1m" collect_interval = "1m"
[webserver] [webserver]
enable = false enable = false
port = "8080" bind = "127.0.0.1:8080"
address = "127.0.0.1"
webroot = "webroot" webroot = "webroot"
[nodes] [nodes]
enable = true enable = true
nodes_version = 2 nodes_version = 2
nodes_path = "/var/www/html/meshviewer/data/nodes_all.json" nodes_path = "/var/www/html/meshviewer/data/nodes_all.json"
graphs_path = "/var/www/html/meshviewer/data/graph.json" graph_path = "/var/www/html/meshviewer/data/graph.json"
aliases_path = "/var/www/html/meshviewer/data/aliases.json"
# Export nodes and graph periodically # Export nodes and graph periodically
save_interval = "5s" save_interval = "5s"

View File

@ -1,77 +0,0 @@
package models
import (
"encoding/json"
"io/ioutil"
"log"
"sync"
"time"
"github.com/FreifunkBremen/respond-collector/data"
)
// Alias a change request for other nodes
type Alias struct {
Hostname string `json:"hostname,omitempty"`
Location *data.Location `json:"location,omitempty"`
Wireless *data.Wireless `json:"wireless,omitempty"`
Owner string `json:"owner,omitempty"`
}
// Aliases struct: cache DB of Node's structs
type Aliases struct {
List map[string]*Alias `json:"nodes"` // the current nodemap, indexed by node ID
config *Config
sync.Mutex
}
// NewAliases create Nodes structs
func NewAliases(config *Config) *Aliases {
aliases := &Aliases{
List: make(map[string]*Alias),
config: config,
}
if config.Nodes.AliasesPath != "" {
aliases.load()
}
go aliases.worker()
return aliases
}
// Update a alias in aliases cache
func (e *Aliases) Update(nodeID string, newalias *Alias) {
e.Lock()
e.List[nodeID] = newalias
e.Unlock()
}
func (e *Aliases) load() {
path := e.config.Nodes.AliasesPath
log.Println("loading", path)
if data, err := ioutil.ReadFile(path); err == nil {
if err = json.Unmarshal(data, e); err == nil {
log.Println("loaded", len(e.List), "aliases")
} else {
log.Println("failed to unmarshal nodes:", err)
}
} else {
log.Println("failed loading cached nodes:", err)
}
}
// Periodically saves the cached DB to json file
func (e *Aliases) worker() {
c := time.Tick(time.Second * 5)
for range c {
log.Println("saving", len(e.List), "aliases")
e.Lock()
save(e, e.config.Nodes.AliasesPath)
e.Unlock()
}
}

View File

@ -1,55 +0,0 @@
package models
// Ansible struct
type Ansible struct {
Nodes []string `json:"nodes"`
Meta struct {
HostVars map[string]*AnsibleHostVars `json:"hostvars,omitempty"`
} `json:"_meta"`
}
// AnsibleHostVars new values for a node
type AnsibleHostVars struct {
Address string `json:"ansible_ssh_host"`
Hostname string `json:"node_name,omitempty"`
Owner string `json:"owner,omitempty"`
Channel24 uint32 `json:"radio24_channel,omitempty"`
TxPower24 uint32 `json:"radio24_txpower,omitempty"`
Channel5 uint32 `json:"radio5_channel,omitempty"`
TxPower5 uint32 `json:"radio5_txpower,omitempty"`
GeoLatitude float64 `json:"geo_latitude,omitempty"`
GeoLongitude float64 `json:"geo_longitude,omitempty"`
}
// GenerateAnsible but nodes and aliases together to a ansible change output
func GenerateAnsible(nodes *Nodes, aliases map[string]*Alias) *Ansible {
ansible := &Ansible{Nodes: make([]string, 0)}
ansible.Meta.HostVars = make(map[string]*AnsibleHostVars)
for nodeid, alias := range aliases {
if node := nodes.List[nodeid]; node != nil {
ansible.Nodes = append(ansible.Nodes, nodeid)
vars := &AnsibleHostVars{
Hostname: alias.Hostname,
Owner: alias.Owner,
}
if node.Nodeinfo.Network.Addresses != nil {
vars.Address = node.Nodeinfo.Network.Addresses[0]
}
if alias.Wireless != nil {
vars.Channel24 = alias.Wireless.Channel24
vars.TxPower24 = alias.Wireless.TxPower24
vars.Channel5 = alias.Wireless.Channel5
vars.TxPower5 = alias.Wireless.TxPower5
}
if alias.Location != nil {
vars.GeoLatitude = alias.Location.Latitude
vars.GeoLongitude = alias.Location.Longtitude
}
ansible.Meta.HostVars[nodeid] = vars
}
}
return ansible
}

View File

@ -15,22 +15,15 @@ type Config struct {
} }
Webserver struct { Webserver struct {
Enable bool Enable bool
Port string Bind string
Address string
Webroot string Webroot string
API struct {
Passphrase string
NewNodes bool
Aliases bool
}
} }
Nodes struct { Nodes struct {
Enable bool Enable bool
NodesDynamicPath string
NodesPath string
NodesVersion int NodesVersion int
GraphsPath string NodesPath string
AliasesPath string NodesDynamicPath string
GraphPath string
SaveInterval Duration // Save nodes periodically SaveInterval Duration // Save nodes periodically
PruneAfter Duration // Remove nodes after n days of inactivity PruneAfter Duration // Remove nodes after n days of inactivity
} }

View File

@ -211,7 +211,7 @@ func (nodes *Nodes) save() {
} }
} }
if path := nodes.config.Nodes.GraphsPath; path != "" { if path := nodes.config.Nodes.GraphPath; path != "" {
save(nodes.BuildGraph(), path) save(nodes.BuildGraph(), path)
} }
} }

24
webserver/webserver.go Normal file
View File

@ -0,0 +1,24 @@
package webserver
import (
"net/http"
"github.com/NYTimes/gziphandler"
)
// New creates a new webserver and starts it
func New(bindAddr, webroot string) *http.Server {
srv := &http.Server{
Addr: bindAddr,
Handler: gziphandler.GzipHandler(http.FileServer(http.Dir(webroot))),
}
go func() {
// service connections
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}()
return srv
}