From aa6829da20b05f0a2ff55b76dc9ecbdc64a4f5f0 Mon Sep 17 00:00:00 2001 From: Pavel Podkorytov Date: Tue, 16 Oct 2018 15:22:59 +0500 Subject: [PATCH 1/6] config: webserver section: remove CORSAllowedOrigin, add Headers subsections --- config/config.go | 7 +++---- config/config_test.go | 13 ++++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/config/config.go b/config/config.go index 93c4f98b8..565559657 100644 --- a/config/config.go +++ b/config/config.go @@ -12,7 +12,6 @@ import ( "time" "github.com/BurntSushi/toml" - "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/internal/env" "github.com/go-spatial/tegola/internal/log" @@ -34,9 +33,9 @@ type Config struct { } type Webserver struct { - HostName env.String `toml:"hostname"` - Port env.String `toml:"port"` - CORSAllowedOrigin env.String `toml:"cors_allowed_origin"` + HostName env.String `toml:"hostname"` + Port env.String `toml:"port"` + Headers env.Dict `toml:"headers"` } // A Map represents a map in the Tegola Config file. diff --git a/config/config_test.go b/config/config_test.go index b89393220..99be5cfce 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -99,6 +99,10 @@ func TestParse(t *testing.T) { port = ":8080" cors_allowed_origin = "tegola.io" + [webserver.headers] + Access-Control-Allow-Origin = "*" + Access-Control-Allow-Methods = "GET, OPTIONS" + [cache] type = "file" basepath = "/tmp/tegola-cache" @@ -133,9 +137,12 @@ func TestParse(t *testing.T) { TileBuffer: env.IntPtr(env.Int(12)), LocationName: "", Webserver: config.Webserver{ - HostName: "cdn.tegola.io", - Port: ":8080", - CORSAllowedOrigin: "tegola.io", + HostName: "cdn.tegola.io", + Port: ":8080", + Headers: env.Dict{ + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + }, }, Cache: env.Dict{ "type": "file", From cac13fdff06f1f51038c2260cc0b073dfdd9e55d Mon Sep 17 00:00:00 2001 From: Pavel Podkorytov Date: Tue, 16 Oct 2018 17:33:51 +0500 Subject: [PATCH 2/6] Add blacklisted headers --- config/config.go | 11 +++++++ config/config_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++ config/errors.go | 8 +++++ 3 files changed, 89 insertions(+) diff --git a/config/config.go b/config/config.go index 565559657..ea489f8f4 100644 --- a/config/config.go +++ b/config/config.go @@ -17,6 +17,8 @@ import ( "github.com/go-spatial/tegola/internal/log" ) +var blacklistHeaders = []string{"content-encoding", "content-length", "content-type"} + // Config represents a tegola config file. type Config struct { // the tile buffer to use @@ -125,6 +127,15 @@ func (c *Config) Validate() error { } } + // check for blacklisted headers + for k := range c.Webserver.Headers { + for _, v := range blacklistHeaders { + if v == strings.ToLower(k) { + return ErrInvalidHeader{Header: k} + } + } + } + return nil } diff --git a/config/config_test.go b/config/config_test.go index 99be5cfce..be71da895 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -697,6 +697,76 @@ func TestValidate(t *testing.T) { ProviderLayer2: "provider2.water_default_z", }, }, + "6 blocked headers": { + config: config.Config{ + LocationName: "", + Webserver: config.Webserver{ + Port: ":8080", + Headers: env.Dict{ + "Content-Encoding": "plain/text", + }, + }, + Providers: []env.Dict{ + { + "name": "provider1", + "type": "postgis", + "host": "localhost", + "port": int64(5432), + "database": "osm_water", + "user": "admin", + "password": "", + "layers": []map[string]interface{}{ + { + "name": "water", + "geometry_fieldname": "geom", + "id_fieldname": "gid", + "sql": "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!", + }, + }, + }, + { + "name": "provider2", + "type": "postgis", + "host": "localhost", + "port": int64(5432), + "database": "osm_water", + "user": "admin", + "password": "", + "layers": []map[string]interface{}{ + { + "name": "water", + "geometry_fieldname": "geom", + "id_fieldname": "gid", + "sql": "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!", + }, + }, + }, + }, + Maps: []config.Map{ + { + Name: "osm", + Attribution: "Test Attribution", + Bounds: []env.Float{-180, -85.05112877980659, 180, 85.0511287798066}, + Center: [3]env.Float{-76.275329586789, 39.153492567373, 8.0}, + Layers: []config.MapLayer{ + { + ProviderLayer: "provider1.water", + MinZoom: env.UintPtr(10), + MaxZoom: env.UintPtr(15), + }, + { + ProviderLayer: "provider2.water", + MinZoom: env.UintPtr(16), + MaxZoom: env.UintPtr(20), + }, + }, + }, + }, + }, + expectedErr: config.ErrInvalidHeader{ + Header: "Content-Encoding", + }, + }, } for name, tc := range tests { diff --git a/config/errors.go b/config/errors.go index dde1016ed..1dd613df5 100644 --- a/config/errors.go +++ b/config/errors.go @@ -52,3 +52,11 @@ type ErrMissingEnvVar struct { func (e ErrMissingEnvVar) Error() string { return fmt.Sprintf("config: config file is referencing an environment variable that is not set (%v)", e.EnvVar) } + +type ErrInvalidHeader struct { + Header string +} + +func (e ErrInvalidHeader) Error() string { + return fmt.Sprintf("config: header (%v) blacklisted", e.Header) +} From df508ae2f25d767765dfb085c18afb5b1e331fd2 Mon Sep 17 00:00:00 2001 From: Pavel Podkorytov Date: Thu, 18 Oct 2018 12:08:24 +0500 Subject: [PATCH 3/6] Add ability to redefine CORS headers from [webserver.headers] config --- cmd/tegola/cmd/server.go | 25 ++++++++++++++++++------- cmd/tegola_lambda/main.go | 21 +++++++++++++++++---- server/middleware_cors.go | 2 +- server/server.go | 6 ++++-- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/cmd/tegola/cmd/server.go b/cmd/tegola/cmd/server.go index 7b7ed5195..e8e81f989 100644 --- a/cmd/tegola/cmd/server.go +++ b/cmd/tegola/cmd/server.go @@ -2,19 +2,21 @@ package cmd import ( "context" + "log" "net/http" "time" - "github.com/spf13/cobra" - gdcmd "github.com/go-spatial/tegola/internal/cmd" "github.com/go-spatial/tegola/provider" "github.com/go-spatial/tegola/server" + "github.com/spf13/cobra" ) var ( - serverPort string - defaultHTTPPort = ":8080" + serverPort string + defaultHTTPPort = ":8080" + defaultCORSAllowedOrigin = "*" + defaultCORSAllowedMethods = "GET, OPTIONS" ) var serverCmd = &cobra.Command{ @@ -37,10 +39,19 @@ var serverCmd = &cobra.Command{ server.Version = Version server.HostName = string(conf.Webserver.HostName) - // set the CORSAllowedOrigin if a value is provided - if conf.Webserver.CORSAllowedOrigin != "" { - server.CORSAllowedOrigin = string(conf.Webserver.CORSAllowedOrigin) + // set the CORSAllowedOrigin to configured or default value + header, err := conf.Webserver.Headers.String("Access-Control-Allow-Origin", &defaultCORSAllowedOrigin) + if err != nil { + log.Fatal(err) + } + server.CORSAllowedOrigin = header + + // set the CORSAllowedMethods to configured or default value + header, err = conf.Webserver.Headers.String("Access-Control-Allow-Methods", &defaultCORSAllowedMethods) + if err != nil { + log.Fatal(err) } + server.CORSAllowedMethods = header // set tile buffer if conf.TileBuffer != nil { diff --git a/cmd/tegola_lambda/main.go b/cmd/tegola_lambda/main.go index 2adcdded0..b2fabac94 100644 --- a/cmd/tegola_lambda/main.go +++ b/cmd/tegola_lambda/main.go @@ -7,7 +7,6 @@ import ( "os" "github.com/arolek/algnhsa" - "github.com/go-spatial/tegola/atlas" "github.com/go-spatial/tegola/cmd/internal/register" "github.com/go-spatial/tegola/config" @@ -19,6 +18,11 @@ import ( // set at build time via the CI var Version = "version not set" +var ( + defaultCORSAllowedOrigin = "*" + defaultCORSAllowedMethods = "GET, OPTIONS" +) + func init() { // override the URLRoot func with a lambda specific one server.URLRoot = URLRoot @@ -81,10 +85,19 @@ func main() { server.HostName = string(conf.Webserver.HostName) } - // set the CORSAllowedOrigin if a value is provided - if conf.Webserver.CORSAllowedOrigin != "" { - server.CORSAllowedOrigin = string(conf.Webserver.CORSAllowedOrigin) + // set the CORSAllowedOrigin to configured or default value + header, err := conf.Webserver.Headers.String("Access-Control-Allow-Origin", &defaultCORSAllowedOrigin) + if err != nil { + log.Fatal(err) + } + server.CORSAllowedOrigin = header + + // set the CORSAllowedMethods to configured or default value + header, err = conf.Webserver.Headers.String("Access-Control-Allow-Methods", &defaultCORSAllowedMethods) + if err != nil { + log.Fatal(err) } + server.CORSAllowedMethods = header // set tile buffer if conf.TileBuffer != nil { diff --git a/server/middleware_cors.go b/server/middleware_cors.go index 735e50e0c..85c243691 100644 --- a/server/middleware_cors.go +++ b/server/middleware_cors.go @@ -6,7 +6,7 @@ func CORSHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", CORSAllowedOrigin) - w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Methods", CORSAllowedMethods) // stop here if the request is an OPTIONS preflight if r.Method == "OPTIONS" { diff --git a/server/server.go b/server/server.go index 2e2bfaf2e..a32339467 100644 --- a/server/server.go +++ b/server/server.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/dimfeld/httptreemux" - "github.com/go-spatial/tegola" "github.com/go-spatial/tegola/atlas" "github.com/go-spatial/tegola/internal/log" @@ -36,6 +35,9 @@ var ( // configurable via the tegola config.toml file (set in main.go) CORSAllowedOrigin string = "*" + // CORSAllowedMethods is the "Access-Control-Allow-Methods" CORS header. + CORSAllowedMethods string = "GET, OPTIONS" + // TileBuffer is the tile buffer to use. // configurable via tegola config.tomal file (set in main.go) TileBuffer float64 = tegola.DefaultTileBuffer @@ -148,6 +150,6 @@ var URLRoot = func(r *http.Request) string { // corsHanlder is used to respond to all OPTIONS requests for registered routes func corsHandler(w http.ResponseWriter, r *http.Request, params map[string]string) { w.Header().Set("Access-Control-Allow-Origin", CORSAllowedOrigin) - w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Methods", CORSAllowedMethods) return } From aa112648b6436a25927386cb0f7a5799f8b4dbeb Mon Sep 17 00:00:00 2001 From: Pavel Podkorytov Date: Thu, 18 Oct 2018 12:26:18 +0500 Subject: [PATCH 4/6] Add HeadersHandler middleware, enable it to tegola and tegola_lambda --- cmd/tegola/cmd/server.go | 3 +++ cmd/tegola_lambda/main.go | 3 +++ server/middleware_headers.go | 30 ++++++++++++++++++++++++++++++ server/server.go | 15 ++++++++++----- 4 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 server/middleware_headers.go diff --git a/cmd/tegola/cmd/server.go b/cmd/tegola/cmd/server.go index e8e81f989..2e650e48f 100644 --- a/cmd/tegola/cmd/server.go +++ b/cmd/tegola/cmd/server.go @@ -53,6 +53,9 @@ var serverCmd = &cobra.Command{ } server.CORSAllowedMethods = header + // set the http reply headers + server.Headers = conf.Webserver.Headers + // set tile buffer if conf.TileBuffer != nil { server.TileBuffer = float64(*conf.TileBuffer) diff --git a/cmd/tegola_lambda/main.go b/cmd/tegola_lambda/main.go index b2fabac94..f5eef1541 100644 --- a/cmd/tegola_lambda/main.go +++ b/cmd/tegola_lambda/main.go @@ -99,6 +99,9 @@ func main() { } server.CORSAllowedMethods = header + // set the http reply headers + server.Headers = conf.Webserver.Headers + // set tile buffer if conf.TileBuffer != nil { server.TileBuffer = float64(*conf.TileBuffer) diff --git a/server/middleware_headers.go b/server/middleware_headers.go new file mode 100644 index 000000000..c5649dd0c --- /dev/null +++ b/server/middleware_headers.go @@ -0,0 +1,30 @@ +package server + +import ( + "net/http" + "strings" +) + +func HeadersHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + for name, value := range Headers { + // skip CORS headers + if strings.ToLower(name) == "access-control-allow-origin" { + continue + } + if strings.ToLower(name) == "access-control-allow-methods" { + continue + } + + v, ok := value.(string) + if ok { + w.Header().Set(name, v) + } + } + + next.ServeHTTP(w, r) + + return + }) +} diff --git a/server/server.go b/server/server.go index a32339467..1c34fd775 100644 --- a/server/server.go +++ b/server/server.go @@ -36,8 +36,13 @@ var ( CORSAllowedOrigin string = "*" // CORSAllowedMethods is the "Access-Control-Allow-Methods" CORS header. + // configurable via the tegola config.toml file (set in main.go) CORSAllowedMethods string = "GET, OPTIONS" + // Headers is the map of http reply headers. + // configurable via the tegola config.toml file (set in main.go) + Headers map[string]interface{} + // TileBuffer is the tile buffer to use. // configurable via tegola config.tomal file (set in main.go) TileBuffer float64 = tegola.DefaultTileBuffer @@ -52,16 +57,16 @@ func NewRouter(a *atlas.Atlas) *httptreemux.TreeMux { r.OptionsHandler = corsHandler // capabilities endpoints - group.UsingContext().Handler("GET", "/capabilities", CORSHandler(HandleCapabilities{})) - group.UsingContext().Handler("GET", "/capabilities/:map_name", CORSHandler(HandleMapCapabilities{})) + group.UsingContext().Handler("GET", "/capabilities", HeadersHandler(CORSHandler(HandleCapabilities{}))) + group.UsingContext().Handler("GET", "/capabilities/:map_name", HeadersHandler(CORSHandler(HandleMapCapabilities{}))) // map tiles hMapLayerZXY := HandleMapLayerZXY{Atlas: a} - group.UsingContext().Handler("GET", "/maps/:map_name/:z/:x/:y", CORSHandler(GZipHandler(TileCacheHandler(a, hMapLayerZXY)))) - group.UsingContext().Handler("GET", "/maps/:map_name/:layer_name/:z/:x/:y", CORSHandler(GZipHandler(TileCacheHandler(a, hMapLayerZXY)))) + group.UsingContext().Handler("GET", "/maps/:map_name/:z/:x/:y", HeadersHandler(CORSHandler(GZipHandler(TileCacheHandler(a, hMapLayerZXY))))) + group.UsingContext().Handler("GET", "/maps/:map_name/:layer_name/:z/:x/:y", HeadersHandler(CORSHandler(GZipHandler(TileCacheHandler(a, hMapLayerZXY))))) // map style - group.UsingContext().Handler("GET", "/maps/:map_name/style.json", CORSHandler(HandleMapStyle{})) + group.UsingContext().Handler("GET", "/maps/:map_name/style.json", HeadersHandler(CORSHandler(HandleMapStyle{}))) // setup viewer routes, which can be excluded via build flags setupViewer(group) From e288aa07fb5beb717b33510d8d642caedffcaa5d Mon Sep 17 00:00:00 2001 From: Pavel Podkorytov Date: Thu, 18 Oct 2018 12:31:21 +0500 Subject: [PATCH 5/6] Add [webserver.headers] section in README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 79c4bd13b..9b4bf5b3a 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,10 @@ Under the `maps` section, map layers are associated with data provider layers an [webserver] port = ":9090" # port to bind the web server to. defaults ":8080" + [webserver.headers] + Access-Control-Allow-Origin = "*" + Cache-Control = "no-cache, no-store, must-revalidate" + [cache] # configure a tile cache type = "file" # a file cache will cache to the local file system basepath = "/tmp/tegola" # where to write the file cache From ec8547cc7bfc299380c1724683b05e563a425675 Mon Sep 17 00:00:00 2001 From: Alexander Rolek Date: Thu, 18 Oct 2018 16:02:03 -0700 Subject: [PATCH 6/6] Additional changes to configurable header support. #519 --- cmd/tegola/cmd/server.go | 24 +++------------- cmd/tegola_lambda/main.go | 19 ------------ config/config_test.go | 56 ------------------------------------ server/middleware_cors.go | 20 ------------- server/middleware_headers.go | 17 ++++------- server/server.go | 10 +++---- 6 files changed, 14 insertions(+), 132 deletions(-) delete mode 100644 server/middleware_cors.go diff --git a/cmd/tegola/cmd/server.go b/cmd/tegola/cmd/server.go index 2e650e48f..115a56855 100644 --- a/cmd/tegola/cmd/server.go +++ b/cmd/tegola/cmd/server.go @@ -2,21 +2,19 @@ package cmd import ( "context" - "log" "net/http" "time" + "github.com/spf13/cobra" + gdcmd "github.com/go-spatial/tegola/internal/cmd" "github.com/go-spatial/tegola/provider" "github.com/go-spatial/tegola/server" - "github.com/spf13/cobra" ) var ( - serverPort string - defaultHTTPPort = ":8080" - defaultCORSAllowedOrigin = "*" - defaultCORSAllowedMethods = "GET, OPTIONS" + serverPort string + defaultHTTPPort = ":8080" ) var serverCmd = &cobra.Command{ @@ -39,20 +37,6 @@ var serverCmd = &cobra.Command{ server.Version = Version server.HostName = string(conf.Webserver.HostName) - // set the CORSAllowedOrigin to configured or default value - header, err := conf.Webserver.Headers.String("Access-Control-Allow-Origin", &defaultCORSAllowedOrigin) - if err != nil { - log.Fatal(err) - } - server.CORSAllowedOrigin = header - - // set the CORSAllowedMethods to configured or default value - header, err = conf.Webserver.Headers.String("Access-Control-Allow-Methods", &defaultCORSAllowedMethods) - if err != nil { - log.Fatal(err) - } - server.CORSAllowedMethods = header - // set the http reply headers server.Headers = conf.Webserver.Headers diff --git a/cmd/tegola_lambda/main.go b/cmd/tegola_lambda/main.go index f5eef1541..da7a35f3f 100644 --- a/cmd/tegola_lambda/main.go +++ b/cmd/tegola_lambda/main.go @@ -18,11 +18,6 @@ import ( // set at build time via the CI var Version = "version not set" -var ( - defaultCORSAllowedOrigin = "*" - defaultCORSAllowedMethods = "GET, OPTIONS" -) - func init() { // override the URLRoot func with a lambda specific one server.URLRoot = URLRoot @@ -85,20 +80,6 @@ func main() { server.HostName = string(conf.Webserver.HostName) } - // set the CORSAllowedOrigin to configured or default value - header, err := conf.Webserver.Headers.String("Access-Control-Allow-Origin", &defaultCORSAllowedOrigin) - if err != nil { - log.Fatal(err) - } - server.CORSAllowedOrigin = header - - // set the CORSAllowedMethods to configured or default value - header, err = conf.Webserver.Headers.String("Access-Control-Allow-Methods", &defaultCORSAllowedMethods) - if err != nil { - log.Fatal(err) - } - server.CORSAllowedMethods = header - // set the http reply headers server.Headers = conf.Webserver.Headers diff --git a/config/config_test.go b/config/config_test.go index be71da895..7c701e9ba 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -706,62 +706,6 @@ func TestValidate(t *testing.T) { "Content-Encoding": "plain/text", }, }, - Providers: []env.Dict{ - { - "name": "provider1", - "type": "postgis", - "host": "localhost", - "port": int64(5432), - "database": "osm_water", - "user": "admin", - "password": "", - "layers": []map[string]interface{}{ - { - "name": "water", - "geometry_fieldname": "geom", - "id_fieldname": "gid", - "sql": "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!", - }, - }, - }, - { - "name": "provider2", - "type": "postgis", - "host": "localhost", - "port": int64(5432), - "database": "osm_water", - "user": "admin", - "password": "", - "layers": []map[string]interface{}{ - { - "name": "water", - "geometry_fieldname": "geom", - "id_fieldname": "gid", - "sql": "SELECT gid, ST_AsBinary(geom) AS geom FROM simplified_water_polygons WHERE geom && !BBOX!", - }, - }, - }, - }, - Maps: []config.Map{ - { - Name: "osm", - Attribution: "Test Attribution", - Bounds: []env.Float{-180, -85.05112877980659, 180, 85.0511287798066}, - Center: [3]env.Float{-76.275329586789, 39.153492567373, 8.0}, - Layers: []config.MapLayer{ - { - ProviderLayer: "provider1.water", - MinZoom: env.UintPtr(10), - MaxZoom: env.UintPtr(15), - }, - { - ProviderLayer: "provider2.water", - MinZoom: env.UintPtr(16), - MaxZoom: env.UintPtr(20), - }, - }, - }, - }, }, expectedErr: config.ErrInvalidHeader{ Header: "Content-Encoding", diff --git a/server/middleware_cors.go b/server/middleware_cors.go deleted file mode 100644 index 85c243691..000000000 --- a/server/middleware_cors.go +++ /dev/null @@ -1,20 +0,0 @@ -package server - -import "net/http" - -func CORSHandler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Access-Control-Allow-Origin", CORSAllowedOrigin) - w.Header().Set("Access-Control-Allow-Methods", CORSAllowedMethods) - - // stop here if the request is an OPTIONS preflight - if r.Method == "OPTIONS" { - return - } - - next.ServeHTTP(w, r) - - return - }) -} diff --git a/server/middleware_headers.go b/server/middleware_headers.go index c5649dd0c..28b0d599c 100644 --- a/server/middleware_headers.go +++ b/server/middleware_headers.go @@ -1,22 +1,15 @@ package server -import ( - "net/http" - "strings" -) +import "net/http" func HeadersHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - for name, value := range Headers { - // skip CORS headers - if strings.ToLower(name) == "access-control-allow-origin" { - continue - } - if strings.ToLower(name) == "access-control-allow-methods" { - continue - } + // default CORS headers. may be overwritten by the user + w.Header().Set("Access-Control-Allow-Origin", CORSAllowedOrigin) + w.Header().Set("Access-Control-Allow-Methods", CORSAllowedMethods) + for name, value := range Headers { v, ok := value.(string) if ok { w.Header().Set(name, v) diff --git a/server/server.go b/server/server.go index 1c34fd775..1895ebfc3 100644 --- a/server/server.go +++ b/server/server.go @@ -57,16 +57,16 @@ func NewRouter(a *atlas.Atlas) *httptreemux.TreeMux { r.OptionsHandler = corsHandler // capabilities endpoints - group.UsingContext().Handler("GET", "/capabilities", HeadersHandler(CORSHandler(HandleCapabilities{}))) - group.UsingContext().Handler("GET", "/capabilities/:map_name", HeadersHandler(CORSHandler(HandleMapCapabilities{}))) + group.UsingContext().Handler("GET", "/capabilities", HeadersHandler(HandleCapabilities{})) + group.UsingContext().Handler("GET", "/capabilities/:map_name", HeadersHandler(HandleMapCapabilities{})) // map tiles hMapLayerZXY := HandleMapLayerZXY{Atlas: a} - group.UsingContext().Handler("GET", "/maps/:map_name/:z/:x/:y", HeadersHandler(CORSHandler(GZipHandler(TileCacheHandler(a, hMapLayerZXY))))) - group.UsingContext().Handler("GET", "/maps/:map_name/:layer_name/:z/:x/:y", HeadersHandler(CORSHandler(GZipHandler(TileCacheHandler(a, hMapLayerZXY))))) + group.UsingContext().Handler("GET", "/maps/:map_name/:z/:x/:y", HeadersHandler(GZipHandler(TileCacheHandler(a, hMapLayerZXY)))) + group.UsingContext().Handler("GET", "/maps/:map_name/:layer_name/:z/:x/:y", HeadersHandler(GZipHandler(TileCacheHandler(a, hMapLayerZXY)))) // map style - group.UsingContext().Handler("GET", "/maps/:map_name/style.json", HeadersHandler(CORSHandler(HandleMapStyle{}))) + group.UsingContext().Handler("GET", "/maps/:map_name/style.json", HeadersHandler(HandleMapStyle{})) // setup viewer routes, which can be excluded via build flags setupViewer(group)