diff --git a/api.go b/api.go index 5a9bdbbe23..1fa40f0164 100644 --- a/api.go +++ b/api.go @@ -703,9 +703,18 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ return nil } -func ListenAndServe(addr string, srv *Server, logging bool) error { +func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + w.WriteHeader(http.StatusOK) + return nil +} +func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") + w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") +} + +func createRouter(srv *Server, logging bool) (*mux.Router, error) { r := mux.NewRouter() - log.Printf("Listening for HTTP on %s\n", addr) m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ "GET": { @@ -745,6 +754,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { "/containers/{name:.*}": deleteContainers, "/images/{name:.*}": deleteImages, }, + "OPTIONS": { + "": optionsHandler, + }, } for method, routes := range m { @@ -769,6 +781,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { if err != nil { version = APIVERSION } + if srv.enableCors { + writeCorsHeaders(w, r) + } if version == 0 || version > APIVERSION { w.WriteHeader(http.StatusNotFound) return @@ -777,9 +792,24 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { httpError(w, err) } } - r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) - r.Path(localRoute).Methods(localMethod).HandlerFunc(f) + + 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) + } } } + return r, nil +} + +func ListenAndServe(addr string, srv *Server, logging bool) error { + log.Printf("Listening for HTTP on %s\n", addr) + + r, err := createRouter(srv, logging) + if err != nil { + return err + } return http.ListenAndServe(addr, r) } diff --git a/api_test.go b/api_test.go index e122786b86..8785da68d9 100644 --- a/api_test.go +++ b/api_test.go @@ -1239,6 +1239,73 @@ func TestDeleteContainers(t *testing.T) { } } +func TestOptionsRoute(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime, enableCors: true} + + r := httptest.NewRecorder() + router, err := createRouter(srv, false) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("OPTIONS", "/", nil) + if err != nil { + t.Fatal(err) + } + + router.ServeHTTP(r, req) + if r.Code != http.StatusOK { + t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) + } +} + +func TestGetEnabledCors(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime, enableCors: true} + + r := httptest.NewRecorder() + + router, err := createRouter(srv, false) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("GET", "/version", nil) + if err != nil { + t.Fatal(err) + } + + router.ServeHTTP(r, req) + if r.Code != http.StatusOK { + t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) + } + + allowOrigin := r.Header().Get("Access-Control-Allow-Origin") + allowHeaders := r.Header().Get("Access-Control-Allow-Headers") + allowMethods := r.Header().Get("Access-Control-Allow-Methods") + + if allowOrigin != "*" { + t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin) + } + if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept" { + t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept\", %s found.", allowHeaders) + } + if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" { + t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) + } +} + func TestDeleteImages(t *testing.T) { //FIXME: Implement this test t.Log("Test not implemented") diff --git a/docker/docker.go b/docker/docker.go index dada16e11e..74236613a7 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -33,6 +33,7 @@ func main() { bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to") + flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flag.Parse() if *bridgeName != "" { docker.NetworkBridgeIface = *bridgeName @@ -65,7 +66,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, host, port, *flAutoRestart); err != nil { + if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors); err != nil { log.Fatal(err) os.Exit(-1) } @@ -104,7 +105,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile, addr string, port int, autoRestart bool) error { +func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error { if addr != "127.0.0.1" { log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } @@ -122,7 +123,7 @@ func daemon(pidfile, addr string, port int, autoRestart bool) error { os.Exit(0) }() - server, err := docker.NewServer(autoRestart) + server, err := docker.NewServer(autoRestart, enableCors) if err != nil { return err } diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index c7540edaa8..3a4c8dc024 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -1057,6 +1057,14 @@ Here are the steps of 'docker run' : In this first version of the API, some of the endpoints, like /attach, /pull or /push uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. +3.3 CORS Requests +----------------- + +To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. + + docker -d -H="192.168.1.9:4243" -api-enable-cors + + ================================== Docker Remote API Client Libraries ================================== @@ -1080,3 +1088,4 @@ and we will add the libraries here. | Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui | | **WebUI** | | | +----------------------+----------------+--------------------------------------------+ + diff --git a/server.go b/server.go index f34235b057..7e9e3ea315 100644 --- a/server.go +++ b/server.go @@ -879,7 +879,7 @@ func (srv *Server) ImageInspect(name string) (*Image, error) { return nil, fmt.Errorf("No such image: %s", name) } -func NewServer(autoRestart bool) (*Server, error) { +func NewServer(autoRestart, enableCors bool) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } @@ -888,12 +888,14 @@ func NewServer(autoRestart bool) (*Server, error) { return nil, err } srv := &Server{ - runtime: runtime, + runtime: runtime, + enableCors: enableCors, } runtime.srv = srv return srv, nil } type Server struct { - runtime *Runtime + runtime *Runtime + enableCors bool }