Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

21.0.0 Release Notes #4386

Closed
devinivy opened this issue Nov 7, 2022 · 3 comments
Closed

21.0.0 Release Notes #4386

devinivy opened this issue Nov 7, 2022 · 3 comments
Labels
breaking changes Change that can breaking existing code release notes Major release documentation
Milestone

Comments

@devinivy
Copy link
Member

devinivy commented Nov 7, 2022

Summary

hapi v21.0.0 is a medium-sized release focused on modernization and miscellaneous API improvements. All modules in the hapi.js ecosystem have been updated to officially support Node.js v18, be compatible with ESM projects, and drop support for Node.js v12. Plugins in the hapi.js ecosystem now support hapi v20+.

  • Upgrade time: medium - none to a couple of hours for many users, but could be more impactful for those running Node.js v12.
  • Complexity: low - requires following the list of changes to verify their impact.
  • Risk: low - small number of changes with simple ways to retain previous behavior in most cases.
  • Dependencies: medium - most existing plugins will work as-is, but upgrading from hapi v19 or Node.js v12 may require care.

Breaking Changes

Ecosystem-wide

  • Plugins throughout the ecosystem (inert, vision, h2o2, jwt, log, cookie, scooter, etc.) now support hapi v20+, dropping support for hapi v19 and below.
  • Catbox caching engines (catbox-memory, catbox-redis, catbox-memcached, catbox-object) now export the engine as a property Engine rather than as the default export, in order to work well for ESM users. E.g. require('catbox-redis') becomes require('catbox-redis').Engine.

New Features

Ecosystem-wide

  • All modules in the ecosystem support Node.js v18.
  • All modules in the ecosystem can be used in ESM projects.
    • In lab v25 you may write your tests in ESM.
    • In glue v9 you may specify paths to ESM plugins or cache engines.

Bug fixes

Migration Checklist

TLDR

While a more extensive migration guide exists below, the vast majority of hapi users should be looking for the following:

  • Ensure you're using Node.js v14 LTS or above.
  • If you use a hapi catbox engine such as catbox-redis, you may optionally choose to update it to the latest version. If you do so, update any references to it to reflect that the engine is now exported as Engine. E.g. require('@hapi/catbox-redis') becomes require('@hapi/catbox-redis').Engine.
  • If you use route.options.jsonp, remove this option and instead set route.options.cors to true. You should update any clients that utilize JSONP to instead make a regular AJAX/fetch() requests.
  • Ensure that any stream responses that you serve are an instance of Node.js's Stream.Readable. You can easily check by exercising the route, and seeing if hapi serves a 500 with a message indicating there's an issue. A common fix is to pipe your non-standard stream through Node.js's Stream.PassThrough, e.g. nonStandardStream.pipe(new Stream.PassThrough()).
  • If you use emit() asynchronously, e.g. await server.events.emit(), then switch it to use the new gauge() method, e.g. await server.events.gauge() with the same arguments.
  • If you have any listeners to the server's 'start' or 'stop' events that are asynchronous, switch these event listeners to a server lifecycle extension, server.ext('onPreStart', ...) or server.ext('onPostStop', ...) respectively.

Node.js version

Make sure your Node.js version is v14 LTS or newer. This release uses language features that are only available in Node.js v14.15.0 or higher and will not start on older versions. Node.js v18 is the current LTS and is the recommended version.

Default server host aligned with Node.js defaults

When creating a server with Hapi.server(), if you do not specify server.options.host then hapi used to default to 0.0.0.0, i.e. all available IPv4 network interfaces. This did not jibe with IPv6 or dual-stack hosts, particularly since as of Node.js v17, the dns module would resolve a request to localhost to IPv6 network interfaces when available. So hapi now defaults server.options.host to ::1 on IPv6-compatible machines, and otherwise to 0.0.0.0, in alignment with Node.js's bare HTTP server.

Checklist:

  • To restore the previous behavior, set server.options.host to 0.0.0.0, e.g. Hapi.server({ host: '0.0.0.0' }).

Catbox engines are now exported as Engine

In order to be more ESM-friendly, the catbox engines (catbox-memory, catbox-redis, catbox-memcached, catbox-object) now export the engine as a property Engine rather than as the default export. There have also been changes to catbox-memcached's engine options.

Checklist:

  • Optionally update to the latest version of your catbox engine.
  • If you do so, update any references to catbox engines to reflect the new Engine export. For example:
    const CatboxRedis = require('@hapi/catbox-redis');
    -const engine = new CatboxRedis();
    +const engine = new CatboxRedis.Engine();
    const server = Hapi.server({ cache: { engine } });
  • If you use catbox-memcached, note that the engine's location option has been renamed to server, and it takes a new format following from memcache-client's configuration.

Remove support for JSONP

JSONP (or, "JSON with Padding") was an approach to circumvent the browser same-origin policy when making AJAX requests for data. With the advent and broad client support for CORS, JSONP no longer belongs in hapi core, so the route.options.jsonp option has been removed.

