forked from TrueCloudLab/frostfs-http-gw
Compare commits
3 commits
a9aeea1c70
...
46c63edd67
Author | SHA1 | Date | |
---|---|---|---|
46c63edd67 | |||
901b8ff95b | |||
8dc5272965 |
7 changed files with 272 additions and 117 deletions
|
@ -7,6 +7,7 @@ This document outlines major changes between releases.
|
||||||
### Added
|
### Added
|
||||||
- Support percent-encoding for GET queries (#134)
|
- Support percent-encoding for GET queries (#134)
|
||||||
- Add `trace_id` to logs (#148)
|
- Add `trace_id` to logs (#148)
|
||||||
|
- Add `cors` config params (#158)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Update go version to 1.22 (#132)
|
- Update go version to 1.22 (#132)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -89,15 +88,30 @@ type (
|
||||||
appSettings struct {
|
appSettings struct {
|
||||||
reconnectInterval time.Duration
|
reconnectInterval time.Duration
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
defaultTimestamp bool
|
defaultTimestamp bool
|
||||||
zipCompression bool
|
zipCompression bool
|
||||||
clientCut bool
|
clientCut bool
|
||||||
returnIndexPage bool
|
returnIndexPage bool
|
||||||
indexPageTemplate string
|
indexPageTemplate string
|
||||||
bufferMaxSizeForPut uint64
|
bufferMaxSizeForPut uint64
|
||||||
namespaceHeader string
|
namespaceHeader string
|
||||||
defaultNamespaces []string
|
defaultNamespaces []string
|
||||||
|
corsAllowOrigin string
|
||||||
|
corsAllowMethods []string
|
||||||
|
corsAllowHeaders []string
|
||||||
|
corsExposeHeaders []string
|
||||||
|
corsAllowCredentials bool
|
||||||
|
corsMaxAge int
|
||||||
|
}
|
||||||
|
|
||||||
|
CORS struct {
|
||||||
|
AllowOrigin string
|
||||||
|
AllowMethods []string
|
||||||
|
AllowHeaders []string
|
||||||
|
ExposeHeaders []string
|
||||||
|
AllowCredentials bool
|
||||||
|
MaxAge int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -159,23 +173,59 @@ func newApp(ctx context.Context, opt ...Option) App {
|
||||||
a.initResolver()
|
a.initResolver()
|
||||||
a.initMetrics()
|
a.initMetrics()
|
||||||
a.initTracing(ctx)
|
a.initTracing(ctx)
|
||||||
a.loadIndexPageTemplate()
|
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *app) initAppSettings() {
|
||||||
|
a.settings = &appSettings{
|
||||||
|
reconnectInterval: fetchReconnectInterval(a.cfg),
|
||||||
|
}
|
||||||
|
a.settings.update(a.cfg, a.log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *appSettings) update(v *viper.Viper, l *zap.Logger) {
|
||||||
|
defaultTimestamp := v.GetBool(cfgUploaderHeaderEnableDefaultTimestamp)
|
||||||
|
zipCompression := v.GetBool(cfgZipCompression)
|
||||||
|
returnIndexPage := v.GetBool(cfgIndexPageEnabled)
|
||||||
|
clientCut := v.GetBool(cfgClientCut)
|
||||||
|
bufferMaxSizeForPut := v.GetUint64(cfgBufferMaxSizeForPut)
|
||||||
|
namespaceHeader := v.GetString(cfgResolveNamespaceHeader)
|
||||||
|
defaultNamespaces := fetchDefaultNamespaces(v)
|
||||||
|
indexPage, indexEnabled := fetchIndexPageTemplate(v, l)
|
||||||
|
corsAllowOrigin := v.GetString(cfgCORSAllowOrigin)
|
||||||
|
corsAllowMethods := v.GetStringSlice(cfgCORSAllowMethods)
|
||||||
|
corsAllowHeaders := v.GetStringSlice(cfgCORSAllowHeaders)
|
||||||
|
corsExposeHeaders := v.GetStringSlice(cfgCORSExposeHeaders)
|
||||||
|
corsAllowCredentials := v.GetBool(cfgCORSAllowCredentials)
|
||||||
|
corsMaxAge := fetchCORSMaxAge(v)
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
s.defaultTimestamp = defaultTimestamp
|
||||||
|
s.zipCompression = zipCompression
|
||||||
|
s.returnIndexPage = returnIndexPage
|
||||||
|
s.clientCut = clientCut
|
||||||
|
s.bufferMaxSizeForPut = bufferMaxSizeForPut
|
||||||
|
s.namespaceHeader = namespaceHeader
|
||||||
|
s.defaultNamespaces = defaultNamespaces
|
||||||
|
s.returnIndexPage = indexEnabled
|
||||||
|
s.indexPageTemplate = indexPage
|
||||||
|
s.corsAllowOrigin = corsAllowOrigin
|
||||||
|
s.corsAllowMethods = corsAllowMethods
|
||||||
|
s.corsAllowHeaders = corsAllowHeaders
|
||||||
|
s.corsExposeHeaders = corsExposeHeaders
|
||||||
|
s.corsAllowCredentials = corsAllowCredentials
|
||||||
|
s.corsMaxAge = corsMaxAge
|
||||||
|
}
|
||||||
|
|
||||||
func (s *appSettings) DefaultTimestamp() bool {
|
func (s *appSettings) DefaultTimestamp() bool {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
return s.defaultTimestamp
|
return s.defaultTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) setDefaultTimestamp(val bool) {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.defaultTimestamp = val
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) ZipCompression() bool {
|
func (s *appSettings) ZipCompression() bool {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
|
@ -197,42 +247,27 @@ func (s *appSettings) IndexPageTemplate() string {
|
||||||
return s.indexPageTemplate
|
return s.indexPageTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) setZipCompression(val bool) {
|
func (s *appSettings) CORS() CORS {
|
||||||
s.mu.Lock()
|
s.mu.RLock()
|
||||||
s.zipCompression = val
|
defer s.mu.RUnlock()
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) setReturnIndexPage(val bool) {
|
allowMethods := make([]string, len(s.corsAllowMethods))
|
||||||
s.mu.Lock()
|
copy(allowMethods, s.corsAllowMethods)
|
||||||
s.returnIndexPage = val
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) setIndexTemplate(val string) {
|
allowHeaders := make([]string, len(s.corsAllowHeaders))
|
||||||
s.mu.Lock()
|
copy(allowHeaders, s.corsAllowHeaders)
|
||||||
s.indexPageTemplate = val
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) loadIndexPageTemplate() {
|
exposeHeaders := make([]string, len(s.corsExposeHeaders))
|
||||||
if !a.settings.IndexPageEnabled() {
|
copy(exposeHeaders, s.corsExposeHeaders)
|
||||||
return
|
|
||||||
|
return CORS{
|
||||||
|
AllowOrigin: s.corsAllowOrigin,
|
||||||
|
AllowMethods: allowMethods,
|
||||||
|
AllowHeaders: allowHeaders,
|
||||||
|
ExposeHeaders: exposeHeaders,
|
||||||
|
AllowCredentials: s.corsAllowCredentials,
|
||||||
|
MaxAge: s.corsMaxAge,
|
||||||
}
|
}
|
||||||
reader, err := os.Open(a.cfg.GetString(cfgIndexPageTemplatePath))
|
|
||||||
if err != nil {
|
|
||||||
a.settings.setIndexTemplate("")
|
|
||||||
a.log.Warn(logs.FailedToReadIndexPageTemplate, zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tmpl, err := io.ReadAll(reader)
|
|
||||||
if err != nil {
|
|
||||||
a.settings.setIndexTemplate("")
|
|
||||||
a.log.Warn(logs.FailedToReadIndexPageTemplate, zap.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
a.settings.setIndexTemplate(string(tmpl))
|
|
||||||
a.log.Info(logs.SetCustomIndexPageTemplate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) ClientCut() bool {
|
func (s *appSettings) ClientCut() bool {
|
||||||
|
@ -241,29 +276,27 @@ func (s *appSettings) ClientCut() bool {
|
||||||
return s.clientCut
|
return s.clientCut
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) setClientCut(val bool) {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.clientCut = val
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) BufferMaxSizeForPut() uint64 {
|
func (s *appSettings) BufferMaxSizeForPut() uint64 {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
return s.bufferMaxSizeForPut
|
return s.bufferMaxSizeForPut
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) setBufferMaxSizeForPut(val uint64) {
|
func (s *appSettings) NamespaceHeader() string {
|
||||||
s.mu.Lock()
|
s.mu.RLock()
|
||||||
s.bufferMaxSizeForPut = val
|
defer s.mu.RUnlock()
|
||||||
s.mu.Unlock()
|
return s.namespaceHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) initAppSettings() {
|
func (s *appSettings) FormContainerZone(ns string) (zone string, isDefault bool) {
|
||||||
a.settings = &appSettings{
|
s.mu.RLock()
|
||||||
reconnectInterval: fetchReconnectInterval(a.cfg),
|
namespaces := s.defaultNamespaces
|
||||||
|
s.mu.RUnlock()
|
||||||
|
if slices.Contains(namespaces, ns) {
|
||||||
|
return v2container.SysAttributeZoneDefault, true
|
||||||
}
|
}
|
||||||
a.updateSettings()
|
|
||||||
|
return ns + ".ns", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) initResolver() {
|
func (a *app) initResolver() {
|
||||||
|
@ -539,26 +572,15 @@ func (a *app) configReload(ctx context.Context) {
|
||||||
a.stopServices()
|
a.stopServices()
|
||||||
a.startServices()
|
a.startServices()
|
||||||
|
|
||||||
a.updateSettings()
|
a.settings.update(a.cfg, a.log)
|
||||||
|
|
||||||
a.metrics.SetEnabled(a.cfg.GetBool(cfgPrometheusEnabled))
|
a.metrics.SetEnabled(a.cfg.GetBool(cfgPrometheusEnabled))
|
||||||
a.initTracing(ctx)
|
a.initTracing(ctx)
|
||||||
a.loadIndexPageTemplate()
|
|
||||||
a.setHealthStatus()
|
a.setHealthStatus()
|
||||||
|
|
||||||
a.log.Info(logs.SIGHUPConfigReloadCompleted)
|
a.log.Info(logs.SIGHUPConfigReloadCompleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) updateSettings() {
|
|
||||||
a.settings.setDefaultTimestamp(a.cfg.GetBool(cfgUploaderHeaderEnableDefaultTimestamp))
|
|
||||||
a.settings.setZipCompression(a.cfg.GetBool(cfgZipCompression))
|
|
||||||
a.settings.setReturnIndexPage(a.cfg.GetBool(cfgIndexPageEnabled))
|
|
||||||
a.settings.setClientCut(a.cfg.GetBool(cfgClientCut))
|
|
||||||
a.settings.setBufferMaxSizeForPut(a.cfg.GetUint64(cfgBufferMaxSizeForPut))
|
|
||||||
a.settings.setNamespaceHeader(a.cfg.GetString(cfgResolveNamespaceHeader))
|
|
||||||
a.settings.setDefaultNamespaces(a.cfg.GetStringSlice(cfgResolveDefaultNamespaces))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) startServices() {
|
func (a *app) startServices() {
|
||||||
pprofConfig := metrics.Config{Enabled: a.cfg.GetBool(cfgPprofEnabled), Address: a.cfg.GetString(cfgPprofAddress)}
|
pprofConfig := metrics.Config{Enabled: a.cfg.GetBool(cfgPprofEnabled), Address: a.cfg.GetString(cfgPprofAddress)}
|
||||||
pprofService := metrics.NewPprofService(a.log, pprofConfig)
|
pprofService := metrics.NewPprofService(a.log, pprofConfig)
|
||||||
|
@ -579,7 +601,6 @@ func (a *app) stopServices() {
|
||||||
svc.ShutDown(ctx)
|
svc.ShutDown(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) configureRouter(handler *handler.Handler) {
|
func (a *app) configureRouter(handler *handler.Handler) {
|
||||||
r := router.New()
|
r := router.New()
|
||||||
r.RedirectTrailingSlash = true
|
r.RedirectTrailingSlash = true
|
||||||
|
@ -590,20 +611,96 @@ func (a *app) configureRouter(handler *handler.Handler) {
|
||||||
response.Error(r, "Method Not Allowed", fasthttp.StatusMethodNotAllowed)
|
response.Error(r, "Method Not Allowed", fasthttp.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.POST("/upload/{cid}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.Upload))))))
|
r.POST("/upload/{cid}", a.addMiddlewares(handler.Upload))
|
||||||
|
r.OPTIONS("/upload/{cid}", a.addPreflight())
|
||||||
a.log.Info(logs.AddedPathUploadCid)
|
a.log.Info(logs.AddedPathUploadCid)
|
||||||
r.GET("/get/{cid}/{oid:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.DownloadByAddressOrBucketName))))))
|
r.GET("/get/{cid}/{oid:*}", a.addMiddlewares(handler.DownloadByAddressOrBucketName))
|
||||||
r.HEAD("/get/{cid}/{oid:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.HeadByAddressOrBucketName))))))
|
r.HEAD("/get/{cid}/{oid:*}", a.addMiddlewares(handler.HeadByAddressOrBucketName))
|
||||||
|
r.OPTIONS("/get/{cid}/{oid:*}", a.addPreflight())
|
||||||
a.log.Info(logs.AddedPathGetCidOid)
|
a.log.Info(logs.AddedPathGetCidOid)
|
||||||
r.GET("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.DownloadByAttribute))))))
|
r.GET("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.addMiddlewares(handler.DownloadByAttribute))
|
||||||
r.HEAD("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.HeadByAttribute))))))
|
r.HEAD("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.addMiddlewares(handler.HeadByAttribute))
|
||||||
|
r.OPTIONS("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.addPreflight())
|
||||||
a.log.Info(logs.AddedPathGetByAttributeCidAttrKeyAttrVal)
|
a.log.Info(logs.AddedPathGetByAttributeCidAttrKeyAttrVal)
|
||||||
r.GET("/zip/{cid}/{prefix:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.DownloadZipped))))))
|
r.GET("/zip/{cid}/{prefix:*}", a.addMiddlewares(handler.DownloadZipped))
|
||||||
|
r.OPTIONS("/zip/{cid}/{prefix:*}", a.addPreflight())
|
||||||
a.log.Info(logs.AddedPathZipCidPrefix)
|
a.log.Info(logs.AddedPathZipCidPrefix)
|
||||||
|
|
||||||
a.webServer.Handler = r.Handler
|
a.webServer.Handler = r.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *app) addMiddlewares(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
|
list := []func(fasthttp.RequestHandler) fasthttp.RequestHandler{
|
||||||
|
a.tracer,
|
||||||
|
a.logger,
|
||||||
|
a.canonicalizer,
|
||||||
|
a.tokenizer,
|
||||||
|
a.reqNamespace,
|
||||||
|
a.cors,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(list) - 1; i >= 0; i-- {
|
||||||
|
h = list[i](h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) addPreflight() fasthttp.RequestHandler {
|
||||||
|
list := []func(fasthttp.RequestHandler) fasthttp.RequestHandler{
|
||||||
|
a.tracer,
|
||||||
|
a.logger,
|
||||||
|
a.reqNamespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
h := a.preflightHandler
|
||||||
|
for i := len(list) - 1; i >= 0; i-- {
|
||||||
|
h = list[i](h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) preflightHandler(c *fasthttp.RequestCtx) {
|
||||||
|
cors := a.settings.CORS()
|
||||||
|
setCORSHeaders(c, cors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) cors(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
|
return func(c *fasthttp.RequestCtx) {
|
||||||
|
h(c)
|
||||||
|
code := c.Response.StatusCode()
|
||||||
|
if code >= fasthttp.StatusOK && code < fasthttp.StatusMultipleChoices {
|
||||||
|
cors := a.settings.CORS()
|
||||||
|
setCORSHeaders(c, cors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCORSHeaders(c *fasthttp.RequestCtx, cors CORS) {
|
||||||
|
c.Response.Header.Set(fasthttp.HeaderAccessControlMaxAge, strconv.Itoa(cors.MaxAge))
|
||||||
|
|
||||||
|
if len(cors.AllowOrigin) != 0 {
|
||||||
|
c.Response.Header.Set(fasthttp.HeaderAccessControlAllowOrigin, cors.AllowOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cors.AllowMethods) != 0 {
|
||||||
|
c.Response.Header.Set(fasthttp.HeaderAccessControlAllowMethods, strings.Join(cors.AllowMethods, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cors.AllowHeaders) != 0 {
|
||||||
|
c.Response.Header.Set(fasthttp.HeaderAccessControlAllowHeaders, strings.Join(cors.AllowHeaders, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cors.ExposeHeaders) != 0 {
|
||||||
|
c.Response.Header.Set(fasthttp.HeaderAccessControlExposeHeaders, strings.Join(cors.ExposeHeaders, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cors.AllowCredentials {
|
||||||
|
c.Response.Header.Set(fasthttp.HeaderAccessControlAllowCredentials, "true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *app) logger(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
func (a *app) logger(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
return func(req *fasthttp.RequestCtx) {
|
return func(req *fasthttp.RequestCtx) {
|
||||||
requiredFields := []zap.Field{zap.Uint64("id", req.ID())}
|
requiredFields := []zap.Field{zap.Uint64("id", req.ID())}
|
||||||
|
@ -847,39 +944,6 @@ func (a *app) setRuntimeParameters() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) NamespaceHeader() string {
|
|
||||||
s.mu.RLock()
|
|
||||||
defer s.mu.RUnlock()
|
|
||||||
return s.namespaceHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) setNamespaceHeader(nsHeader string) {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.namespaceHeader = nsHeader
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) FormContainerZone(ns string) (zone string, isDefault bool) {
|
|
||||||
s.mu.RLock()
|
|
||||||
namespaces := s.defaultNamespaces
|
|
||||||
s.mu.RUnlock()
|
|
||||||
if slices.Contains(namespaces, ns) {
|
|
||||||
return v2container.SysAttributeZoneDefault, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return ns + ".ns", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *appSettings) setDefaultNamespaces(namespaces []string) {
|
|
||||||
for i := range namespaces { // to be set namespaces in env variable as `HTTP_GW_RESOLVE_BUCKET_DEFAULT_NAMESPACES="" "root"`
|
|
||||||
namespaces[i] = strings.Trim(namespaces[i], "\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
s.defaultNamespaces = namespaces
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *app) scheduleReconnect(ctx context.Context, srv *fasthttp.Server) {
|
func (a *app) scheduleReconnect(ctx context.Context, srv *fasthttp.Server) {
|
||||||
go func() {
|
go func() {
|
||||||
t := time.NewTicker(a.settings.reconnectInterval)
|
t := time.NewTicker(a.settings.reconnectInterval)
|
||||||
|
|
|
@ -102,7 +102,7 @@ func runServer(pathToWallet string) (App, context.CancelFunc) {
|
||||||
v.Set(cfgWalletPath, pathToWallet)
|
v.Set(cfgWalletPath, pathToWallet)
|
||||||
v.Set(cfgWalletPassphrase, "")
|
v.Set(cfgWalletPassphrase, "")
|
||||||
|
|
||||||
l, lvl := newStdoutLogger(zapcore.DebugLevel)
|
l, lvl := newStdoutLogger(v, zapcore.DebugLevel)
|
||||||
application := newApp(cancelCtx, WithConfig(v), WithLogger(l, lvl))
|
application := newApp(cancelCtx, WithConfig(v), WithLogger(l, lvl))
|
||||||
go application.Serve()
|
go application.Serve()
|
||||||
|
|
||||||
|
@ -525,7 +525,7 @@ func putObject(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID
|
||||||
id, err := clientPool.PutObject(ctx, prm)
|
id, err := clientPool.PutObject(ctx, prm)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return id
|
return id.ObjectID
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeBearerToken(t *testing.T, key *keys.PrivateKey, ownerID user.ID, version string) string {
|
func makeBearerToken(t *testing.T, key *keys.PrivateKey, ownerID user.ID, version string) string {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -55,6 +56,8 @@ const (
|
||||||
|
|
||||||
defaultReconnectInterval = time.Minute
|
defaultReconnectInterval = time.Minute
|
||||||
|
|
||||||
|
defaultCORSMaxAge = 600 // seconds
|
||||||
|
|
||||||
cfgServer = "server"
|
cfgServer = "server"
|
||||||
cfgTLSEnabled = "tls.enabled"
|
cfgTLSEnabled = "tls.enabled"
|
||||||
cfgTLSCertFile = "tls.cert_file"
|
cfgTLSCertFile = "tls.cert_file"
|
||||||
|
@ -140,6 +143,14 @@ const (
|
||||||
cfgResolveNamespaceHeader = "resolve_bucket.namespace_header"
|
cfgResolveNamespaceHeader = "resolve_bucket.namespace_header"
|
||||||
cfgResolveDefaultNamespaces = "resolve_bucket.default_namespaces"
|
cfgResolveDefaultNamespaces = "resolve_bucket.default_namespaces"
|
||||||
|
|
||||||
|
// CORS.
|
||||||
|
cfgCORSAllowOrigin = "cors.allow_origin"
|
||||||
|
cfgCORSAllowMethods = "cors.allow_methods"
|
||||||
|
cfgCORSAllowHeaders = "cors.allow_headers"
|
||||||
|
cfgCORSExposeHeaders = "cors.expose_headers"
|
||||||
|
cfgCORSAllowCredentials = "cors.allow_credentials"
|
||||||
|
cfgCORSMaxAge = "cors.max_age"
|
||||||
|
|
||||||
// Command line args.
|
// Command line args.
|
||||||
cmdHelp = "help"
|
cmdHelp = "help"
|
||||||
cmdVersion = "version"
|
cmdVersion = "version"
|
||||||
|
@ -505,6 +516,46 @@ func fetchReconnectInterval(cfg *viper.Viper) time.Duration {
|
||||||
return reconnect
|
return reconnect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchIndexPageTemplate(v *viper.Viper, l *zap.Logger) (string, bool) {
|
||||||
|
if !v.GetBool(cfgIndexPageEnabled) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := os.Open(v.GetString(cfgIndexPageTemplatePath))
|
||||||
|
if err != nil {
|
||||||
|
l.Warn(logs.FailedToReadIndexPageTemplate, zap.Error(err))
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
l.Warn(logs.FailedToReadIndexPageTemplate, zap.Error(err))
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Info(logs.SetCustomIndexPageTemplate)
|
||||||
|
return string(tmpl), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchDefaultNamespaces(v *viper.Viper) []string {
|
||||||
|
namespaces := v.GetStringSlice(cfgResolveDefaultNamespaces)
|
||||||
|
|
||||||
|
for i := range namespaces { // to be set namespaces in env variable as `HTTP_GW_RESOLVE_BUCKET_DEFAULT_NAMESPACES="" "root"`
|
||||||
|
namespaces[i] = strings.Trim(namespaces[i], "\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
return namespaces
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchCORSMaxAge(v *viper.Viper) int {
|
||||||
|
maxAge := v.GetInt(cfgCORSMaxAge)
|
||||||
|
if maxAge <= 0 {
|
||||||
|
maxAge = defaultCORSMaxAge
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxAge
|
||||||
|
}
|
||||||
|
|
||||||
func fetchServers(v *viper.Viper, log *zap.Logger) []ServerInfo {
|
func fetchServers(v *viper.Viper, log *zap.Logger) []ServerInfo {
|
||||||
var servers []ServerInfo
|
var servers []ServerInfo
|
||||||
seen := make(map[string]struct{})
|
seen := make(map[string]struct{})
|
||||||
|
|
|
@ -126,3 +126,10 @@ HTTP_GW_RESOLVE_BUCKET_DEFAULT_NAMESPACES="" "root"
|
||||||
# Max attempt to make successful tree request.
|
# Max attempt to make successful tree request.
|
||||||
# default value is 0 that means the number of attempts equals to number of nodes in pool.
|
# default value is 0 that means the number of attempts equals to number of nodes in pool.
|
||||||
HTTP_GW_FROSTFS_TREE_POOL_MAX_ATTEMPTS=0
|
HTTP_GW_FROSTFS_TREE_POOL_MAX_ATTEMPTS=0
|
||||||
|
|
||||||
|
HTTP_GW_CORS_ALLOW_ORIGIN="*"
|
||||||
|
HTTP_GW_CORS_ALLOW_METHODS="GET" "POST"
|
||||||
|
HTTP_GW_CORS_ALLOW_HEADERS="*"
|
||||||
|
HTTP_GW_CORS_EXPOSE_HEADERS="*"
|
||||||
|
HTTP_GW_CORS_ALLOW_CREDENTIALS=false
|
||||||
|
HTTP_GW_CORS_MAX_AGE=600
|
||||||
|
|
|
@ -138,3 +138,11 @@ cache:
|
||||||
resolve_bucket:
|
resolve_bucket:
|
||||||
namespace_header: X-Frostfs-Namespace
|
namespace_header: X-Frostfs-Namespace
|
||||||
default_namespaces: [ "", "root" ]
|
default_namespaces: [ "", "root" ]
|
||||||
|
|
||||||
|
cors:
|
||||||
|
allow_origin: ""
|
||||||
|
allow_methods: []
|
||||||
|
allow_headers: []
|
||||||
|
expose_headers: []
|
||||||
|
allow_credentials: false
|
||||||
|
max_age: 600
|
||||||
|
|
|
@ -363,3 +363,27 @@ index_page:
|
||||||
|-----------------|----------|---------------|---------------|---------------------------------------------------------------------------------|
|
|-----------------|----------|---------------|---------------|---------------------------------------------------------------------------------|
|
||||||
| `enabled` | `bool` | yes | `false` | Flag to enable index_page return if no object with specified S3-name was found. |
|
| `enabled` | `bool` | yes | `false` | Flag to enable index_page return if no object with specified S3-name was found. |
|
||||||
| `template_path` | `string` | yes | `""` | Path to .gotmpl file with html template for index_page. |
|
| `template_path` | `string` | yes | `""` | Path to .gotmpl file with html template for index_page. |
|
||||||
|
|
||||||
|
# `cors` section
|
||||||
|
|
||||||
|
Parameters for CORS (used in OPTIONS requests and responses in all handlers).
|
||||||
|
If values are not set, headers will not be included to response.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cors:
|
||||||
|
allow_origin: "*"
|
||||||
|
allow_methods: ["GET", "HEAD"]
|
||||||
|
allow_headers: ["Authorization"]
|
||||||
|
expose_headers: ["*"]
|
||||||
|
allow_credentials: false
|
||||||
|
max_age: 600
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|
|---------------------|------------|---------------|---------------|--------------------------------------------------------|
|
||||||
|
| `allow_origin` | `string` | yes | | Values for `Access-Control-Allow-Origin` headers. |
|
||||||
|
| `allow_methods` | `[]string` | yes | | Values for `Access-Control-Allow-Methods` headers. |
|
||||||
|
| `allow_headers` | `[]string` | yes | | Values for `Access-Control-Allow-Headers` headers. |
|
||||||
|
| `expose_headers` | `[]string` | yes | | Values for `Access-Control-Expose-Headers` headers. |
|
||||||
|
| `allow_credentials` | `bool` | yes | `false` | Values for `Access-Control-Allow-Credentials` headers. |
|
||||||
|
| `max_age` | `int` | yes | `600` | Values for `Access-Control-Max-Age ` headers. |
|
||||||
|
|
Loading…
Reference in a new issue