Giter Site home page Giter Site logo

http-terminator's Introduction

http-terminator ๐Ÿฆพ

Travis build status Coveralls NPM version Canonical Code Style Twitter Follow

Gracefully terminates HTTP(S) server.

Behaviour

When you call server.close(), it stops the server from accepting new connections, but it keeps the existing connections open indefinitely. This can result in your server hanging indefinitely due to keep-alive connections or because of the ongoing requests that do not produce a response. Therefore, in order to close the server, you must track creation of all connections and terminate them yourself.

http-terminator implements the logic for tracking all connections and their termination upon a timeout. http-terminator also ensures graceful communication of the server intention to shutdown to any clients that are currently receiving response from this server.

API

import {
  createHttpTerminator,
} from 'http-terminator';

/**
 * @property gracefulTerminationTimeout Number of milliseconds to allow for the active sockets to complete serving the response (default: 5000).
 * @property server Instance of http.Server.
 */
type HttpTerminatorConfigurationInputType = {|
  +gracefulTerminationTimeout?: number,
  +server: Server,
|};

/**
 * @property terminate Terminates HTTP server.
 */
type HttpTerminatorType = {|
  +terminate: () => Promise<void>,
|};


const httpTerminator: HttpTerminatorType = createHttpTerminator(
  configuration: HttpTerminatorConfigurationInputType
);

Usage

Use createHttpTerminator to create an instance of http-terminator and instead of using server.close(), use httpTerminator.terminate(), e.g.

import http from 'http';
import {
  createHttpTerminator,
} from 'http-terminator';

const server = http.createServer();

const httpTerminator = createHttpTerminator({
  server,
});

await httpTerminator.terminate();

Usage with Express

Usage with Express example:

import express from 'express';
import {
  createHttpTerminator,
} from 'http-terminator';

const app = express();

const server = app.listen();

const httpTerminator = createHttpTerminator({
  server,
});

await httpTerminator.terminate();

Usage with Fastify

Usage with Fastify example:

import fastify from 'fastify';
import {
  createHttpTerminator,
} from 'http-terminator';

const app = fastify();

void app.listen(0);

const httpTerminator = createHttpTerminator({
  server: app.server,
});

await httpTerminator.terminate();

Usage with Koa

Usage with Koa example:

import Koa from 'koa';
import {
  createHttpTerminator,
} from 'http-terminator';

const app = new Koa();

const server = app.listen();

const httpTerminator = createHttpTerminator({
  server,
});

await httpTerminator.terminate();

Usage with other HTTP frameworks

As it should be clear from the usage examples for Node.js HTTP server, Express and Koa, http-terminator works by accessing an instance of a Node.js http.Server. To understand how to use http-terminator with your framework, identify how to access an instance of http.Server and use it to create a http-terminator instance.

Alternative libraries

There are several alternative libraries that implement comparable functionality, e.g.

The main benefit of http-terminator is that:

  • it does not monkey-patch Node.js API
  • it immediately destroys all sockets without an attached HTTP request
  • it allows graceful timeout to sockets with ongoing HTTP requests
  • it properly handles HTTPS connections
  • it informs connections using keep-alive that server is shutting down by setting a connection: close header
  • it does not terminate the Node.js process

FAQ

What is the use case for http-terminator?

To gracefully terminate a HTTP server.

We say that a service is gracefully terminated when service stops accepting new clients, but allows time to complete the existing requests.

There are several reasons to terminate services gracefully:

  • Terminating a service gracefully ensures that the client experience is not affected (assuming the service is load-balanced).
  • If your application is stateful, then when services are not terminated gracefully, you are risking data corruption.
  • Forcing termination of the service with a timeout ensures timely termination of the service (otherwise the service can remain hanging indefinitely).

http-terminator's People

Contributors

bvgusak avatar domdinnes avatar gajus avatar ndhoule avatar pbharat84 avatar sraka1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

http-terminator's Issues

calling `server.close` immediately vs later

noob question

I'm trying to rampup on the Node.js server internals. I noticed that in the code base you call the server.close well after you being to close/destroy connections: https://github.com/gajus/http-terminator/blob/master/src/factories/createInternalHttpTerminator.ts#L154

