Add CORS support to handler.

This commit is contained in:
Cody Soyland 2018-05-24 08:42:34 -05:00
parent 3943e3e3cb
commit 1a9beb0916
8 changed files with 142 additions and 49 deletions

16
Gopkg.lock generated
View file

@ -82,6 +82,12 @@
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
version = "v1.1"
[[projects]]
name = "github.com/gorilla/handlers"
packages = ["."]
revision = "90663712d74cb411cbef281bc1e08c19d1a76145"
version = "v1.3.0"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
@ -206,22 +212,14 @@
[[projects]]
name = "github.com/shirou/gopsutil"
packages = [
"cpu",
"host",
"internal/common",
"mem",
"net",
"process"
]
revision = "bfe3c2e8f406bf352bc8df81f98c752224867349"
version = "v2.17.11"
[[projects]]
branch = "master"
name = "github.com/shirou/w32"
packages = ["."]
revision = "bb4de0191aa41b5507caa14b0650cdbddcd9280b"
[[projects]]
branch = "master"
name = "github.com/spf13/afero"
@ -304,6 +302,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "61a00007fe7398d958fc8668543d42293f1310f9f9b74c63ff96f5a892eab1e7"
inputs-digest = "8f633d73d966ca439d2fdf3704a41d8ea59be8ed9a2cab0ab73de4b72c5772ba"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -33,6 +33,9 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) {
// TLS
SetTLSConfig(flags, &srv.Config.TLS.CertificatePath, &srv.Config.TLS.CertificateKeyPath, &srv.Config.TLS.SkipVerify)
// Handler
flags.StringSliceVarP(&srv.Config.Handler.AllowedOrigins, "handler.allowed-origins", "", []string{"http://localhost/"}, "Comma separated list of allowed origin hosts (for CORS/WebUI).")
// Cluster
flags.BoolVarP(&srv.Config.Cluster.Disabled, "cluster.disabled", "", srv.Config.Cluster.Disabled, "Disabled multi-node cluster communication (used for testing)")
flags.BoolVarP(&srv.Config.Cluster.Coordinator, "cluster.coordinator", "", srv.Config.Cluster.Coordinator, "Host that will act as cluster coordinator during startup and resizing.")

View file

@ -31,14 +31,16 @@ import (
"time"
"github.com/gogo/protobuf/proto"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/pilosa/pilosa/internal"
"github.com/pkg/errors"
)
// Handler represents an HTTP handler.
type Handler struct {
Router *mux.Router
Handler http.Handler
FileSystem FileSystem
@ -48,6 +50,8 @@ type Handler struct {
validators map[string]*queryValidationSpec
API *API
AllowedOrigins []string
}
// externalPrefixFlag denotes endpoints that are intended to be exposed to clients.
@ -67,15 +71,36 @@ type errorResponse struct {
Error string `json:"error"`
}
// HandlerOption is a functional option type for pilosa.Handler
type HandlerOption func(s *Handler) error
func OptHandlerAllowedOrigins(origins []string) HandlerOption {
return func(h *Handler) error {
h.Handler = handlers.CORS(
handlers.AllowedOrigins(origins),
handlers.AllowedHeaders([]string{"Content-Type"}),
)(h.Handler)
return nil
}
}
// NewHandler returns a new instance of Handler with a default logger.
func NewHandler() *Handler {
func NewHandler(opts ...HandlerOption) (*Handler, error) {
handler := &Handler{
FileSystem: NopFileSystem,
Logger: NopLogger,
}
handler.Router = NewRouter(handler)
handler.Handler = NewRouter(handler)
handler.populateValidators()
return handler
for _, opt := range opts {
err := opt(handler)
if err != nil {
return nil, errors.Wrap(err, "applying option")
}
}
return handler, nil
}
func (h *Handler) populateValidators() {
@ -183,7 +208,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}()
t := time.Now()
h.Router.ServeHTTP(w, r)
h.Handler.ServeHTTP(w, r)
dif := time.Since(t)
// Calculate per request StatsD metrics when the handler is fully configured.

View file

@ -37,7 +37,7 @@ import (
)
func TestHandlerPanics(t *testing.T) {
h := test.NewHandler()
h := test.MustNewHandler()
bufLogger := test.NewBufferLogger()
h.Handler.Logger = bufLogger
@ -65,7 +65,7 @@ func TestHandler_NotFound(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
@ -100,7 +100,7 @@ func TestHandler_Schema(t *testing.T) {
t.Fatal(err)
}
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
w := httptest.NewRecorder()
@ -139,7 +139,7 @@ func TestHandler_Status(t *testing.T) {
t.Fatal(err)
}
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
h.API.Cluster.SetState(pilosa.ClusterStateNormal)
@ -158,7 +158,7 @@ func TestHandler_Status(t *testing.T) {
func TestHandler_Info(t *testing.T) {
s := test.NewServer()
defer s.Close()
h := test.NewHandler()
h := test.MustNewHandler()
w := httptest.NewRecorder()
h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/info", nil))
@ -173,7 +173,7 @@ func TestHandler_Info(t *testing.T) {
func TestHandler_ClusterResizeAbort(t *testing.T) {
t.Run("No resize job", func(t *testing.T) {
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Cluster.SetState(pilosa.ClusterStateResizing)
@ -202,7 +202,7 @@ func TestHandler_MaxSlices(t *testing.T) {
hldr.MustCreateFragmentIfNotExists("i1", "f1", pilosa.ViewStandard, 0).MustSetBits(40, (0*SliceWidth)+2)
hldr.MustCreateFragmentIfNotExists("i1", "f1", pilosa.ViewStandard, 0).MustSetBits(40, (0*SliceWidth)+8)
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
w := httptest.NewRecorder()
@ -243,7 +243,7 @@ func TestHandler_MaxSlices_Inverse(t *testing.T) {
t.Fatal(err)
}
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
w := httptest.NewRecorder()
@ -260,7 +260,7 @@ func TestHandler_Query_Args_URL(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -288,7 +288,7 @@ func TestHandler_Query_Args_Protobuf(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -328,7 +328,7 @@ func TestHandler_Query_Args_Err(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
@ -341,7 +341,7 @@ func TestHandler_Query_Args_Err(t *testing.T) {
}
func TestHandler_Query_Params_Err(t *testing.T) {
w := httptest.NewRecorder()
test.NewHandler().ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1&db=sample", strings.NewReader("Bitmap(id=100)")))
test.MustNewHandler().ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1&db=sample", strings.NewReader("Bitmap(id=100)")))
if w.Code != http.StatusBadRequest {
t.Fatalf("unexpected status code: %d", w.Code)
} else if body := w.Body.String(); body != `{"error":"db is not a valid argument"}`+"\n" {
@ -355,7 +355,7 @@ func TestHandler_Query_Uint64_JSON(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -376,7 +376,7 @@ func TestHandler_Query_Uint64_Protobuf(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -406,7 +406,7 @@ func TestHandler_Query_Bitmap_JSON(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -439,7 +439,7 @@ func TestHandler_Query_Row_ColumnAttrs_JSON(t *testing.T) {
t.Fatal(err)
}
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -462,7 +462,7 @@ func TestHandler_Query_Row_Protobuf(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -510,7 +510,7 @@ func TestHandler_Query_Row_ColumnAttrs_Protobuf(t *testing.T) {
t.Fatal(err)
}
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -571,7 +571,7 @@ func TestHandler_Query_Pairs_JSON(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -595,7 +595,7 @@ func TestHandler_Query_Pairs_Protobuf(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -628,7 +628,7 @@ func TestHandler_Query_Err_JSON(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -649,7 +649,7 @@ func TestHandler_Query_Err_Protobuf(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) {
@ -677,7 +677,7 @@ func TestHandler_Query_MethodNotAllowed(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
w := httptest.NewRecorder()
@ -692,7 +692,7 @@ func TestHandler_Query_ErrParse(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
w := httptest.NewRecorder()
@ -749,7 +749,7 @@ func TestHandler_DeleteFrame(t *testing.T) {
t.Fatal(err)
}
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
w := httptest.NewRecorder()
@ -1155,7 +1155,7 @@ func TestHandler_Version(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
@ -1178,7 +1178,7 @@ func TestHandler_Fragment_Nodes(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(3)
h.API.Cluster.ReplicaN = 2
@ -1214,7 +1214,7 @@ func TestHandler_Expvars(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Cluster = test.NewCluster(1)
h.API.Holder = hldr.Holder
w := httptest.NewRecorder()
@ -1237,7 +1237,7 @@ func TestHandler_RecalculateCaches(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
@ -1253,7 +1253,7 @@ func TestHandler_WebUI(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.NewHandler()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
h.FileSystem = &statik.FileSystem{}
@ -1276,3 +1276,41 @@ func TestHandler_WebUI(t *testing.T) {
t.Fatalf("WebUI is not being served correctly.")
}
}
func TestHandler_CORS(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
s := test.NewServer()
s.Handler.API.Holder = hldr.Holder
defer s.Close()
// No CORS config present, so should fail
handler := test.MustNewHandler()
req := test.MustNewHTTPRequest("OPTIONS", "/index/foo/query", nil)
req.Header.Add("Origin", "http://test/")
req.Header.Add("Access-Control-Request-Method", "POST")
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
result := w.Result()
// This handler does not support CORS, return Method Not Allowed (405)
if result.StatusCode != 405 {
t.Fatalf("CORS preflight status should be 405, but is %v", result.StatusCode)
}
// CORS config should allow preflight response
handler = test.MustNewHandler(pilosa.OptHandlerAllowedOrigins([]string{"http://test/"}))
w = httptest.NewRecorder()
handler.ServeHTTP(w, req)
result = w.Result()
if result.StatusCode != 200 {
t.Fatalf("CORS preflight status should be 200, but is %v", result.StatusCode)
}
if w.HeaderMap["Access-Control-Allow-Origin"][0] != "http://test/" {
t.Fatal("CORS header not present")
}
}

View file

@ -203,11 +203,15 @@ func OptServerURI(uri *URI) ServerOption {
// NewServer returns a new instance of Server.
func NewServer(opts ...ServerOption) (*Server, error) {
handler, err := NewHandler()
if err != nil {
return nil, errors.Wrap(err, "initializing handler")
}
s := &Server{
closing: make(chan struct{}),
Cluster: NewCluster(),
Holder: NewHolder(),
handler: NewHandler(),
handler: handler,
Broadcaster: NopBroadcaster,
BroadcastReceiver: NopBroadcastReceiver,
diagnostics: NewDiagnosticsCollector(DefaultDiagnosticServer),

View file

@ -57,6 +57,11 @@ type Config struct {
// Verbose toggles verbose logging which can be useful for debugging.
Verbose bool `toml:"verbose"`
// HTTP Handler options
Handler struct {
AllowedOrigins []string `toml:"allowed-origins"`
}
// TLS
TLS TLSConfig

View file

@ -165,7 +165,10 @@ func (m *Command) SetupServer() error {
}
m.logger.Printf("%s %s, build time %s\n", productName, pilosa.Version, pilosa.BuildTime)
handler := pilosa.NewHandler()
handler, err := pilosa.NewHandler(pilosa.OptHandlerAllowedOrigins(m.Config.Handler.AllowedOrigins))
if err != nil {
return errors.Wrap(err, "wrapping handler")
}
handler.Logger = m.logger
handler.FileSystem = &statik.FileSystem{}
handler.API = pilosa.NewAPI()

View file

@ -36,9 +36,13 @@ type Handler struct {
}
// NewHandler returns a new instance of Handler.
func NewHandler() *Handler {
func NewHandler(opts ...pilosa.HandlerOption) (*Handler, error) {
handler, err := pilosa.NewHandler(opts...)
if err != nil {
return nil, err
}
h := &Handler{
Handler: pilosa.NewHandler(),
Handler: handler,
}
h.API = pilosa.NewAPI()
h.Handler.API = h.API
@ -47,6 +51,15 @@ func NewHandler() *Handler {
// Handler test messages can no-op.
h.API.Broadcaster = pilosa.NopBroadcaster
return h, nil
}
// MustNewHandler returns a new instance of Handler.
func MustNewHandler(opts ...pilosa.HandlerOption) *Handler {
h, err := NewHandler(opts...)
if err != nil {
panic(err)
}
return h
}
@ -70,8 +83,12 @@ type Server struct {
// NewServer returns a test server running on a random port.
func NewServer() *Server {
handler, err := NewHandler()
if err != nil {
panic(err)
}
s := &Server{
Handler: NewHandler(),
Handler: handler,
}
s.Server = httptest.NewServer(s.Handler.Handler)