Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(919)

Side by Side Diff: go/src/infra/appengine/test-results/frontend/get.go

Issue 2251623002: test-results: Get handler cleanup (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@x_3
Patch Set: Small fix to bytes.Buffer type Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | go/src/infra/appengine/test-results/frontend/get_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 package frontend 1 package frontend
2 2
3 import ( 3 import (
4 "bytes" 4 "bytes"
5 "encoding/json" 5 "encoding/json"
6 "fmt" 6 "fmt"
7 "infra/appengine/test-results/model" 7 "infra/appengine/test-results/model"
8 "io" 8 "io"
9 "net/http" 9 "net/http"
10 "regexp"
11 "time" 10 "time"
12 11
13 "github.com/luci/gae/service/datastore" 12 "github.com/luci/gae/service/datastore"
14 "github.com/luci/luci-go/common/logging" 13 "github.com/luci/luci-go/common/logging"
15 "github.com/luci/luci-go/server/router" 14 "github.com/luci/luci-go/server/router"
16 "github.com/luci/luci-go/server/templates" 15 "github.com/luci/luci-go/server/templates"
17 "golang.org/x/net/context" 16 "golang.org/x/net/context"
18 ) 17 )
19 18
20 const ( 19 const (
21 // paramsTimeFormat is the time format string in incoming GET 20 // paramsTimeFormat is the time format string in incoming GET
22 // /testfile requests. 21 // /testfile requests.
23 paramsTimeFormat = "2006-01-02T15:04:05Z" // RFC3339, but enforce Z for timezone. 22 paramsTimeFormat = "2006-01-02T15:04:05Z" // RFC3339, but enforce Z for timezone.
24 23
25 // httpTimeFormat is the time format used in HTTP headers. 24 // httpTimeFormat is the time format used in HTTP headers.
26 // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 25 // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
27 // (Section 14.18 Date). 26 // (Section 14.18 Date).
28 httpTimeFormat = time.RFC1123 27 httpTimeFormat = time.RFC1123
29 28
30 // httpNoTZTimeFormat is httpTimeFormat with the timezone removed. 29 // httpNoTZTimeFormat is httpTimeFormat with the timezone removed.
31 httpNoTZTimeFormat = "Mon, 02 Jan 2006 15:04:05" 30 httpNoTZTimeFormat = "Mon, 02 Jan 2006 15:04:05"
32 ) 31 )
33 32
34 // callbackNameRx matches start of strings that look like 33 // getHandler is the HTTP handler for GET /testfile requests.
35 // JavaScript function names. Not a comprehensive solution. 34 func getHandler(ctx *router.Context) {
36 var callbackNameRx = regexp.MustCompile(`^[A-Za-z0-9_]+$`)
37
38 // GetHandler is the HTTP handler for GET /testfile requests.
39 func GetHandler(ctx *router.Context) {
40 c, w, r := ctx.Context, ctx.Writer, ctx.Request 35 c, w, r := ctx.Context, ctx.Writer, ctx.Request
41 if err := r.ParseForm(); err != nil { 36 if err := r.ParseForm(); err != nil {
42 http.Error(w, err.Error(), http.StatusInternalServerError) 37 http.Error(w, err.Error(), http.StatusInternalServerError)
43 logging.Errorf(c, "failed to parse form: %v", err) 38 logging.Errorf(c, "failed to parse form: %v", err)
44 return 39 return
45 } 40 }
46 41
47 params, err := NewURLParams(r.Form) 42 params, err := NewURLParams(r.Form)
48 if err != nil { 43 if err != nil {
49 e := fmt.Sprintf("failed to parse URL parameters: %+v: %v", para ms, err) 44 e := fmt.Sprintf("failed to parse URL parameters: %+v: %v", para ms, err)
(...skipping 18 matching lines...) Expand all
68 63
69 key, err := datastore.NewKeyEncoded(params.Key) 64 key, err := datastore.NewKeyEncoded(params.Key)
70 if err != nil { 65 if err != nil {
71 http.Error(w, err.Error(), http.StatusBadRequest) 66 http.Error(w, err.Error(), http.StatusBadRequest)
72 logging.Errorf(c, "failed to encode key: %v: %v", key, err) 67 logging.Errorf(c, "failed to encode key: %v: %v", key, err)
73 return 68 return
74 } 69 }
75 70
76 tf := model.TestFile{ID: key.IntID()} 71 tf := model.TestFile{ID: key.IntID()}
77 72
78 » if err := datastore.Get(c).Get(&tf); err == datastore.ErrNoSuchEntity { 73 » if err := datastore.Get(c).Get(&tf); err != nil {
79 » » http.Error(w, err.Error(), http.StatusNotFound) 74 » » if err == datastore.ErrNoSuchEntity {
80 » » logging.Errorf(c, "TestFile with ID %v not found: %v", key.IntID (), err) 75 » » » http.Error(w, err.Error(), http.StatusNotFound)
81 » » return 76 » » » logging.Errorf(c, "TestFile with ID %v not found: %v", k ey.IntID(), err)
82 » } else if err != nil { 77 » » » return
78 » » }
83 http.Error(w, err.Error(), http.StatusInternalServerError) 79 http.Error(w, err.Error(), http.StatusInternalServerError)
84 logging.Errorf(c, "failed to get TestFile with ID %v: %v", key.I ntID(), err) 80 logging.Errorf(c, "failed to get TestFile with ID %v: %v", key.I ntID(), err)
85 return 81 return
86 } 82 }
87 83
88 modTime, err := time.Parse(r.Header.Get("If-Modified-Since"), httpTimeFo rmat) 84 modTime, err := time.Parse(r.Header.Get("If-Modified-Since"), httpTimeFo rmat)
89 if err == nil && !tf.LastMod.After(modTime) { 85 if err == nil && !tf.LastMod.After(modTime) {
90 w.WriteHeader(http.StatusNotModified) 86 w.WriteHeader(http.StatusNotModified)
91 return 87 return
92 } 88 }
(...skipping 15 matching lines...) Expand all
108 logging.Errorf(c, "GetAll failed for query: %+v: %v", q, err) 104 logging.Errorf(c, "GetAll failed for query: %+v: %v", q, err)
109 return 105 return
110 } 106 }
111 if len(testFiles) == 0 { 107 if len(testFiles) == 0 {
112 e := fmt.Sprintf("no TestFile found for query: %+v", q) 108 e := fmt.Sprintf("no TestFile found for query: %+v", q)
113 http.Error(w, e, http.StatusNotFound) 109 http.Error(w, e, http.StatusNotFound)
114 logging.Errorf(c, e) 110 logging.Errorf(c, e)
115 return 111 return
116 } 112 }
117 113
118 args := templates.Args{
119 "Master": params.Master,
120 "Builder": params.Builder,
121 "TestType": params.TestType,
122 "BuildNumber": params.BuildNumber,
123 "Name": params.Name,
124 "Files": testFiles,
125 }
126
127 if params.Callback != "" { 114 if params.Callback != "" {
128 b, err := keysJSON(c, testFiles) 115 b, err := keysJSON(c, testFiles)
129 if err != nil { 116 if err != nil {
130 http.Error(w, err.Error(), http.StatusInternalServerErro r) 117 http.Error(w, err.Error(), http.StatusInternalServerErro r)
131 logging.Errorf(c, "failed to create callback JSON: %v: % v", testFiles, err) 118 logging.Errorf(c, "failed to create callback JSON: %v: % v", testFiles, err)
132 return 119 return
133 } 120 }
134 respondJSON(c, w, bytes.NewReader(b), testFiles[0].LastMod, para ms.Callback) 121 respondJSON(c, w, bytes.NewReader(b), testFiles[0].LastMod, para ms.Callback)
135 return 122 return
136 } 123 }
137 124
138 » templates.MustRender(c, w, "pages/showfilelist.html", args) 125 » templates.MustRender(c, w, "pages/showfilelist.html", templates.Args{
126 » » "Master": params.Master,
127 » » "Builder": params.Builder,
128 » » "TestType": params.TestType,
129 » » "BuildNumber": params.BuildNumber,
130 » » "Name": params.Name,
131 » » "Files": testFiles,
132 » })
139 } 133 }
140 134
141 func keysJSON(c context.Context, tfiles []*model.TestFile) ([]byte, error) { 135 func keysJSON(c context.Context, tfiles []*model.TestFile) ([]byte, error) {
142 type K struct { 136 type K struct {
143 Key string `json:"key"` 137 Key string `json:"key"`
144 } 138 }
145 keys := make([]K, len(tfiles)) 139 keys := make([]K, len(tfiles))
146 for i, tf := range tfiles { 140 for i, tf := range tfiles {
147 keys[i] = K{datastore.Get(c).KeyForObj(tf).Encode()} 141 keys[i] = K{datastore.Get(c).KeyForObj(tf).Encode()}
148 } 142 }
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
214 http.Error(w, err.Error(), http.StatusInternalServerErro r) 208 http.Error(w, err.Error(), http.StatusInternalServerErro r)
215 logging.Errorf(c, "failed to clean test results JSON: %v ", err) 209 logging.Errorf(c, "failed to clean test results JSON: %v ", err)
216 return 210 return
217 } 211 }
218 aggr := model.AggregateResult{Builder: params.Builder} 212 aggr := model.AggregateResult{Builder: params.Builder}
219 if err := json.NewDecoder(data).Decode(&aggr); err != nil { 213 if err := json.NewDecoder(data).Decode(&aggr); err != nil {
220 http.Error(w, err.Error(), http.StatusInternalServerErro r) 214 http.Error(w, err.Error(), http.StatusInternalServerErro r)
221 logging.Errorf(c, "failed to unmarshal test results JSON : %+v: %v", data, err) 215 logging.Errorf(c, "failed to unmarshal test results JSON : %+v: %v", data, err)
222 return 216 return
223 } 217 }
224 » » aggr.Tests.ToTestList() 218
225 » » buf := &bytes.Buffer{} 219 » » tl := aggr.ToTestList()
226 » » if err := json.NewEncoder(buf).Encode(aggr.Tests); err != nil { 220 » » buf := bytes.Buffer{}
221 » » if err := json.NewEncoder(&buf).Encode(&tl); err != nil {
227 http.Error(w, err.Error(), http.StatusInternalServerErro r) 222 http.Error(w, err.Error(), http.StatusInternalServerErro r)
228 logging.Errorf(c, "failed to marshal test list JSON: %+v , %v", aggr.Tests, err) 223 logging.Errorf(c, "failed to marshal test list JSON: %+v , %v", aggr.Tests, err)
229 return 224 return
230 } 225 }
231 » » finalData = buf 226 » » finalData = &buf
232 } 227 }
233 228
234 respondJSON(c, w, finalData, tf.LastMod, params.Callback) 229 respondJSON(c, w, finalData, tf.LastMod, params.Callback)
235 } 230 }
236 231
237 // ErrNoMatches is returned when a query returns 0 entities. 232 // ErrNoMatches is returned when a query returns 0 entities.
238 type ErrNoMatches string 233 type ErrNoMatches string
239 234
240 func (e ErrNoMatches) Error() string { 235 func (e ErrNoMatches) Error() string {
241 return string(e) 236 return string(e)
242 } 237 }
243 238
244 // getFirstTestFile returns the first TestFile for the supplied query. The limit 239 // getFirstTestFile returns the first TestFile for the supplied query. The limit
245 // on the query is set to 1 before running the query. 240 // on the query is set to 1 before running the query.
246 func getFirstTestFile(c context.Context, q *datastore.Query) (*model.TestFile, e rror) { 241 func getFirstTestFile(c context.Context, q *datastore.Query) (*model.TestFile, e rror) {
247 q = q.Limit(1) 242 q = q.Limit(1)
248 var tfs []*model.TestFile 243 var tfs []*model.TestFile
249 if err := datastore.Get(c).GetAll(q, &tfs); err != nil { 244 if err := datastore.Get(c).GetAll(q, &tfs); err != nil {
250 logging.Errorf(c, "GetAll failed for query: %+v: %v", q, err) 245 logging.Errorf(c, "GetAll failed for query: %+v: %v", q, err)
251 return nil, err 246 return nil, err
252 } 247 }
253 if len(tfs) == 0 { 248 if len(tfs) == 0 {
254 e := ErrNoMatches(fmt.Sprintf("no TestFile found for query: %+v" , q)) 249 e := ErrNoMatches(fmt.Sprintf("no TestFile found for query: %+v" , q))
255 logging.Errorf(c, e.Error()) 250 logging.Errorf(c, e.Error())
256 return nil, e 251 return nil, e
257 } 252 }
258 return tfs[0], nil 253 return tfs[0], nil
259 } 254 }
260 255
261 // respondJSON writes the supplied JSON data to w. If the supplied callback stri ng matches 256 // respondJSON writes the supplied JSON data to w. If the supplied
262 // callbackNameRx, data is wrapped in a JSONP-style function with the supplied c allback 257 // callback string is not empty, data is wrapped in a JSONP-style
263 // string as the function name. 258 // function with the supplied callback string as the function name.
264 func respondJSON(c context.Context, w http.ResponseWriter, data io.Reader, lastM od time.Time, callback string) { 259 func respondJSON(c context.Context, w http.ResponseWriter, data io.Reader, lastM od time.Time, callback string) {
265 » if callbackNameRx.MatchString(callback) { 260 » if callback != "" {
266 data = wrapCallback(data, callback) 261 data = wrapCallback(data, callback)
267 } 262 }
268 w.Header().Set("Last-Modified", lastMod.Format(httpNoTZTimeFormat)+" GMT ") 263 w.Header().Set("Last-Modified", lastMod.Format(httpNoTZTimeFormat)+" GMT ")
269 w.Header().Set("Content-Type", "application/json") 264 w.Header().Set("Content-Type", "application/json")
270 n, err := io.Copy(w, data) 265 n, err := io.Copy(w, data)
271 if err != nil { 266 if err != nil {
272 logging.Errorf(c, "error writing JSON response: %#v, %v, wrote % d bytes", data, err, n) 267 logging.Errorf(c, "error writing JSON response: %#v, %v, wrote % d bytes", data, err, n)
273 } 268 }
274 } 269 }
275 270
276 // wrapCallback returns an io.Reader that wraps the data in r in a 271 // wrapCallback returns an io.Reader that wraps the data in r in a
277 // JavaScript-style function call with the supplied name as the function name. 272 // JavaScript-style function call with the supplied name as the function name.
278 func wrapCallback(r io.Reader, name string) io.Reader { 273 func wrapCallback(r io.Reader, name string) io.Reader {
279 start := bytes.NewReader([]byte(name + "(")) 274 start := bytes.NewReader([]byte(name + "("))
280 end := bytes.NewReader([]byte(");")) 275 end := bytes.NewReader([]byte(");"))
281 return io.MultiReader(start, r, end) 276 return io.MultiReader(start, r, end)
282 } 277 }
OLDNEW
« no previous file with comments | « no previous file | go/src/infra/appengine/test-results/frontend/get_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698