Checklist:

  • Identify any routes using route.options.jsonp and remove the option.
  • You may consider replacing route.options.jsonp with route.options.cors. Setting route.options.cors to true on a route will allow cross-origin requests from any domain, similar to JSONP. You would followup by updating clients to use a standard AJAX request rather than the JSONP approach of appending a script tag to the page.

Support only standard Stream.Readable implementations for responses

Over Node.js's lifetime, we have seen several implementations of streams, and it used to be more common for libraries to implement their own non-standard stream interfaces. The Node.js ecosystem has settled down in this regard as of Node.js v14, and this has allowed hapi to remove code catering to non-standard implementations.

Checklist:

  • Identify any route handlers that respond with a stream. Lifecycle handlers that takeover the response with a stream are also affected.
  • Ensure that the stream object used for the response is an instance of Node.js's Stream.Readable.
  • You may check this by exercising the route. If the response is not a stream, the route will serve a 500 response, and the internal message will read: Cannot reply with a stream-like object that is not an instance of Stream.Readable. If the server serves the stream successfully, then you do not need to make any changes.
  • In any cases where you are responding with a stream that is not a Stream.Readable, determine the source of the non-standard stream implementation that is being used. For example, the stream may come from an outdated version of a dependency that works with an old version of streams.
  • If you can upgrade the dependency producing the non-standard stream implementation, consider doing so.
  • As a last resort, often you can convert a non-standard readable stream into a Stream.Readable by piping it through Node.js's Stream.PassThrough:
    -return h.response(nonStandardStream);
    +const standardStream = nonStandardStream.pipe(new Stream.PassThrough())
    +return h.response(standardStream);

Updated server.events interface

The server.events interface comes from the podium package, which has been upgrade to v5 with some breaking changes. Notably, async usage of emit() should be replaced with await gauge().

Checklist:

  • Identify any asynchronous usage of emit, e.g. await server.events.emit(), and replace it with the new gauge method: await server.events.gauge(). Synchronous usage of emit, e.g. server.events.emit(), does not need to be updated.
  • Any usage of server.events.registerPodium() should be removed. The events of any podiums registered to server.events should instead be registered directly to server.events using server.event().

Server lifecycle no longer waits on 'start' and 'stop' event handlers

When calling await server.start() and await server.stop(), hapi previously waited for all asynchronous event handlers for 'start' and 'stop' to complete. These event handlers no longer delay server start and stop.

Checklist:

  • Identify any asynchronous event handlers used with server.events.on('start', ...) and server.events.on('stop', ...).
  • If await server.start() should wait on any of the async 'start' event handlers, switch the event handler to a server lifecycle extension via server.ext('onPreStart', ...) or server.ext('onPostStart', ...).
  • If await server.stop() should wait on any of the async 'stop' event handlers, switch the event handler to a server lifecycle extension via server.ext('onPreStop', ...) or server.ext('onPostStop', ...).

Change default x-xss-protection header to 0

The route.options.security.xss option governs the x-xss-protection security header. In the past this option has been true or false, defaulting to true which resulted in a header value of 1; mode=block. This is no longer standard practice, and is recommended against by OWASP. The option now has been expanded to take values 'disable', 'enable', or false. When set to the default value of 'disable', it results in a header value of 0.

Checklist:

  • To restore the previous default behavior, you may set route.options.security.xss to 'enable'.
  • If you explicitly set route.options.security.xss to true, instead set it to 'disable' or 'enable'. It's recommended to switch to the new default of 'disable'.

Default status code for CORS preflight requests has changed

In past versions of hapi, successful CORS preflight requests have been served with the status code determined by route.options.response.emptyStatusCode, which is 204 by default. Despite the fact that these responses are indeed empty, it turns out to be compatible with a wider variety of clients to serve a 200, so in the this version of hapi we have switched the status code of CORS preflights to 200.

Checklist:

  • To restore the previous default behavior, you may set the new option route.options.cors.preflightStatusCode to 200.

Custom response varieties ignore return value of prepare()

If you create a custom response variety using request.generateResponse(), it has been an implicit requirement that prepare(response) return response. If you returned a different response object, then response would be orphaned and it would not be closed, resulting in potential resource leaks. If you returned nothing, it would lead to a runtime error. In order to avoid this, hapi now ignores the return value of prepare().

Checklist:

  • If you use request.generateResponse() with prepare(), then it's suggested that your implementation of prepare() no longer return any value. Instead, simply mutate the response passed into the method.
@hueniverse
Copy link
Contributor

Finally hapi can drink.

@wiggisser
Copy link

Actually, using hapi@21 with node v14.xx will throw an error if xx < 10 because it uses performance.eventLoopUtilization which was only introduced in node v14.10 and above ...

@devinivy
Copy link
Member Author

Hey, that's a good point. I will update the notes to reflect that we support Node.js v14 LTS, which starts at v14.15.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking changes Change that can breaking existing code release notes Major release documentation
Projects
None yet
Development

No branches or pull requests

3 participants