0

When I try to send a HEAD request for sendFile I get the following error:

app.head(filePath, { logLevel: LOG_LEVEL }, async (request, reply) => {
    console.log('head');
    try {
      const { '*': uriPath } = request.params;
      const isFile = !!uriPath.match(/\.[a-zA-Z0-9]{1,5}(\?.*)?/);
      if (isFile) {
        setCacheControl(reply, FILE_CACHE_CONTROL_MAX_AGE);
        reply.sendFile(uriPath);
      } else {
        const indexPath = 'index.html';
        const indexStr = await fs.readFile(path.join(serveRoot, indexPath), {
          encoding: 'utf-8',
        });
        const indexPayload = await injectEnv(indexStr);
        setCacheControl(reply, INDEX_CACHE_CONTROL_MAX_AGE);
        reply.type('text/html');
        reply.send(indexPayload);
      }
    } catch (e) {
      console.error(e);
    }
  });
web_1  | {"level":50,"time":1580244056047,"pid":1,"hostname":"3ee631923a16","reqId":5,"err":{"type":"FastifyError","message":"FST_ERR_PROMISE_NOT_FULLFILLED: Promise may not be fulfilled with 'undefined' when statusCode is not 204","stack":"FastifyError [FST_ERR_PROMISE_NOT_FULLFILLED]: FST_ERR_PROMISE_NOT_FULLFILLED: Promise may not be fulfilled with 'undefined' when statusCode is not 204\n    at /usr/src/server/node_modules/fastify/lib/wrapThenable.js:34:30\n    at processTicksAndRejections (internal/process/task_queues.js:85:5)","name":"FastifyError [FST_ERR_PROMISE_NOT_FULLFILLED]","code":"FST_ERR_PROMISE_NOT_FULLFILLED","statusCode":500},"msg":"Promise may not be fulfilled with 'undefined' when statusCode is not 204","v":1}

The way that express handles this is by simply passing through HEAD requests to the GET method, and then letting send (the underlying package that sends responses for both fastify and express) handle it here by not sending the output but sending the headers.

But fastify seems to incorrectly mark this as an error here

Toli
  • 5,547
  • 8
  • 36
  • 56

2 Answers2

0

Here a working example:

const fs = require('fs').promises
const path = require('path')
const app = require('fastify')({ logger: true })

app.head('/', async (request, reply) => {
  request.log.debug('head')
  try {
    const indexStr = await fs.readFile(path.join(__dirname, 'index.html'), { encoding: 'utf-8' })
    reply.type('text/html')
    return indexStr
  } catch (e) {
    request.log.error(e)
    return e
  }
})

app.listen(3000)

// curl --location --head 'http://localhost:3000/'

When an error is thrown and caught the promise is fulfilled with undefined that is causing the error you linked in the source code.

Moreover, when you use async functions as handlers, you should return what you want to send in the body or use return reply.send(content)

Anyway, consider to don't use HEAD methot because the standard says:

A response to a HEAD method should not have a body. If so, it must be ignored. Even so, entity headers describing the content of the body, like Content-Length may be included in the response. They don't relate to the body of the HEAD response, which should be empty, but to the body which a similar request using the GET method would have returned as a response.

So your body will be empty:

HTTP/1.1 200 OK

content-type: text/html

content-length: 41

Date: Wed, ---

Connection: keep-alive

Community
  • 1
  • 1
Manuel Spigolon
  • 11,003
  • 5
  • 50
  • 73
  • This works fine for `index.html` or some string that I return but when I pass back `return reply.sendFile()` it gives me the `undefined` error. When I do `reply.sendFile(uriPath); return reply.send('');` it replies with a content length of 0 and no etags – Toli Jan 29 '20 at 15:19
  • `sendFile` API from `fastify-static` doesn't return anything, so this could be an issue with the plugin – Manuel Spigolon Jan 29 '20 at 16:14
0

Solved it. The key is to return the binary stream for filesreturn fs.readFile(uriPath); and the string for string responses return indexPayload;


import path from 'path';
import fastify from 'fastify';
import staticServe from 'fastify-static';

import { promises as fs } from 'fs';


(async () => {
  const app = fastify({
    logger: !!LOG_LEVEL,
  });

  const filePath = `${ROOT_PATH}*`;
  const serveRoot = path.resolve(SERVE_PATH);


  app.register(staticServe, {
    root: serveRoot,
    serve: false,
  });

  // @ts-ignore
  const getHandler = async (request, reply) => {
    try {
      const { '*': uriPath } = request.params;

      const isFile = !!uriPath.match(/\.[a-zA-Z0-9]{1,5}(\?.*)?/);
      if (isFile) {
        setCacheControl(reply, FILE_CACHE_CONTROL_MAX_AGE);
        reply.sendFile(uriPath);
        return fs.readFile(uriPath);
      } else {
        const indexPath = 'index.html';
        const indexStr = await fs.readFile(path.join(serveRoot, indexPath), {
          encoding: 'utf-8',
        });
        const indexPayload = await injectEnv(indexStr);
        setCacheControl(reply, INDEX_CACHE_CONTROL_MAX_AGE);
        reply.type('text/html');
        return indexPayload;
      }
    } catch (e) {
      request.log.error(e);
      return e;
    }
  };

  app.get(filePath, { logLevel: LOG_LEVEL }, getHandler);

  // More info here on how it works
  // https://github.com/fastify/fastify/issues/2061
  app.head(filePath, { logLevel: LOG_LEVEL }, getHandler);

  app.listen(Number.parseInt(PORT, 10), '0.0.0.0', (err: Error) => {
    if (err) {
      throw err;
    }
  });
})();

Toli
  • 5,547
  • 8
  • 36
  • 56