├── .editorconfig ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ └── codeql.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── examples ├── README.md ├── express-advanced-server.js ├── express-noexit-server.js ├── express-simple-server.js ├── fastity-simple-server.js ├── http-simple-server.js ├── http2-simple-server.js └── koa-simple-server.js ├── lib ├── index.d.ts └── index.js └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # http://editorconfig.org/ 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_style = space 12 | indent_size = 2 13 | insert_final_newline = true 14 | trim_trailing_whitespace = true 15 | 16 | [*.md] 17 | trim_trailing_whitespace = true 18 | 19 | [{.travis.yml,package.json}] 20 | # The indent size used in the `package.json` file cannot be changed 21 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516 22 | indent_style = space 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es2021": true, 6 | "node": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "ecmaVersion": 12 11 | }, 12 | "rules": { 13 | "semi": [ 14 | "error", 15 | "always" 16 | ], 17 | "eqeqeq": "off", 18 | "curly": "error", 19 | "quotes": [ 20 | "error", 21 | "single" 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: 'sebhildebrandt' 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: #['link'] 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '43 7 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # MacOS 26 | .DS_Store 27 | .Spotlight-V100 28 | .Trashes 29 | Icon? 30 | ._* 31 | 32 | # Windows 33 | Thumbs.db 34 | ehthumbs.db 35 | Desktop.ini 36 | 37 | # Linux 38 | .directory 39 | *~ 40 | 41 | # NPM 42 | node_modules 43 | .nodemonignore 44 | npm-debug.log 45 | npm* 46 | *.gz 47 | examples/package* 48 | 49 | # Other 50 | *_conflict- 51 | .notes.txt 52 | .idea 53 | *.ipr 54 | *.iws 55 | *.sublime-project 56 | *.sublime-workspace 57 | .*.swp 58 | .svn 59 | .hg 60 | CVS 61 | .eslintrc.json 62 | package-lock.json 63 | yarn.lock 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2025 Sebastian Hildebrandt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # http-graceful-shutdown 2 | 3 | ``` 4 | _ _ _ __ _ _ _ _ 5 | | |_| |_| |_ _ __ ___ __ _ _ _ __ _ __ ___ / _|_ _| |___ __| |_ _ _| |_ __| |_____ __ ___ _ 6 | | ' \ _| _| '_ \___/ _` | '_/ _` / _/ -_) _| || | |___(_-< ' \ || | _/ _` / _ \ V V / ' \ 7 | |_||_\__|\__| .__/ \__, |_| \__,_\__\___|_| \_,_|_| /__/_||_\_,_|\__\__,_\___/\_/\_/|_||_| 8 | |_| |___/ 9 | ``` 10 | 11 | Gracefully shuts down [node.js][nodejs-url] http server. More than 10 Mio 12 | downloads overall. 13 | 14 | [![NPM Version][npm-image]][npm-url] 15 | [![NPM Downloads][downloads-image]][downloads-url] 16 | [![Git Issues][issues-img]][issues-url] 17 | [![Closed Issues][closed-issues-img]][closed-issues-url] 18 | [![deps status][dependencies-img]][dependencies-url] 19 | [![Caretaker][caretaker-image]][caretaker-url] 20 | [![MIT license][license-img]][license-url] 21 | 22 | **Version 3.0** just released. This version is fully backwards compatible to 23 | version 2.x but adds much better handling under the hood. More that 10 Mio 24 | downloads. 25 | 26 | - can be used with [express][express-url], [koa][koa-url], 27 | [fastify][fastify-url], native node [http][http-url], [http2][http2-url] ... 28 | see examples 29 | - simple to use 30 | - configurable to your needs 31 | - add your own cleanup function 32 | 33 | ### Features 34 | 35 | `http-graceful-shutdown` manages a secure and save shutdown of your http server 36 | application: 37 | 38 | - tracks all connections 39 | - stops the server from accepting new connections on shutdown 40 | - graceful communication to all connected clients of server intention to shut 41 | down 42 | - immediately destroys all sockets without an attached HTTP request 43 | - properly handles all HTTP and HTTPS connections 44 | - possibility to define cleanup functions (e.g. closing DB connections) 45 | - preShutdown function if you need to have all HTTP sockets available and 46 | untouched 47 | - choose between shutting down by function call or triggered by SIGINT, SIGTERM, 48 | ... 49 | - choose between final forceful process termination node.js (process.exit) or 50 | clearing event loop (options). 51 | 52 | ## Quick Start 53 | 54 | ### Installation 55 | 56 | ```bash 57 | $ npm install http-graceful-shutdown 58 | ``` 59 | 60 | ### Basic Usage 61 | 62 | ```js 63 | const gracefulShutdown = require('http-graceful-shutdown'); 64 | ... 65 | // app: can be http, https, express, koa, fastity, ... 66 | server = app.listen(...); 67 | ... 68 | 69 | // this enables the graceful shutdown 70 | gracefulShutdown(server); 71 | ``` 72 | 73 | ## Explanation 74 | 75 | ### Functionality 76 | 77 | ``` 78 | PARENT Process (e.g. nodemon, shell, kubernetes, ...) 79 | ─────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────── 80 | │ Signal (SIGINT, SIGTERM, ...) 81 | │ 82 | │ 83 | (1) (2) v NODE SERVER (HTTP, Express, koa, fastity, ...) 84 | ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 85 | │ │ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ <─ shutdown procedure 86 | │ │ shutdown initiated │ │ │ 87 | │ │ │ │ │ 88 | │ │ │ │ (8) shutdown function (9) finally fn │ 89 | │ │ ▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄ │ │ ▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ │ 90 | │ └ (3) (4) close │ └ (7) destroy │ 91 | │ preShutdown idle sockets │ remaining sockets │ 92 | │ │ │ (10) 93 | serve │ serving req. (open connection) │ (5) └ SERVER terminated 94 | ▄▄▄│ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄┤ ^ blocked 95 | ^ │ ^ last request before │ │ 96 | │ │ │ receiving shutdown signal │ │ 97 | │ │ │ │ │ 98 | │ │ │ │ │ 99 | │ │ │ │ │ 100 | │ │ │ Long request │ │ 101 | Request │ V Resp │ V Resp. │ 102 | │ │ │ CLIENT 103 | ────────┴─────────┴─────────────────────────────────────────────────┴───────────────────────────────────────────────────────── 104 | ``` 105 | 106 | 1. usually, your NODE http server (the black bar in the middle) replies to 107 | client requests and sends responses 108 | 2. if your server receives a termination signal (e.g. SIGINT - Ctrl-C) from its 109 | parent, http-graceful-shutdown starts the shutdown procedure 110 | 3. first, http-graceful-shutdown will run the "preShutdown" (async) function. 111 | Place your own function here (passed to the options object), if you need to 112 | have all HTTP sockets available and untouched. 113 | 4. then all empty connections are closed and destroyed and 114 | 5. http-graceful-shutdown will block any new requests 115 | 6. if possible, http-graceful-shutdown communicates to the clients that the 116 | server is about to close (connection close header) 117 | 7. http-graceful-shutdown now tries to wait till all sockets are finished, then 118 | destroys the all remaining sockets 119 | 8. now it is time to run the "onShutdown" (async) function (if such a function 120 | is passed to the options object) 121 | 9. as soon as this onShutdown function has ended, the "finally" (sync) function 122 | is executed (if passed to the options) 123 | 10. now the event loop is cleared up OR process.exit() is triggered (can be 124 | defined in the options) and the server process ends. 125 | 126 | ## Options 127 | 128 | | option | default | Comments | 129 | | ----------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- | 130 | | timeout | 30000 | timeout till forced shutdown (in milliseconds) | 131 | | signals | 'SIGINT SIGTERM' | define the signals, that should be handled (separated by SPACE) | 132 | | development | false | if set to true, no graceful shutdown is proceeded to speed up dev-process | 133 | | preShutdown | - | not time-consuming callback function. Needs to return a promise.
Here, all HTTP sockets are still available and untouched | 134 | | onShutdown | - | not time-consuming callback function. Needs to return a promise. | 135 | | forceExit | true | force process.exit - otherwise just let event loop clear | 136 | | finally | - | small, not time-consuming function, that will
be handled at the end of the shutdown (not in dev-mode) | 137 | 138 | ### Option Explanation 139 | 140 | - **timeout:** You can define the maximum time that the shutdown process may 141 | take (timeout option). If after this time, connections are still open or the 142 | shutdown process is still running, then the remaining connections will be 143 | forcibly closed and the server process is terminated. 144 | - **signals** Here you can define which signals can trigger the shutdown process 145 | (SIGINT, SIGTERM, SIGKILL, SIGHUP, SIGUSR2, ...) 146 | - **development** If true, the shutdown process is much shorter, because it just 147 | terminates the server, ignoring open connections, shutdown function, finally 148 | function ... 149 | - **preShutdown** Place your own (not time-consuming) callback function here, if 150 | you need to have all HTTP sockets available and untouched during cleanup. 151 | Needs to return a promise. (async). If you add an input parameter to your 152 | cleanup function (optional), the SIGNAL type that caused the shutdown is 153 | passed to your cleanup function. See example. 154 | - **onShutdown** place your (not time-consuming) callback function, that will 155 | handle your additional cleanup things (e.g. close DB connections). Needs to 156 | return a promise. (async). If you add an input parameter to your cleanup 157 | function (optional), the SIGNAL type that caused the shutdown is passed to 158 | your cleanup function. See example. 159 | - **finally** here you can place a small (not time-consuming) function, that 160 | will be handled at the end of the shutdown e.g. for logging of shutdown. 161 | (sync) 162 | - **forceExit** force process.exit() at the end of the shutdown process, 163 | otherwise just let event loop clear 164 | 165 | ### Advanced Options Example 166 | 167 | You can pass an options-object to specify your specific options for the graceful 168 | shutdown 169 | 170 | The following example uses all possible options: 171 | 172 | ```js 173 | const gracefulShutdown = require('http-graceful-shutdown'); 174 | ... 175 | // app: can be http, https, express, koa, fastity, ... 176 | server = app.listen(...); 177 | ... 178 | 179 | // your personal cleanup function 180 | // - must return a promise 181 | // - the input parameter is optional (only needed if you want to 182 | // access the signal type inside this function) 183 | // - this function here in this example takes one second to complete 184 | function shutdownFunction(signal) { 185 | return new Promise((resolve) => { 186 | console.log('... called signal: ' + signal); 187 | console.log('... in cleanup') 188 | setTimeout(function() { 189 | console.log('... cleanup finished'); 190 | resolve(); 191 | }, 1000) 192 | }); 193 | } 194 | 195 | // finally function 196 | // -- sync function 197 | // -- should be very short (not time consuming) 198 | function finalFunction() { 199 | console.log('Server gracefulls shutted down.....') 200 | } 201 | 202 | // this enables the graceful shutdown with advanced options 203 | gracefulShutdown(server, 204 | { 205 | signals: 'SIGINT SIGTERM', 206 | timeout: 10000, // timeout: 10 secs 207 | development: false, // not in dev mode 208 | forceExit: true, // triggers process.exit() at the end of shutdown process 209 | preShutdown: preShutdownFunction, // needed operation before httpConnections are shutted down 210 | onShutdown: shutdownFunction, // shutdown function (async) - e.g. for cleanup DB, ... 211 | finally: finalFunction // finally function (sync) - e.g. for logging 212 | } 213 | ); 214 | ``` 215 | 216 | ### Trigger shutdown manually 217 | 218 | You can now trigger gracefulShutdown programatically (e.g. for tests) like so: 219 | 220 | ```js 221 | let shutdown 222 | beforeAll(() => { 223 | shutdown = gracefulShutdown(...) 224 | }) 225 | 226 | afterAll(async () => { 227 | await shutdown() 228 | }) 229 | ``` 230 | 231 | ### Do not force process.exit() 232 | 233 | With the `forceExit` option, you can define how your node server process ends: 234 | when setting `forceExit` to `false`, you just let the event loop clear and then 235 | the proccess ends automatically: 236 | 237 | ```js 238 | const gracefulShutdown = require('http-graceful-shutdown'); 239 | ... 240 | // app: can be http, https, express, koa, fastity, ... 241 | server = app.listen(...); 242 | ... 243 | 244 | // enable graceful shutdown with options: 245 | // this option lets the event loop clear to end your node server 246 | // no explicit process.exit() will be triggered. 247 | 248 | gracefulShutdown(server, { 249 | forceExit: false 250 | }); 251 | ``` 252 | 253 | If you want an explicit process.exit() at the end, set `forceExit` to `true` 254 | (which is the default). 255 | 256 | ### Debug 257 | 258 | If you want to get debug notes ([debug][debug-url] is a dependency of this 259 | module), just set the DEBUG environment variable to enable debugging: 260 | 261 | ``` 262 | export DEBUG=http-graceful-shutdown 263 | ``` 264 | 265 | OR on Windows: 266 | 267 | ``` 268 | set DEBUG=http-graceful-shutdown 269 | ``` 270 | 271 | ## Examples 272 | 273 | You can find examples how to use `http-graceful-shutdown` with Express, Koa, 274 | http, http2, fastify in the `examples` directory. To run the examples, be sure 275 | to install debug and express, koa or fastify. 276 | 277 | ``` 278 | npm install debug express koa fastify 279 | ``` 280 | 281 | ## Version history 282 | 283 | | Version | Date | Comment | 284 | | ------- | ---------- | ----------------------------------------------------------------- | 285 | | 3.1.14 | 2025-01-03 | updated docs | 286 | | 3.1.13 | 2023-02-11 | fix forceExit default value | 287 | | 3.1.12 | 2022-12-04 | changed lgtm to github scanning | 288 | | 3.1.11 | 2022-11-18 | updated examples | 289 | | 3.1.10 | 2022-11-17 | forceExit handling adapted | 290 | | 3.1.9 | 2022-10-24 | updated docs, code cleanup | 291 | | 3.1.8 | 2022-07-27 | updated docs, fixed typos | 292 | | 3.1.7 | 2022-03-18 | updated dependencies, updated docs | 293 | | 3.1.6 | 2022-02-27 | updated dependencies | 294 | | 3.1.5 | 2021-11-08 | updated docs | 295 | | 3.1.4 | 2021-08-27 | updated docs | 296 | | 3.1.3 | 2021-08-03 | fixed handle events once (thanks to Igor Basov) | 297 | | 3.1.2 | 2021-06-15 | fixed cleanupHttp() no timeout | 298 | | 3.1.1 | 2021-05-13 | updated docs | 299 | | 3.1.0 | 2021-05-08 | refactoring, added preShutdown | 300 | | 3.0.2 | 2021-04-08 | updated docs | 301 | | 3.0.1 | 2021-02-26 | code cleanup | 302 | | 3.0.0 | 2021-02-25 | version 3.0 release | 303 | | 2.4.0 | 2021-02-15 | added forceExit option (defaults to true) | 304 | | 2.3.2 | 2019-06-14 | typescript typings fix | 305 | | 2.3.1 | 2019-05-31 | updated docs, added typescript typings | 306 | | 2.3.0 | 2019-05-30 | added manual shutdown (for tests) see docs below | 307 | | 2.2.3 | 2019-02-01 | updated docs, debug | 308 | | 2.2.2 | 2018-12-28 | updated docs, keywords | 309 | | 2.2.1 | 2018-11-20 | updated docs | 310 | | 2.2.0 | 2018-11-19 | added (optional) signal type to shutdown function - see example | 311 | | 2.1.3 | 2018-11-06 | updated docs | 312 | | 2.1.2 | 2018-11-03 | updated dependencies (version bump), updated docs | 313 | | 2.1.1 | 2018-02-28 | extended `isFunction` to support e.g. AsyncFunctions | 314 | | 2.1.0 | 2018-02-11 | bug fixing onShutdown method was called before `server.close` | 315 | | 2.0.6 | 2017-11-06 | updated docs, code cleanup | 316 | | 2.0.5 | 2017-11-06 | updated dependencies, modifications gitignore, added docs | 317 | | 2.0.4 | 2017-09-21 | updated dependencies, modifications gitignore | 318 | | 2.0.3 | 2017-06-18 | updated dependencies | 319 | | 2.0.2 | 2017-05-27 | fixed return value 0 | 320 | | 2.0.1 | 2017-04-24 | modified documentation | 321 | | 2.0.0 | 2017-04-24 | added 'onShutdown' option, renamed 'callback' option to 'finally' | 322 | | 1.0.6 | 2016-02-03 | adding more explicit debug information and documentation | 323 | | 1.0.5 | 2016-02-01 | better handling of closing connections | 324 | | 1.0.4 | 2015-10-01 | small fixes | 325 | | 1.0.3 | 2015-09-15 | updated docs | 326 | | 1.0.1 | 2015-09-14 | updated docs, reformated code | 327 | | 1.0.0 | 2015-09-14 | initial release | 328 | 329 | ## Comments 330 | 331 | If you have ideas, comments or questions, please do not hesitate to contact me. 332 | 333 | Sincerely, 334 | 335 | Sebastian Hildebrandt, [+innovations](http://www.plus-innovations.com) 336 | 337 | ## Credits 338 | 339 | Written by Sebastian Hildebrandt 340 | [sebhildebrandt](https://github.com/sebhildebrandt) 341 | 342 | #### Contributors 343 | 344 | - Deepak Bhattarai [bring2dip](https://github.com/bring2dip) 345 | - Shen [shenfe](https://github.com/shenfe) 346 | - Jeff Hansen [jeffijoe](https://github.com/jeffijoe) 347 | - Igor Basov [IgorBasov](https://github.com/IgorBasov) 348 | 349 | ## License [![MIT license][license-img]][license-url] 350 | 351 | > The [`MIT`][license-url] License (MIT) 352 | > 353 | > Copyright © 2015-2025 Sebastian Hildebrandt, 354 | > [+innovations](http://www.plus-innovations.com). 355 | > 356 | > Permission is hereby granted, free of charge, to any person obtaining a copy 357 | > of this software and associated documentation files (the "Software"), to deal 358 | > in the Software without restriction, including without limitation the rights 359 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 360 | > copies of the Software, and to permit persons to whom the Software is 361 | > furnished to do so, subject to the following conditions: 362 | > 363 | > The above copyright notice and this permission notice shall be included in all 364 | > copies or substantial portions of the Software. 365 | > 366 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 367 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 368 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 369 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 370 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 371 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 372 | > SOFTWARE. 373 | 374 | [npm-image]: https://img.shields.io/npm/v/http-graceful-shutdown.svg?style=flat-square 375 | [npm-url]: https://npmjs.org/package/http-graceful-shutdown 376 | [downloads-image]: https://img.shields.io/npm/dm/http-graceful-shutdown.svg?style=flat-square 377 | [downloads-url]: https://npmjs.org/package/http-graceful-shutdown 378 | [license-url]: https://github.com/sebhildebrandt/http-graceful-shutdown/blob/master/LICENSE 379 | [license-img]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square 380 | [npmjs-license]: https://img.shields.io/npm/l/http-graceful-shutdown.svg?style=flat-square 381 | [caretaker-url]: https://github.com/sebhildebrandt 382 | [caretaker-image]: https://img.shields.io/badge/caretaker-sebhildebrandt-blue.svg?style=flat-square 383 | [nodejs-url]: https://nodejs.org/en/ 384 | [express-url]: https://github.com/strongloop/expressjs.com 385 | [koa-url]: https://github.com/koajs/koa 386 | [fastify-url]: https://www.fastify.io 387 | [http-url]: https://nodejs.org/api/http.html 388 | [http2-url]: https://nodejs.org/api/http2.html 389 | [debug-url]: https://github.com/visionmedia/debug 390 | [dependencies-url]: https://www.npmjs.com/package/http-graceful-shutdown?activeTab=dependencies 391 | [dependencies-img]: https://img.shields.io/librariesio/release/npm/http-graceful-shutdown.svg?style=flat-square 392 | [daviddm-url]: https://david-dm.org/sebhildebrandt/http-graceful-shutdown 393 | [daviddm-img]: https://img.shields.io/david/sebhildebrandt/http-graceful-shutdown.svg?style=flat-square 394 | [issues-img]: https://img.shields.io/github/issues/sebhildebrandt/http-graceful-shutdown.svg?style=flat-square 395 | [issues-url]: https://github.com/sebhildebrandt/http-graceful-shutdown/issues 396 | [closed-issues-img]: https://img.shields.io/github/issues-closed-raw/sebhildebrandt/http-graceful-shutdown.svg?style=flat-square&color=brightgreen 397 | [closed-issues-url]: https://github.com/sebhildebrandt/http-graceful-shutdown/issues?q=is%3Aissue+is%3Aclosed 398 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 23 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## You want to contribute some code? 2 | 3 | We are always looking for quality contributions and will be happy to accept your pull requests as long as they adhere to some basic rules: 4 | 5 | * Please make sure that your contribution fits well in the project's context: 6 | * we are aiming to provide a high quality multi platform library, without as less as possible dependencies 7 | 8 | * Please assure that you are submitting quality code, specifically make sure that: 9 | * your commits should not be braking changes - if possible. 10 | * your PR are well testet 11 | * if your commit needs a major version bump (breaking change), please leave a clear message in your comments 12 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Issue Type 2 | 3 | Describe your bug here ... 4 | ### Expected behavior 5 | 6 | - 7 | - 8 | - 9 | 10 | ### Actual behavior 11 | 12 | - 13 | - 14 | - 15 | 16 | ### Steps to reproduce the problem 17 | 18 | - 19 | - 20 | - 21 | 22 | ### Specifications 23 | 24 | - package version: 25 | - operating system: 26 | - hardware: 27 | 28 | ### Problem and possible solution 29 | 30 | If you have a solution in mind (or any idea what could cause this issue), we would be happy, if you can describe it here ... 31 | -------------------------------------------------------------------------------- /docs/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Pull Request 2 | 3 | Fixes # 4 | 5 | #### Changes proposed: 6 | 7 | Specify weather this is a fix / enhancement / update 8 | 9 | #### Description (what is this PR about) 10 | 11 | Brief explanation of the changes you have made and/or the new content you are contributing. 12 | 13 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | 4 | Here you find examples how to use http-graceful-shutdown with `Express`, `Koa`, `http`, `http2`, `fastify`. 5 | 6 | ## Prerequisites 7 | 8 | To run the examples, be sure to install debug and express, koa or fastify. 9 | 10 | ``` 11 | npm install debug express koa fastify 12 | ``` 13 | 14 | ## Starting Examples 15 | 16 | Then you can start each of the examples, e.g.: 17 | 18 | ``` 19 | node express-simple-server.js 20 | node express-advanced-server.js 21 | node express-noexit-server.js 22 | node fastify-simple-server.js 23 | node koa-simple-server.js 24 | node http-simple-server.js 25 | node http2-simple-server.js 26 | ``` 27 | 28 | To stop each of the application (and see `http-graceful-shutdown` in action), just press `CTRL-C`. Just have a look at the source code of each of the examples to see details how to use and configure `http-graceful-shutdown`. 29 | -------------------------------------------------------------------------------- /examples/express-advanced-server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const gracefulShutdown = require('../lib/index'); 3 | const app = express(); 4 | const port = 3000; 5 | 6 | const server = app.listen(port, () => { 7 | console.log('HTTP-GRACEFUL-SHUTDOWN'); 8 | console.log('-------------------------------------------'); 9 | console.log('Advanced EXPRESS test using advanced options and cleanup function'); 10 | console.log(`Listening at http://localhost:${port}`); 11 | console.log(); 12 | console.log('Press Ctrl-C to test shutdown'); 13 | }); 14 | 15 | app.get('/', (req, res) => { 16 | setTimeout(() => { 17 | res.send('Hello World!'); 18 | }, 15000); 19 | 20 | }); 21 | 22 | // personal preShutdown function 23 | // - must return a promise 24 | // - the input parameter is optional (only needed if you want to 25 | // access the signal type inside this function) 26 | // - used, when you need to have HTTP sockets still available and untouched by shutdown process 27 | // - this function here in this example takes 500ms to complete 28 | function preShutdown(signal) { 29 | return new Promise((resolve) => { 30 | console.log(); 31 | console.log('"preShutdown" function'); 32 | console.log('... called signal: ' + signal); 33 | console.log('... for 500 ms'); 34 | console.log('...'); 35 | setTimeout(function () { 36 | console.log('... preShutdown finished'); 37 | resolve(); 38 | }, 500); 39 | }); 40 | } 41 | // personal cleanup function 42 | // - must return a promise 43 | // - the input parameter is optional (only needed if you want to 44 | // access the signal type inside this function) 45 | // - this function here in this example takes one second to complete 46 | function cleanup(signal) { 47 | return new Promise((resolve) => { 48 | console.log(); 49 | console.log('"onShutdown" function'); 50 | console.log('... called signal: ' + signal); 51 | console.log('... in cleanup'); 52 | console.log('... for 5 seconds'); 53 | console.log('...'); 54 | setTimeout(function () { 55 | console.log('... cleanup finished'); 56 | resolve(); 57 | }, 5000); 58 | }); 59 | } 60 | 61 | // this enables the graceful shutdown with advanced options 62 | gracefulShutdown(server, 63 | { 64 | signals: 'SIGINT SIGTERM', 65 | timeout: 3000, 66 | development: false, 67 | preShutdown: preShutdown, 68 | onShutdown: cleanup, 69 | forceExit: true, 70 | finally: function () { 71 | console.log(); 72 | console.log('In "finally" function'); 73 | console.log('Server graceful shut down completed.'); 74 | } 75 | } 76 | ); 77 | -------------------------------------------------------------------------------- /examples/express-noexit-server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const gracefulShutdown = require('../lib/index'); 3 | const app = express(); 4 | const port = 3000; 5 | 6 | const server = app.listen(port, () => { 7 | console.log('HTTP-GRACEFUL-SHUTDOWN'); 8 | console.log('-----------------------------------------'); 9 | console.log('Simple EXPRESS test - no force exit'); 10 | console.log('Here we do not force process.exit()'); 11 | console.log('Instead, we rely on event loop to clear up'); 12 | console.log(); 13 | console.log(`Listening at http://localhost:${port}`); 14 | console.log(); 15 | console.log('Press Ctrl-C to test shutdown'); 16 | }); 17 | 18 | app.get('/', (req, res) => { 19 | res.send('Hello World!'); 20 | }); 21 | 22 | gracefulShutdown(server, 23 | { 24 | forceExit: false, // do not perform process.exit() 25 | finally: function () { 26 | console.log(); 27 | console.log('Server graceful shut down completed.'); 28 | } 29 | } 30 | ); 31 | 32 | -------------------------------------------------------------------------------- /examples/express-simple-server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const gracefulShutdown = require('../lib/index'); 3 | const app = express(); 4 | const port = 3000; 5 | 6 | const server = app.listen(port, () => { 7 | console.log('HTTP-GRACEFUL-SHUTDOWN'); 8 | console.log('-----------------------------------------'); 9 | console.log('Simple EXPRESS test using default options'); 10 | console.log(`Listening at http://localhost:${port}`); 11 | console.log(); 12 | console.log('Press Ctrl-C to test shutdown'); 13 | }); 14 | 15 | app.get('/', (req, res) => { 16 | res.send('Hello World!'); 17 | }); 18 | 19 | gracefulShutdown(server, 20 | { 21 | finally: function () { 22 | console.log(); 23 | console.log('Server graceful shut down completed.'); 24 | } 25 | } 26 | ); 27 | 28 | -------------------------------------------------------------------------------- /examples/fastity-simple-server.js: -------------------------------------------------------------------------------- 1 | const Fastify = require('fastify'); 2 | const gracefulShutdown = require('../lib/index'); 3 | const port = 3000; 4 | 5 | const fastify = Fastify(); 6 | 7 | // Declare a route 8 | fastify.get('/', function (request, reply) { 9 | reply.send({ hello: 'world' }); 10 | }); 11 | 12 | // Run the server! 13 | fastify.listen(port, function (err, address) { 14 | if (err) { 15 | fastify.log.error(err); 16 | process.exit(1); 17 | } 18 | console.log('HTTP-GRACEFUL-SHUTDOWN'); 19 | console.log('-------------------------------------------'); 20 | console.log('Simple FASTIFY test using default options'); 21 | console.log(`Listening at http://localhost:${port}`); 22 | console.log(); 23 | console.log('Press Ctrl-C to test shutdown'); 24 | }); 25 | 26 | gracefulShutdown(fastify.server, 27 | { 28 | finally: function () { 29 | console.log(); 30 | console.log('Server graceful shut down completed.'); 31 | } 32 | } 33 | ); 34 | 35 | -------------------------------------------------------------------------------- /examples/http-simple-server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const gracefulShutdown = require('../lib/index'); 3 | const port = 3000; 4 | 5 | const server = http.createServer((req, res) => { 6 | res.setHeader('Content-Type', 'text/html; charset=utf-8'); 7 | res.writeHead(200); 8 | res.end('Hello World'); 9 | }); 10 | 11 | server.listen(port, () => { 12 | console.log('HTTP-GRACEFUL-SHUTDOWN'); 13 | console.log('-----------------------------------------'); 14 | console.log('Simple HTTP test using default options'); 15 | console.log(`Listening at http://localhost:${port}`); 16 | console.log(); 17 | console.log('Press Ctrl-C to test shutdown'); 18 | }); 19 | 20 | gracefulShutdown(server, 21 | { 22 | finally: function () { 23 | console.log(); 24 | console.log('Server graceful shut down completed.'); 25 | } 26 | } 27 | ); 28 | 29 | -------------------------------------------------------------------------------- /examples/http2-simple-server.js: -------------------------------------------------------------------------------- 1 | const http2 = require('http2'); 2 | const gracefulShutdown = require('../lib/index'); 3 | const port = 3000; 4 | 5 | const server = http2.createServer(); 6 | 7 | server.on('stream', (stream, headers) => { 8 | stream.respond({ 9 | 'content-type': 'text/html; charset=utf-8', 10 | ':status': 200 11 | }); 12 | stream.end('

