docs: function requestHandler(req, res, next) {
    const params = req && req.params;
    const start = Date.now();
    let source;
    let opts;
  
    return Promise.try(() => {
      source = core.getPublicSource(params.src);
  
      if (!_.contains(source.formats, params.format)) {
        throw new Err('Format %s is not known', params.format).metrics('err.req.format');
      }
  
      params.z = core.validateZoom(params.z, source);
      params.scale = core.validateScale(params.scale, source);
  
      params.x = checkType.strToInt(params.x);
      params.y = checkType.strToInt(params.y);
      if (
        !qidx.isValidCoordinate(params.x, params.z) || !qidx.isValidCoordinate(params.y, params.z)
      ) {
        throw new Err('x,y coordinates are not valid, or not allowed for this zoom').metrics('err.req.coords');
      }
  
      opts = {
        z: params.z,
        x: params.x,
        y: params.y,
      };
      if (params.format !== 'pbf') {
        // Ensure that PNGs are not 32bit
        // TODO: this should be source-configurable
        opts.format = params.format === 'png' ? 'png8:m=h' : params.format;
        if (params.scale) {
          opts.scale = params.scale;
        }
      }
      if (req.query && req.query.lang) {
        if (!langCodeRe.test(req.query.lang)) {
          throw new Err('lang param is not valid').metrics('err.req.lang');
        }
        opts.lang = req.query.lang;
      }
  
      // fixme: Force all tiles to be treated as vector
      opts.treatAsVector = true;
  
      return source.getHandler().getAsync(opts);
    }).then((result) => {
      let mx;
  
      core.setResponseHeaders(res, source, result.headers);
  
      if (params.format === 'json') {
        // Allow JSON to be shortened to simplify debugging
        res.json(filterJson(req.query, result.data));
      } else {
        res.send(result.data);
      }
  
      mx = util.format('req.%s.%s.%s', params.src, params.z, params.format);
      if (params.scale) {
        // replace '.' with ',' -- otherwise grafana treats it as a divider
        mx += `.${params.scale.toString().replace('.', ',')}`;
      }
      core.metrics.endTiming(mx, start);
    }).catch(err => core.reportRequestError(err, res)).catch(next);
  }