/
handlers.go
142 lines (109 loc) · 3.43 KB
/
handlers.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
package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
)
// resultPage represents the data needed to render an HTML result page or a JSON API return.
type resultPage struct {
Search SearchRequest `json:"s"`
Type string `json:"t,omitempty"`
Result SearchResult `json:"r"`
}
// getSearchRequest interprets the query in the URL by transforming a http.Request into a SearchRequest.
func getSearchRequest(r *http.Request) *SearchRequest {
sr := SearchRequest{}
sr.Query = strings.Trim(r.FormValue("q"), " ")
sr.Lang = r.FormValue("g")
sr.Page, _ = strconv.Atoi(r.FormValue("p"))
if sr.Page == 0 || sr.Query == "" {
sr.Page = 1
}
// Keeping Lang empty (=autodetect on client side) is acceptable
// only if the query is empty (and we will land on the full homepage)
// If we have a query, we unfortunately have to guess an english default
if sr.Lang == "" && sr.Query != "" {
sr.Lang = "en"
}
err := r.Body.Close()
if err != nil {
fmt.Printf("Warning: Could not close request Body")
}
return &sr
}
// sendResultPage renders a resultPage to HTML and sends it to the client.
func sendResultPage(w http.ResponseWriter, r *http.Request, page *resultPage) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
// If we are in debug mode, read the template from disk at each request!
if Config.Debug {
LoadTemplates()
}
err := Templates["index.html"].ExecuteTemplate(w, "index.html", page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// TruncateQuery allows only Config.MaxQueryTerms words to enter the actual search
func TruncateQuery(q string) (string, string) {
words := strings.Fields(q)
allowedLength := Config.MaxQueryTerms
var extra string // the starting word that are truncated
if len(words) > allowedLength {
q = strings.Join(words[:allowedLength], " ")
extra = words[allowedLength]
}
return q, extra
}
// SearchHandler handles HTTP queries to home or result pages (/ or /?q=*).
func SearchHandler(w http.ResponseWriter, r *http.Request) {
search := getSearchRequest(r)
// Empty query: render the "home" version
if search.Query == "" {
page := resultPage{Type: "home", Search: *search}
sendResultPage(w, r, &page)
return
}
truncatedQuery, extra := TruncateQuery(search.Query)
if extra != "" {
search.Query = truncatedQuery
}
// Perform the search itself
result, err := search.PerformSearchWithTiming()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// If we used a !bang or asked for a redirect, do it now
if result.Redirect != "" {
http.Redirect(w, r, result.Redirect, 302)
return
}
result.Extra = extra
page := resultPage{Search: *search, Result: *result}
sendResultPage(w, r, &page)
}
// APISearchHandler handles HTTP queries to our JSON API (/api/search?q=*)
func APISearchHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
search := getSearchRequest(r)
truncatedQuery, extra := TruncateQuery(search.Query)
if extra != "" {
search.Query = truncatedQuery
}
// Perform the search itself
result, err := search.PerformSearchWithTiming()
result.Extra = extra
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Write the result to the client as JSON
err = json.NewEncoder(w).Encode(result)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}