WeatherService/src/server/WeatherServer.go

208 lines
6.3 KiB
Go

package server
import (
"encoding/json"
"github.com/auth0/go-jwt-middleware"
"github.com/dgrijalva/jwt-go"
"github.com/getsentry/sentry-go/http"
"github.com/gorilla/mux"
"github.com/justinas/alice"
"github.com/justinas/nosurf"
"github.com/throttled/throttled"
"github.com/throttled/throttled/store/memstore"
"github.com/victorspringer/http-cache"
"github.com/victorspringer/http-cache/adapter/memory"
"joethei.xyz/weather/client"
"joethei.xyz/weather/config"
"log"
"net/http"
"strconv"
"time"
)
var conf *config.Configuration
func StartServer() {
conf = config.LoadConfiguration()
port := strconv.Itoa(conf.Port)
log.Println("starting server on port " + port)
store, err := memstore.New(65536)
if err != nil {
log.Fatal(err)
}
quota := throttled.RateQuota{
MaxRate: throttled.PerMin(20), MaxBurst: 30,
}
rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
if err != nil {
log.Fatal(err)
}
httpRateLimiter := throttled.HTTPRateLimiter{
RateLimiter: rateLimiter,
VaryBy: &throttled.VaryBy{Path: true},
}
memcached, err := memory.NewAdapter(
memory.AdapterWithAlgorithm(memory.LRU),
memory.AdapterWithCapacity(10000000),
)
if err != nil {
log.Fatal(err)
}
cacheClient, err := cache.NewClient(
cache.ClientWithAdapter(memcached),
cache.ClientWithTTL(10*time.Minute),
cache.ClientWithRefreshKey("opn"),
)
if err != nil {
log.Fatal(err)
}
jwtAuth := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte(conf.JwtSecret), nil
},
SigningMethod: jwt.SigningMethodHS512,
})
chain := alice.New(jwtAuth.Handler, timeoutHandler, httpRateLimiter.RateLimit, nosurf.NewPure, cacheClient.Middleware)
if conf.SentryDsn != "" {
sentryHandler := sentryhttp.New(sentryhttp.Options{
Repanic: true,
})
chain.Append(sentryHandler.Handle)
}
router := mux.NewRouter()
router.HandleFunc("/", defaultHandler)
router.HandleFunc("/current/{latitude},{longitude}", chain.ThenFunc(currentHandler).ServeHTTP)
router.HandleFunc("/forecast/{latitude},{longitude}", chain.ThenFunc(forecastHandler).ServeHTTP)
if err := http.ListenAndServe(":"+port, router); err != nil {
panic(err)
}
}
func defaultHandler(writer http.ResponseWriter, _ *http.Request) {
writer.WriteHeader(http.StatusOK)
if _, err := writer.Write([]byte("Hello World")); err != nil {
panic(err)
}
}
func currentHandler(writer http.ResponseWriter, req *http.Request) {
writer.Header().Set("Content-Type", "application/json")
vars := mux.Vars(req)
longitude, err := strconv.ParseFloat(vars["longitude"], 32)
if err != nil {
panic(err)
}
latitude, err := strconv.ParseFloat(vars["latitude"], 32)
if err != nil {
panic(err)
}
language, units := getOptionalArgs(req)
data := client.GetByCoordinates(longitude, latitude, language, units)
writer.WriteHeader(http.StatusOK)
if err := json.NewEncoder(writer).Encode(ConvertCurrent(data)); err != nil {
panic(err)
}
}
func forecastHandler(writer http.ResponseWriter, req *http.Request) {
writer.Header().Set("Content-Type", "application/json")
vars := mux.Vars(req)
longitude, err := strconv.ParseFloat(vars["longitude"], 32)
if err != nil {
panic(err)
}
latitude, err := strconv.ParseFloat(vars["latitude"], 32)
if err != nil {
panic(err)
}
language, units := getOptionalArgs(req)
data := client.GetByCoordinates(longitude, latitude, language, units)
writer.WriteHeader(http.StatusOK)
if err := json.NewEncoder(writer).Encode(ConvertDaily(data)); err != nil {
panic(err)
}
}
func timeoutHandler(h http.Handler) http.Handler {
return http.TimeoutHandler(h, 1*time.Second, "timed out")
}
func getOptionalArgs(req *http.Request) (language string, units config.Units) {
language = conf.Language
units = conf.Units
if lang := req.URL.Query().Get("language"); lang != "" {
language = lang
}
if unit := req.URL.Query().Get("units"); unit != "" {
units = config.StringToUnit(unit)
}
return language, units
}
func ConvertCurrent(weatherData client.WeatherResponse) Current {
return Current{
Sunrise: time.Unix(weatherData.Current.Sunrise, 0),
Sunset: time.Unix(weatherData.Current.Sunset, 0),
Temperature: weatherData.Current.Temperature,
FeelsLike: weatherData.Current.FeelsLike,
Pressure: weatherData.Current.Pressure,
Humidity: weatherData.Current.Humidity,
DewPoint: weatherData.Current.DewPoint,
UVI: weatherData.Current.UVI,
Clouds: weatherData.Current.Clouds,
Visibility: weatherData.Current.Visibility,
WindSpeed: weatherData.Current.WindSpeed,
WindDegree: weatherData.Current.WindDegree,
Description: weatherData.Current.Weather[0].Description,
Icon: "https://openweathermap.org/img/wn/" + weatherData.Current.Weather[0].Icon + "@4x.png",
}
}
func ConvertDaily(weatherData client.WeatherResponse) []Daily {
var result []Daily
for i := 0; i < len(weatherData.Daily); i++ {
var daily = Daily{
Date: time.Unix(weatherData.Daily[i].Date, 0),
Sunrise: time.Unix(weatherData.Daily[i].Sunrise, 0),
Sunset: time.Unix(weatherData.Daily[i].Sunset, 0),
TemperatureDay: weatherData.Daily[i].Temperature.Day,
TemperatureNight: weatherData.Daily[i].Temperature.Night,
TemperatureMin: weatherData.Daily[i].Temperature.Min,
TemperatureMax: weatherData.Daily[i].Temperature.Max,
FeelsLikeDay: weatherData.Daily[i].FeelsLike.Day,
FeelsLikeNight: weatherData.Daily[i].FeelsLike.Night,
Pressure: weatherData.Daily[i].Pressure,
Humidity: weatherData.Daily[i].Humidity,
DewPoint: weatherData.Daily[i].DewPoint,
UVI: weatherData.Daily[i].UVI,
Clouds: weatherData.Daily[i].Clouds,
Visibility: weatherData.Daily[i].Visibility,
WindSpeed: weatherData.Daily[i].WindSpeed,
WindDegree: weatherData.Daily[i].WindDegree,
Description: weatherData.Daily[i].Weather[0].Description,
Icon: "https://openweathermap.org/img/wn/" + weatherData.Daily[i].Weather[0].Icon + "@4x.png",
ProbabilityOfPrecipitation: weatherData.Daily[i].PossibilityOfPrecipitation,
}
result = append(result, daily)
}
return result
}