Hello World

'); 13 | }); 14 | 15 | server.listen(port, () => { 16 | console.log('HTTP-GRACEFUL-SHUTDOWN'); 17 | console.log('-----------------------------------------'); 18 | console.log('Simple HTTP2 test using default options'); 19 | console.log(`Listening at http://localhost:${port}`); 20 | console.log(); 21 | console.log('Press Ctrl-C to test shutdown'); 22 | }); 23 | 24 | gracefulShutdown(server, 25 | { 26 | finally: function () { 27 | console.log(); 28 | console.log('Server graceful shut down completed.'); 29 | } 30 | } 31 | ); 32 | 33 | -------------------------------------------------------------------------------- /examples/koa-simple-server.js: -------------------------------------------------------------------------------- 1 | const koa = require('koa'); 2 | const gracefulShutdown = require('../lib/index'); 3 | const app = new koa(); 4 | const port = 3000; 5 | 6 | const server = app.listen(port, () => { 7 | console.log('HTTP-GRACEFUL-SHUTDOWN'); 8 | console.log('-------------------------------------------'); 9 | console.log('Simple KOA test using default options'); 10 | console.log(`Listening at http://localhost:${port}`); 11 | console.log(); 12 | console.log('Press Ctrl-C to test shutdown'); 13 | }); 14 | 15 | app.use(async ctx => { 16 | ctx.body = 'Hello World'; 17 | }); 18 | 19 | gracefulShutdown(server, 20 | { 21 | finally: function () { 22 | console.log(); 23 | console.log('Server graceful shut down completed.'); 24 | } 25 | } 26 | ); 27 | 28 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for http-graceful-shutdown 2.4 2 | // Project: https://github.com/sebhildebrandt/http-graceful-shutdown 3 | // Definitions by: sebhildebrandt 4 | 5 | /// 6 | 7 | declare function GracefulShutdown( 8 | server: any, 9 | options?: GracefulShutdown.Options 10 | ): () => Promise 11 | 12 | declare namespace GracefulShutdown { 13 | interface Options { 14 | signals?: string; 15 | timeout?: number; 16 | development?: boolean; 17 | forceExit?: boolean; 18 | preShutdown?: (signal?: string) => Promise; 19 | onShutdown?: (signal?: string) => Promise; 20 | finally?: () => void; 21 | } 22 | } 23 | 24 | export = GracefulShutdown; 25 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // ============================================================================= 3 | // _ 4 | // |_ _|_ _|_ ._ __ _ ._ _. _ _ _|_ | __ _ |_ _|_ _| _ ._ 5 | // | | |_ |_ |_) (_| | (_| (_ (/_ | |_| | _> | | |_| |_ (_| (_) \/\/ | | 6 | // | _| 7 | // ----------------------------------------------------------------------------- 8 | // gracefully shuts downs http server 9 | // can be used with http, express, koa, ... 10 | // (c) 2025 Sebastian Hildebrandt 11 | // License: MIT 12 | // ============================================================================= 13 | 14 | const debug = require('debug')('http-graceful-shutdown'); 15 | const http = require('http'); 16 | 17 | /** 18 | * Gracefully shuts down `server` when the process receives 19 | * the passed signals 20 | * 21 | * @param {http.Server} server 22 | * @param {object} opts 23 | * signals: string (each signal separated by SPACE) 24 | * timeout: timeout value for forceful shutdown in ms 25 | * forceExit: force process.exit() - otherwise just let event loop clear 26 | * development: boolean value (if true, no graceful shutdown to speed up development 27 | * preShutdown: optional function. Needs to return a promise. - HTTP sockets are still available and untouched 28 | * onShutdown: optional function. Needs to return a promise. 29 | * finally: optional function, handled at the end of the shutdown. 30 | */ 31 | 32 | function GracefulShutdown(server, opts) { 33 | 34 | // option handling 35 | // ---------------------------------- 36 | opts = opts || {}; 37 | 38 | // merge opts with default options 39 | let options = Object.assign({ 40 | signals: 'SIGINT SIGTERM', 41 | timeout: 30000, 42 | development: false, 43 | forceExit: true, 44 | onShutdown: (signal) => Promise.resolve(signal), 45 | preShutdown: (signal) => Promise.resolve(signal), 46 | }, opts); 47 | 48 | let isShuttingDown = false; 49 | let connections = {}; 50 | let connectionCounter = 0; 51 | let secureConnections = {}; 52 | let secureConnectionCounter = 0; 53 | let failed = false; 54 | let finalRun = false; 55 | 56 | function onceFactory() { 57 | let called = false; 58 | return (emitter, events, callback) => { 59 | function call() { 60 | if (!called) { 61 | called = true; 62 | return callback.apply(this, arguments); 63 | } 64 | } 65 | events.forEach(e => emitter.on(e, call)); 66 | }; 67 | } 68 | 69 | const signals = options.signals 70 | .split(' ') 71 | .map(s => s.trim()) 72 | .filter(s => !!s.length); 73 | 74 | const once = onceFactory(); 75 | 76 | once(process, signals, (signal) => { 77 | debug('received shut down signal', signal); 78 | shutdown(signal) 79 | .then(() => { 80 | if (options.forceExit) { 81 | process.exit(failed ? 1 : 0); 82 | } 83 | }) 84 | .catch((err) => { 85 | debug('server shut down error occurred', err); 86 | process.exit(1); 87 | }); 88 | }); 89 | 90 | // helper function 91 | // ---------------------------------- 92 | function isFunction(functionToCheck) { 93 | let getType = Object.prototype.toString.call(functionToCheck); 94 | return /^\[object\s([a-zA-Z]+)?Function\]$/.test(getType); 95 | } 96 | 97 | function destroy(socket, force = false) { 98 | if ((socket._isIdle && isShuttingDown) || force) { 99 | socket.destroy(); 100 | if (socket.server instanceof http.Server) { 101 | delete connections[socket._connectionId]; 102 | } else { 103 | delete secureConnections[socket._connectionId]; 104 | } 105 | } 106 | } 107 | 108 | function destroyAllConnections(force = false) { 109 | 110 | // destroy empty and idle connections / all connections (if force = true) 111 | debug('Destroy Connections : ' + (force ? 'forced close' : 'close')); 112 | let counter = 0; 113 | let secureCounter = 0; 114 | Object.keys(connections).forEach(function (key) { 115 | const socket = connections[key]; 116 | const serverResponse = socket._httpMessage; 117 | 118 | // send connection close header to open connections 119 | if (serverResponse && !force) { 120 | if (!serverResponse.headersSent) { 121 | serverResponse.setHeader('connection', 'close'); 122 | } 123 | } else { 124 | counter++; 125 | destroy(socket); 126 | } 127 | }); 128 | 129 | debug('Connections destroyed : ' + counter); 130 | debug('Connection Counter : ' + connectionCounter); 131 | 132 | Object.keys(secureConnections).forEach(function (key) { 133 | const socket = secureConnections[key]; 134 | const serverResponse = socket._httpMessage; 135 | 136 | // send connection close header to open connections 137 | if (serverResponse && !force) { 138 | if (!serverResponse.headersSent) { 139 | serverResponse.setHeader('connection', 'close'); 140 | } 141 | } else { 142 | secureCounter++; 143 | destroy(socket); 144 | } 145 | }); 146 | 147 | debug('Secure Connections destroyed : ' + secureCounter); 148 | debug('Secure Connection Counter : ' + secureConnectionCounter); 149 | } 150 | 151 | // set up server/process events 152 | // ---------------------------------- 153 | server.on('request', function (req, res) { 154 | req.socket._isIdle = false; 155 | if (isShuttingDown && !res.headersSent) { 156 | res.setHeader('connection', 'close'); 157 | } 158 | 159 | res.on('finish', function () { 160 | req.socket._isIdle = true; 161 | destroy(req.socket); 162 | }); 163 | }); 164 | 165 | server.on('connection', function (socket) { 166 | if (isShuttingDown) { 167 | socket.destroy(); 168 | } else { 169 | let id = connectionCounter++; 170 | socket._isIdle = true; 171 | socket._connectionId = id; 172 | connections[id] = socket; 173 | 174 | socket.once('close', () => { 175 | delete connections[socket._connectionId]; 176 | }); 177 | } 178 | }); 179 | 180 | server.on('secureConnection', (socket) => { 181 | 182 | if (isShuttingDown) { 183 | socket.destroy(); 184 | } else { 185 | let id = secureConnectionCounter++; 186 | socket._isIdle = true; 187 | socket._connectionId = id; 188 | secureConnections[id] = socket; 189 | 190 | socket.once('close', () => { 191 | delete secureConnections[socket._connectionId]; 192 | }); 193 | } 194 | }); 195 | 196 | process.on('close', function () { 197 | debug('closed'); 198 | }); 199 | 200 | // shutdown event (per signal) 201 | // ---------------------------------- 202 | function shutdown(sig) { 203 | 204 | function cleanupHttp() { 205 | destroyAllConnections(); 206 | debug('Close http server'); 207 | 208 | return new Promise((resolve, reject) => { 209 | server.close((err) => { 210 | if (err) { 211 | return reject(err); 212 | } 213 | return resolve(true); 214 | }); 215 | }); 216 | } 217 | 218 | debug('shutdown signal - ' + sig); 219 | 220 | // Don't bother with graceful shutdown on development to speed up round trip 221 | if (options.development) { 222 | debug('DEV-Mode - immediate forceful shutdown'); 223 | return process.exit(0); 224 | } 225 | 226 | function finalHandler() { 227 | if (!finalRun) { 228 | finalRun = true; 229 | if (options.finally && isFunction(options.finally)) { 230 | debug('executing finally()'); 231 | options.finally(); 232 | } 233 | } 234 | 235 | return Promise.resolve(); 236 | } 237 | 238 | // returns true if should force shut down. returns false for shut down without force 239 | function waitForReadyToShutDown(totalNumInterval) { 240 | debug(`waitForReadyToShutDown... ${totalNumInterval}`); 241 | 242 | if (totalNumInterval === 0) { // timeout reached 243 | debug( 244 | `Could not close connections in time (${options.timeout}ms), will forcefully shut down` 245 | ); 246 | return Promise.resolve(true); 247 | } 248 | 249 | // test all connections closed already? 250 | const allConnectionsClosed = Object.keys(connections).length === 0 && Object.keys(secureConnections).length === 0; 251 | 252 | if (allConnectionsClosed) { 253 | debug('All connections closed. Continue to shutting down'); 254 | return Promise.resolve(false); 255 | } 256 | 257 | debug('Schedule the next waitForReadyToShutdown'); 258 | return new Promise((resolve) => { 259 | setTimeout(() => { 260 | resolve(waitForReadyToShutDown(totalNumInterval - 1)); 261 | }, 250); 262 | }); 263 | } 264 | 265 | if (isShuttingDown) { 266 | return Promise.resolve(); 267 | } 268 | 269 | debug('shutting down'); 270 | 271 | return options 272 | .preShutdown(sig) 273 | .then(() => { 274 | isShuttingDown = true; 275 | cleanupHttp(); 276 | }) 277 | .then(() => { 278 | const pollIterations = options.timeout 279 | ? Math.round(options.timeout / 250) 280 | : 0; 281 | 282 | return waitForReadyToShutDown(pollIterations); 283 | }) 284 | .then((force) => { 285 | debug('Do onShutdown now'); 286 | 287 | // if after waiting for connections to drain within timeout period 288 | // or if timeout has reached, we forcefully disconnect all sockets 289 | if (force) { 290 | destroyAllConnections(force); 291 | } 292 | 293 | return options.onShutdown(sig); 294 | }) 295 | .then(finalHandler) 296 | .catch((err) => { 297 | const errString = typeof err === 'string' ? err : JSON.stringify(err); 298 | debug(errString); 299 | failed = true; 300 | throw errString; 301 | }); 302 | } 303 | 304 | function shutdownManual() { 305 | return shutdown('manual'); 306 | } 307 | 308 | return shutdownManual; 309 | } 310 | 311 | module.exports = GracefulShutdown; 312 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-graceful-shutdown", 3 | "version": "3.1.14", 4 | "description": "gracefully shuts downs http server", 5 | "main": "./lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "files": [ 11 | "lib/" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/sebhildebrandt/http-graceful-shutdown.git" 16 | }, 17 | "keywords": [ 18 | "http", 19 | "https", 20 | "koa", 21 | "express", 22 | "fastify", 23 | "shutdown", 24 | "graceful", 25 | "force", 26 | "graceful-shutdown", 27 | "http-shutdown", 28 | "http-terminate", 29 | "shutdown", 30 | "kubernetes", 31 | "prometheus", 32 | "backend" 33 | ], 34 | "author": "Sebastian Hildebrandt (https://plus-innovations.com)", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/sebhildebrandt/http-graceful-shutdown/issues" 38 | }, 39 | "homepage": "https://github.com/sebhildebrandt/http-graceful-shutdown", 40 | "dependencies": { 41 | "debug": "^4.3.4" 42 | }, 43 | "engineStrict": true, 44 | "engines": { 45 | "node": ">=4.0.0" 46 | } 47 | } 48 | --------------------------------------------------------------------------------