ENH: Add support for returning missing images as HTTP 404 instead of blank PNGs (#177)

This commit is contained in:
Brendan Ward 2024-01-17 09:51:41 -08:00 committed by GitHub
parent 8403335a48
commit 04d08f1258
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 91 additions and 59 deletions

View File

@ -2,6 +2,10 @@
## 0.11.0 (in development) ## 0.11.0 (in development)
- support returning missing image tiles as HTTP 404 instead of blank tiles using
the `--missing-image-tile-404` option (#177).
## 0.10.0 ## 0.10.0
- supports GCC11 on Ubuntu 22.04 (#166) - supports GCC11 on Ubuntu 22.04 (#166)

View File

@ -53,32 +53,36 @@ From within the repository root ($GOPATH/bin needs to be in your $PATH):
``` ```
$ mbtileserver --help $ mbtileserver --help
Serve tiles from mbtiles files. Serve tiles from mbtiles files
Usage: Usage:
mbtileserver [flags] mbtileserver [flags]
Flags: Flags:
-c, --cert string X.509 TLS certificate filename. If present, will be used to enable SSL on the server. --basemap-style-url string Basemap style URL for preview endpoint (can include authorization token parameter if required by host)
-d, --dir string Directory containing mbtiles files. Directory containing mbtiles files. Can be a comma-delimited list of directories. (default "./tilesets") --basemap-tiles-url string Basemap raster tiles URL pattern for preview endpoint (can include authorization token parameter if required by host): https://some.host/{z}/{x}/{y}.png
--disable-preview Disable map preview for each tileset (enabled by default) -c, --cert string X.509 TLS certificate filename. If present, will be used to enable SSL on the server.
--disable-svc-list Disable services list endpoint (enabled by default) -d, --dir string Directory containing mbtiles files. Can be a comma-delimited list of directories. (default "./tilesets")
--disable-tilejson Disable TileJSON endpoint for each tileset (enabled by default) --disable-preview Disable map preview for each tileset (enabled by default)
--domain string Domain name of this server. NOTE: only used for AutoTLS. --disable-svc-list Disable services list endpoint (enabled by default)
--dsn string Sentry DSN --disable-tilejson Disable TileJSON endpoint for each tileset (enabled by default)
--enable-arcgis Enable ArcGIS Mapserver endpoints --domain string Domain name of this server. NOTE: only used for AutoTLS.
--enable-fs-watch Enable reloading of tilesets by watching filesystem --dsn string Sentry DSN
--enable-reload-signal Enable graceful reload using HUP signal to the server process --enable-arcgis Enable ArcGIS Mapserver endpoints
--generate-ids Automatically generate tileset IDs instead of using relative path --enable-fs-watch Enable reloading of tilesets by watching filesystem
-h, --help help for mbtileserver --enable-reload-signal Enable graceful reload using HUP signal to the server process
-k, --key string TLS private key --generate-ids Automatically generate tileset IDs instead of using relative path
-p, --port int Server port. Default is 443 if --cert or --tls options are used, otherwise 8000. (default -1) -h, --help help for mbtileserver
-r, --redirect Redirect HTTP to HTTPS --host string IP address to listen on. Default is all interfaces. (default "0.0.0.0")
--root-url string Root URL of services endpoint (default "/services") -k, --key string TLS private key
-s, --secret-key string Shared secret key used for HMAC request authentication --missing-image-tile-404 Return HTTP 404 error code when image tile is misssing instead of default behavior to return blank PNG
--tiles-only Only enable tile endpoints (shortcut for --disable-svc-list --disable-tilejson --disable-preview) -p, --port int Server port. Default is 443 if --cert or --tls options are used, otherwise 8000. (default -1)
-t, --tls Auto TLS via Let's Encrypt -r, --redirect Redirect HTTP to HTTPS
-v, --verbose Verbose logging --root-url string Root URL of services endpoint (default "/services")
-s, --secret-key string Shared secret key used for HMAC request authentication
--tiles-only Only enable tile endpoints (shortcut for --disable-svc-list --disable-tilejson --disable-preview)
-t, --tls Auto TLS via Let's Encrypt
-v, --verbose Verbose logging
``` ```
So hosting tiles is as easy as putting your mbtiles files in the `tilesets` So hosting tiles is as easy as putting your mbtiles files in the `tilesets`
@ -306,6 +310,18 @@ These are provided at:
where `<format>` is one of `png`, `jpg`, `webp`, `pbf` depending on the type of data in the tileset. where `<format>` is one of `png`, `jpg`, `webp`, `pbf` depending on the type of data in the tileset.
### Missing tiles
Missing vector tiles are always returned as HTTP 204.
Missing image tiles are returned as blank PNGs with the same dimensions as the tileset to give seamless display of
these tiles in interactive maps.
When serving image tiles that encode data (e.g., terrain) instead of purely for display, this can cause issues. In
this case, you can use the `--missing-image-tile-404` option. This behavior will be applied to all image tilesets.
## TileJSON API ## TileJSON API
`mbtileserver` automatically creates a TileJSON endpoint for each service at `/services/<tileset_id>`. `mbtileserver` automatically creates a TileJSON endpoint for each service at `/services/<tileset_id>`.

View File

@ -13,14 +13,15 @@ import (
// ServiceSetConfig provides configuration options for a ServiceSet // ServiceSetConfig provides configuration options for a ServiceSet
type ServiceSetConfig struct { type ServiceSetConfig struct {
EnableServiceList bool EnableServiceList bool
EnableTileJSON bool EnableTileJSON bool
EnablePreview bool EnablePreview bool
EnableArcGIS bool EnableArcGIS bool
BasemapStyleURL string BasemapStyleURL string
BasemapTilesURL string BasemapTilesURL string
RootURL *url.URL ReturnMissingImageTile404 bool
ErrorWriter io.Writer RootURL *url.URL
ErrorWriter io.Writer
} }
// ServiceSet is a group of tilesets plus configuration options. // ServiceSet is a group of tilesets plus configuration options.
@ -28,12 +29,13 @@ type ServiceSetConfig struct {
type ServiceSet struct { type ServiceSet struct {
tilesets map[string]*Tileset tilesets map[string]*Tileset
enableServiceList bool enableServiceList bool
enableTileJSON bool enableTileJSON bool
enablePreview bool enablePreview bool
enableArcGIS bool enableArcGIS bool
basemapStyleURL string basemapStyleURL string
basemapTilesURL string basemapTilesURL string
returnMissingImageTile404 bool
rootURL *url.URL rootURL *url.URL
errorWriter io.Writer errorWriter io.Writer
@ -48,15 +50,16 @@ func New(cfg *ServiceSetConfig) (*ServiceSet, error) {
} }
s := &ServiceSet{ s := &ServiceSet{
tilesets: make(map[string]*Tileset), tilesets: make(map[string]*Tileset),
enableServiceList: cfg.EnableServiceList, enableServiceList: cfg.EnableServiceList,
enableTileJSON: cfg.EnableTileJSON, enableTileJSON: cfg.EnableTileJSON,
enablePreview: cfg.EnablePreview, enablePreview: cfg.EnablePreview,
enableArcGIS: cfg.EnableArcGIS, enableArcGIS: cfg.EnableArcGIS,
basemapStyleURL: cfg.BasemapStyleURL, basemapStyleURL: cfg.BasemapStyleURL,
basemapTilesURL: cfg.BasemapTilesURL, basemapTilesURL: cfg.BasemapTilesURL,
rootURL: cfg.RootURL, returnMissingImageTile404: cfg.ReturnMissingImageTile404,
errorWriter: cfg.ErrorWriter, rootURL: cfg.RootURL,
errorWriter: cfg.ErrorWriter,
} }
return s, nil return s, nil

View File

@ -219,7 +219,7 @@ func (ts *Tileset) tileHandler(w http.ResponseWriter, r *http.Request) {
if ts == nil || !ts.published { if ts == nil || !ts.published {
// In order to not break any requests from when this tileset was published // In order to not break any requests from when this tileset was published
// return the appropriate not found handler for the original tile format. // return the appropriate not found handler for the original tile format.
tileNotFoundHandler(w, r, ts.tileformat, ts.tilesize) tileNotFoundHandler(w, r, ts.tileformat, ts.tilesize, ts.svc.returnMissingImageTile404)
return return
} }
@ -255,7 +255,7 @@ func (ts *Tileset) tileHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if data == nil || len(data) <= 1 { if data == nil || len(data) <= 1 {
tileNotFoundHandler(w, r, ts.tileformat, ts.tilesize) tileNotFoundHandler(w, r, ts.tileformat, ts.tilesize, ts.svc.returnMissingImageTile404)
return return
} }
@ -327,13 +327,18 @@ func (ts *Tileset) previewHandler(w http.ResponseWriter, r *http.Request) {
// tileNotFoundHandler is an http.HandlerFunc that writes the default response // tileNotFoundHandler is an http.HandlerFunc that writes the default response
// for a non-existing tile of type f to w // for a non-existing tile of type f to w
func tileNotFoundHandler(w http.ResponseWriter, r *http.Request, f mbtiles.TileFormat, tilesize uint32) { func tileNotFoundHandler(w http.ResponseWriter, r *http.Request, f mbtiles.TileFormat, tilesize uint32, returnMissingImageTile404 bool) {
switch f { switch f {
case mbtiles.PNG, mbtiles.JPG, mbtiles.WEBP: case mbtiles.PNG, mbtiles.JPG, mbtiles.WEBP:
// Return blank PNG for all image types if returnMissingImageTile404 {
w.Header().Set("Content-Type", "image/png") // Return 404
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusNotFound)
w.Write(BlankPNG(tilesize)) } else {
// Return blank PNG for all image types
w.Header().Set("Content-Type", "image/png")
w.WriteHeader(http.StatusOK)
w.Write(BlankPNG(tilesize))
}
case mbtiles.PBF: case mbtiles.PBF:
// Return 204 // Return 204
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)

20
main.go
View File

@ -82,6 +82,7 @@ var (
tilesOnly bool tilesOnly bool
basemapStyleURL string basemapStyleURL string
basemapTilesURL string basemapTilesURL string
missingImageTile404 bool
) )
func init() { func init() {
@ -112,6 +113,8 @@ func init() {
flags.StringVar(&basemapStyleURL, "basemap-style-url", "", "Basemap style URL for preview endpoint (can include authorization token parameter if required by host)") flags.StringVar(&basemapStyleURL, "basemap-style-url", "", "Basemap style URL for preview endpoint (can include authorization token parameter if required by host)")
flags.StringVar(&basemapTilesURL, "basemap-tiles-url", "", "Basemap raster tiles URL pattern for preview endpoint (can include authorization token parameter if required by host): https://some.host/{z}/{x}/{y}.png") flags.StringVar(&basemapTilesURL, "basemap-tiles-url", "", "Basemap raster tiles URL pattern for preview endpoint (can include authorization token parameter if required by host): https://some.host/{z}/{x}/{y}.png")
flags.BoolVarP(&missingImageTile404, "missing-image-tile-404", "", false, "Return HTTP 404 error code when image tile is misssing instead of default behavior to return blank PNG")
flags.BoolVarP(&verbose, "verbose", "v", false, "Verbose logging") flags.BoolVarP(&verbose, "verbose", "v", false, "Verbose logging")
if env := os.Getenv("HOST"); env != "" { if env := os.Getenv("HOST"); env != "" {
@ -292,14 +295,15 @@ func serve() {
} }
svcSet, err := handlers.New(&handlers.ServiceSetConfig{ svcSet, err := handlers.New(&handlers.ServiceSetConfig{
RootURL: rootURL, RootURL: rootURL,
ErrorWriter: &errorLogger{log: log.New()}, ErrorWriter: &errorLogger{log: log.New()},
EnableServiceList: !disableServiceList, EnableServiceList: !disableServiceList,
EnableTileJSON: !disableTileJSON, EnableTileJSON: !disableTileJSON,
EnablePreview: !disablePreview, EnablePreview: !disablePreview,
EnableArcGIS: enableArcGIS, EnableArcGIS: enableArcGIS,
BasemapStyleURL: basemapStyleURL, BasemapStyleURL: basemapStyleURL,
BasemapTilesURL: basemapTilesURL, BasemapTilesURL: basemapTilesURL,
ReturnMissingImageTile404: missingImageTile404,
}) })
if err != nil { if err != nil {
log.Fatalln("Could not construct ServiceSet") log.Fatalln("Could not construct ServiceSet")