-
Notifications
You must be signed in to change notification settings - Fork 44
/
main.go
132 lines (106 loc) · 2.9 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package main
import (
"encoding/json"
"flag"
"log"
"net/http"
"strings"
"time"
)
func main() {
wundergroundAPIKey := flag.String("wunderground.api.key", "0123456789abcdef", "wunderground.com API key")
flag.Parse()
mw := multiWeatherProvider{
openWeatherMap{},
weatherUnderground{apiKey: *wundergroundAPIKey},
}
http.HandleFunc("/weather/", func(w http.ResponseWriter, r *http.Request) {
begin := time.Now()
city := strings.SplitN(r.URL.Path, "/", 3)[2]
temp, err := mw.temperature(city)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]interface{}{
"city": city,
"temp": temp,
"took": time.Since(begin).String(),
})
})
http.ListenAndServe(":8080", nil)
}
type weatherProvider interface {
temperature(city string) (float64, error) // in Kelvin, naturally
}
type multiWeatherProvider []weatherProvider
func (w multiWeatherProvider) temperature(city string) (float64, error) {
// Make a channel for temperatures, and a channel for errors.
// Each provider will push a value into only one.
temps := make(chan float64, len(w))
errs := make(chan error, len(w))
// For each provider, spawn a goroutine with an anonymous function.
// That function will invoke the temperature method, and forward the response.
for _, provider := range w {
go func(p weatherProvider) {
k, err := p.temperature(city)
if err != nil {
errs <- err
return
}
temps <- k
}(provider)
}
sum := 0.0
// Collect a temperature or an error from each provider.
for i := 0; i < len(w); i++ {
select {
case temp := <-temps:
sum += temp
case err := <-errs:
return 0, err
}
}
// Return the average, same as before.
return sum / float64(len(w)), nil
}
type openWeatherMap struct{}
func (w openWeatherMap) temperature(city string) (float64, error) {
resp, err := http.Get("http://api.openweathermap.org/data/2.5/weather?q=" + city)
if err != nil {
return 0, err
}
defer resp.Body.Close()
var d struct {
Main struct {
Kelvin float64 `json:"temp"`
} `json:"main"`
}
if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
return 0, err
}
log.Printf("openWeatherMap: %s: %.2f", city, d.Main.Kelvin)
return d.Main.Kelvin, nil
}
type weatherUnderground struct {
apiKey string
}
func (w weatherUnderground) temperature(city string) (float64, error) {
resp, err := http.Get("http://api.wunderground.com/api/" + w.apiKey + "/conditions/q/" + city + ".json")
if err != nil {
return 0, err
}
defer resp.Body.Close()
var d struct {
Observation struct {
Celsius float64 `json:"temp_c"`
} `json:"current_observation"`
}
if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
return 0, err
}
kelvin := d.Observation.Celsius + 273.15
log.Printf("weatherUnderground: %s: %.2f", city, kelvin)
return kelvin, nil
}