From the documentation on server.close https://nodejs.org/dist/latest-v17.x/docs/api/net.html#serverclosecallback

Stops the server from accepting new connections and keeps existing connections.

Wouldn't you want to call this first to stop accepting new connections and then perform the closing/destroying of any socket that is left? Also, since server.close stops accepting new connections connections event wouldn't be emitted any more, right? So having the following:

if (terminating) {
      socket.destroy();
    }

in the connection event handler wouldn't be needed?

I think I'm overlooking something but can't seem to figure it out.

Question: How come still `TCPSERVERWRAP`?

With simple usage just as with the express example on the README, the express listener is still running from the return statement in the snippet from node_modules/express/lib/application.js below:

app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

I've verified that I only have one instance of express listening, by putting an unmistakeable console.log just before that return statement above.

$ node --version
v14.11.0

$ npm ls express
redacted-package
โ””โ”€โ”€ [email protected] 

The problem is:

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  โ—  TCPSERVERWRAP



      at Function.listen (../node_modules/express/lib/application.js:618:24)
      at Object.<anonymous> (index.ts:1195:32)

The app that won't stop (only the relevant parts are pasted):

import express from "express";

const app = express();

const server: Server = app.listen(port, () =>
  logger.info(`Listening on port ${port}...`),
);

module.exports = server;
import { createHttpTerminator, HttpTerminator } from 'http-terminator';

let server: Server;
let httpTerminator: HttpTerminator;

describe("/", () => {
  beforeEach(async () => {
    server = require("../../../index"); // This is the file shown in the block above
    httpTerminator = createHttpTerminator({
      gracefulTerminationTimeout: 0,
      server,
    });
    void (await redactedPromiseReturner());
  });

  afterEach(async () => {    
    void (await httpTerminator.terminate());
  });

What are some troubleshooting tips?

Node engine is too permissive

Running on node 14.15.5 gives an error:

/Users/agoldis/currents-demo/node_modules/fast-json-stringify/index.js:83
  rootSchemaId = schema.$id || randomUUID()
                               ^
TypeError: randomUUID is not a function
    at build (/Users/agoldis/currents-demo/node_modules/fast-json-stringify/index.js:83:32)
    at Object.<anonymous> (/Users/agoldis/currents-demo/node_modules/roarr/dist/src/Roarr.js:15:57)

However, engines field of package.json claims

"engines": {
    "node": ">=14"
  },

14.17+ would be a better option

Question about usage

The README gives an impression that I must call terminate() in my app startup code. Am I supposed to call it write after I start listening or should I call the function in process.on('SIGTERM'...) call.

Missing typescript declaration file

Hi,
Would love to try this module out. It's however missing a typescript declaration file which makes it a no go for me at the moment. Would you consider adding one?

Encounter error when running tsc

I'm using http-terminator in my typescript cli project, but when I run tsc to build my cli, I encountered error as below

> tsc
../../node_modules/http-terminator/dist/src/types.d.ts:4:29 - error TS2307: Cannot find module 'node:stream' or its corresponding type declarations.

4 import type { Duplex } from 'node:stream';
                              ~~~~~~~~~~~~~


Found 1 error.

and here are my base tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",
        "declaration": true,
        "noImplicitAny": false,
        "removeComments": true,
        "noLib": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "moduleResolution": "node",
        "target": "es6",
        "sourceMap": true,
        "lib": ["es6", "DOM", "scripthost"],
        "noUnusedLocals": true,
        "types": ["node","mocha"]
    },
    "include": ["packages"],
    "exclude": ["node_modules", "**/*.spec.ts"]
}

and here are my package's tsconfig.json

{
    "extends": "../../tsconfig.json",
    "compilerOptions": {
        "outDir": "./lib"
    },
    "include": ["./src"]
}

I'm wondering if this error is caused by my tsconfig, but I have no idea about how to fix this.

Call a cleanup function after all all connections are closed

Hi, I was wondering if there is an easy way to call a cleanup function once all the connections are closed.

Also, I wasn't quite clear what the difference between using this module vs server.close() is. Am I correct in understanding that this library does the business of closing persistent connections which server.close() does not?

Thanks for creating this!

Error: TypeError: Cannot read property 'on' of undefined

