diff --git a/Gopkg.lock b/Gopkg.lock index b36afa3..2339668 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -353,6 +353,12 @@ revision = "fde5e16d32adc7ad637e9cd9ad21d4ebc6192535" version = "v0.2.0" +[[projects]] + name = "github.com/zbindenren/negroni-prometheus" + packages = ["."] + revision = "b4860d1377680423b4c7f957ef4fce828deacf65" + version = "v0.1.1" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -434,6 +440,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e9fd1884fe1cc9d2801810e3934b9caa124434efda447ee2f93435737b2e735f" + inputs-digest = "2468168ab5c826aef27405ea9a3e72c6a99fbea6639099f2600470330509f8d9" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 6abc629..0e7882b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -84,3 +84,7 @@ [[constraint]] branch = "master" name = "github.com/satori/go.uuid" + +[[constraint]] + name = "github.com/zbindenren/negroni-prometheus" + version = "0.1.1" diff --git a/handlers/bootstrap.go b/handlers/bootstrap.go index 9d35369..b912000 100644 --- a/handlers/bootstrap.go +++ b/handlers/bootstrap.go @@ -19,6 +19,7 @@ import ( "github.com/play-with-docker/play-with-docker/config" "github.com/play-with-docker/play-with-docker/event" "github.com/play-with-docker/play-with-docker/pwd" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/urfave/negroni" ) @@ -27,8 +28,19 @@ var core pwd.PWDApi var e event.EventApi var landings = map[string][]byte{} +var latencyHistogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "pwd_handlers_duration_ms", + Help: "How long it took to process a specific handler, in a specific host", + Buckets: []float64{300, 1200, 5000}, +}, []string{"action"}) + type HandlerExtender func(h *mux.Router) +func init() { + prometheus.MustRegister(latencyHistogramVec) + +} + func Bootstrap(c pwd.PWDApi, ev event.EventApi) { core = c e = ev @@ -53,6 +65,10 @@ func Register(extend HandlerExtender) { corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}", DeleteInstance).Methods("DELETE") corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/exec", Exec).Methods("POST") + r.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/editor.html", func(rw http.ResponseWriter, r *http.Request) { + http.ServeFile(rw, r, "www/editor.html") + }) + r.HandleFunc("/ooc", func(rw http.ResponseWriter, r *http.Request) { http.ServeFile(rw, r, "./www/ooc.html") }).Methods("GET") @@ -64,9 +80,6 @@ func Register(extend HandlerExtender) { r.HandleFunc("/robots.txt", func(rw http.ResponseWriter, r *http.Request) { http.ServeFile(rw, r, "www/robots.txt") }) - r.HandleFunc("/sdk.js", func(rw http.ResponseWriter, r *http.Request) { - http.ServeFile(rw, r, "www/sdk.js") - }) corsRouter.HandleFunc("/sessions/{sessionId}/ws/", WSH) r.Handle("/metrics", promhttp.Handler()) @@ -90,6 +103,7 @@ func Register(extend HandlerExtender) { } n := negroni.Classic() + r.PathPrefix("/").Handler(negroni.New(negroni.Wrap(corsHandler(corsRouter)))) n.UseHandler(r) @@ -128,7 +142,11 @@ func Register(extend HandlerExtender) { rr.HandleFunc("/ping", Ping).Methods("GET") rr.Handle("/metrics", promhttp.Handler()) rr.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { - http.Redirect(rw, r, fmt.Sprintf("https://%s", r.Host), http.StatusMovedPermanently) + target := fmt.Sprintf("https://%s%s", r.Host, r.URL.Path) + if len(r.URL.RawQuery) > 0 { + target += "?" + r.URL.RawQuery + } + http.Redirect(rw, r, target, http.StatusMovedPermanently) }) nr := negroni.Classic() nr.UseHandler(rr) diff --git a/handlers/close_session.go b/handlers/close_session.go index f8009cc..22753b1 100644 --- a/handlers/close_session.go +++ b/handlers/close_session.go @@ -5,16 +5,20 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" ) func CloseSession(rw http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) sessionId := vars["sessionId"] - session := core.SessionGet(sessionId) - if session == nil { + session, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { rw.WriteHeader(http.StatusNotFound) return + } else if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return } if err := core.SessionClose(session); err != nil { diff --git a/handlers/delete_instance.go b/handlers/delete_instance.go index d8088c6..8a7d397 100644 --- a/handlers/delete_instance.go +++ b/handlers/delete_instance.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" ) func DeleteInstance(rw http.ResponseWriter, req *http.Request) { @@ -11,7 +12,7 @@ func DeleteInstance(rw http.ResponseWriter, req *http.Request) { sessionId := vars["sessionId"] instanceName := vars["instanceName"] - s := core.SessionGet(sessionId) + s, err := core.SessionGet(sessionId) if s != nil { i := core.InstanceGet(s, instanceName) err := core.InstanceDelete(s, i) @@ -19,8 +20,11 @@ func DeleteInstance(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusInternalServerError) return } - } else { - rw.WriteHeader(http.StatusNotFound) + } else if err == storage.NotFoundError { + rw.WriteHeader(http.StatusInternalServerError) + return + } else if err != nil { + rw.WriteHeader(http.StatusInternalServerError) return } } diff --git a/handlers/exec.go b/handlers/exec.go index 37370ed..55eb939 100644 --- a/handlers/exec.go +++ b/handlers/exec.go @@ -28,7 +28,7 @@ func Exec(rw http.ResponseWriter, req *http.Request) { return } - s := core.SessionGet(sessionId) + s, _ := core.SessionGet(sessionId) if s == nil { rw.WriteHeader(http.StatusNotFound) return diff --git a/handlers/file_upload.go b/handlers/file_upload.go index dda880b..3578d35 100644 --- a/handlers/file_upload.go +++ b/handlers/file_upload.go @@ -7,6 +7,7 @@ import ( "path/filepath" "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" ) func FileUpload(rw http.ResponseWriter, req *http.Request) { @@ -14,7 +15,14 @@ func FileUpload(rw http.ResponseWriter, req *http.Request) { sessionId := vars["sessionId"] instanceName := vars["instanceName"] - s := core.SessionGet(sessionId) + s, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { + rw.WriteHeader(http.StatusNotFound) + return + } else if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } i := core.InstanceGet(s, instanceName) // allow up to 32 MB which is the default diff --git a/handlers/get_session.go b/handlers/get_session.go index a473f45..66ce321 100644 --- a/handlers/get_session.go +++ b/handlers/get_session.go @@ -7,6 +7,7 @@ import ( "github.com/gorilla/mux" "github.com/play-with-docker/play-with-docker/pwd/types" + "github.com/play-with-docker/play-with-docker/storage" ) type SessionInfo struct { @@ -18,8 +19,11 @@ func GetSession(rw http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) sessionId := vars["sessionId"] - session := core.SessionGet(sessionId) - if session == nil { + session, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { + rw.WriteHeader(http.StatusNotFound) + return + } else if err != nil { rw.WriteHeader(http.StatusNotFound) return } diff --git a/handlers/home.go b/handlers/home.go index 3fc46f9..b203fd3 100644 --- a/handlers/home.go +++ b/handlers/home.go @@ -7,17 +7,21 @@ import ( "path/filepath" "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/storage" ) func Home(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) sessionId := vars["sessionId"] - s := core.SessionGet(sessionId) - if s == nil { + s, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { // Session doesn't exist (can happen if closing the sessions an reloading the page, or similar). w.WriteHeader(http.StatusNotFound) return + } else if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return } if s.Stack != "" { go core.SessionDeployStack(s) diff --git a/handlers/new_instance.go b/handlers/new_instance.go index 7ce24ae..f91f685 100644 --- a/handlers/new_instance.go +++ b/handlers/new_instance.go @@ -20,7 +20,11 @@ func NewInstance(rw http.ResponseWriter, req *http.Request) { json.NewDecoder(req.Body).Decode(&body) - s := core.SessionGet(sessionId) + s, err := core.SessionGet(sessionId) + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } playground := core.PlaygroundGet(s.PlaygroundId) if playground == nil { diff --git a/handlers/ping.go b/handlers/ping.go index a6993d7..33d47ad 100644 --- a/handlers/ping.go +++ b/handlers/ping.go @@ -12,6 +12,7 @@ import ( ) func Ping(rw http.ResponseWriter, req *http.Request) { + defer latencyHistogramVec.WithLabelValues("ping").Observe(float64(time.Since(time.Now()).Nanoseconds()) / 1000000) // Get system load average of the last 5 minutes and compare it against a threashold. c, err := docker.NewEnvClient() @@ -27,6 +28,7 @@ func Ping(rw http.ResponseWriter, req *http.Request) { if _, err := c.Info(ctx); err != nil && err == context.DeadlineExceeded { log.Printf("Docker info took to long to respond\n") rw.WriteHeader(http.StatusGatewayTimeout) + return } a, err := load.Avg() diff --git a/handlers/session_setup.go b/handlers/session_setup.go index 28799fe..f12d062 100644 --- a/handlers/session_setup.go +++ b/handlers/session_setup.go @@ -17,9 +17,13 @@ func SessionSetup(rw http.ResponseWriter, req *http.Request) { json.NewDecoder(req.Body).Decode(&body) - s := core.SessionGet(sessionId) + s, err := core.SessionGet(sessionId) + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } - err := core.SessionSetup(s, body) + err = core.SessionSetup(s, body) if err != nil { if pwd.SessionNotEmpty(err) { log.Println("Cannot setup a session that contains instances") diff --git a/handlers/ws.go b/handlers/ws.go index bdde831..fbf850a 100644 --- a/handlers/ws.go +++ b/handlers/ws.go @@ -10,6 +10,7 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/play-with-docker/play-with-docker/event" + "github.com/play-with-docker/play-with-docker/storage" "github.com/satori/go.uuid" ) @@ -144,8 +145,8 @@ func ws(so *socket) { sessionId := vars["sessionId"] - session := core.SessionGet(sessionId) - if session == nil { + session, err := core.SessionGet(sessionId) + if err == storage.NotFoundError { log.Printf("Session with id [%s] does not exist!\n", sessionId) return } diff --git a/pwd/pwd.go b/pwd/pwd.go index ddf678a..6f8fcf0 100644 --- a/pwd/pwd.go +++ b/pwd/pwd.go @@ -77,7 +77,7 @@ type PWDApi interface { SessionClose(session *types.Session) error SessionGetSmallestViewPort(sessionId string) types.ViewPort SessionDeployStack(session *types.Session) error - SessionGet(id string) *types.Session + SessionGet(id string) (*types.Session, error) SessionSetup(session *types.Session, conf SessionSetupConf) error InstanceNew(session *types.Session, conf types.InstanceConfig) (*types.Instance, error) diff --git a/pwd/session.go b/pwd/session.go index 228e54a..0470ebf 100644 --- a/pwd/session.go +++ b/pwd/session.go @@ -205,17 +205,17 @@ func (p *pwd) SessionDeployStack(s *types.Session) error { return nil } -func (p *pwd) SessionGet(sessionId string) *types.Session { +func (p *pwd) SessionGet(sessionId string) (*types.Session, error) { defer observeAction("SessionGet", time.Now()) s, err := p.storage.SessionGet(sessionId) if err != nil { log.Println(err) - return nil + return nil, err } - return s + return s, nil } func (p *pwd) SessionSetup(session *types.Session, sconf SessionSetupConf) error { diff --git a/www/assets/app.js b/www/assets/app.js index 9a9bb05..b7a1ff6 100644 --- a/www/assets/app.js +++ b/www/assets/app.js @@ -1,7 +1,7 @@ (function() { 'use strict'; - var app = angular.module('DockerPlay', ['ngMaterial', 'ngFileUpload']); + var app = angular.module('DockerPlay', ['ngMaterial', 'ngFileUpload', 'ngclipboard']); // Automatically redirects user to a new session when bypassing captcha. // Controller keeps code/logic separate from the HTML diff --git a/www/default/index.html b/www/default/index.html index 59fb4b7..07de953 100644 --- a/www/default/index.html +++ b/www/default/index.html @@ -116,6 +116,9 @@ {{deleteInstanceBtnText}} + SSH + Copied + @@ -291,6 +294,8 @@ + +