1//Copyright 2017 Improbable. All Rights Reserved. 2// See LICENSE for licensing terms. 3 4package grpcweb 5 6import ( 7 "net/http" 8 "time" 9) 10 11var ( 12 defaultOptions = &options{ 13 allowedRequestHeaders: []string{"*"}, 14 corsForRegisteredEndpointsOnly: true, 15 originFunc: func(origin string) bool { return false }, 16 allowNonRootResources: false, 17 } 18) 19 20type options struct { 21 allowedRequestHeaders []string 22 corsForRegisteredEndpointsOnly bool 23 originFunc func(origin string) bool 24 enableWebsockets bool 25 websocketPingInterval time.Duration 26 websocketOriginFunc func(req *http.Request) bool 27 allowNonRootResources bool 28 endpointsFunc *func() []string 29} 30 31func evaluateOptions(opts []Option) *options { 32 optCopy := &options{} 33 *optCopy = *defaultOptions 34 for _, o := range opts { 35 o(optCopy) 36 } 37 return optCopy 38} 39 40type Option func(*options) 41 42// WithOriginFunc allows for customizing what CORS Origin requests are allowed. 43// 44// This is controlling the CORS pre-flight `Access-Control-Allow-Origin`. This mechanism allows you to limit the 45// availability of the APIs based on the domain name of the calling website (Origin). You can provide a function that 46// filters the allowed Origin values. 47// 48// The default behaviour is to deny all requests from remote origins. 49// 50// The relevant CORS pre-flight docs: 51// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin 52func WithOriginFunc(originFunc func(origin string) bool) Option { 53 return func(o *options) { 54 o.originFunc = originFunc 55 } 56} 57 58// WithCorsForRegisteredEndpointsOnly allows for customizing whether OPTIONS requests with the `X-GRPC-WEB` header will 59// only be accepted if they match a registered gRPC endpoint. 60// 61// This should be set to false to allow handling gRPC requests for unknown endpoints (e.g. for proxying). 62// 63// The default behaviour is `true`, i.e. only allows CORS requests for registered endpoints. 64func WithCorsForRegisteredEndpointsOnly(onlyRegistered bool) Option { 65 return func(o *options) { 66 o.corsForRegisteredEndpointsOnly = onlyRegistered 67 } 68} 69 70// WithEndpointsFunc allows for providing a custom function that provides all supported endpoints for use when the 71// when `WithCorsForRegisteredEndpoints` option` is not set to false (i.e. the default state). 72// 73// When wrapping a http.Handler with `WrapHttpHandler`, failing to specify the `WithEndpointsFunc` option will cause 74// all CORS requests to result in a 403 error for websocket requests (if websockets are enabled) or be passed to the 75// handler http.Handler or grpc.Server backend (i.e. as if it wasn't wrapped). 76// 77// When wrapping grpc.Server with `WrapGrpcServer`, registered endpoints will be automatically detected, however if this 78// `WithEndpointsFunc` option is specified, the server will not be queried for its endpoints and this function will 79// be called instead. 80func WithEndpointsFunc(endpointsFunc func() []string) Option { 81 return func(o *options) { 82 o.endpointsFunc = &endpointsFunc 83 } 84} 85 86// WithAllowedRequestHeaders allows for customizing what gRPC request headers a browser can add. 87// 88// This is controlling the CORS pre-flight `Access-Control-Allow-Headers` method and applies to *all* gRPC handlers. 89// However, a special `*` value can be passed in that allows 90// the browser client to provide *any* header, by explicitly whitelisting all `Access-Control-Request-Headers` of the 91// pre-flight request. 92// 93// The default behaviour is `[]string{'*'}`, allowing all browser client headers. This option overrides that default, 94// while maintaining a whitelist for gRPC-internal headers. 95// 96// Unfortunately, since the CORS pre-flight happens independently from gRPC handler execution, it is impossible to 97// automatically discover it from the gRPC handler itself. 98// 99// The relevant CORS pre-flight docs: 100// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers 101func WithAllowedRequestHeaders(headers []string) Option { 102 return func(o *options) { 103 o.allowedRequestHeaders = headers 104 } 105} 106 107// WithWebsockets allows for handling grpc-web requests of websockets - enabling bidirectional requests. 108// 109// The default behaviour is false, i.e. to disallow websockets 110func WithWebsockets(enableWebsockets bool) Option { 111 return func(o *options) { 112 o.enableWebsockets = enableWebsockets 113 } 114} 115 116// WithWebsocketPingInterval enables websocket keepalive pinging with the configured timeout. 117// 118// The default behaviour is to disable websocket pinging. 119func WithWebsocketPingInterval(websocketPingInterval time.Duration) Option { 120 return func(o *options) { 121 o.websocketPingInterval = websocketPingInterval 122 } 123} 124 125// WithWebsocketOriginFunc allows for customizing the acceptance of Websocket requests - usually to check that the origin 126// is valid. 127// 128// The default behaviour is to check that the origin of the request matches the host of the request and deny all requests from remote origins. 129func WithWebsocketOriginFunc(websocketOriginFunc func(req *http.Request) bool) Option { 130 return func(o *options) { 131 o.websocketOriginFunc = websocketOriginFunc 132 } 133} 134 135// WithAllowNonRootResource enables the gRPC wrapper to serve requests that have a path prefix 136// added to the URL, before the service name and method placeholders. 137// 138// This should be set to false when exposing the endpoint as the root resource, to avoid 139// the performance cost of path processing for every request. 140// 141// The default behaviour is `false`, i.e. always serves requests assuming there is no prefix to the gRPC endpoint. 142func WithAllowNonRootResource(allowNonRootResources bool) Option { 143 return func(o *options) { 144 o.allowNonRootResources = allowNonRootResources 145 } 146} 147