Browse Source

Merge pull request #320 from philips/add-cors-options

feat(server): introduce a cors handler
Brandon Philips 12 years ago
parent
commit
4520705bdf

+ 26 - 11
mod/dashboard/app/scripts/common/services/etcd.js

@@ -2,10 +2,11 @@
 
 angular.module('etcd', [])
 
-.factory('EtcdV2', ['$http', function($http) {
+.factory('EtcdV2', ['$http', '$q', function($http, $q) {
   var keyPrefix = '/v2/keys/'
   var statsPrefix = '/v2/stats/'
   var baseURL = '/v2/'
+  var leaderURL = ''
 
   delete $http.defaults.headers.common['X-Requested-With'];
 
@@ -45,19 +46,23 @@ angular.module('etcd', [])
     };
 
     self.set = function(keyValue) {
-      return $http({
-        url: self.path(),
-        data: $.param({value: keyValue}),
-        method: 'PUT',
-        headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+      return getLeader().then(function(leader) {
+        return $http({
+          url: leader + self.path(),
+          data: $.param({value: keyValue}),
+          method: 'PUT',
+          headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+        });
       });
     };
 
     self.deleteKey = function(keyValue) {
-      return $http({
-        url: self.path(),
-        method: 'DELETE',
-        headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+      return getLeader().then(function(leader) {
+        return $http({
+          url: leader + self.path(),
+          method: 'DELETE',
+          headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+        });
       });
     };
 
@@ -79,8 +84,18 @@ angular.module('etcd', [])
     return self
   }
 
+  function getLeader() {
+    return newStat('leader').get().then(function(response) {
+      return newKey('/_etcd/machines/' + response.data.leader).get().then(function(response) {
+        // TODO: do something better here p.s. I hate javascript
+        var data = JSON.parse('{"' + decodeURI(response.data.value.replace(/&/g, "\",\"").replace(/=/g,"\":\"")) + '"}');
+        return data.etcd;
+      });
+    });
+  }
+
   return {
     getStat: newStat,
-    getKey: newKey
+    getKey: newKey,
   }
 }]);

+ 4 - 4
mod/dashboard/app/scripts/controllers/browser.js

@@ -94,23 +94,23 @@ angular.module('etcdBrowser', ['ngRoute', 'etcd', 'timeRelative'])
 
   $scope.saveData = function() {
     // TODO: fixup etcd to allow for empty values
-    $scope.key.set($scope.singleValue || ' ').success(function (data, status, headers, config) {
+    $scope.key.set($scope.singleValue || ' ').then(function(response) {
       $scope.save = 'etcd-save-hide';
       $scope.preview = 'etcd-preview-hide';
       $scope.back();
       $scope.writingNew = false;
-    }).error(function (data, status, headers, config) {
+    }, function (response) {
       $scope.showSaveError(data.message);
     });
   };
 
   $scope.deleteKey = function() {
-    $scope.key.deleteKey().success(function (data, status, headers, config) {
+    $scope.key.deleteKey().then(function(response) {
       //TODO: remove loader
       $scope.save = 'etcd-save-hide';
       $scope.preview = 'etcd-preview-hide';
       $scope.back();
-    }).error(function (data, status, headers, config) {
+    }, function (response) {
       //TODO: remove loader
       //show errors
       $scope.showBrowseError('Could not delete the key');

File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/scripts-browser-modules.js.go


File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/scripts-browser-scripts.js.go


File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/scripts-stats-modules.js.go


File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/scripts-stats-scripts.js.go


File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/styles-main.css.go


File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/views-stats.html.go


+ 78 - 0
server/cors_handler.go

@@ -0,0 +1,78 @@
+/*
+Copyright 2013 CoreOS Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package server
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+
+	"github.com/gorilla/mux"
+)
+
+type corsHandler struct {
+	router *mux.Router
+	corsOrigins map[string]bool
+}
+
+// AllowOrigins sets a comma-delimited list of origins that are allowed.
+func (s *corsHandler) AllowOrigins(origins []string) error {
+	// Construct a lookup of all origins.
+	m := make(map[string]bool)
+	for _, v := range origins {
+		if v != "*" {
+			if _, err := url.Parse(v); err != nil {
+				return fmt.Errorf("Invalid CORS origin: %s", err)
+			}
+		}
+		m[v] = true
+	}
+	s.corsOrigins = m
+
+	return nil
+}
+
+// OriginAllowed determines whether the server will allow a given CORS origin.
+func (c *corsHandler) OriginAllowed(origin string) bool {
+	return c.corsOrigins["*"] || c.corsOrigins[origin]
+}
+
+// addHeader adds the correct cors headers given an origin
+func (h *corsHandler) addHeader(w http.ResponseWriter, origin string) {
+	w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
+	w.Header().Add("Access-Control-Allow-Origin", origin)
+}
+
+// ServeHTTP adds the correct CORS headers based on the origin and returns immediatly
+// with a 200 OK if the method is OPTIONS.
+func (h *corsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	// Write CORS header.
+	if h.OriginAllowed("*") {
+		h.addHeader(w, "*")
+	} else if origin := req.Header.Get("Origin"); h.OriginAllowed(origin) {
+		h.addHeader(w, origin)
+	}
+
+	if req.Method == "OPTIONS" {
+		w.WriteHeader(http.StatusOK)
+		return
+	}
+
+	h.router.ServeHTTP(w, req)
+}
+
+

+ 16 - 30
server/server.go

@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"net"
 	"net/http"
-	"net/url"
 	"strings"
 	"time"
 
@@ -32,14 +31,18 @@ type Server struct {
 	url         string
 	tlsConf     *TLSConfig
 	tlsInfo     *TLSInfo
-	corsOrigins map[string]bool
+	router      *mux.Router
+	corsHandler *corsHandler
 }
 
 // Creates a new Server.
 func New(name string, urlStr string, bindAddr string, tlsConf *TLSConfig, tlsInfo *TLSInfo, peerServer *PeerServer, registry *Registry, store store.Store) *Server {
+	r := mux.NewRouter()
+	cors := &corsHandler{router: r}
+
 	s := &Server{
 		Server: http.Server{
-			Handler:   mux.NewRouter(),
+			Handler:   cors,
 			TLSConfig: &tlsConf.Server,
 			Addr:      bindAddr,
 		},
@@ -50,6 +53,8 @@ func New(name string, urlStr string, bindAddr string, tlsConf *TLSConfig, tlsInf
 		tlsConf:    tlsConf,
 		tlsInfo:    tlsInfo,
 		peerServer: peerServer,
+		router:     r,
+		corsHandler: cors,
 	}
 
 	// Install the routes.
@@ -124,7 +129,7 @@ func (s *Server) installV2() {
 }
 
 func (s *Server) installMod() {
-	r := s.Handler.(*mux.Router)
+	r := s.router
 	r.PathPrefix("/mod").Handler(http.StripPrefix("/mod", mod.HttpHandler()))
 }
 
@@ -144,20 +149,13 @@ func (s *Server) handleFuncV2(path string, f func(http.ResponseWriter, *http.Req
 
 // Adds a server handler to the router.
 func (s *Server) handleFunc(path string, f func(http.ResponseWriter, *http.Request) error) *mux.Route {
-	r := s.Handler.(*mux.Router)
+	r := s.router
 
 	// Wrap the standard HandleFunc interface to pass in the server reference.
 	return r.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
 		// Log request.
 		log.Debugf("[recv] %s %s %s [%s]", req.Method, s.url, req.URL.Path, req.RemoteAddr)
 
-		// Write CORS header.
-		if s.OriginAllowed("*") {
-			w.Header().Add("Access-Control-Allow-Origin", "*")
-		} else if origin := req.Header.Get("Origin"); s.OriginAllowed(origin) {
-			w.Header().Add("Access-Control-Allow-Origin", origin)
-		}
-
 		// Execute handler function and return error if necessary.
 		if err := f(w, req); err != nil {
 			if etcdErr, ok := err.(*etcdErr.Error); ok {
@@ -302,26 +300,14 @@ func (s *Server) Dispatch(c raft.Command, w http.ResponseWriter, req *http.Reque
 	}
 }
 
-// Sets a comma-delimited list of origins that are allowed.
-func (s *Server) AllowOrigins(origins []string) error {
-	// Construct a lookup of all origins.
-	m := make(map[string]bool)
-	for _, v := range origins {
-		if v != "*" {
-			if _, err := url.Parse(v); err != nil {
-				return fmt.Errorf("Invalid CORS origin: %s", err)
-			}
-		}
-		m[v] = true
-	}
-	s.corsOrigins = m
-
-	return nil
+// OriginAllowed determines whether the server will allow a given CORS origin.
+func (s *Server) OriginAllowed(origin string) bool {
+	return s.corsHandler.OriginAllowed(origin)
 }
 
-// Determines whether the server will allow a given CORS origin.
-func (s *Server) OriginAllowed(origin string) bool {
-	return s.corsOrigins["*"] || s.corsOrigins[origin]
+// AllowOrigins sets a comma-delimited list of origins that are allowed.
+func (s *Server) AllowOrigins(origins []string) error {
+	return s.corsHandler.AllowOrigins(origins)
 }
 
 // Handler to return the current version of etcd.

Some files were not shown because too many files changed in this diff