tempestas

A REST API for processing sensor.community data
git clone https://git.bracken.jp/tempestas.git
Log | Files | Refs | README | LICENSE

http.go (3579B)


      1 package http
      2 
      3 import (
      4 	"encoding/json"
      5 	"fmt"
      6 	"net/http"
      7 	"path/filepath"
      8 	"strconv"
      9 	"time"
     10 
     11 	"git.bracken.jp/tempestas/airrohr"
     12 	"git.bracken.jp/tempestas/storage"
     13 	"goji.io"
     14 	"goji.io/pat"
     15 )
     16 
     17 type HttpServer struct {
     18 	ds          storage.DataStore
     19 	resourceDir string
     20 }
     21 
     22 func CreateServer(ds storage.DataStore, resourceDir string) *HttpServer {
     23 	s := new(HttpServer)
     24 	s.ds = ds
     25 	s.resourceDir = resourceDir
     26 	return s
     27 }
     28 
     29 func (s HttpServer) ListenAndServe(addr string) error {
     30 	mux := goji.NewMux()
     31 
     32 	// API endpoints.
     33 	mux.HandleFunc(pat.Post("/sensor/airrohr/"), s.PostAirrohr)
     34 	mux.HandleFunc(pat.Get("/sensor/reading/:sensorId/:readingType"), s.QueryReadings)
     35 	mux.HandleFunc(pat.Get("/sensor/sensors/"), s.QuerySensors)
     36 
     37 	// UI endpoints.
     38 	fs := http.FileServer(http.Dir(s.resourceDir))
     39 	mux.Handle(pat.Get("/static/*"), http.StripPrefix("/static", fs))
     40 	mux.HandleFunc(pat.Get("/dashboard/"), s.PresentDashboard)
     41 
     42 	return http.ListenAndServe(addr, mux)
     43 }
     44 
     45 func (s HttpServer) PostAirrohr(w http.ResponseWriter, r *http.Request) {
     46 	contentType := r.Header.Get("Content-Type")
     47 	if contentType != "application/json" {
     48 		errorResponse(w, "Content-Type must be application/json", http.StatusUnsupportedMediaType)
     49 		return
     50 	}
     51 	var rpt airrohr.Report
     52 	err := airrohr.Parse(r.Body, &rpt)
     53 	if err != nil {
     54 		fmt.Println("Error: unknown JSON format from sensor: " + err.Error())
     55 		errorResponse(w, "Bad Request. "+err.Error(), http.StatusBadRequest)
     56 		return
     57 	}
     58 	t := time.Now().UTC()
     59 	fmt.Println(t.String() + " Report received from sensor " + rpt.SensorId)
     60 	for _, v := range rpt.Values {
     61 		fvalue, err := strconv.ParseFloat(v.Value, 64)
     62 		if err != nil {
     63 			fmt.Println("Error: received non-numeric value '" + v.Value + "'")
     64 			continue
     65 		}
     66 		rdg := storage.Reading{rpt.SensorId, rpt.SoftwareVersion, t, v.Type, fvalue}
     67 		if err = s.ds.StoreReading(&rdg); err != nil {
     68 			fmt.Println("Error: failed to write sensor data: " + err.Error())
     69 		}
     70 	}
     71 	errorResponse(w, "Success", http.StatusOK)
     72 }
     73 
     74 func (s HttpServer) QueryReadings(w http.ResponseWriter, r *http.Request) {
     75 	start := time.Now().Add(time.Hour * -24).UTC()
     76 	end := time.Now().UTC()
     77 
     78 	sensorId := pat.Param(r, "sensorId")
     79 	readingType := pat.Param(r, "readingType")
     80 
     81 	// TODO: extract start/end from r.URL.Query() with a default if not present.
     82 	readings, err := s.ds.QueryReadings(sensorId, readingType, start, end)
     83 	if err != nil {
     84 		fmt.Println("Error: failed to query sensor data: " + err.Error())
     85 		errorResponse(w, "Bad Request. "+err.Error(), http.StatusBadRequest)
     86 		return
     87 	}
     88 
     89 	resp, _ := json.Marshal(readings)
     90 	writeResponse(w, resp, http.StatusOK)
     91 }
     92 
     93 func (s HttpServer) QuerySensors(w http.ResponseWriter, r *http.Request) {
     94 	sensors, err := s.ds.QuerySensors()
     95 	if err != nil {
     96 		fmt.Println("Error: failed to query sensors: " + err.Error())
     97 		errorResponse(w, "Bad Request. "+err.Error(), http.StatusBadRequest)
     98 		return
     99 	}
    100 	resp, _ := json.Marshal(sensors)
    101 	writeResponse(w, resp, http.StatusOK)
    102 }
    103 
    104 func (s HttpServer) PresentDashboard(w http.ResponseWriter, r *http.Request) {
    105 	http.ServeFile(w, r, filepath.Join(s.resourceDir, "dashboard.html"))
    106 }
    107 
    108 func errorResponse(w http.ResponseWriter, message string, httpStatusCode int) {
    109 	resp := make(map[string]string)
    110 	resp["message"] = message
    111 	jsonResp, _ := json.Marshal(resp)
    112 	writeResponse(w, jsonResp, httpStatusCode)
    113 }
    114 
    115 func writeResponse(w http.ResponseWriter, resp []byte, httpStatusCode int) {
    116 	w.Header().Set("Access-Control-Allow-Origin", "*")
    117 	w.Header().Set("Content-Type", "application/json")
    118 	w.WriteHeader(httpStatusCode)
    119 	w.Write(resp)
    120 }