Skip to content

Commit 7dc9082

Browse files
authored
Allow Access-Control-Max-Age customization (#1069)
The change gives ability to control the cache age of the preflight requests to Cross-Origin resources. The default value remains 10 minutes.
1 parent 018bcd3 commit 7dc9082

File tree

4 files changed

+54
-4
lines changed

4 files changed

+54
-4
lines changed

go/grpcweb/DOC.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,17 @@ endpoints (e.g. for proxying).
118118
The default behaviour is `true`, i.e. only allows CORS requests for registered
119119
endpoints.
120120

121+
#### func WithCorsMaxAge
122+
123+
```go
124+
func WithCorsMaxAge(maxAge time.Duration) Option
125+
```
126+
WithCorsMaxAge customize the `Access-Control-Max-Age: <delta-seconds>` header
127+
which controls the cache age for a CORS preflight request that checks to see if
128+
the CORS protocol is understood.
129+
130+
The default age is 10 minutes.
131+
121132
#### func WithEndpointsFunc
122133

123134
```go

go/grpcweb/options.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ var (
1414
corsForRegisteredEndpointsOnly: true,
1515
originFunc: func(origin string) bool { return false },
1616
allowNonRootResources: false,
17+
corsMaxAge: 10 * time.Minute,
1718
}
1819
)
1920

2021
type options struct {
2122
allowedRequestHeaders []string
2223
corsForRegisteredEndpointsOnly bool
24+
corsMaxAge time.Duration
2325
originFunc func(origin string) bool
2426
enableWebsockets bool
2527
websocketPingInterval time.Duration
@@ -68,6 +70,16 @@ func WithCorsForRegisteredEndpointsOnly(onlyRegistered bool) Option {
6870
}
6971
}
7072

73+
// WithCorsMaxAge customize the `Access-Control-Max-Age: <delta-seconds>` header which controls the cache age for a CORS preflight
74+
// request that checks to see if the CORS protocol is understood.
75+
//
76+
// The default age is 10 minutes.
77+
func WithCorsMaxAge(maxAge time.Duration) Option {
78+
return func(o *options) {
79+
o.corsMaxAge = maxAge
80+
}
81+
}
82+
7183
// WithEndpointsFunc allows for providing a custom function that provides all supported endpoints for use when the
7284
// when `WithCorsForRegisteredEndpoints` option` is not set to false (i.e. the default state).
7385
//

go/grpcweb/wrapper.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ func wrapGrpc(options []Option, handler http.Handler, endpointsFunc func() []str
7070
corsWrapper := cors.New(cors.Options{
7171
AllowOriginFunc: opts.originFunc,
7272
AllowedHeaders: allowedHeaders,
73-
ExposedHeaders: nil, // make sure that this is *nil*, otherwise the WebResponse overwrite will not work.
74-
AllowCredentials: true, // always allow credentials, otherwise :authorization headers won't work
75-
MaxAge: int(10 * time.Minute / time.Second), // make sure pre-flights don't happen too often (every 5s for Chromium :( )
73+
ExposedHeaders: nil, // make sure that this is *nil*, otherwise the WebResponse overwrite will not work.
74+
AllowCredentials: true, // always allow credentials, otherwise :authorization headers won't work
75+
MaxAge: int(opts.corsMaxAge.Seconds()),
7676
})
7777
websocketOriginFunc := opts.websocketOriginFunc
7878
if websocketOriginFunc == nil {
@@ -120,7 +120,7 @@ func (w *WrappedGrpcServer) ServeHTTP(resp http.ResponseWriter, req *http.Reques
120120
return
121121
}
122122
}
123-
resp.WriteHeader(403)
123+
resp.WriteHeader(http.StatusForbidden)
124124
_, _ = resp.Write(make([]byte, 0))
125125
return
126126
}

go/grpcweb/wrapper_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,33 @@ func (s *GrpcWebWrapperTestSuite) TestCORSPreflight_AllowedByOriginFunc() {
449449
assert.Equal(s.T(), 500, corsResp.StatusCode, "cors should return 500 as grpc server does not understand that endpoint")
450450
}
451451

452+
func (s *GrpcWebWrapperTestSuite) TestCORSPreflight_CorsMaxAge() {
453+
/**
454+
OPTIONS /improbable.grpcweb.test.TestService/Ping
455+
Access-Control-Request-Method: POST
456+
Access-Control-Request-Headers: origin, x-requested-with, accept
457+
Origin: http://foo.client.com
458+
*/
459+
headers := http.Header{}
460+
headers.Add("Access-Control-Request-Method", "POST")
461+
headers.Add("Access-Control-Request-Headers", "origin, x-something-custom, x-grpc-web, accept")
462+
headers.Add("Origin", "https://foo.client.com")
463+
464+
// Create a new server which customizes a cache time of the preflight request to a Cross-Origin Resource.
465+
s.wrappedServer = grpcweb.WrapServer(s.grpcServer,
466+
grpcweb.WithOriginFunc(func(string) bool {
467+
return true
468+
}),
469+
grpcweb.WithCorsMaxAge(time.Hour),
470+
)
471+
472+
corsResp, err := s.makeRequest("OPTIONS", "/improbable.grpcweb.test.TestService/PingList", headers, nil, false)
473+
assert.NoError(s.T(), err, "cors preflight should not return errors")
474+
475+
preflight := corsResp.Header
476+
assert.Equal(s.T(), "3600", preflight.Get("Access-Control-Max-Age"), "allowed max age must be in the response headers")
477+
}
478+
452479
func (s *GrpcWebWrapperTestSuite) TestCORSPreflight_EndpointsOnlyTrueWithHandlerFunc() {
453480
/**
454481
OPTIONS /improbable.grpcweb.test.TestService/Ping

0 commit comments

Comments
 (0)