Commit 3f3c2d93 authored by Andrei Mihu's avatar Andrei Mihu
Browse files

Add support for custom response headers set in server configuration.

parent df7cba08
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr
## [Unreleased]
### Added
- Allow the socket acceptor to read session tokens from request headers.
- Add support for custom response headers set in server configuration.

### Changed
- Stricter validation of limit in runtime storage list operations.
+18 −1
Original line number Diff line number Diff line
@@ -253,13 +253,30 @@ func StartApiServer(logger *zap.Logger, startupLogger *zap.Logger, db *sql.DB, p
	CORSMethods := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE"})
	handlerWithCORS := handlers.CORS(CORSHeaders, CORSOrigins, CORSMethods)(grpcGatewayRouter)

	// Enable configured response headers, if any are set. Do not override values that may have been set by server processing.
	optionalResponseHeaderHandler := handlerWithCORS
	if headers := config.GetSocket().Headers; len(headers) > 0 {
		optionalResponseHeaderHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Preemptively set custom response headers. Further processing will override them if needed for proper functionality.
			wHeaders := w.Header()
			for key, value := range headers {
				if wHeaders.Get(key) == "" {
					wHeaders.Set(key, value)
				}
			}

			// Allow core server processing to handle the request.
			handlerWithCORS.ServeHTTP(w, r)
		})
	}

	// Set up and start GRPC Gateway server.
	s.grpcGatewayServer = &http.Server{
		ReadTimeout:    time.Millisecond * time.Duration(int64(config.GetSocket().ReadTimeoutMs)),
		WriteTimeout:   time.Millisecond * time.Duration(int64(config.GetSocket().WriteTimeoutMs)),
		IdleTimeout:    time.Millisecond * time.Duration(int64(config.GetSocket().IdleTimeoutMs)),
		MaxHeaderBytes: 5120,
		Handler:        handlerWithCORS,
		Handler:        optionalResponseHeaderHandler,
	}
	if config.GetSocket().TLSCert != nil {
		s.grpcGatewayServer.TLSConfig = &tls.Config{Certificates: config.GetSocket().TLSCert}
+13 −0
Original line number Diff line number Diff line
@@ -352,6 +352,17 @@ func CheckConfig(logger *zap.Logger, config Config) map[string]string {
		configWarnings["runtime.read_only_globals"] = "Deprecated configuration parameter"
	}

	if l := len(config.GetSocket().ResponseHeaders); l > 0 {
		config.GetSocket().Headers = make(map[string]string, l)
		for _, header := range config.GetSocket().ResponseHeaders {
			parts := strings.SplitN(header, "=", 2)
			if len(parts) != 2 {
				logger.Fatal("Response headers configuration invalid, format must be 'key=value'", zap.String("param", "socket.response_headers"))
			}
			config.GetSocket().Headers[parts[0]] = parts[1]
		}
	}

	// Log warnings for SSL usage.
	if config.GetSocket().SSLCertificate != "" && config.GetSocket().SSLPrivateKey == "" {
		logger.Fatal("SSL configuration invalid, specify both socket.ssl_certificate and socket.ssl_private_key", zap.String("param", "socket.ssl_certificate"))
@@ -660,6 +671,8 @@ type SocketConfig struct {
	OutgoingQueueSize    int               `yaml:"outgoing_queue_size" json:"outgoing_queue_size" usage:"The maximum number of messages waiting to be sent to the client. If this is exceeded the client is considered too slow and will disconnect. Used when processing real-time connections."`
	SSLCertificate       string            `yaml:"ssl_certificate" json:"ssl_certificate" usage:"Path to certificate file if you want the server to use SSL directly. Must also supply ssl_private_key. NOT recommended for production use."`
	SSLPrivateKey        string            `yaml:"ssl_private_key" json:"ssl_private_key" usage:"Path to private key file if you want the server to use SSL directly. Must also supply ssl_certificate. NOT recommended for production use."`
	ResponseHeaders      []string          `yaml:"response_headers" json:"response_headers" usage:"Additional headers to send to clients with every response. Values here are only used if the response would not otherwise contain a value for the specified headers."`
	Headers              map[string]string `yaml:"-" json:"-"` // Created by parsing ResponseHeaders above, not set from input args directly.
	CertPEMBlock         []byte            `yaml:"-" json:"-"` // Created by fully reading the file contents of SSLCertificate, not set from input args directly.
	KeyPEMBlock          []byte            `yaml:"-" json:"-"` // Created by fully reading the file contents of SSLPrivateKey, not set from input args directly.
	TLSCert              []tls.Certificate `yaml:"-" json:"-"` // Created by processing CertPEMBlock and KeyPEMBlock, not set from input args directly.