Browse code

* Remote Api: Add flag to enable cross domain requests

Victor Vieux authored on 2013/06/11 19:12:36
Showing 5 changed files
... ...
@@ -703,9 +703,18 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
703 703
 	return nil
704 704
 }
705 705
 
706
-func ListenAndServe(addr string, srv *Server, logging bool) error {
706
+func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
707
+	w.WriteHeader(http.StatusOK)
708
+	return nil
709
+}
710
+func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
711
+	w.Header().Add("Access-Control-Allow-Origin", "*")
712
+	w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
713
+	w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
714
+}
715
+
716
+func createRouter(srv *Server, logging bool) (*mux.Router, error) {
707 717
 	r := mux.NewRouter()
708
-	log.Printf("Listening for HTTP on %s\n", addr)
709 718
 
710 719
 	m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
711 720
 		"GET": {
... ...
@@ -745,6 +754,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
745 745
 			"/containers/{name:.*}": deleteContainers,
746 746
 			"/images/{name:.*}":     deleteImages,
747 747
 		},
748
+		"OPTIONS": {
749
+			"": optionsHandler,
750
+		},
748 751
 	}
749 752
 
750 753
 	for method, routes := range m {
... ...
@@ -769,6 +781,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
769 769
 				if err != nil {
770 770
 					version = APIVERSION
771 771
 				}
772
+				if srv.enableCors {
773
+					writeCorsHeaders(w, r)
774
+				}
772 775
 				if version == 0 || version > APIVERSION {
773 776
 					w.WriteHeader(http.StatusNotFound)
774 777
 					return
... ...
@@ -777,9 +792,24 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
777 777
 					httpError(w, err)
778 778
 				}
779 779
 			}
780
-			r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
781
-			r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
780
+
781
+			if localRoute == "" {
782
+				r.Methods(localMethod).HandlerFunc(f)
783
+			} else {
784
+				r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
785
+				r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
786
+			}
782 787
 		}
783 788
 	}
789
+	return r, nil
790
+}
791
+
792
+func ListenAndServe(addr string, srv *Server, logging bool) error {
793
+	log.Printf("Listening for HTTP on %s\n", addr)
794
+
795
+	r, err := createRouter(srv, logging)
796
+	if err != nil {
797
+		return err
798
+	}
784 799
 	return http.ListenAndServe(addr, r)
785 800
 }
... ...
@@ -1239,6 +1239,73 @@ func TestDeleteContainers(t *testing.T) {
1239 1239
 	}
1240 1240
 }
1241 1241
 
