Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/tegola/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ var serverCmd = &cobra.Command{
server.Headers[name] = val
}

if conf.Webserver.URIPrefix != "" {
server.URIPrefix = string(conf.Webserver.URIPrefix)
}

// start our webserver
srv := server.Start(nil, serverPort)
shutdown(srv)
Expand Down
8 changes: 6 additions & 2 deletions cmd/tegola_lambda/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ func main() {
server.Headers[name] = val
}

if conf.Webserver.URIPrefix != "" {
server.URIPrefix = string(conf.Webserver.URIPrefix)
}

// http route setup
mux := server.NewRouter(nil)

Expand All @@ -105,7 +109,7 @@ func main() {

// URLRoot overrides the default server.URLRoot function in order to include the "stage" part of the root
// that is part of lambda's URL scheme
func URLRoot(r *http.Request) string {
func URLRoot(r *http.Request) *url.URL {
u := url.URL{
Scheme: scheme(r),
Host: r.Header.Get("Host"),
Expand All @@ -116,7 +120,7 @@ func URLRoot(r *http.Request) string {
u.Path = ctx.RequestContext.Stage
}

return u.String()
return &u
}

// various checks to determine if the request is http or https. the scheme is needed for the TileJSON URLs
Expand Down
20 changes: 14 additions & 6 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/*
config loads and understands the tegola config format.
*/
// Package config loads and understands the tegola config format.
package config

import (
Expand Down Expand Up @@ -35,9 +33,10 @@ type Config struct {
}

type Webserver struct {
HostName env.String `toml:"hostname"`
Port env.String `toml:"port"`
Headers env.Dict `toml:"headers"`
HostName env.String `toml:"hostname"`
Port env.String `toml:"port"`
URIPrefix env.String `toml:"uri_prefix"`
Headers env.Dict `toml:"headers"`
}

// A Map represents a map in the Tegola Config file.
Expand Down Expand Up @@ -140,6 +139,15 @@ func (c *Config) Validate() error {
}
}

// check if webserver.uri_prefix is set and if so
// confirm it starts with a forward slash "/"
if string(c.Webserver.URIPrefix) != "" {
uriPrefix := string(c.Webserver.URIPrefix)
if string(uriPrefix[0]) != "/" {
return ErrInvalidURIPrefix(uriPrefix)
}
}

return nil
}

Expand Down
6 changes: 6 additions & 0 deletions config/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,9 @@ type ErrInvalidHeader struct {
func (e ErrInvalidHeader) Error() string {
return fmt.Sprintf("config: header (%v) blacklisted", e.Header)
}

type ErrInvalidURIPrefix string

func (e ErrInvalidURIPrefix) Error() string {
return fmt.Sprintf("config: invalid uri_prefix (%v). uri_prefix must start with a forward slash '/' ", string(e))
}
1 change: 1 addition & 0 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Access-Control-Allow-Origin = "*"

- `port` (string): [Optional] Port and bind string. For example ":9090" or "127.0.0.1:9090". Defaults to ":8080"
- `hostname` (string): [Optional] The hostname to use in the various JSON endpoints. This is useful if tegola is behind a proxy and can't read the API consumer's request host directly.
- `uri_prefix` (string): [Optional] A prefix to add to all API routes. This is useful when tegola is behind a proxy (i.e. example.com/tegola). The prexfix will be added to all URLs included in the capabilities endpoint responses.

## Local development of the embedded viewer

Expand Down
12 changes: 6 additions & 6 deletions server/handle_capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package server

import (
"encoding/json"
"fmt"
"net/http"
"net/url"

"github.com/go-spatial/geom"
"github.com/go-spatial/tegola/atlas"
Expand Down Expand Up @@ -44,11 +44,11 @@ func (req HandleCapabilities) ServeHTTP(w http.ResponseWriter, r *http.Request)

// iterate our registered maps
for _, m := range atlas.AllMaps() {
var debugQuery string
debugQuery := url.Values{}

// if we have a debug param add it to our URLs
if query.Get("debug") == "true" {
debugQuery = "?debug=true"
debugQuery.Set("debug", "true")

// update our map to include the debug layers
m = m.AddDebugLayers()
Expand All @@ -61,9 +61,9 @@ func (req HandleCapabilities) ServeHTTP(w http.ResponseWriter, r *http.Request)
Bounds: m.Bounds,
Center: m.Center,
Tiles: []string{
fmt.Sprintf("%v/maps/%v/{z}/{x}/{y}.pbf%v", URLRoot(r), m.Name, debugQuery),
buildCapabilitiesURL(r, []string{"maps", m.Name, "{z}/{x}/{y}.pbf"}, debugQuery),
},
Capabilities: fmt.Sprintf("%v/capabilities/%v.json%v", URLRoot(r), m.Name, debugQuery),
Capabilities: buildCapabilitiesURL(r, []string{"capabilities", m.Name + ".json"}, debugQuery),
}

for i := range m.Layers {
Expand Down Expand Up @@ -94,7 +94,7 @@ func (req HandleCapabilities) ServeHTTP(w http.ResponseWriter, r *http.Request)
cLayer := CapabilitiesLayer{
Name: m.Layers[i].MVTName(),
Tiles: []string{
fmt.Sprintf("%v/maps/%v/%v/{z}/{x}/{y}.pbf%v", URLRoot(r), m.Name, m.Layers[i].MVTName(), debugQuery),
buildCapabilitiesURL(r, []string{"maps", m.Name, m.Layers[i].MVTName(), "{z}/{x}/{y}.pbf"}, debugQuery),
},
MinZoom: m.Layers[i].MinZoom,
MaxZoom: m.Layers[i].MaxZoom,
Expand Down
12 changes: 6 additions & 6 deletions server/handle_capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,39 +259,39 @@ func TestHandleCapabilities(t *testing.T) {
Attribution: "test attribution",
Center: [3]float64{1.0, 2.0, 3.0},
Bounds: tegola.WGS84Bounds,
Capabilities: "http://cdn.tegola.io:8080/capabilities/test-map.json?debug=true",
Capabilities: "http://cdn.tegola.io/capabilities/test-map.json?debug=true",
Tiles: []string{
"http://cdn.tegola.io:8080/maps/test-map/{z}/{x}/{y}.pbf?debug=true",
"http://cdn.tegola.io/maps/test-map/{z}/{x}/{y}.pbf?debug=true",
},
Layers: []server.CapabilitiesLayer{
{
Name: testLayer1.MVTName(),
Tiles: []string{
fmt.Sprintf("http://cdn.tegola.io:8080/maps/test-map/%v/{z}/{x}/{y}.pbf?debug=true", testLayer1.MVTName()),
fmt.Sprintf("http://cdn.tegola.io/maps/test-map/%v/{z}/{x}/{y}.pbf?debug=true", testLayer1.MVTName()),
},
MinZoom: testLayer1.MinZoom,
MaxZoom: testLayer3.MaxZoom, // layer 1 and layer 3 share a name in our test so the zoom range includes the entire zoom range
},
{
Name: "test-layer-2-name",
Tiles: []string{
fmt.Sprintf("http://cdn.tegola.io:8080/maps/test-map/%v/{z}/{x}/{y}.pbf?debug=true", testLayer2.MVTName()),
fmt.Sprintf("http://cdn.tegola.io/maps/test-map/%v/{z}/{x}/{y}.pbf?debug=true", testLayer2.MVTName()),
},
MinZoom: testLayer2.MinZoom,
MaxZoom: testLayer2.MaxZoom,
},
{
Name: "debug-tile-outline",
Tiles: []string{
"http://cdn.tegola.io:8080/maps/test-map/debug-tile-outline/{z}/{x}/{y}.pbf?debug=true",
"http://cdn.tegola.io/maps/test-map/debug-tile-outline/{z}/{x}/{y}.pbf?debug=true",
},
MinZoom: 0,
MaxZoom: atlas.MaxZoom,
},
{
Name: "debug-tile-center",
Tiles: []string{
"http://cdn.tegola.io:8080/maps/test-map/debug-tile-center/{z}/{x}/{y}.pbf?debug=true",
"http://cdn.tegola.io/maps/test-map/debug-tile-center/{z}/{x}/{y}.pbf?debug=true",
},
MinZoom: 0,
MaxZoom: atlas.MaxZoom,
Expand Down
10 changes: 5 additions & 5 deletions server/handle_map_capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package server

import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"strings"

"github.com/dimfeld/httptreemux"
Expand Down Expand Up @@ -66,10 +66,10 @@ func (req HandleMapCapabilities) ServeHTTP(w http.ResponseWriter, r *http.Reques
// parse our query string
var query = r.URL.Query()

var debugQuery string
debugQuery := url.Values{}
// if we have a debug param add it to our URLs
if query.Get("debug") == "true" {
debugQuery = "?debug=true"
debugQuery.Set("debug", "true")

// update our map to include the debug layers
m = m.AddDebugLayers()
Expand Down Expand Up @@ -125,7 +125,7 @@ func (req HandleMapCapabilities) ServeHTTP(w http.ResponseWriter, r *http.Reques
MinZoom: m.Layers[i].MinZoom,
MaxZoom: m.Layers[i].MaxZoom,
Tiles: []string{
fmt.Sprintf("%v/maps/%v/%v/{z}/{x}/{y}.pbf%v", URLRoot(r), req.mapName, m.Layers[i].MVTName(), debugQuery),
buildCapabilitiesURL(r, []string{"maps", req.mapName, m.Layers[i].MVTName(), "{z}/{x}/{y}.pbf"}, debugQuery),
},
}

Expand All @@ -145,7 +145,7 @@ func (req HandleMapCapabilities) ServeHTTP(w http.ResponseWriter, r *http.Reques
tileJSON.VectorLayers = append(tileJSON.VectorLayers, layer)
}

tileURL := fmt.Sprintf("%v/maps/%v/{z}/{x}/{y}.pbf%v", URLRoot(r), req.mapName, debugQuery)
tileURL := buildCapabilitiesURL(r, []string{"maps", req.mapName, "{z}/{x}/{y}.pbf"}, debugQuery)

// build our URL scheme for the tile grid
tileJSON.Tiles = append(tileJSON.Tiles, tileURL)
Expand Down
72 changes: 39 additions & 33 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ package server
import (
"fmt"
"net/http"
"strings"
"net/url"
"path"

"github.com/dimfeld/httptreemux"

Expand Down Expand Up @@ -35,6 +36,10 @@ var (
// configurable via the tegola config.toml file (set in main.go)
Headers = map[string]string{}

// URIPrefix sets a prefix on all server endpoints. This is often used
// when the server sits behind a reverse proxy with a prefix (i.e. /tegola)
URIPrefix = "/"

// DefaultCORSHeaders define the default CORS response headers added to all requests
DefaultCORSHeaders = map[string]string{
"Access-Control-Allow-Origin": "*",
Expand All @@ -45,7 +50,7 @@ var (
// NewRouter set's up the our routes.
func NewRouter(a *atlas.Atlas) *httptreemux.TreeMux {
r := httptreemux.New()
group := r.NewGroup("/")
group := r.NewGroup(URIPrefix)

// one handler to respond to all OPTIONS requests for registered routes with our CORS headers
r.OptionsHandler = corsHandler
Expand Down Expand Up @@ -94,38 +99,15 @@ func Start(a *atlas.Atlas, port string) *http.Server {
return srv
}

// hostName determines the hostname:port to return based on the following hierarchy
// - HostName / Port values as configured via the config file
// - The request host / port if config HostName or Port is missing
// hostName determines weather to use an user defined HostName
// or the host from the incoming request
func hostName(r *http.Request) string {
var requestHostname string
var requestPort string

substrs := strings.Split(r.Host, ":")

switch len(substrs) {
case 1:
requestHostname = substrs[0]
case 2:
requestHostname = substrs[0]
requestPort = substrs[1]
default:
log.Warnf("multiple colons (':') in host string: %v", r.Host)
}

retHost := HostName
if HostName == "" {
retHost = requestHostname
}

if Port != "" && Port != "none" {
return retHost + Port
}
if requestPort != "" && Port != "none" {
return retHost + ":" + requestPort
// if the HostName has been configured, don't mutate it
if HostName != "" {
return HostName
}

return retHost
return r.Host
}

// various checks to determin if the request is http or https. the scheme is needed for the TileURLs
Expand All @@ -142,8 +124,32 @@ func scheme(r *http.Request) string {

// URLRoot builds a string containing the scheme, host and port based on a combination of user defined values,
// headers and request parameters. The function is public so it can be overridden for other implementations.
var URLRoot = func(r *http.Request) string {
return fmt.Sprintf("%v://%v", scheme(r), hostName(r))
var URLRoot = func(r *http.Request) *url.URL {
root := url.URL{
Scheme: scheme(r),
Host: hostName(r),
}

return &root
}

// buildCapabilitiesURL is responsible for building the various URLs which are returned by
// the capabilities endpoints using the request, uri parts, and query params the function
// will determine the protocol host:port and URI prefix that need to be included based on
// user defined configurations and request context
func buildCapabilitiesURL(r *http.Request, uriParts []string, query url.Values) string {
uri := path.Join(uriParts...)
q := query.Encode()
if q != "" {
// prepend our query identifier
q = "?" + q
}

// usually the url.URL package would be used for building the URL, but the
// uri template for the tiles contains characters that the package does not like:
// {z}/{x}/{y}. These values are escaped during the String() call which does not
// work for the capabilities URLs.
return fmt.Sprintf("%v%v%v", URLRoot(r), path.Join(URIPrefix, uri), q)
}

// corsHanlder is used to respond to all OPTIONS requests for registered routes
Expand Down
Loading