Add CORS headers to HTTP handlers
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
698b97e904
commit
cf90100c5f
|
@ -41,6 +41,7 @@ type Config struct {
|
||||||
S3Bucket string
|
S3Bucket string
|
||||||
AssetsHTTPRoot string
|
AssetsHTTPRoot string
|
||||||
FFmpegWorkerPoolSize int
|
FFmpegWorkerPoolSize int
|
||||||
|
CORSAllowedOrigins []string
|
||||||
}
|
}
|
||||||
|
|
||||||
const Prefix = "CLIPPER_"
|
const Prefix = "CLIPPER_"
|
||||||
|
@ -141,6 +142,12 @@ func NewFromEnv() (Config, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var corsAllowedOrigins []string
|
||||||
|
corsAllowedOriginsName := envPrefix("CORS_ALLOWED_ORIGINS")
|
||||||
|
if s := os.Getenv(corsAllowedOriginsName); s != "" {
|
||||||
|
corsAllowedOrigins = strings.Split(s, ",")
|
||||||
|
}
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
Environment: env,
|
Environment: env,
|
||||||
BindAddr: bindAddr,
|
BindAddr: bindAddr,
|
||||||
|
@ -156,5 +163,6 @@ func NewFromEnv() (Config, error) {
|
||||||
FileStoreHTTPRoot: fileStoreHTTPRoot,
|
FileStoreHTTPRoot: fileStoreHTTPRoot,
|
||||||
FileStoreHTTPBaseURL: fileStoreHTTPBaseURL,
|
FileStoreHTTPBaseURL: fileStoreHTTPBaseURL,
|
||||||
FFmpegWorkerPoolSize: ffmpegWorkerPoolSize,
|
FFmpegWorkerPoolSize: ffmpegWorkerPoolSize,
|
||||||
|
CORSAllowedOrigins: corsAllowedOrigins,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,4 +271,24 @@ func TestNewFromEnv(t *testing.T) {
|
||||||
assert.Equal(t, "eu-west-1", c.AWSRegion)
|
assert.Equal(t, "eu-west-1", c.AWSRegion)
|
||||||
assert.Equal(t, "bucket", c.S3Bucket)
|
assert.Equal(t, "bucket", c.S3Bucket)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("CORS_ALLOWED_ORIGINS", func(t *testing.T) {
|
||||||
|
defer clearenv()
|
||||||
|
setupenv()
|
||||||
|
|
||||||
|
os.Setenv("CLIPPER_CORS_ALLOWED_ORIGINS", "")
|
||||||
|
c, err := config.NewFromEnv()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Nil(t, c.CORSAllowedOrigins)
|
||||||
|
|
||||||
|
os.Setenv("CLIPPER_CORS_ALLOWED_ORIGINS", "*")
|
||||||
|
c, err = config.NewFromEnv()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"*"}, c.CORSAllowedOrigins)
|
||||||
|
|
||||||
|
os.Setenv("CLIPPER_CORS_ALLOWED_ORIGINS", "https://www1.example.com,https://www2.example.com")
|
||||||
|
c, err = config.NewFromEnv()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"https://www1.example.com", "https://www2.example.com"}, c.CORSAllowedOrigins)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,10 @@ require (
|
||||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
|
||||||
github.com/dop251/goja v0.0.0-20220124171016-cfb079cdc7b4 // indirect
|
github.com/dop251/goja v0.0.0-20220124171016-cfb079cdc7b4 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/gorilla/handlers v1.5.1 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
|
|
@ -151,6 +151,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
|
||||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||||
|
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
@ -232,6 +234,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||||
|
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"git.netflux.io/rob/clipper/filestore"
|
"git.netflux.io/rob/clipper/filestore"
|
||||||
"git.netflux.io/rob/clipper/media"
|
"git.netflux.io/rob/clipper/media"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/improbable-eng/grpc-web/go/grpcweb"
|
"github.com/improbable-eng/grpc-web/go/grpcweb"
|
||||||
|
@ -50,7 +51,7 @@ func newHTTPHandler(grpcHandler *grpcweb.WrappedGrpcServer, mediaSetService Medi
|
||||||
h.
|
h.
|
||||||
Methods("POST").
|
Methods("POST").
|
||||||
Path("/api/media_sets/{id}/clip").
|
Path("/api/media_sets/{id}/clip").
|
||||||
HandlerFunc(h.handleClip)
|
Handler(http.HandlerFunc(h.handleClip))
|
||||||
|
|
||||||
if c.FileStore == config.FileSystemStore {
|
if c.FileStore == config.FileSystemStore {
|
||||||
h.
|
h.
|
||||||
|
@ -63,6 +64,8 @@ func newHTTPHandler(grpcHandler *grpcweb.WrappedGrpcServer, mediaSetService Medi
|
||||||
Methods("GET").
|
Methods("GET").
|
||||||
Handler(assetsHandler)
|
Handler(assetsHandler)
|
||||||
|
|
||||||
|
h.Use(handlers.CORS(handlers.AllowedOrigins(c.CORSAllowedOrigins)))
|
||||||
|
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,13 @@ import (
|
||||||
func TestHandler(t *testing.T) {
|
func TestHandler(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
path, body, method, contentType string
|
path, body, method, contentType, origin string
|
||||||
config config.Config
|
config config.Config
|
||||||
wantStartFrame, wantEndFrame int64
|
wantStartFrame, wantEndFrame int64
|
||||||
wantAudioFormat media.AudioFormat
|
wantAudioFormat media.AudioFormat
|
||||||
wantStatus int
|
wantStatus int
|
||||||
wantContentType, wantContentDisp, wantBody string
|
wantHeaders map[string]string
|
||||||
|
wantBody string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "assets disabled, file system store disabled, GET /",
|
name: "assets disabled, file system store disabled, GET /",
|
||||||
|
@ -111,6 +112,32 @@ func TestHandler(t *testing.T) {
|
||||||
config: config.Config{FileStore: config.FileSystemStore, FileStoreHTTPBaseURL: mustParseURL(t, "/"), FileStoreHTTPRoot: "testdata/http/filestore", AssetsHTTPRoot: "testdata/http/assets"},
|
config: config.Config{FileStore: config.FileSystemStore, FileStoreHTTPBaseURL: mustParseURL(t, "/"), FileStoreHTTPRoot: "testdata/http/filestore", AssetsHTTPRoot: "testdata/http/assets"},
|
||||||
wantStatus: http.StatusNotFound,
|
wantStatus: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "assets enabled, configured with custom Allowed-Origins header, origin does not match",
|
||||||
|
path: "/css/style.css",
|
||||||
|
method: http.MethodGet,
|
||||||
|
origin: "https://localhost:3000",
|
||||||
|
config: config.Config{
|
||||||
|
FileStore: config.S3Store,
|
||||||
|
AssetsHTTPRoot: "testdata/http/assets",
|
||||||
|
CORSAllowedOrigins: []string{"https://www.example.com"},
|
||||||
|
},
|
||||||
|
wantHeaders: map[string]string{"access-control-allow-origin": ""},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "assets enabled, configured with custom Allowed-Origins header, origin does match",
|
||||||
|
path: "/css/style.css",
|
||||||
|
method: http.MethodGet,
|
||||||
|
origin: "https://www.example.com",
|
||||||
|
config: config.Config{
|
||||||
|
FileStore: config.S3Store,
|
||||||
|
AssetsHTTPRoot: "testdata/http/assets",
|
||||||
|
CORSAllowedOrigins: []string{"https://www.example.com"},
|
||||||
|
},
|
||||||
|
wantHeaders: map[string]string{"access-control-allow-origin": "https://www.example.com"},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "POST /api/media_sets/:id/clip, NOK, no body",
|
name: "POST /api/media_sets/:id/clip, NOK, no body",
|
||||||
path: "/api/media_sets/05951a4d-584e-4056-9ae7-08b9e4cd355d/clip",
|
path: "/api/media_sets/05951a4d-584e-4056-9ae7-08b9e4cd355d/clip",
|
||||||
|
@ -147,8 +174,10 @@ func TestHandler(t *testing.T) {
|
||||||
wantStartFrame: 0,
|
wantStartFrame: 0,
|
||||||
wantEndFrame: 1024,
|
wantEndFrame: 1024,
|
||||||
wantAudioFormat: media.AudioFormatMP3,
|
wantAudioFormat: media.AudioFormatMP3,
|
||||||
wantContentType: "audio/mp3",
|
wantHeaders: map[string]string{
|
||||||
wantContentDisp: "attachment; filename=clip.mp3",
|
"content-type": "audio/mp3",
|
||||||
|
"content-disposition": "attachment; filename=clip.mp3",
|
||||||
|
},
|
||||||
wantStatus: http.StatusOK,
|
wantStatus: http.StatusOK,
|
||||||
wantBody: "an audio file",
|
wantBody: "an audio file",
|
||||||
},
|
},
|
||||||
|
@ -162,8 +191,10 @@ func TestHandler(t *testing.T) {
|
||||||
wantStartFrame: 4096,
|
wantStartFrame: 4096,
|
||||||
wantEndFrame: 8192,
|
wantEndFrame: 8192,
|
||||||
wantAudioFormat: media.AudioFormatWAV,
|
wantAudioFormat: media.AudioFormatWAV,
|
||||||
wantContentType: "audio/wav",
|
wantHeaders: map[string]string{
|
||||||
wantContentDisp: "attachment; filename=clip.wav",
|
"content-type": "audio/wav",
|
||||||
|
"content-disposition": "attachment; filename=clip.wav",
|
||||||
|
},
|
||||||
wantStatus: http.StatusOK,
|
wantStatus: http.StatusOK,
|
||||||
wantBody: "an audio file",
|
wantBody: "an audio file",
|
||||||
},
|
},
|
||||||
|
@ -192,6 +223,9 @@ func TestHandler(t *testing.T) {
|
||||||
body = strings.NewReader(tc.body)
|
body = strings.NewReader(tc.body)
|
||||||
}
|
}
|
||||||
req := httptest.NewRequest(tc.method, tc.path, body)
|
req := httptest.NewRequest(tc.method, tc.path, body)
|
||||||
|
if tc.origin != "" {
|
||||||
|
req.Header.Add("origin", tc.origin)
|
||||||
|
}
|
||||||
if tc.contentType != "" {
|
if tc.contentType != "" {
|
||||||
req.Header.Add("content-type", tc.contentType)
|
req.Header.Add("content-type", tc.contentType)
|
||||||
}
|
}
|
||||||
|
@ -206,11 +240,8 @@ func TestHandler(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, tc.wantBody, string(body))
|
assert.Equal(t, tc.wantBody, string(body))
|
||||||
}
|
}
|
||||||
if tc.wantContentType != "" {
|
for k, v := range tc.wantHeaders {
|
||||||
assert.Equal(t, tc.wantContentType, resp.Header.Get("content-type"))
|
assert.Equal(t, v, resp.Header.Get(k))
|
||||||
}
|
|
||||||
if tc.wantContentDisp != "" {
|
|
||||||
assert.Equal(t, tc.wantContentDisp, resp.Header.Get("content-disposition"))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,7 @@ func Start(options Options) error {
|
||||||
mediaSetController := &mediaSetServiceController{mediaSetService: mediaSetService, logger: options.Logger.Sugar().Named("controller")}
|
mediaSetController := &mediaSetServiceController{mediaSetService: mediaSetService, logger: options.Logger.Sugar().Named("controller")}
|
||||||
pbmediaset.RegisterMediaSetServiceServer(grpcServer, mediaSetController)
|
pbmediaset.RegisterMediaSetServiceServer(grpcServer, mediaSetController)
|
||||||
|
|
||||||
|
// TODO: implement CORS headers
|
||||||
grpcHandler := grpcweb.WrapServer(grpcServer, grpcweb.WithOriginFunc(func(string) bool { return true }))
|
grpcHandler := grpcweb.WrapServer(grpcServer, grpcweb.WithOriginFunc(func(string) bool { return true }))
|
||||||
httpHandler := newHTTPHandler(grpcHandler, mediaSetService, conf, options.Logger.Sugar().Named("httpHandler"))
|
httpHandler := newHTTPHandler(grpcHandler, mediaSetService, conf, options.Logger.Sugar().Named("httpHandler"))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue