├── .eslintignore ├── .eslintrc ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ └── scorecards.yml ├── .gitignore ├── HISTORY.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .eslintrc └── test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard" 3 | } 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: monthly 12 | open-pull-requests-limit: 10 13 | ignore: 14 | - dependency-name: "*" 15 | update-types: ["version-update:semver-major"] 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | test: 16 | permissions: 17 | checks: write # for coverallsapp/github-action to create new checks 18 | contents: read # for actions/checkout to fetch code 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | name: 23 | - Node.js 0.8 24 | - Node.js 0.10 25 | - Node.js 0.12 26 | - io.js 1.x 27 | - io.js 2.x 28 | - io.js 3.x 29 | - Node.js 4.x 30 | - Node.js 5.x 31 | - Node.js 6.x 32 | - Node.js 7.x 33 | - Node.js 8.x 34 | - Node.js 9.x 35 | - Node.js 10.x 36 | - Node.js 11.x 37 | - Node.js 12.x 38 | - Node.js 13.x 39 | - Node.js 14.x 40 | - Node.js 15.x 41 | - Node.js 16.x 42 | - Node.js 17.x 43 | - Node.js 18.x 44 | - Node.js 19.x 45 | - Node.js 20.x 46 | - Node.js 21.x 47 | - Node.js 22.x 48 | 49 | include: 50 | 51 | - name: Node.js 0.8 52 | node-version: "0.8" 53 | npm-i: mocha@2.5.3 54 | npm-rm: nyc 55 | 56 | - name: Node.js 0.10 57 | node-version: "0.10" 58 | npm-i: mocha@3.5.3 nyc@10.3.2 59 | 60 | - name: Node.js 0.12 61 | node-version: "0.12" 62 | npm-i: mocha@3.5.3 nyc@10.3.2 63 | 64 | - name: io.js 1.x 65 | node-version: "1.8" 66 | npm-i: mocha@3.5.3 nyc@10.3.2 67 | 68 | - name: io.js 2.x 69 | node-version: "2.5" 70 | npm-i: mocha@3.5.3 nyc@10.3.2 71 | 72 | - name: io.js 3.x 73 | node-version: "3.3" 74 | npm-i: mocha@3.5.3 nyc@10.3.2 75 | 76 | - name: Node.js 4.x 77 | node-version: "4.9" 78 | npm-i: mocha@5.2.0 nyc@11.9.0 79 | 80 | - name: Node.js 5.x 81 | node-version: "5.12" 82 | npm-i: mocha@5.2.0 nyc@11.9.0 83 | 84 | - name: Node.js 6.x 85 | node-version: "6.17" 86 | npm-i: mocha@6.2.2 nyc@14.1.1 87 | 88 | - name: Node.js 7.x 89 | node-version: "7.10" 90 | npm-i: mocha@6.2.2 nyc@14.1.1 91 | 92 | - name: Node.js 8.x 93 | node-version: "8.17" 94 | npm-i: mocha@7.1.2 nyc@14.1.1 95 | 96 | - name: Node.js 9.x 97 | node-version: "9.11" 98 | npm-i: mocha@7.1.2 nyc@14.1.1 99 | 100 | - name: Node.js 10.x 101 | node-version: "10.24" 102 | npm-i: mocha@8.4.0 103 | 104 | - name: Node.js 11.x 105 | node-version: "11.15" 106 | npm-i: mocha@8.4.0 107 | 108 | - name: Node.js 12.x 109 | node-version: "12.22" 110 | npm-i: mocha@9.2.2 111 | 112 | - name: Node.js 13.x 113 | node-version: "13.14" 114 | npm-i: mocha@9.2.2 115 | 116 | - name: Node.js 14.x 117 | node-version: "14.21" 118 | 119 | - name: Node.js 15.x 120 | node-version: "15.14" 121 | 122 | - name: Node.js 16.x 123 | node-version: "16.20" 124 | 125 | - name: Node.js 17.x 126 | node-version: "17.9" 127 | 128 | - name: Node.js 18.x 129 | node-version: "18.18" 130 | 131 | - name: Node.js 19.x 132 | node-version: "19" 133 | 134 | - name: Node.js 20.x 135 | node-version: "20" 136 | 137 | - name: Node.js 21.x 138 | node-version: "21" 139 | 140 | - name: Node.js 22.x 141 | node-version: "22" 142 | 143 | steps: 144 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 145 | 146 | - name: Install Node.js ${{ matrix.node-version }} 147 | shell: bash -eo pipefail -l {0} 148 | run: | 149 | nvm install --default ${{ matrix.node-version }} 150 | if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then 151 | nvm install --alias=npm 0.10 152 | nvm use ${{ matrix.node-version }} 153 | if [[ "$(npm -v)" == 1.1.* ]]; then 154 | nvm exec npm npm install -g npm@1.1 155 | ln -fs "$(which npm)" "$(dirname "$(nvm which npm)")/npm" 156 | else 157 | sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" 158 | fi 159 | npm config set strict-ssl false 160 | fi 161 | dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" 162 | 163 | - name: Configure npm 164 | run: | 165 | if [[ "$(npm config get package-lock)" == "true" ]]; then 166 | npm config set package-lock false 167 | else 168 | npm config set shrinkwrap false 169 | fi 170 | 171 | - name: Remove npm module(s) ${{ matrix.npm-rm }} 172 | run: npm rm --silent --save-dev ${{ matrix.npm-rm }} 173 | if: matrix.npm-rm != '' 174 | 175 | - name: Install npm module(s) ${{ matrix.npm-i }} 176 | run: npm install --save-dev ${{ matrix.npm-i }} 177 | if: matrix.npm-i != '' 178 | 179 | - name: Setup Node.js version-specific dependencies 180 | shell: bash 181 | run: | 182 | # eslint for linting 183 | # - remove on Node.js < 8 184 | if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 8 ]]; then 185 | node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ 186 | grep -E '^eslint(-|$)' | \ 187 | sort -r | \ 188 | xargs -n1 npm rm --silent --save-dev 189 | fi 190 | 191 | - name: Install Node.js dependencies 192 | run: npm install 193 | 194 | - name: List environment 195 | id: list_env 196 | shell: bash 197 | run: | 198 | echo "node@$(node -v)" 199 | echo "npm@$(npm -v)" 200 | npm -s ls ||: 201 | (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' 202 | 203 | - name: Run tests 204 | shell: bash 205 | run: | 206 | if npm -ps ls nyc | grep -q nyc; then 207 | npm run test-ci 208 | else 209 | npm test 210 | fi 211 | 212 | - name: Lint code 213 | if: steps.list_env.outputs.eslint != '' 214 | run: npm run lint 215 | 216 | - name: Collect code coverage 217 | uses: coverallsapp/github-action@09b709cf6a16e30b0808ba050c7a6e8a5ef13f8d # master 218 | if: steps.list_env.outputs.nyc != '' 219 | with: 220 | github-token: ${{ secrets.GITHUB_TOKEN }} 221 | flag-name: run-${{ matrix.test_number }} 222 | parallel: true 223 | 224 | coverage: 225 | permissions: 226 | checks: write # for coverallsapp/github-action to create new checks 227 | needs: test 228 | runs-on: ubuntu-latest 229 | steps: 230 | - name: Upload code coverage 231 | uses: coverallsapp/github-action@09b709cf6a16e30b0808ba050c7a6e8a5ef13f8d # master 232 | with: 233 | github-token: ${{ secrets.GITHUB_TOKEN }} 234 | parallel-finished: true 235 | -------------------------------------------------------------------------------- /.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: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 42 | with: 43 | languages: javascript 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | 48 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 49 | # If this step fails, then you should remove it and run the build manually (see below) 50 | # - name: Autobuild 51 | # uses: github/codeql-action/autobuild@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 52 | 53 | # ℹ️ Command-line programs to run using the OS shell. 54 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 55 | 56 | # If the Autobuild fails above, remove it and uncomment the following three lines. 57 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 58 | 59 | # - run: | 60 | # echo "Run, Build Application using script" 61 | # ./location_of_script_within_repo/buildscript.sh 62 | 63 | - name: Perform CodeQL Analysis 64 | uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 65 | with: 66 | category: "/language:javascript" -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '20 7 * * 2' 14 | push: 15 | branches: ["master"] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | contents: read 30 | actions: read 31 | # To allow GraphQL ListCommits to work 32 | issues: read 33 | pull-requests: read 34 | # To detect SAST tools 35 | checks: read 36 | 37 | steps: 38 | - name: "Checkout code" 39 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 40 | with: 41 | persist-credentials: false 42 | 43 | - name: "Run analysis" 44 | uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 45 | with: 46 | results_file: results.sarif 47 | results_format: sarif 48 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 49 | # - you want to enable the Branch-Protection check on a *public* repository, or 50 | # - you are installing Scorecards on a *private* repository 51 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 52 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 53 | 54 | # Public repositories: 55 | # - Publish results to OpenSSF REST API for easy access by consumers 56 | # - Allows the repository to include the Scorecard badge. 57 | # - See https://github.com/ossf/scorecard-action#publishing-results. 58 | # For private repositories: 59 | # - `publish_results` will always be set to `false`, regardless 60 | # of the value entered here. 61 | publish_results: true 62 | 63 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 64 | # format to the repository Actions tab. 65 | - name: "Upload artifact" 66 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 67 | with: 68 | name: SARIF file 69 | path: results.sarif 70 | retention-days: 5 71 | 72 | # Upload the results to GitHub's code scanning dashboard. 73 | - name: "Upload to code-scanning" 74 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 75 | with: 76 | sarif_file: results.sarif 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | npm-debug.log 4 | package-lock.json -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 2.3.3 / 2024-10-07 2 | ========== 3 | 4 | * deps: depd@~2.0.0 5 | - Remove remove use of `eval` 6 | * deps: depd@~1.1.2 7 | - Remove unnecessary `Buffer` loading 8 | - perf: remove argument reassignment 9 | 10 | 2.3.2 / 2015-11-15 11 | ================== 12 | 13 | * deps: depd@~1.1.0 14 | - Enable strict mode in more places 15 | - Support web browser loading 16 | * deps: on-headers@~1.0.1 17 | - perf: enable strict mode 18 | * perf: enable strict mode 19 | 20 | 2.3.1 / 2015-05-14 21 | ================== 22 | 23 | * deps: depd@~1.0.1 24 | 25 | 2.3.0 / 2015-02-15 26 | ================== 27 | 28 | * Add function argument to support recording of response time 29 | 30 | 2.2.0 / 2014-09-22 31 | ================== 32 | 33 | * Add `suffix` option 34 | * deps: depd@~1.0.0 35 | 36 | 2.1.0 / 2014-09-16 37 | ================== 38 | 39 | * Add `header` option for custom header name 40 | * Change `digits` argument to an `options` argument 41 | 42 | 2.0.1 / 2014-08-10 43 | ================== 44 | 45 | * deps: on-headers@~1.0.0 46 | 47 | 2.0.0 / 2014-05-31 48 | ================== 49 | 50 | * add `digits` argument 51 | * do not override existing `X-Response-Time` header 52 | * timer not subject to clock drift 53 | * timer resolution down to nanoseconds 54 | * use `on-headers` module 55 | 56 | 1.0.0 / 2014-02-08 57 | ================== 58 | 59 | * Genesis from `connect` 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 Jonathan Ong 4 | Copyright (c) 2014-2015 Douglas Christopher Wilson 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # response-time 2 | 3 | [![NPM Version][npm-version-image]][npm-url] 4 | [![NPM Downloads][npm-downloads-image]][npm-url] 5 | [![Node.js Version][node-image]][node-url] 6 | [![Build Status][ci-image]][ci-url] 7 | [![Test Coverage][coveralls-image]][coveralls-url] 8 | 9 | Response time for Node.js servers. 10 | 11 | This module creates a middleware that records the response time for 12 | requests in HTTP servers. The "response time" is defined here as the 13 | elapsed time from when a request enters this middleware to when the 14 | headers are written out to the client. 15 | 16 | ## Installation 17 | 18 | This is a [Node.js](https://nodejs.org/en/) module available through the 19 | [npm registry](https://www.npmjs.com/). Installation is done using the 20 | [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): 21 | 22 | ```sh 23 | $ npm install response-time 24 | ``` 25 | 26 | ## API 27 | 28 | 29 | 30 | ```js 31 | var responseTime = require('response-time') 32 | ``` 33 | 34 | ### responseTime([options]) 35 | 36 | Create a middleware that adds a `X-Response-Time` header to responses. If 37 | you don't want to use this module to automatically set a header, please 38 | see the section about [`responseTime(fn)`](#responsetimefn). 39 | 40 | #### Options 41 | 42 | The `responseTime` function accepts an optional `options` object that may 43 | contain any of the following keys: 44 | 45 | ##### digits 46 | 47 | The fixed number of digits to include in the output, which is always in 48 | milliseconds, defaults to `3` (ex: `2.300ms`). 49 | 50 | ##### header 51 | 52 | The name of the header to set, defaults to `X-Response-Time`. 53 | 54 | ##### suffix 55 | 56 | Boolean to indicate if units of measurement suffix should be added to 57 | the output, defaults to `true` (ex: `2.300ms` vs `2.300`). 58 | 59 | ### responseTime(fn) 60 | 61 | Create a new middleware that records the response time of a request and 62 | makes this available to your own function `fn`. The `fn` argument will be 63 | invoked as `fn(req, res, time)`, where `time` is a number in milliseconds. 64 | 65 | ## Examples 66 | 67 | ### express/connect 68 | 69 | ```js 70 | var express = require('express') 71 | var responseTime = require('response-time') 72 | 73 | var app = express() 74 | 75 | app.use(responseTime()) 76 | 77 | app.get('/', function (req, res) { 78 | res.send('hello, world!') 79 | }) 80 | ``` 81 | 82 | ### vanilla http server 83 | 84 | ```js 85 | var finalhandler = require('finalhandler') 86 | var http = require('http') 87 | var responseTime = require('response-time') 88 | 89 | // create "middleware" 90 | var _responseTime = responseTime() 91 | 92 | http.createServer(function (req, res) { 93 | var done = finalhandler(req, res) 94 | _responseTime(req, res, function (err) { 95 | if (err) return done(err) 96 | 97 | // respond to request 98 | res.setHeader('content-type', 'text/plain') 99 | res.end('hello, world!') 100 | }) 101 | }) 102 | ``` 103 | 104 | ### response time metrics 105 | 106 | ```js 107 | var express = require('express') 108 | var responseTime = require('response-time') 109 | var StatsD = require('node-statsd') 110 | 111 | var app = express() 112 | var stats = new StatsD() 113 | 114 | stats.socket.on('error', function (error) { 115 | console.error(error.stack) 116 | }) 117 | 118 | app.use(responseTime(function (req, res, time) { 119 | var stat = (req.method + req.url).toLowerCase() 120 | .replace(/[:.]/g, '') 121 | .replace(/\//g, '_') 122 | stats.timing(stat, time) 123 | })) 124 | 125 | app.get('/', function (req, res) { 126 | res.send('hello, world!') 127 | }) 128 | ``` 129 | 130 | ## License 131 | 132 | [MIT](LICENSE) 133 | 134 | [npm-version-image]: https://badgen.net/npm/v/response-time 135 | [npm-url]: https://npmjs.org/package/response-time 136 | [npm-downloads-image]: https://badgen.net/npm/dm/response-time 137 | [node-image]: https://badgen.net/npm/node/response-time 138 | [node-url]: https://nodejs.org/en/download 139 | [ci-image]: https://badgen.net/github/checks/express/response-time/master?label=ci 140 | [ci-url]: https://github.com/express/response-time/actions/workflows/ci.yml 141 | [coveralls-image]: https://badgen.net/coveralls/c/github/express/response-time/master 142 | [coveralls-url]: https://coveralls.io/r/express/response-time?branch=master 143 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * response-time 3 | * Copyright(c) 2011 TJ Holowaychuk 4 | * Copyright(c) 2014 Jonathan Ong 5 | * Copyright(c) 2014-2015 Douglas Christopher Wilson 6 | * MIT Licensed 7 | */ 8 | 9 | 'use strict' 10 | 11 | /** 12 | * Module dependencies 13 | * @private 14 | */ 15 | 16 | var deprecate = require('depd')('response-time') 17 | var onHeaders = require('on-headers') 18 | 19 | /** 20 | * Module exports. 21 | * @public 22 | */ 23 | 24 | module.exports = responseTime 25 | 26 | /** 27 | * Create a middleware to add a `X-Response-Time` header displaying 28 | * the response duration in milliseconds. 29 | * 30 | * @param {object|function} [options] 31 | * @param {number} [options.digits=3] 32 | * @param {string} [options.header=X-Response-Time] 33 | * @param {boolean} [options.suffix=true] 34 | * @return {function} 35 | * @public 36 | */ 37 | 38 | function responseTime (options) { 39 | var opts = options || {} 40 | 41 | if (typeof options === 'number') { 42 | // back-compat single number argument 43 | deprecate('number argument: use {digits: ' + JSON.stringify(options) + '} instead') 44 | opts = { digits: options } 45 | } 46 | 47 | // get the function to invoke 48 | var fn = typeof opts !== 'function' 49 | ? createSetHeader(opts) 50 | : opts 51 | 52 | return function responseTime (req, res, next) { 53 | var startAt = process.hrtime() 54 | 55 | onHeaders(res, function onHeaders () { 56 | var diff = process.hrtime(startAt) 57 | var time = diff[0] * 1e3 + diff[1] * 1e-6 58 | 59 | fn(req, res, time) 60 | }) 61 | 62 | next() 63 | } 64 | } 65 | 66 | /** 67 | * Create function to set response time header. 68 | * @private 69 | */ 70 | 71 | function createSetHeader (options) { 72 | // response time digits 73 | var digits = options.digits !== undefined 74 | ? options.digits 75 | : 3 76 | 77 | // header name 78 | var header = options.header || 'X-Response-Time' 79 | 80 | // display suffix 81 | var suffix = options.suffix !== undefined 82 | ? Boolean(options.suffix) 83 | : true 84 | 85 | return function setResponseHeader (req, res, time) { 86 | if (res.getHeader(header)) { 87 | return 88 | } 89 | 90 | var val = time.toFixed(digits) 91 | 92 | if (suffix) { 93 | val += 'ms' 94 | } 95 | 96 | res.setHeader(header, val) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "response-time", 3 | "description": "Response time for Node.js servers", 4 | "version": "2.3.3", 5 | "author": "Jonathan Ong (http://jongleberry.com)", 6 | "contributors": [ 7 | "Douglas Christopher Wilson " 8 | ], 9 | "license": "MIT", 10 | "keywords": [ 11 | "http", 12 | "res", 13 | "response time", 14 | "x-response-time" 15 | ], 16 | "repository": "expressjs/response-time", 17 | "dependencies": { 18 | "depd": "~2.0.0", 19 | "on-headers": "~1.0.1" 20 | }, 21 | "devDependencies": { 22 | "after": "0.8.2", 23 | "eslint": "4.19.1", 24 | "eslint-config-standard": "11.0.0", 25 | "eslint-plugin-import": "2.10.0", 26 | "eslint-plugin-markdown": "1.0.0-beta.6", 27 | "eslint-plugin-node": "6.0.1", 28 | "eslint-plugin-promise": "3.7.0", 29 | "eslint-plugin-standard": "3.0.1", 30 | "mocha": "6.1.4", 31 | "nyc": "14.1.1", 32 | "supertest": "1.1.0" 33 | }, 34 | "files": [ 35 | "LICENSE", 36 | "HISTORY.md", 37 | "index.js" 38 | ], 39 | "engines": { 40 | "node": ">= 0.8.0" 41 | }, 42 | "scripts": { 43 | "lint": "eslint --plugin markdown --ext js,md .", 44 | "test": "mocha --reporter spec", 45 | "test-ci": "nyc --reporter=lcovonly --reporter=text npm test", 46 | "test-cov": "nyc --reporter=html --reporter=text npm test" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 2 | process.env.NO_DEPRECATION = 'response-time' 3 | 4 | var after = require('after') 5 | var assert = require('assert') 6 | var http = require('http') 7 | var request = require('supertest') 8 | var responseTime = require('..') 9 | 10 | describe('responseTime()', function () { 11 | it('should send X-Response-Time header', function (done) { 12 | var server = createServer() 13 | request(server) 14 | .get('/') 15 | .expect('X-Response-Time', /^[0-9.]+ms$/, done) 16 | }) 17 | 18 | it('should not override X-Response-Time header', function (done) { 19 | var server = createServer(undefined, function (req, res) { 20 | res.setHeader('X-Response-Time', 'bogus') 21 | }) 22 | 23 | request(server) 24 | .get('/') 25 | .expect('X-Response-Time', 'bogus', done) 26 | }) 27 | 28 | describe('with "digits"', function () { 29 | it('should default to 3', function (done) { 30 | var server = createServer() 31 | request(server) 32 | .get('/') 33 | .expect('X-Response-Time', /^[0-9]+\.[0-9]{3}ms$/, done) 34 | }) 35 | 36 | it('should allow custom digits', function (done) { 37 | var server = createServer(5) 38 | request(server) 39 | .get('/') 40 | .expect('X-Response-Time', /^[0-9]+\.[0-9]{5}ms$/, done) 41 | }) 42 | 43 | it('should allow no digits', function (done) { 44 | var server = createServer(0) 45 | request(server) 46 | .get('/') 47 | .expect('X-Response-Time', /^[0-9]+ms$/, done) 48 | }) 49 | }) 50 | }) 51 | 52 | describe('responseTime(fn)', function () { 53 | it('should call fn with response time', function (done) { 54 | var cb = after(2, done) 55 | var start = process.hrtime() 56 | var server = createServer(function (req, res, time) { 57 | var diff = process.hrtime(start) 58 | var max = diff[0] * 1e3 + diff[1] * 1e-6 59 | assert.equal(req.url, '/') 60 | assert.equal(res.statusCode, 200) 61 | assert.ok(time >= 0) 62 | assert.ok(time <= max) 63 | cb() 64 | }) 65 | 66 | request(server) 67 | .get('/') 68 | .expect(200, cb) 69 | }) 70 | 71 | it('should not send X-Response-Time header', function (done) { 72 | var cb = after(2, done) 73 | var server = createServer(function () { 74 | cb() 75 | }) 76 | 77 | request(server) 78 | .get('/') 79 | .expect(shouldNotHaveHeader('X-Response-Time')) 80 | .expect(200, cb) 81 | }) 82 | }) 83 | 84 | describe('responseTime(options)', function () { 85 | describe('with "digits" option', function () { 86 | it('should default to 3', function (done) { 87 | var server = createServer() 88 | request(server) 89 | .get('/') 90 | .expect('X-Response-Time', /^[0-9]+\.[0-9]{3}ms$/, done) 91 | }) 92 | 93 | it('should allow custom digits', function (done) { 94 | var server = createServer({ digits: 5 }) 95 | request(server) 96 | .get('/') 97 | .expect('X-Response-Time', /^[0-9]+\.[0-9]{5}ms$/, done) 98 | }) 99 | 100 | it('should allow no digits', function (done) { 101 | var server = createServer({ digits: 0 }) 102 | request(server) 103 | .get('/') 104 | .expect('X-Response-Time', /^[0-9]+ms$/, done) 105 | }) 106 | }) 107 | 108 | describe('with "header" option', function () { 109 | it('should default to X-Response-Time', function (done) { 110 | var server = createServer() 111 | request(server) 112 | .get('/') 113 | .expect('X-Response-Time', /^[0-9.]+ms$/, done) 114 | }) 115 | 116 | it('should allow custom header name', function (done) { 117 | var server = createServer({ header: 'X-Time-Taken' }) 118 | request(server) 119 | .get('/') 120 | .expect('X-Time-Taken', /^[0-9.]+ms$/, done) 121 | }) 122 | }) 123 | 124 | describe('with "suffix" option', function () { 125 | it('should default to true', function (done) { 126 | var server = createServer() 127 | request(server) 128 | .get('/') 129 | .expect('X-Response-Time', /^[0-9.]+ms$/, done) 130 | }) 131 | 132 | it('should allow disabling suffix', function (done) { 133 | var server = createServer({ suffix: false }) 134 | request(server) 135 | .get('/') 136 | .expect('X-Response-Time', /^[0-9.]+$/, done) 137 | }) 138 | }) 139 | }) 140 | 141 | function createServer (opts, fn) { 142 | var _responseTime = responseTime(opts) 143 | return http.createServer(function (req, res) { 144 | _responseTime(req, res, function (err) { 145 | setTimeout(function () { 146 | fn && fn(req, res) 147 | res.statusCode = err ? (err.status || 500) : 200 148 | res.end(err ? err.message : 'OK') 149 | }, 10) 150 | }) 151 | }) 152 | } 153 | 154 | function shouldNotHaveHeader (header) { 155 | return function (res) { 156 | assert.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header) 157 | } 158 | } 159 | --------------------------------------------------------------------------------