From 8eeff01939e3b9f09e5233f6cde60e1c10af0a63 Mon Sep 17 00:00:00 2001 From: benoitc Date: Fri, 5 Jul 2013 19:55:15 +0200 Subject: [PATCH 1/5] add websocket support to /container//attach/ws This function add the possibility to attach containers streams to a websocket. When a websocket is asked the request is upgraded to this protocol.. --- api.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/api.go b/api.go index 1970feaec6..45261973a7 100644 --- a/api.go +++ b/api.go @@ -1,6 +1,7 @@ package docker import ( + "code.google.com/p/go.net/websocket" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -693,6 +694,52 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return nil } +func wsContainersAttach(srv *Server, ws *websocket.Conn, vars map[string]string) error { + + r := ws.Request() + + if err := parseForm(r); err != nil { + return err + } + logs, err := getBoolParam(r.Form.Get("logs")) + if err != nil { + return err + } + stream, err := getBoolParam(r.Form.Get("stream")) + if err != nil { + return err + } + stdin, err := getBoolParam(r.Form.Get("stdin")) + if err != nil { + return err + } + stdout, err := getBoolParam(r.Form.Get("stdout")) + if err != nil { + return err + } + stderr, err := getBoolParam(r.Form.Get("stderr")) + if err != nil { + return err + } + + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + + if _, err := srv.ContainerInspect(name); err != nil { + return err + } + + defer ws.Close() + + if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil { + return err + } + + return nil +} + func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") @@ -898,12 +945,14 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { if srv.enableCors { writeCorsHeaders(w, r) } + if version == 0 || version > APIVERSION { w.WriteHeader(http.StatusNotFound) return } if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil { + utils.Debugf("Error: %s", err) httpError(w, err) } } @@ -914,8 +963,26 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) r.Path(localRoute).Methods(localMethod).HandlerFunc(f) } + } } + attachHandler := websocket.Handler(func(ws *websocket.Conn) { + r := ws.Request() + utils.Debugf("Calling %s %s", r.Method, r.RequestURI) + + if logging { + log.Println(r.Method, r.RequestURI) + } + if err := wsContainersAttach(srv, ws, mux.Vars(r)); err != nil { + utils.Debugf("Error: %s", err) + ws.Close() + } + }) + + attachRoute := "/containers/{name:.*}/attach/ws" + r.Path("/v{version:[0-9.]+}" + attachRoute).Methods("GET").Handler(attachHandler) + r.Path(attachRoute).Methods("GET").Handler(attachHandler) + return r, nil } From 166eba3e28cc440e06e97ed1bda5b1c3881d46da Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 13 Jul 2013 16:59:07 +0200 Subject: [PATCH 2/5] put the websocket route in the map containing all routes Instead of handling the websocket differently just handle it as a normal route and upgrade it to a websocket. --- api.go | 139 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 72 insertions(+), 67 deletions(-) diff --git a/api.go b/api.go index 45261973a7..ca59bb3cdf 100644 --- a/api.go +++ b/api.go @@ -22,6 +22,9 @@ const APIVERSION = 1.3 const DEFAULTHTTPHOST string = "127.0.0.1" const DEFAULTHTTPPORT int = 4243 +type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error +type WsApiFunc func(srv *Server, ws *websocket.Conn, vars map[string]string) error + func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() if err != nil { @@ -694,9 +697,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return nil } -func wsContainersAttach(srv *Server, ws *websocket.Conn, vars map[string]string) error { - - r := ws.Request() +func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err @@ -731,11 +732,14 @@ func wsContainersAttach(srv *Server, ws *websocket.Conn, vars map[string]string) return err } - defer ws.Close() + h := websocket.Handler(func(ws *websocket.Conn) { + defer ws.Close() - if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil { - return err - } + if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil { + utils.Debugf("Error: %s", err) + } + }) + h.ServeHTTP(w, r) return nil } @@ -873,24 +877,68 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } +func logRequest(logging bool, localMethod string, localRoute string, r *http.Request) { + utils.Debugf("Calling %s %s", localMethod, localRoute) + + if logging { + log.Println(r.Method, r.RequestURI) + } +} + +func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // log the request + utils.Debugf("Calling %s %s", localMethod, localRoute) + + if logging { + log.Println(r.Method, r.RequestURI) + } + + if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { + userAgent := strings.Split(r.Header.Get("User-Agent"), "/") + if len(userAgent) == 2 && userAgent[1] != VERSION { + utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION) + } + } + version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64) + if err != nil { + version = APIVERSION + } + if srv.enableCors { + writeCorsHeaders(w, r) + } + + if version == 0 || version > APIVERSION { + w.WriteHeader(http.StatusNotFound) + return + } + + if err := handlerFunc(srv, version, w, r, mux.Vars(r)); err != nil { + utils.Debugf("Error: %s", err) + httpError(w, err) + } + } +} + func createRouter(srv *Server, logging bool) (*mux.Router, error) { r := mux.NewRouter() m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ "GET": { - "/auth": getAuth, - "/version": getVersion, - "/info": getInfo, - "/images/json": getImagesJSON, - "/images/viz": getImagesViz, - "/images/search": getImagesSearch, - "/images/{name:.*}/history": getImagesHistory, - "/images/{name:.*}/json": getImagesByName, - "/containers/ps": getContainersJSON, - "/containers/json": getContainersJSON, - "/containers/{name:.*}/export": getContainersExport, - "/containers/{name:.*}/changes": getContainersChanges, - "/containers/{name:.*}/json": getContainersByName, + "/auth": getAuth, + "/version": getVersion, + "/info": getInfo, + "/images/json": getImagesJSON, + "/images/viz": getImagesViz, + "/images/search": getImagesSearch, + "/images/{name:.*}/history": getImagesHistory, + "/images/{name:.*}/json": getImagesByName, + "/containers/ps": getContainersJSON, + "/containers/json": getContainersJSON, + "/containers/{name:.*}/export": getContainersExport, + "/containers/{name:.*}/changes": getContainersChanges, + "/containers/{name:.*}/json": getContainersByName, + "/containers/{name:.*}/attach/ws": wsContainersAttach, }, "POST": { "/auth": postAuth, @@ -924,64 +972,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { utils.Debugf("Registering %s, %s", method, route) // NOTE: scope issue, make sure the variables are local and won't be changed localRoute := route - localMethod := method localFct := fct - f := func(w http.ResponseWriter, r *http.Request) { - utils.Debugf("Calling %s %s", localMethod, localRoute) + localMethod := method - if logging { - log.Println(r.Method, r.RequestURI) - } - if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { - userAgent := strings.Split(r.Header.Get("User-Agent"), "/") - if len(userAgent) == 2 && userAgent[1] != VERSION { - utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION) - } - } - version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64) - if err != nil { - version = APIVERSION - } - if srv.enableCors { - writeCorsHeaders(w, r) - } - - if version == 0 || version > APIVERSION { - w.WriteHeader(http.StatusNotFound) - return - } - - if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil { - utils.Debugf("Error: %s", err) - httpError(w, err) - } - } + // build the handler function + f := makeHttpHandler(srv, logging, localMethod, localRoute, localFct) + // add the new route if localRoute == "" { r.Methods(localMethod).HandlerFunc(f) } else { r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) r.Path(localRoute).Methods(localMethod).HandlerFunc(f) } - } } - attachHandler := websocket.Handler(func(ws *websocket.Conn) { - r := ws.Request() - utils.Debugf("Calling %s %s", r.Method, r.RequestURI) - - if logging { - log.Println(r.Method, r.RequestURI) - } - if err := wsContainersAttach(srv, ws, mux.Vars(r)); err != nil { - utils.Debugf("Error: %s", err) - ws.Close() - } - }) - - attachRoute := "/containers/{name:.*}/attach/ws" - r.Path("/v{version:[0-9.]+}" + attachRoute).Methods("GET").Handler(attachHandler) - r.Path(attachRoute).Methods("GET").Handler(attachHandler) return r, nil } From 507cef8bcea5b56d25d34ca7073c46f608962f19 Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 13 Jul 2013 17:03:04 +0200 Subject: [PATCH 3/5] useless type --- api.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api.go b/api.go index ca59bb3cdf..e27158e7a8 100644 --- a/api.go +++ b/api.go @@ -23,7 +23,6 @@ const DEFAULTHTTPHOST string = "127.0.0.1" const DEFAULTHTTPPORT int = 4243 type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error -type WsApiFunc func(srv *Server, ws *websocket.Conn, vars map[string]string) error func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() From a3b1a9f01a1c08c63e6c37945414481176817ba6 Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 13 Jul 2013 17:04:04 +0200 Subject: [PATCH 4/5] useless function. forgot to remove it. --- api.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/api.go b/api.go index e27158e7a8..f86672c6df 100644 --- a/api.go +++ b/api.go @@ -876,14 +876,6 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func logRequest(logging bool, localMethod string, localRoute string, r *http.Request) { - utils.Debugf("Calling %s %s", localMethod, localRoute) - - if logging { - log.Println(r.Method, r.RequestURI) - } -} - func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // log the request From d639f61ec1c80f8d8d65386ec9331dbb6e218cb0 Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 13 Jul 2013 19:19:38 +0200 Subject: [PATCH 5/5] reuse the type --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index f86672c6df..6246d06d67 100644 --- a/api.go +++ b/api.go @@ -914,7 +914,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s func createRouter(srv *Server, logging bool) (*mux.Router, error) { r := mux.NewRouter() - m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ + m := map[string]map[string]HttpApiFunc{ "GET": { "/auth": getAuth, "/version": getVersion,