1242
+func TestOptionsRoute(t *testing.T) {
1243
+	runtime, err := newTestRuntime()
1244
+	if err != nil {
1245
+		t.Fatal(err)
1246
+	}
1247
+	defer nuke(runtime)
1248
+
1249
+	srv := &Server{runtime: runtime, enableCors: true}
1250
+
1251
+	r := httptest.NewRecorder()
1252
+	router, err := createRouter(srv, false)
1253
+	if err != nil {
1254
+		t.Fatal(err)
1255
+	}
1256
+
1257
+	req, err := http.NewRequest("OPTIONS", "/", nil)
1258
+	if err != nil {
1259
+		t.Fatal(err)
1260
+	}
1261
+
1262
+	router.ServeHTTP(r, req)
1263
+	if r.Code != http.StatusOK {
1264
+		t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
1265
+	}
1266
+}
1267
+
1268
+func TestGetEnabledCors(t *testing.T) {
1269
+	runtime, err := newTestRuntime()
1270
+	if err != nil {
1271
+		t.Fatal(err)
1272
+	}
1273
+	defer nuke(runtime)
1274
+
1275
+	srv := &Server{runtime: runtime, enableCors: true}
1276
+
1277
+	r := httptest.NewRecorder()
1278
+
1279
+	router, err := createRouter(srv, false)
1280
+	if err != nil {
1281
+		t.Fatal(err)
1282
+	}
1283
+
1284
+	req, err := http.NewRequest("GET", "/version", nil)
1285
+	if err != nil {
1286
+		t.Fatal(err)
1287
+	}
1288
+
1289
+	router.ServeHTTP(r, req)
1290
+	if r.Code != http.StatusOK {
1291
+		t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
1292
+	}
1293
+
1294
+	allowOrigin := r.Header().Get("Access-Control-Allow-Origin")
1295
+	allowHeaders := r.Header().Get("Access-Control-Allow-Headers")
1296
+	allowMethods := r.Header().Get("Access-Control-Allow-Methods")
1297
+
1298
+	if allowOrigin != "*" {
1299
+		t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin)
1300
+	}
1301
+	if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept" {
1302
+		t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept\", %s found.", allowHeaders)
1303
+	}
1304
+	if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" {
1305
+		t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods)
1306
+	}
1307
+}
1308
+
1242 1309
 func TestDeleteImages(t *testing.T) {
1243 1310
 	//FIXME: Implement this test
1244 1311
 	t.Log("Test not implemented")
... ...
@@ -33,6 +33,7 @@ func main() {
33 33
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
34 34
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
35 35
 	flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
36
+	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
36 37
 	flag.Parse()
37 38
 	if *bridgeName != "" {
38 39
 		docker.NetworkBridgeIface = *bridgeName
... ...
@@ -65,7 +66,7 @@ func main() {
65 65
 			flag.Usage()
66 66
 			return
67 67
 		}
68
-		if err := daemon(*pidfile, host, port, *flAutoRestart); err != nil {
68
+		if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors); err != nil {
69 69
 			log.Fatal(err)
70 70
 			os.Exit(-1)
71 71
 		}
... ...
@@ -104,7 +105,7 @@ func removePidFile(pidfile string) {
104 104
 	}
105 105
 }
106 106
 
107
-func daemon(pidfile, addr string, port int, autoRestart bool) error {
107
+func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error {
108 108
 	if addr != "127.0.0.1" {
109 109
 		log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
110 110
 	}
... ...
@@ -122,7 +123,7 @@ func daemon(pidfile, addr string, port int, autoRestart bool) error {
122 122
 		os.Exit(0)
123 123
 	}()
124 124
 
125
-	server, err := docker.NewServer(autoRestart)
125
+	server, err := docker.NewServer(autoRestart, enableCors)
126 126
 	if err != nil {
127 127
 		return err
128 128
 	}
... ...
@@ -1057,6 +1057,14 @@ Here are the steps of 'docker run' :
1057 1057
 In this first version of the API, some of the endpoints, like /attach, /pull or /push uses hijacking to transport stdin,
1058 1058
 stdout and stderr on the same socket. This might change in the future.
1059 1059
 
1060
+3.3 CORS Requests
1061
+-----------------
1062
+
1063
+To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
1064
+    
1065
+    docker -d -H="192.168.1.9:4243" -api-enable-cors
1066
+
1067
+
1060 1068
 ==================================
1061 1069
 Docker Remote API Client Libraries
1062 1070
 ==================================
... ...
@@ -1080,3 +1088,4 @@ and we will add the libraries here.
1080 1080
 | Javascript (Angular) | dockerui       | https://github.com/crosbymichael/dockerui  |
1081 1081
 | **WebUI**            |                |                                            |
1082 1082
 +----------------------+----------------+--------------------------------------------+
1083
+
... ...
@@ -879,7 +879,7 @@ func (srv *Server) ImageInspect(name string) (*Image, error) {
879 879
 	return nil, fmt.Errorf("No such image: %s", name)
880 880
 }
881 881
 
882
-func NewServer(autoRestart bool) (*Server, error) {
882
+func NewServer(autoRestart, enableCors bool) (*Server, error) {
883 883
 	if runtime.GOARCH != "amd64" {
884 884
 		log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
885 885
 	}
... ...
@@ -888,12 +888,14 @@ func NewServer(autoRestart bool) (*Server, error) {
888 888
 		return nil, err
889 889
 	}
890 890
 	srv := &Server{
891
-		runtime: runtime,
891
+		runtime:    runtime,
892
+		enableCors: enableCors,
892 893
 	}
893 894
 	runtime.srv = srv
894 895
 	return srv, nil
895 896
 }
896 897
 
897 898
 type Server struct {
898
-	runtime *Runtime
899
+	runtime    *Runtime
900
+	enableCors bool
899 901
 }