From 3b23ec1e1c6ca3122aca5902d1c83f5e1894f128 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Sat, 16 Apr 2022 08:51:54 -0400 Subject: [PATCH] [Maps] Use content-encoding header from ES for mvt response (#130417) * Use content-encoding header from ES for mvt We can't always assume the content-encoding will be gzip. When SSL is enabled in Elasticsearch, the http.compression is disabled by default. We can use the headers from the Elasticsearch response to form the Kibana response. You should have HTTPS enabled to test this PR. Use `yarn es snapshot --ssl` and `yarn start --ssl`. (cherry picked from commit ac5aca44e8874bcf03c56e4698c740b3ee649475) --- .../plugins/maps/server/mvt/get_grid_tile.ts | 7 +++--- x-pack/plugins/maps/server/mvt/get_tile.ts | 7 +++--- x-pack/plugins/maps/server/mvt/mvt_routes.ts | 22 +++++++++++++------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/maps/server/mvt/get_grid_tile.ts b/x-pack/plugins/maps/server/mvt/get_grid_tile.ts index 754fdb9c1f4d2..620971d3097dd 100644 --- a/x-pack/plugins/maps/server/mvt/get_grid_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_grid_tile.ts @@ -7,6 +7,7 @@ import { CoreStart, Logger } from 'src/core/server'; import type { DataRequestHandlerContext } from 'src/plugins/data/server'; +import { IncomingHttpHeaders } from 'http'; import { Stream } from 'stream'; import { RENDER_AS } from '../../common/constants'; import { isAbortError } from './util'; @@ -40,7 +41,7 @@ export async function getEsGridTile({ renderAs: RENDER_AS; gridPrecision: number; abortController: AbortController; -}): Promise { +}): Promise<{ stream: Stream | null; headers?: IncomingHttpHeaders }> { try { const path = `/${encodeURIComponent(index)}/_mvt/${geometryFieldName}/${z}/${x}/${y}`; const body = { @@ -80,13 +81,13 @@ export async function getEsGridTile({ } ); - return tile.body as Stream; + return { stream: tile.body as Stream, headers: tile.headers }; } catch (e) { if (!isAbortError(e)) { // These are often circuit breaking exceptions // Should return a tile with some error message logger.warn(`Cannot generate ES-grid-tile for ${z}/${x}/${y}: ${e.message}`); } - return null; + return { stream: null }; } } diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index 7e9bc01c5c317..669ca7e20fc4c 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -7,6 +7,7 @@ import { CoreStart, Logger } from 'src/core/server'; import type { DataRequestHandlerContext } from 'src/plugins/data/server'; +import { IncomingHttpHeaders } from 'http'; import { Stream } from 'stream'; import { isAbortError } from './util'; import { makeExecutionContext } from '../../common/execution_context'; @@ -36,7 +37,7 @@ export async function getEsTile({ logger: Logger; requestBody: any; abortController: AbortController; -}): Promise { +}): Promise<{ stream: Stream | null; headers?: IncomingHttpHeaders }> { try { const path = `/${encodeURIComponent(index)}/_mvt/${geometryFieldName}/${z}/${x}/${y}`; @@ -80,13 +81,13 @@ export async function getEsTile({ } ); - return tile.body as Stream; + return { stream: tile.body as Stream, headers: tile.headers }; } catch (e) { if (!isAbortError(e)) { // These are often circuit breaking exceptions // Should return a tile with some error message logger.warn(`Cannot generate ES-grid-tile for ${z}/${x}/${y}: ${e.message}`); } - return null; + return { stream: null }; } } diff --git a/x-pack/plugins/maps/server/mvt/mvt_routes.ts b/x-pack/plugins/maps/server/mvt/mvt_routes.ts index dde68bd0d1335..e5b3d69fcb3a3 100644 --- a/x-pack/plugins/maps/server/mvt/mvt_routes.ts +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -6,6 +6,7 @@ */ import { Stream } from 'stream'; +import { IncomingHttpHeaders } from 'http'; import { schema } from '@kbn/config-schema'; import { CoreStart, KibanaRequest, KibanaResponseFactory, Logger } from 'src/core/server'; import { IRouter } from 'src/core/server'; @@ -57,7 +58,7 @@ export function initMVTRoutes({ const abortController = makeAbortController(request); - const gzippedTile = await getEsTile({ + const { stream, headers } = await getEsTile({ url: `${API_ROOT_PATH}/${MVT_GETTILE_API_PATH}/{z}/{x}/{y}.pbf`, core, logger, @@ -71,7 +72,7 @@ export function initMVTRoutes({ abortController, }); - return sendResponse(response, gzippedTile); + return sendResponse(response, stream, headers); } ); @@ -103,7 +104,7 @@ export function initMVTRoutes({ const abortController = makeAbortController(request); - const gzipTileStream = await getEsGridTile({ + const { stream, headers } = await getEsGridTile({ url: `${API_ROOT_PATH}/${MVT_GETGRIDTILE_API_PATH}/{z}/{x}/{y}.pbf`, core, logger, @@ -119,20 +120,27 @@ export function initMVTRoutes({ abortController, }); - return sendResponse(response, gzipTileStream); + return sendResponse(response, stream, headers); } ); } -function sendResponse(response: KibanaResponseFactory, gzipTileStream: Stream | null) { +function sendResponse( + response: KibanaResponseFactory, + gzipTileStream: Stream | null, + headers?: IncomingHttpHeaders +) { const cacheControl = `public, max-age=${CACHE_TIMEOUT_SECONDS}`; const lastModified = `${new Date().toUTCString()}`; - if (gzipTileStream) { + if (gzipTileStream && headers) { + // use the content-encoding and content-length headers from elasticsearch if they exist + const { 'content-length': contentLength, 'content-encoding': contentEncoding } = headers; return response.ok({ body: gzipTileStream, headers: { 'content-disposition': 'inline', - 'content-encoding': 'gzip', + ...(contentLength && { 'content-length': contentLength }), + ...(contentEncoding && { 'content-encoding': contentEncoding }), 'Content-Type': 'application/x-protobuf', 'Cache-Control': cacheControl, 'Last-Modified': lastModified,