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: weatherData.Current.Weather[0].Icon, } } 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: weatherData.Daily[i].Weather[0].Icon, ProbabilityOfPrecipitation: weatherData.Daily[i].PossibilityOfPrecipitation, } result = append(result, daily) } return result }