-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
202 lines (166 loc) · 4.76 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"sort"
"time"
"google.golang.org/appengine"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
var (
// html templates
booklistTmpl = parseTemplate("list.html")
authordetailTmpl = parseTemplate("detail.html")
tosPrivacy = parseTemplate("tos_privacy.html")
// Google Books API uri format
googlebooksformat = "https://www.googleapis.com/books/v1/volumes?q=%s&startIndex=%v&maxResults=%v&country=%s&langRestrict=%s&download=epub&printType=books&showPreorders=false&fields=%s"
)
// Book holds the an item (a Volume) from the result of a Google Books query
type Book struct {
ID string `json:"id"`
VolumeInfo struct {
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Pages int `json:"pageCount"`
Authors []string `json:"authors"`
} `json:"volumeInfo"`
}
// Books is a collection container that implements sort interface for sorting by pages
type Books []Book
func (slice Books) Len() int {
return len(slice)
}
func (slice Books) Less(i, j int) bool {
return slice[i].VolumeInfo.Pages > slice[j].VolumeInfo.Pages
}
func (slice Books) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}
func main() {
log.Println("bookshelf")
r := mux.NewRouter()
r.Path("/").Methods("GET").Handler(apiHandler(listBooksHandler))
r.Path("/author/{author}").Methods("GET").Handler(apiHandler(showBooksByAuthorHandler))
r.Path("/tos").Methods("GET").Handler(apiHandler(tosPrivacyHandler))
r.Path("/privacy").Methods("GET").Handler(apiHandler(tosPrivacyHandler))
// AppEngine / Compute Engine Health check endpoint
r.Methods("GET").Path("/_ah/health").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
// Apache format logging
http.Handle("/", handlers.CombinedLoggingHandler(os.Stderr, r))
appengine.Main()
}
func listBooksHandler(w http.ResponseWriter, r *http.Request) *apiError {
authors := struct {
Authors []string
}{
Authors: []string{
"Miguel de Cervantes",
"Charles Dickens",
"Antoine de Saint-Exupéry",
"J. K. Rowling",
"J. R. R. Tolkien",
"Agatha Christie",
"Lewis Carroll",
"C. S. Lewis",
"Dan Brown",
"Arthur Conan Doyle",
"Jules Verne",
"Stephen King",
"Stieg Larsson",
"George Orwell",
"Ian Fleming",
"James Patterson",
"Anne Rice",
"Terry Pratchett",
"George R. R. Martin",
"Edgar Rice Burroughs",
"Michael Connelly",
"Jo Nesbo",
},
}
return booklistTmpl.Execute(w, r, authors)
}
func showBooksByAuthorHandler(w http.ResponseWriter, r *http.Request) *apiError {
// grab request variables
vars := mux.Vars(r)
timeStart := time.Now()
books, err := getAuthorBooks(vars["author"])
if err != nil {
return apiErrorf(err, "Unable to retrieve Author Info: %v")
}
sort.Sort(books)
authorInfo := struct {
Author string
Books Books
Time string
}{
Author: vars["author"],
Books: books,
Time: fmt.Sprintf("%.2f s", time.Since(timeStart).Seconds()),
}
return authordetailTmpl.Execute(w, r, authorInfo)
}
// getAuthorBooks uses the Google Books API to retrieve books by an author
func getAuthorBooks(author string) (Books, error) {
var books []Book
googlebooksresponse := struct {
Items []Book `json:"items"`
}{}
// https://www.googleapis.com/books/v1/volumes?q=%s&startIndex=%v&maxResults=%v&country=%s&langRestrict=%s&download=epub&printType=books&showPreorders=false=fields=%s"
uri := fmt.Sprintf(googlebooksformat,
url.QueryEscape(author), // query
0, // start
40, // count
"US", // country
"en", // language
"items(id,accessInfo(epub/isAvailable),volumeInfo(title,subtitle,language,pageCount,authors))", // filter fields
)
log.Println(uri)
resp, err := http.Get(uri)
if err != nil {
return books, err
}
defer resp.Body.Close()
bytesdata, err := ioutil.ReadAll(resp.Body)
if err != nil {
return books, err
}
err = json.Unmarshal(bytesdata, &googlebooksresponse)
books = googlebooksresponse.Items
return books, nil
}
func tosPrivacyHandler(w http.ResponseWriter, r *http.Request) *apiError {
return tosPrivacy.Execute(w, r, nil)
}
// http://blog.golang.org/error-handling-and-go
type apiHandler func(http.ResponseWriter, *http.Request) *apiError
type apiError struct {
Error error
Message string
Code int
}
func (fn apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e is *apiError, not os.Error.
log.Printf(
"Handler error: status code: %d, message: %s, resulting from err: %#v",
e.Code, e.Message, e.Error,
)
http.Error(w, e.Message, e.Code)
}
}
func apiErrorf(err error, format string, v ...interface{}) *apiError {
return &apiError{
Error: err,
Message: fmt.Sprintf(format, v...),
Code: 500,
}
}