From 9d034c77a5a226cbefd51a2faed35b750398d229 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 25 Jan 2017 17:27:07 +0000 Subject: [PATCH 1/5] Initial rageshake server --- scripts/rageshake.go | 53 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 scripts/rageshake.go diff --git a/scripts/rageshake.go b/scripts/rageshake.go new file mode 100644 index 0000000000..883dd4fa1f --- /dev/null +++ b/scripts/rageshake.go @@ -0,0 +1,53 @@ +// Run a web server capable of dumping bug reports sent by Riot. +// Requires Go 1.5+ +// Usage: go run rageshake.go PORT +// Example: go run rageshake.go 8080 +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" +) + +type LogEntry struct { + ID string `json:"id"` + Lines string `json:"lines"` +} + +type Payload struct { + Text string `json:"text"` + Version string `json:"version"` + UserAgent string `json:"user_agent"` + Logs []LogEntry `json:"logs"` +} + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + if req.Method != "POST" && req.Method != "OPTIONS" { + w.WriteHeader(405) + return + } + // Set CORS + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") + if req.Method == "OPTIONS" { + return; + } + var p Payload + if err := json.NewDecoder(req.Body).Decode(&p); err != nil { + w.WriteHeader(400) + w.Write([]byte("Body is not JSON")) + return + } + // Dump bug report to disk + fmt.Println(p) + + }) + + port := os.Args[1] + log.Fatal(http.ListenAndServe(":"+port, nil)) +} From e8c51a0b543987ce416651c2b52a586eab07fb61 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 26 Jan 2017 11:28:38 +0000 Subject: [PATCH 2/5] gzip bug reports when storing on disk. Set max payload size --- scripts/rageshake.go | 70 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/scripts/rageshake.go b/scripts/rageshake.go index 883dd4fa1f..317b94ec78 100644 --- a/scripts/rageshake.go +++ b/scripts/rageshake.go @@ -5,13 +5,19 @@ package main import ( + "bytes" + "compress/gzip" "encoding/json" - "fmt" + "io/ioutil" "log" "net/http" "os" + "strconv" + "time" ) +var maxPayloadSize = 1024 * 1024 * 55 // 55 MB + type LogEntry struct { ID string `json:"id"` Lines string `json:"lines"` @@ -24,28 +30,64 @@ type Payload struct { Logs []LogEntry `json:"logs"` } +func respond(code int, w http.ResponseWriter) { + w.WriteHeader(code) + w.Write([]byte("{}")) +} + +func gzipAndSave(data []byte, filepath string) error { + var b bytes.Buffer + gz := gzip.NewWriter(&b) + if _, err := gz.Write(data); err != nil { + return err + } + if err := gz.Flush(); err != nil { + return err + } + if err := gz.Close(); err != nil { + return err + } + if err := ioutil.WriteFile(filepath, b.Bytes(), 0644); err != nil { + return err + } + return nil +} + func main() { http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { if req.Method != "POST" && req.Method != "OPTIONS" { - w.WriteHeader(405) + respond(405, w) + return + } + // Set CORS + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") + if req.Method == "OPTIONS" { + respond(200, w) + return + } + if length, err := strconv.Atoi(req.Header.Get("Content-Length")); err != nil || length > maxPayloadSize { + respond(413, w) return } - // Set CORS - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") - if req.Method == "OPTIONS" { - return; - } var p Payload if err := json.NewDecoder(req.Body).Decode(&p); err != nil { - w.WriteHeader(400) - w.Write([]byte("Body is not JSON")) + respond(400, w) return } - // Dump bug report to disk - fmt.Println(p) - + // Dump bug report to disk as form: + // "bugreport-20170115-112233.log.gz" => user text, version, user agent, # logs + // "bugreport-20170115-112233-0.log.gz" => most recent log + // "bugreport-20170115-112233-1.log.gz" => ... + // "bugreport-20170115-112233-N.log.gz" => oldest log + t := time.Now() + prefix := t.Format("bugreport-20060102-150405") + if err := gzipAndSave([]byte(p.Text), prefix+".log.gz"); err != nil { + respond(500, w) + return + } + respond(200, w) }) port := os.Args[1] From aae62ff94e79c4c5ba5f98f8f801a90529aaef4f Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 26 Jan 2017 11:44:07 +0000 Subject: [PATCH 3/5] store logs --- scripts/rageshake.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/rageshake.go b/scripts/rageshake.go index 317b94ec78..6ffd14768d 100644 --- a/scripts/rageshake.go +++ b/scripts/rageshake.go @@ -8,6 +8,7 @@ import ( "bytes" "compress/gzip" "encoding/json" + "fmt" "io/ioutil" "log" "net/http" @@ -83,10 +84,19 @@ func main() { // "bugreport-20170115-112233-N.log.gz" => oldest log t := time.Now() prefix := t.Format("bugreport-20060102-150405") - if err := gzipAndSave([]byte(p.Text), prefix+".log.gz"); err != nil { + summary := fmt.Sprintf( + "%s\n\nNumber of logs: %d\nVersion: %s\nUser-Agent: %s\n", p.Text, len(p.Logs), p.Version, p.UserAgent, + ) + if err := gzipAndSave([]byte(summary), prefix+".log.gz"); err != nil { respond(500, w) return } + for i, log := range p.Logs { + if err := gzipAndSave([]byte(log.Lines), fmt.Sprintf("%s-%d.log.gz", prefix, i)); err != nil { + respond(500, w) + return // TODO: Rollback? + } + } respond(200, w) }) From 6a40ad8a2073de4ae05f1b31cb9e3237ea776153 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 26 Jan 2017 11:57:56 +0000 Subject: [PATCH 4/5] Fail the request if we clash files Rather than make the file names incredibly long (by adding ms), just 500 it and expect the user to resend. --- scripts/rageshake.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/rageshake.go b/scripts/rageshake.go index 6ffd14768d..51532890b0 100644 --- a/scripts/rageshake.go +++ b/scripts/rageshake.go @@ -37,6 +37,9 @@ func respond(code int, w http.ResponseWriter) { } func gzipAndSave(data []byte, filepath string) error { + if _, err := os.Stat(filepath); err == nil { + return fmt.Errorf("file already exists") // the user can just retry + } var b bytes.Buffer gz := gzip.NewWriter(&b) if _, err := gz.Write(data); err != nil { From 22bb0f9d309baddf395281d48897270d916fed82 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 26 Jan 2017 12:17:47 +0000 Subject: [PATCH 5/5] UTC please --- scripts/rageshake.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/rageshake.go b/scripts/rageshake.go index 51532890b0..ebf94e67ac 100644 --- a/scripts/rageshake.go +++ b/scripts/rageshake.go @@ -85,7 +85,7 @@ func main() { // "bugreport-20170115-112233-0.log.gz" => most recent log // "bugreport-20170115-112233-1.log.gz" => ... // "bugreport-20170115-112233-N.log.gz" => oldest log - t := time.Now() + t := time.Now().UTC() prefix := t.Format("bugreport-20060102-150405") summary := fmt.Sprintf( "%s\n\nNumber of logs: %d\nVersion: %s\nUser-Agent: %s\n", p.Text, len(p.Logs), p.Version, p.UserAgent,