Add CORS headers to HTTP handlers
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Rob Watson 2022-01-26 19:27:57 +01:00
parent 698b97e904
commit cf90100c5f
7 changed files with 90 additions and 21 deletions

View File

@ -41,6 +41,7 @@ type Config struct {
S3Bucket string
AssetsHTTPRoot string
FFmpegWorkerPoolSize int
CORSAllowedOrigins []string
}
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{
Environment: env,
BindAddr: bindAddr,
@ -156,5 +163,6 @@ func NewFromEnv() (Config, error) {
FileStoreHTTPRoot: fileStoreHTTPRoot,
FileStoreHTTPBaseURL: fileStoreHTTPBaseURL,
FFmpegWorkerPoolSize: ffmpegWorkerPoolSize,
CORSAllowedOrigins: corsAllowedOrigins,
}, nil
}

View File

@ -271,4 +271,24 @@ func TestNewFromEnv(t *testing.T) {
assert.Equal(t, "eu-west-1", c.AWSRegion)
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)
})
}

View File

@ -40,8 +40,10 @@ require (
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // 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/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/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect

View File

@ -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/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/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/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=
@ -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/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/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.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=

View File

@ -10,6 +10,7 @@ import (
"git.netflux.io/rob/clipper/filestore"
"git.netflux.io/rob/clipper/media"
"github.com/google/uuid"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/improbable-eng/grpc-web/go/grpcweb"
@ -50,7 +51,7 @@ func newHTTPHandler(grpcHandler *grpcweb.WrappedGrpcServer, mediaSetService Medi
h.
Methods("POST").
Path("/api/media_sets/{id}/clip").
HandlerFunc(h.handleClip)
Handler(http.HandlerFunc(h.handleClip))
if c.FileStore == config.FileSystemStore {
h.
@ -63,6 +64,8 @@ func newHTTPHandler(grpcHandler *grpcweb.WrappedGrpcServer, mediaSetService Medi
Methods("GET").
Handler(assetsHandler)
h.Use(handlers.CORS(handlers.AllowedOrigins(c.CORSAllowedOrigins)))
return h
}

View File

@ -20,13 +20,14 @@ import (
func TestHandler(t *testing.T) {
testCases := []struct {
name string
path, body, method, contentType string
config config.Config
wantStartFrame, wantEndFrame int64
wantAudioFormat media.AudioFormat
wantStatus int
wantContentType, wantContentDisp, wantBody string
name string
path, body, method, contentType, origin string
config config.Config
wantStartFrame, wantEndFrame int64
wantAudioFormat media.AudioFormat
wantStatus int
wantHeaders map[string]string
wantBody string
}{
{
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"},
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",
path: "/api/media_sets/05951a4d-584e-4056-9ae7-08b9e4cd355d/clip",
@ -147,10 +174,12 @@ func TestHandler(t *testing.T) {
wantStartFrame: 0,
wantEndFrame: 1024,
wantAudioFormat: media.AudioFormatMP3,
wantContentType: "audio/mp3",
wantContentDisp: "attachment; filename=clip.mp3",
wantStatus: http.StatusOK,
wantBody: "an audio file",
wantHeaders: map[string]string{
"content-type": "audio/mp3",
"content-disposition": "attachment; filename=clip.mp3",
},
wantStatus: http.StatusOK,
wantBody: "an audio file",
},
{
name: "POST /api/media_sets/:id/clip, WAV, OK",
@ -162,10 +191,12 @@ func TestHandler(t *testing.T) {
wantStartFrame: 4096,
wantEndFrame: 8192,
wantAudioFormat: media.AudioFormatWAV,
wantContentType: "audio/wav",
wantContentDisp: "attachment; filename=clip.wav",
wantStatus: http.StatusOK,
wantBody: "an audio file",
wantHeaders: map[string]string{
"content-type": "audio/wav",
"content-disposition": "attachment; filename=clip.wav",
},
wantStatus: http.StatusOK,
wantBody: "an audio file",
},
}
@ -192,6 +223,9 @@ func TestHandler(t *testing.T) {
body = strings.NewReader(tc.body)
}
req := httptest.NewRequest(tc.method, tc.path, body)
if tc.origin != "" {
req.Header.Add("origin", tc.origin)
}
if tc.contentType != "" {
req.Header.Add("content-type", tc.contentType)
}
@ -206,11 +240,8 @@ func TestHandler(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, tc.wantBody, string(body))
}
if tc.wantContentType != "" {
assert.Equal(t, tc.wantContentType, resp.Header.Get("content-type"))
}
if tc.wantContentDisp != "" {
assert.Equal(t, tc.wantContentDisp, resp.Header.Get("content-disposition"))
for k, v := range tc.wantHeaders {
assert.Equal(t, v, resp.Header.Get(k))
}
})
}

View File

@ -99,6 +99,7 @@ func Start(options Options) error {
mediaSetController := &mediaSetServiceController{mediaSetService: mediaSetService, logger: options.Logger.Sugar().Named("controller")}
pbmediaset.RegisterMediaSetServiceServer(grpcServer, mediaSetController)
// TODO: implement CORS headers
grpcHandler := grpcweb.WrapServer(grpcServer, grpcweb.WithOriginFunc(func(string) bool { return true }))
httpHandler := newHTTPHandler(grpcHandler, mediaSetService, conf, options.Logger.Sugar().Named("httpHandler"))