Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 package main | 1 package main |
| 2 | 2 |
| 3 import ( | 3 import ( |
| 4 "encoding/json" | |
| 4 "flag" | 5 "flag" |
| 6 "fmt" | |
| 5 "net/http" | 7 "net/http" |
| 6 ) | 8 » "time" |
| 7 | 9 |
| 8 import ( | |
| 9 "github.com/golang/glog" | 10 "github.com/golang/glog" |
| 11 "github.com/gorilla/mux" | |
| 12 | |
| 13 // "skia.googlesource.com/buildbot.git/golden/go/analysis" | |
|
jcgregorio
2014/10/17 18:07:52
leftover?
stephana
2014/10/17 20:01:45
Yes. Thank you. Removed.
| |
| 14 "skia.googlesource.com/buildbot.git/go/database" | |
| 15 "skia.googlesource.com/buildbot.git/golden/go/analysis" | |
| 16 "skia.googlesource.com/buildbot.git/golden/go/db" | |
| 17 "skia.googlesource.com/buildbot.git/golden/go/expstorage" | |
| 18 "skia.googlesource.com/buildbot.git/golden/go/filediffstore" | |
| 19 "skia.googlesource.com/buildbot.git/perf/go/filetilestore" | |
| 10 ) | 20 ) |
| 11 | 21 |
| 12 // flags | 22 // flags |
| 13 var ( | 23 var ( |
| 14 » port = flag.String("port", ":9000", "HTTP service address (e.g., ': 9000')") | 24 » port = flag.String("port", ":9000", "HTTP service address (e.g., ':9000')") |
| 15 » staticDir = flag.String("static", "./app", "Directory with static conten t to serve") | 25 » local = flag.Bool("local", false, "Running locally if true. As op posed to in production.") |
| 26 » staticDir = flag.String("static_dir", "./app", "Directory with static content to serve") | |
| 27 » tileStoreDir = flag.String("tile_store_dir", "/tmp/tileStore", "What dir ectory to look for tiles in.") | |
| 28 » imageDiffDir = flag.String("image_diff_dir", "/tmp/imagediffdir", "What directory to store diff images in.") | |
| 29 » gsBucketName = flag.String("gs_bucket", "chromium-skia-gm", "Name of the google storage bucket that holds uploaded images.") | |
| 30 » mysqlConnStr = flag.String("mysql_conn", "", "MySQL connection string fo r backend database. If 'local' is false the password in this string will be subs tituted via the metadata server.") | |
| 31 » sqlitePath = flag.String("sqlite_path", "./golden.db", "Filepath of th e embedded SQLite database. Requires 'local' to be set to true and 'mysql_conn' to be empty to take effect.") | |
| 32 ) | |
| 16 | 33 |
| 17 // TODO (stephana): Just ideas to be sorted out later | 34 // Response envelope. All responses follow this format. Some fields might |
| 18 // tempDir = flag.String("temp", "./.cache", | 35 // be empty depending on context. |
| 19 // "Directory to store temporary file and application cache") | 36 type ResponseEnvelope struct { |
| 20 ) | 37 » Data *interface{} `json:"data"` |
| 38 » Err *string `json:"err"` | |
| 39 » Status int `json:"status"` | |
| 40 } | |
| 41 | |
| 42 type RestResources struct { | |
| 43 » analizer *analysis.Analyzer | |
| 44 } | |
| 45 | |
| 46 func NewRestResources(a *analysis.Analyzer) *RestResources { | |
| 47 » return &RestResources{ | |
| 48 » » analizer: a, | |
| 49 » } | |
| 50 } | |
| 51 | |
| 52 // Get the aggregated counts | |
| 53 func (rr *RestResources) GetTileCountsHandler(w http.ResponseWriter, r *http.Req uest) { | |
|
jcgregorio
2014/10/17 18:07:52
sendResponse looks useful, but I'm not sure about
stephana
2014/10/17 20:01:45
I added RestResources as a container for the varia
jcgregorio
2014/10/17 20:12:16
As for global, that's not too much of a concern si
| |
| 54 » result, err := rr.analizer.GetTileCounts() | |
| 55 » if err != nil { | |
| 56 » » sendErrorResponse(w, err.Error(), http.StatusInternalServerError ) | |
| 57 » » return | |
| 58 » } | |
| 59 | |
| 60 » sendResponse(w, result, http.StatusOK) | |
| 61 } | |
| 62 | |
| 63 // Process a diff request send via HTTP. | |
| 64 func (rr *RestResources) GetTestCountsHandler(w http.ResponseWriter, r *http.Req uest) { | |
| 65 » testName := mux.Vars(r)["testname"] | |
| 66 » result, err := rr.analizer.GetTestCounts(testName) | |
| 67 » if err != nil { | |
| 68 » » sendErrorResponse(w, err.Error(), http.StatusInternalServerError ) | |
| 69 » » return | |
| 70 » } | |
| 71 | |
| 72 » sendResponse(w, result, http.StatusOK) | |
| 73 } | |
| 74 | |
| 75 // Send an error response with the given error message. | |
| 76 func sendErrorResponse(w http.ResponseWriter, errorMsg string, status int) { | |
| 77 » resp := ResponseEnvelope{nil, &errorMsg, status} | |
| 78 » sendJson(w, &resp) | |
| 79 } | |
| 80 | |
| 81 // Send a non-error response with the given data. | |
| 82 func sendResponse(w http.ResponseWriter, data interface{}, status int) { | |
| 83 » resp := ResponseEnvelope{&data, nil, status} | |
| 84 » sendJson(w, &resp) | |
| 85 } | |
| 86 | |
| 87 // Parse JSON input and validate it. | |
| 88 // TODO (stephana): Validation is still missing. It's not sufficient | |
| 89 // to just parse the JSON. Could be done with Json schemas. | |
| 90 func parseJson(r *http.Request, v interface{}) error { | |
|
jcgregorio
2014/10/17 18:07:52
Doesn't appear to be used?
stephana
2014/10/17 20:01:45
Agreed that's a remainder from another version. Re
| |
| 91 » // TODO: validate the JSON against a schema. Might not be necessary ! | |
| 92 » decoder := json.NewDecoder(r.Body) | |
| 93 » return decoder.Decode(v) | |
| 94 } | |
| 95 | |
| 96 // Sends the response envelope rendered as JSON. | |
| 97 func sendJson(w http.ResponseWriter, resp *ResponseEnvelope) { | |
| 98 » jsonBytes, err := json.Marshal(resp) | |
| 99 » if err != nil { | |
| 100 » » http.Error(w, err.Error(), http.StatusInternalServerError) | |
| 101 » » return | |
| 102 » } | |
| 103 | |
| 104 » w.Header().Set("Content-Type", "application/json") | |
| 105 » w.Header().Set("Content-Length", fmt.Sprintf("%d", len(jsonBytes))) | |
|
jcgregorio
2014/10/17 18:07:52
Content-Length isn't necessary.
stephana
2014/10/17 20:01:45
Removed.
| |
| 106 » w.Write(jsonBytes) | |
| 107 } | |
| 21 | 108 |
| 22 func main() { | 109 func main() { |
| 23 // parse the arguments | 110 // parse the arguments |
| 24 flag.Parse() | 111 flag.Parse() |
| 25 | 112 |
| 113 // Get the expecations storage, the filediff storage and the tilestore. | |
| 114 diffStore := filediffstore.NewFileDiffStore(nil, *imageDiffDir, *gsBucke tName) | |
| 115 vdb := database.NewVersionedDB(db.GetDatabaseConfig(*mysqlConnStr, *sqli tePath, *local)) | |
| 116 expStore := expstorage.NewSQLExpectationStore(vdb) | |
| 117 tileStore := filetilestore.NewFileTileStore(*tileStoreDir, "golden", -1) | |
| 118 | |
| 119 // Create the analyer and plug it into the rest resources. | |
| 120 analyzer := analysis.NewAnalyzer(expStore, tileStore, diffStore, 5*time. Minute) | |
| 121 resources := NewRestResources(analyzer) | |
| 122 | |
| 123 router := mux.NewRouter() | |
| 124 | |
| 125 // Wire up the resources. We use the 'rest' prefix to avoid any name | |
| 126 // clashes witht the static files being served. | |
| 127 router.HandleFunc("/rest/tilecounts", resources.GetTileCountsHandler) | |
|
jcgregorio
2014/10/17 18:07:52
Consider adding autogzip for JSON responses:
htt
stephana
2014/10/17 20:01:45
I actually had that and removed it. I think we sho
| |
| 128 router.HandleFunc("/rest/tilecounts/{testname}", resources.GetTestCounts Handler) | |
| 129 | |
| 26 // // Static file handling | 130 // // Static file handling |
| 27 » http.Handle("/", http.FileServer(http.Dir(*staticDir))) | 131 » router.Handle("/", http.FileServer(http.Dir(*staticDir))) |
| 28 | 132 |
| 29 » // Wire up the resources | 133 » // Send all requests to the router |
| 134 » http.Handle("/", router) | |
| 30 | 135 |
| 31 // Start the server | 136 // Start the server |
| 32 glog.Infoln("Serving on http://127.0.0.1" + *port) | 137 glog.Infoln("Serving on http://127.0.0.1" + *port) |
| 33 glog.Fatal(http.ListenAndServe(*port, nil)) | 138 glog.Fatal(http.ListenAndServe(*port, nil)) |
| 34 } | 139 } |
| OLD | NEW |