It is entirely possible that I may have misconfigured the setup for this package but just in case I haven't please could you review my code to ensure that I haven't done so..

nextApp.prepare().then(() => {
  const expressApp = express()
  const expressServer = expressApp.listen(3000, (error: Error) => {
    if (error) {
      throw error
    }
    console.log(`App ready on http://localhost:3000`)
  })

  const httpTerminator = createHttpTerminator({
    expressServer,
  })

  httpTerminator.terminate().then(() => {
    console.log('Express server terminated')
  })
})

I am creating a next.js app, which when used with Express uses config like this. See here for a more complete example if you're interested.

When using this, I always receive this error when the express server starts:

This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
TypeError: Cannot read property 'on' of undefined
    at createInternalHttpTerminator (/Users/myUsername/projectName/node_modules/http-terminator/src/factories/createInternalHttpTerminator.js:32:10)
    at Object.createHttpTerminator (/Users/myUsername/projectName/node_modules/http-terminator/src/factories/createHttpTerminator.js:10:26)
    at /Users/myUsername/projectName/src/server.ts:161:24

Line 161 is this const httpTerminator = createHttpTerminator({

Can you see anything obvious that I'm missing or have misconfigured?

v3.0.2 doesn't have dist folder

I'm getting this error

Error: Cannot find module 'node_modules/http-terminator/dist/src/index.js'. Please verify that the package.json has a valid "main" entry
    at tryPackage (node:internal/modules/cjs/loader:353:19)
    at Function.Module._findPath (node:internal/modules/cjs/loader:566:18)
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:919:27)
    at Function.Module._load (node:internal/modules/cjs/loader:778:27)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:94:18)
    at Object.<anonymous> (/Users/kikobeats/Projects/microlink/api/node_modules/lightship/dist/src/factories/createLightship.js:11:27)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)

That the error is right, the folder dist was not published with the release

CleanShot 2021-09-18 at 11 45 55@2x

Does the order of event handlers matter?

I'm writing a http connect forwarding proxy. And we have to handle the connect event.

This library associated a connection event that will eventually either destroy the socket, or handle a close event on the sockets.

Does it matter in what order the event handlers are added?

Furthermore if my event handler, is asynchronous, and awaits for various operations, such as establishing a connection to the target server, then it's possible that the socket created here is left dangling, or even the client socket is destroyed, while I attempt to write to it later.

How do I handle the problem where stop is run, while in the middle of an asynchronous request handler, do we just allow these exceptions to be thrown?

Add support for `Http2Server`

In aabca47 you added the type Http2SecureServer.

Would it also be possible to add the Http2Server type from the node http2 module?

I am happy to PR this if it's something you're willing to accept.

question regarding architecture

excellent project! I was looking through the code and trying to figure out why you have both a .flowconfig and tsconfig.json ?

importing createHttpTerminator with ES modules

Hi,
when creating a node.js project with "type": "module" in it's package.json to use ES modules (and use import / export syntax), the import of createHttpTerminator like this:

// importing createHttpTerminator like the doc
import {
  createHttpTerminator,
} from 'http-terminator';

throw this error:

SyntaxError: The requested module 'http-terminator' does not provide an export named 'createHttpTerminator'
    at ModuleJob._instantiate (internal/modules/esm/module_job.js:92:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:107:20)
    at async Loader.import (internal/modules/esm/loader.js:179:24)

To bypass this, I must import http-terminator like that:

import httpTerminator from 'http-terminator';
const { createHttpTerminator } = httpTerminator;

Seems like there's only a default export and no named export.

Dropped support for node v12

The most recent commit (4b8336b) mentions dropping support for nodejs 10, but it also sets engine >= 14. Is this intentional?

Not that it needs to matter, but the most recent and the next Ubuntu is still on v12. For me, it is just inconvenient and no big deal, but I still wanted to check if it was intentional.

Thanks for your great packages, BTW!

Close immediately after requests are done

In the readme it states that http-terminator should wait for requests to finish before closing connections.

As far as I can tell, if http-terminator is set to have a long timeout, and has to handle a long-running request. It actually doesn't close until the timeout ends, even if all the requests have finished.

Reproduce:

  • Setup a little express app with 2 endpoints:
