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 }