From c5e95dd73456ab45ea30952f89214d88096ba2d5 Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Sat, 4 May 2019 17:35:05 +0200
Subject: [PATCH] Improve cache handling

---
 cache_redis.go | 19 +++++++++----
 config.go      | 31 ++++++++++++++++++++++
 go.mod         |  3 ++-
 go.sum         |  5 ++--
 main.go        | 72 ++++++++++++++++++++++++++++++++++----------------
 5 files changed, 99 insertions(+), 31 deletions(-)
 create mode 100644 config.go

diff --git a/cache_redis.go b/cache_redis.go
index cf4ac66..cc2af24 100644
--- a/cache_redis.go
+++ b/cache_redis.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"encoding/json"
+	"errors"
 	"git.kuschku.de/justjanne/bahn-api"
 	"github.com/go-redis/cache"
 	"github.com/go-redis/redis"
@@ -15,24 +16,32 @@ type RedisCache struct {
 
 func (m RedisCache) Set(key string, value interface{}) error {
 	return m.backend.Set(&cache.Item{
-		Key: key,
-		Object: value,
+		Key:        key,
+		Object:     value,
 		Expiration: m.expirationTime,
 	})
 }
 
 func (m RedisCache) Get(key string, value interface{}) error {
-	return m.backend.Get(key, &value)
+	err := m.backend.Get(key, &value)
+	if err != nil {
+		return err
+	} else if value == nil {
+		return errors.New("redis returned empty result")
+	}
+	return nil
 }
 
-func NewRedisCache(expirationTime time.Duration) bahn.CacheBackend {
+func NewRedisCache(address string, password string, expirationTime time.Duration) bahn.CacheBackend {
 	return RedisCache{
 		backend: &cache.Codec{
 			Redis: redis.NewClient(&redis.Options{
-
+				Addr:     address,
+				Password: password,
 			}),
 			Marshal:   json.Marshal,
 			Unmarshal: json.Unmarshal,
 		},
+		expirationTime: expirationTime,
 	}
 }
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..020ab1e
--- /dev/null
+++ b/config.go
@@ -0,0 +1,31 @@
+package main
+
+import "time"
+
+type Config struct {
+	Endpoints      EndpointConfig `yaml:"endpoints"`
+	Caches         CacheConfig    `yaml:"caches"`
+	RequestTimeout time.Duration  `yaml:"request_timeout"`
+	MaxResults     int            `yaml:"max_results"`
+}
+
+type EndpointConfig struct {
+	Iris          string `yaml:"iris"`
+	CoachSequence string `yaml:"coach_sequence"`
+	Hafas         string `yaml:"hafas"`
+}
+
+type CacheConfig struct {
+	Redis  RedisCacheConfig  `yaml:"redis"`
+	Memory MemoryCacheConfig `yaml:"memory"`
+}
+
+type RedisCacheConfig struct {
+	Address  string        `yaml:"address"`
+	Password string        `yaml:"password"`
+	Timeout  time.Duration `yaml:"timeout"`
+}
+
+type MemoryCacheConfig struct {
+	Timeout time.Duration `yaml:"timeout"`
+}
diff --git a/go.mod b/go.mod
index 52e01cf..571943c 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,9 @@ module git.kuschku.de/justjanne/bahn-proxy
 go 1.12
 
 require (
-	git.kuschku.de/justjanne/bahn-api v0.0.0-20190504114009-71c830fe0812
+	git.kuschku.de/justjanne/bahn-api v0.0.0-20190504153151-3f6741590505
 	github.com/go-redis/cache v6.3.5+incompatible
 	github.com/go-redis/redis v6.15.2+incompatible
 	github.com/patrickmn/go-cache v2.1.0+incompatible
+	gopkg.in/yaml.v2 v2.2.2
 )
diff --git a/go.sum b/go.sum
index 494c061..ce54561 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-git.kuschku.de/justjanne/bahn-api v0.0.0-20190504114009-71c830fe0812 h1:dhL0LHGoud1so9Be3namg7g+/PtGVGhldtw+agNActg=
-git.kuschku.de/justjanne/bahn-api v0.0.0-20190504114009-71c830fe0812/go.mod h1:WQsAQzBR+0dlaVVuwwkx8j5uMoWDIdc8xtX4vlRrp5E=
+git.kuschku.de/justjanne/bahn-api v0.0.0-20190504153151-3f6741590505 h1:5ZpJaz2ltYxdBpqPGVdLfVgUx0GIQ+h+Kt9o/qI/JzI=
+git.kuschku.de/justjanne/bahn-api v0.0.0-20190504153151-3f6741590505/go.mod h1:WQsAQzBR+0dlaVVuwwkx8j5uMoWDIdc8xtX4vlRrp5E=
 github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
 github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/go-redis/cache v6.3.5+incompatible h1:4OUyoXXYRRQ6tKA4ue3TlPUkBzk3occzjtXBZBxCzgs=
@@ -14,4 +14,5 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/main.go b/main.go
index e0180a5..a3a4cdc 100644
--- a/main.go
+++ b/main.go
@@ -2,10 +2,13 @@ package main
 
 import (
 	"encoding/json"
+	"flag"
 	"fmt"
 	"git.kuschku.de/justjanne/bahn-api"
+	"gopkg.in/yaml.v2"
 	"log"
 	"net/http"
+	"os"
 	"path"
 	"sort"
 	"strconv"
@@ -28,22 +31,41 @@ func returnJson(w http.ResponseWriter, data interface{}) error {
 }
 
 func main() {
+	var err error
+
+	configPath := flag.String("config", "config.yaml", "Path to config file")
+	listen := flag.String("listen", ":8080", "Listen address")
+	flag.Parse()
+
+	var configFile *os.File
+	if configFile, err = os.Open(*configPath); err != nil {
+		panic(err)
+	}
+
+	var config Config
+	if err = yaml.NewDecoder(configFile).Decode(&config); err != nil {
+		panic(err)
+	}
+
 	autocompleteStations := loadAutocompleteStations()
 
 	apiClient := bahn.ApiClient{
-		IrisBaseUrl:          "http://iris.noncd.db.de/iris-tts",
-		CoachSequenceBaseUrl: "https://www.apps-bahn.de/wr/wagenreihung/1.0",
-		HafasBaseUrl:         "https://reiseauskunft.bahn.de/bin",
+		IrisBaseUrl:          config.Endpoints.Iris,
+		CoachSequenceBaseUrl: config.Endpoints.CoachSequence,
+		HafasBaseUrl:         config.Endpoints.Hafas,
 		HttpClient: &http.Client{
-			Timeout: time.Second * 10,
+			Timeout: config.RequestTimeout,
 		},
 		Caches: []bahn.CacheBackend{
-			NewMemoryCache(5 * time.Minute),
+			NewMemoryCache(config.Caches.Memory.Timeout),
+			NewRedisCache(
+				config.Caches.Redis.Address,
+				config.Caches.Redis.Password,
+				config.Caches.Redis.Timeout,
+			),
 		},
 	}
 
-	MaxResults := 20
-
 	http.HandleFunc("/autocomplete/", func(w http.ResponseWriter, r *http.Request) {
 		if stationName := strings.TrimSpace(r.FormValue("name")); stationName != "" {
 			var perfectMatch []AutocompleteStation
@@ -62,14 +84,14 @@ func main() {
 					contains = append(contains, station)
 				}
 
-				if len(perfectMatch)+len(prefix)+len(contains) >= MaxResults {
+				if len(perfectMatch)+len(prefix)+len(contains) >= config.MaxResults {
 					break
 				}
 			}
 
 			result := append(append(perfectMatch, prefix...), contains...)
 			if err := returnJson(w, result); err != nil {
-				log.Fatal(err)
+				log.Println(err)
 				return
 			}
 		} else if position, err := PositionFromString(strings.TrimSpace(r.FormValue("position"))); err == nil {
@@ -87,8 +109,8 @@ func main() {
 				return result[i].Distance < result[j].Distance
 			})
 
-			if err := returnJson(w, result[:MaxResults]); err != nil {
-				log.Fatal(err)
+			if err := returnJson(w, result[:config.MaxResults]); err != nil {
+				log.Println(err)
 				return
 			}
 		}
@@ -101,18 +123,18 @@ func main() {
 
 		var evaId int64
 		if evaId, err = strconv.ParseInt(rawEvaId, 10, 64); err != nil {
-			log.Fatal(err)
+			log.Println(err)
 			return
 		}
 
 		var stations []bahn.Station
 		if stations, err = apiClient.Station(evaId); err != nil {
-			log.Fatal(err)
+			log.Println(err)
 			return
 		}
 
 		if err = returnJson(w, stations); err != nil {
-			log.Fatal(err)
+			log.Println(err)
 			return
 		}
 	})
@@ -124,7 +146,7 @@ func main() {
 
 		var evaId int64
 		if evaId, err = strconv.ParseInt(rawEvaId, 10, 64); err != nil {
-			log.Fatal(err)
+			log.Println(err)
 			return
 		}
 
@@ -137,7 +159,7 @@ func main() {
 
 		var timetable bahn.Timetable
 		if timetable, err = apiClient.Timetable(evaId, date); err != nil {
-			log.Fatal(err)
+			log.Println(err)
 			return
 		}
 		for _, stop := range timetable.Stops {
@@ -148,7 +170,7 @@ func main() {
 
 		var realtime bahn.Timetable
 		if realtime, err = apiClient.RealtimeAll(evaId, date); err != nil {
-			log.Fatal(err)
+			log.Println(err)
 			return
 		}
 		for _, stop := range realtime.Stops {
@@ -161,11 +183,13 @@ func main() {
 		for key, combined := range data {
 			if combined.Timetable.Arrival != nil && combined.Timetable.Arrival.Wings != "" {
 				if combined.WingDefinition, err = apiClient.WingDefinition(combined.Timetable.StopId, combined.Timetable.Arrival.Wings); err != nil {
-					log.Fatal(err)
+					log.Println(err)
+					return
 				}
 			} else if combined.Timetable.Departure != nil && combined.Timetable.Departure.Wings != "" {
 				if combined.WingDefinition, err = apiClient.WingDefinition(combined.Timetable.StopId, combined.Timetable.Departure.Wings); err != nil {
-					log.Fatal(err)
+					log.Println(err)
+					return
 				}
 			}
 			data[key] = combined
@@ -183,7 +207,8 @@ func main() {
 				searchQuery := fmt.Sprintf("%s %s", combined.Timetable.TripLabel.TripCategory, combined.Timetable.TripLabel.TripNumber)
 				var suggestions []bahn.Suggestion
 				if suggestions, err = apiClient.Suggestions(searchQuery, moment); err != nil {
-					log.Fatal(err)
+					log.Println(err)
+					return
 				}
 				var targetStation = timetable.Station
 				if combined.Timetable.Departure != nil {
@@ -213,7 +238,8 @@ func main() {
 		for key, combined := range data {
 			if combined.TrainLink != "" {
 				if combined.HafasMessages, err = apiClient.HafasMessages(combined.TrainLink); err != nil {
-					log.Fatal(err)
+					log.Println(err)
+					return
 				}
 			}
 			data[key] = combined
@@ -225,11 +251,11 @@ func main() {
 		}
 
 		if err = returnJson(w, result); err != nil {
-			log.Fatal(err)
+			log.Println(err)
 			return
 		}
 	})
-	if err := http.ListenAndServe(":8080", nil); err != nil {
+	if err := http.ListenAndServe(*listen, nil); err != nil {
 		log.Fatal(err)
 	}
 }
-- 
GitLab