app.get('/', (req, res) => res.send('Hello World!'));
app.get('/long', (req, res) => {
    setTimeout(function () {
        res.status(200).send('Done long request');
    }, 10000);
});
  • Make sure to start your server something like this:
 setInterval(() => this.server.getConnections(
         (err, connections) => console.log(`${connections} connections currently open`)
), 3000);

 this.httpTerminator = createHttpTerminator({
     server: this.server,
     gracefulTerminationTimeout: 60000
});

process.on('SIGINT', function () {
     this.httpTerminator.terminate();
}

Now to test:

  • Run your app.
  • make a request to the homepage
  • Ctrl + c / send SIGINT

See the server closes immediately

  • Run your app.
  • make a request to the /long
  • Ctrl + c / send SIGINT
  • Watch the connections get closed until there is 1 left
  • Note the open request finishes after 10000ms, now there are 0 connections

See the server doesn't close for a very long time.

I would expect that the server would close as soon as the conditions that there are 0 connections and the last request is finished are met OR after 60000ms, whichever is first.

Why not call server.close immediately?

I'm evaluating http-terminator, stoppable, and others.

http-terminator looks really nice in general. One thing I'm confused about, though: why doesn't it call server.close immediately on terminate? It looks like http-terminator first waits for all existing connections to close (helping that along in various ways), and only then actually closes the listener socket with server.close. Can't the server.close call be moved up the function above the various await delay calls, so that new connections fail at the TCP level immediately?

Named import does not work with Node ESM implementation

Using [email protected].

npm version:

{
  npm: '8.1.2',
  node: '16.13.1',
  v8: '9.4.146.24-node.14',
  uv: '1.42.0',
  zlib: '1.2.11',
  brotli: '1.0.9',
  ares: '1.18.1',
  modules: '93',
  nghttp2: '1.45.1',
  napi: '8',
  llhttp: '6.0.4',
  openssl: '1.1.1l+quic',
  cldr: '39.0',
  icu: '69.1',
  tz: '2021a',
  unicode: '13.0',
  ngtcp2: '0.1.0-DEV',
  nghttp3: '0.1.0-DEV'
}

index.mjs:

import {
  createHttpTerminator,
} from 'http-terminator';

Executing:

> node index.mjs 
file:///tmp/index.mjs:2
  createHttpTerminator,
  ^^^^^^^^^^^^^^^^^^^^
SyntaxError: Named export 'createHttpTerminator' not found. The requested module 'http-terminator' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'http-terminator';

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:181:5)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)

Does terminator need to be created before accepting connections?

Small question here! I know that http-terminator tracks some state about the server, e.g. active sockets, in order to do its job. Does that imply that the terminator needs to be created before the server starts serving requests, so that it can track the state of the server prior to termination? I think it could be easy to assume you can just create the terminator at the moment you want to start tearing down the server, so I want to confirm whether or not that is intended usage. Thank you!

Example:

const server = http.createServer();
// Serve some requests
// Begin teardown
const httpTerminator = createHttpTerminator({ server });
await httpTerminator.terminate();

// ... or ...

const server = http.createServer();
const httpTerminator = createHttpTerminator({ server });
// Serve some requests
// Begin teardown
await httpTerminator.terminate();

Resolve fork with lil-http-terminator

Hello. I think this is a great library, but the points made by the author of lil-http-terminator are very valid. That author has removed a lot of unnecessary dependencies and made a smaller project. I agree with their best practices of introducing the minimal amount of code dependencies for both package size as well as code stability reasons.I think it would be nice if these two projects could merge back together and that we could create a plan for getting them back in sync. It would be annoying to find a bug in one and have to fix it in both. Are you open to resolving this fork? Would be happy to help in any ways!

https://www.npmjs.com/package/lil-http-terminator

This module was forked from the amazing http-terminator. The important changes:

Zero dependencies, 11 KB on your disk. The original http-terminator brings in more than 20 sub-dependencies, >450 files, 2 MB total.
Removed TypeScript and a dozen of supporting files, configurations, etc. No more code transpilation.
Simpler API. Now you do require("lil-http-terminator")({ server }); to get a terminator object.
The termination never throws. You don't want to handle unexpected exceptions during your server shutdown.
Termination won't hang forever if server never closes the port because some browsers disrespect connection:close header.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.