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
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -21,12 +21,13 @@ import (
|
|||
func TestHandler(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path, body, method, contentType string
|
||||
path, body, method, contentType, origin string
|
||||
config config.Config
|
||||
wantStartFrame, wantEndFrame int64
|
||||
wantAudioFormat media.AudioFormat
|
||||
wantStatus int
|
||||
wantContentType, wantContentDisp, wantBody string
|
||||
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,8 +174,10 @@ func TestHandler(t *testing.T) {
|
|||
wantStartFrame: 0,
|
||||
wantEndFrame: 1024,
|
||||
wantAudioFormat: media.AudioFormatMP3,
|
||||
wantContentType: "audio/mp3",
|
||||
wantContentDisp: "attachment; filename=clip.mp3",
|
||||
wantHeaders: map[string]string{
|
||||
"content-type": "audio/mp3",
|
||||
"content-disposition": "attachment; filename=clip.mp3",
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantBody: "an audio file",
|
||||
},
|
||||
|
@ -162,8 +191,10 @@ func TestHandler(t *testing.T) {
|
|||
wantStartFrame: 4096,
|
||||
wantEndFrame: 8192,
|
||||
wantAudioFormat: media.AudioFormatWAV,
|
||||
wantContentType: "audio/wav",
|
||||
wantContentDisp: "attachment; filename=clip.wav",
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
|
||||
|
|
Loading…
Reference in New Issue