| ... | ... |
@@ -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 |
} |