├── .dir-locals.el ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── 1-bug-report.md │ └── 2-feature-request.md ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── workflows │ ├── ci.yml │ └── release-please.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .tern-project ├── CHANGELOG.md ├── CHANGES.md ├── CONTRIBUTING.md ├── FEATURE_REQUESTS.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── benchmark ├── benchmarks │ ├── middleware.js │ ├── response-json.js │ ├── response-text.js │ └── router-heavy.js ├── index.js ├── lib │ ├── autocannon.js │ └── bench.js └── package.json ├── bin └── report-latency ├── docs ├── _api │ ├── formatters.md │ ├── plugins.md │ ├── request.md │ ├── response.md │ └── server.md ├── api │ ├── formatters-usage.md │ ├── plugins-usage.md │ ├── request-events.md │ ├── request-log.md │ ├── server-errors.md │ └── server-events.md ├── config │ ├── formatters.yaml │ ├── plugins.yaml │ ├── request.yaml │ └── server.yaml ├── guides │ ├── 4TO5GUIDE.md │ ├── 6to7guide.md │ ├── 8to9guide.md │ ├── client.md │ ├── dtrace.md │ └── server.md └── index.md ├── examples ├── dtrace │ ├── demo.js │ ├── handler-timing.d │ └── hello.js ├── example.js ├── http2 │ ├── http2.js │ └── keys │ │ ├── http2-cert.pem │ │ ├── http2-csr.pem │ │ └── http2-key.pem ├── jsonp │ └── jsonp.js ├── sockio │ ├── package.json │ └── sockio.js ├── spdy │ ├── keys │ │ ├── spdy-cert.pem │ │ ├── spdy-csr.pem │ │ └── spdy-key.pem │ └── spdy.js └── todoapp │ ├── README.md │ ├── lib │ ├── client.js │ ├── index.js │ └── server.js │ ├── main.js │ ├── package.json │ └── test │ └── todo.test.js ├── lib ├── chain.js ├── deprecationWarnings.js ├── dtrace.js ├── errorTypes.js ├── formatters │ ├── binary.js │ ├── index.js │ ├── json.js │ ├── jsonp.js │ └── text.js ├── helpers │ └── chainComposer.js ├── http_date.js ├── index.js ├── plugins │ ├── accept.js │ ├── audit.js │ ├── authorization.js │ ├── bodyParser.js │ ├── bodyReader.js │ ├── conditionalHandler.js │ ├── conditionalRequest.js │ ├── cpuUsageThrottle.js │ ├── date.js │ ├── fieldedTextBodyParser.js │ ├── formBodyParser.js │ ├── fullResponse.js │ ├── gzip.js │ ├── index.js │ ├── inflightRequestThrottle.js │ ├── jsonBodyParser.js │ ├── jsonp.js │ ├── metrics.js │ ├── multipartBodyParser.js │ ├── oauth2TokenParser.js │ ├── pre │ │ ├── context.js │ │ ├── dedupeSlashes.js │ │ ├── pause.js │ │ ├── prePath.js │ │ ├── reqIdHeaders.js │ │ ├── strictQueryParams.js │ │ └── userAgent.js │ ├── query.js │ ├── requestExpiry.js │ ├── requestLogger.js │ ├── static.js │ ├── staticFiles.js │ ├── throttle.js │ └── utils │ │ ├── hrTimeDurationInMs.js │ │ ├── httpDate.js │ │ ├── regex.js │ │ └── shallowCopy.js ├── request.js ├── response.js ├── router.js ├── routerRegistryRadix.js ├── server.js ├── upgrade.js └── utils.js ├── package.json ├── test ├── .eslintrc ├── chain.test.js ├── chainComposer.test.js ├── formatter-optional.test.js ├── formatter.test.js ├── index.test.js ├── keys │ ├── http2-cert.pem │ ├── http2-csr.pem │ └── http2-key.pem ├── lib │ ├── helper.js │ ├── server-withDisableUncaughtException.js │ └── streamRecorder.js ├── plugins │ ├── .eslintrc │ ├── accept.test.js │ ├── audit.test.js │ ├── authorization.test.js │ ├── bodyReader.test.js │ ├── conditionalHandler.test.js │ ├── conditionalRequest.test.js │ ├── context.test.js │ ├── cpuUsageThrottle.test.js │ ├── dedupeSlashes.test.js │ ├── fieldedTextParser.test.js │ ├── files │ │ ├── data-csv.txt │ │ ├── data-tsv.txt │ │ ├── object-csv.json │ │ └── object-tsv.json │ ├── formBodyParser.test.js │ ├── gzip.test.js │ ├── inflightRequestThrottle.test.js │ ├── jsonBodyParser.test.js │ ├── metrics.test.js │ ├── multipart.test.js │ ├── oauth2.test.js │ ├── plugins.test.js │ ├── query.test.js │ ├── reqIdHeaders.test.js │ ├── requestExpiry.test.js │ ├── static.test.js │ ├── staticFiles.test.js │ ├── strictQueryParams.test.js │ ├── testStaticFiles │ │ ├── docs │ │ │ ├── doc.md │ │ │ └── index.html │ │ ├── file1.txt │ │ ├── index.html │ │ └── special │ │ │ └── $_$ │ │ │ └── bad (file).txt │ ├── throttle.test.js │ ├── userAgent.test.js │ └── utilsHrTimeDurationInMs.test.js ├── request.test.js ├── response.test.js ├── router.test.js ├── routerRegistryRadix.test.js ├── server.test.js ├── serverHttp2.test.js ├── upgrade.test.js └── utils.test.js └── tools ├── docsBuild.js └── mk ├── Makefile.defs ├── Makefile.deps └── Makefile.targ /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((indent-tabs-mode . nil) 2 | (tab-width . 4) 3 | (fill-column . 80))) 4 | (js-mode . ((js-indent-level . 4) 5 | (indent-tabs-mode . nil) 6 | ))) 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # node_modules ignored by default 2 | node_modules/ 3 | 4 | # other ignored directories 5 | bin/ 6 | deps/ 7 | docs/ 8 | examples/ 9 | cover_html/ 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | <!-- Thank you for taking the time to open an issue for restify! If this is 8 | your first time here, welcome to our community! We are a group of developers 9 | who work on restify in our free-time. Some of us do it as a hobby, others are 10 | using restify at work. When asking for help here, keep in mind most of us are 11 | volunteers contributing our daily work back to the community at no cost (and 12 | often for no reward). Please be respectful! 13 | 14 | Below you will find two templates, one for filing a bug report, and the other 15 | for requesting a feature. Remove the comments from around the template that is 16 | applicable to your case and fill it out accordingly. This standardization helps 17 | the maintainers gather the information they need up front to verify and respond 18 | to problems accordingly, ensuring you get the fastest response possible! --> 19 | 20 | <!-- REQUIRED: Pre-Submission Checklist --> 21 | 22 | - [ ] Used appropriate template for the issue type 23 | - [ ] Searched both open and closed issues for duplicates of this issue 24 | - [ ] Title adequately and _concisely_ reflects the feature or the bug 25 | 26 | **Restify Version**: 27 | **Node.js Version**: 28 | 29 | ## Expected behaviour 30 | <!-- This section details what you expected restify to do based on the code 31 | that you wrote --> 32 | 33 | ## Actual behaviour 34 | <!-- This section details what restify actually did when you ran your code --> 35 | 36 | ## Repro case 37 | <!-- Please include a simple and concise example reproducing this bug. Please 38 | _do not_ just dump your application here. By either not providing a repro case 39 | or by providing an overly complicated repro case, you are offloading the work 40 | of isolating your bug to other developers, many of which are here voluntarily. 41 | Good repro cases are single file Node.js applications, where the only logic 42 | present is logic necessary to expose the undesired behaviour. You will often 43 | find that when creating your repro case, you will solve the problem yourself! 44 | --> 45 | 46 | ## Cause 47 | <!-- 48 | If you have been able to trace the bug back to it source(s) in the code base, 49 | please link to them here. --> 50 | 51 | ## Are you willing and able to fix this? 52 | <!-- "Yes" or, if "no", what can current contributors do to help you create a 53 | PR? If this issue is unique, as the checklist you completed above suggests, 54 | then you are one of the few people who have encountered this bug in the wild. 55 | While contributors will often help work on issues out of the kindness of their 56 | hearts, its important to remember that you are the largest stakeholder in 57 | seeing this bug resolved as you are the one experiencing it. Kindness and 58 | contributions are what make Free Software go round, help pay it forward! --> 59 | 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | <!-- Thank you for taking the time to open an issue for restify! If this is 8 | your first time here, welcome to our community! We are a group of developers 9 | who work on restify in our free-time. Some of us do it as a hobby, others are 10 | using restify at work. When asking for help here, keep in mind most of us are 11 | volunteers contributing our daily work back to the community at no cost (and 12 | often for no reward). Please be respectful! 13 | 14 | Below you will find two templates, one for filing a bug report, and the other 15 | for requesting a feature. Remove the comments from around the template that is 16 | applicable to your case and fill it out accordingly. This standardization helps 17 | the maintainers gather the information they need up front to verify and respond 18 | to problems accordingly, ensuring you get the fastest response possible! --> 19 | 20 | <!-- REQUIRED: Pre-Submission Checklist --> 21 | 22 | - [ ] Used appropriate template for the issue type 23 | - [ ] Searched both open and closed issues for duplicates of this issue 24 | - [ ] Title adequately and _concisely_ reflects the feature or the bug 25 | 26 | # Feature Request 27 | 28 | ## Use Case 29 | <!-- Why do you want this? --> 30 | 31 | ## Example API 32 | <!-- This should include code snippets and documentation for the proposed 33 | feature --> 34 | 35 | ## Are you willing and able to implement this? 36 | <!-- "Yes" or, if "no", what can current contributors do to help you create a 37 | PR? --> 38 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | <!-- 2 | Thank you for taking the time to open an PR for restify! If this is your first 3 | time here, welcome to our community! We are a group of developers who work on 4 | restify in our free-time. Some of us do it as a hobby, others are using restify 5 | at work. When asking for help here, keep in mind most of us are volunteers 6 | contributing our daily work back to the community at no cost (and often for no 7 | reward). Please be respectful! 8 | 9 | Below you will find a checklist to help you create the best PR possible. While 10 | the checklist items aren't all _strictly_ required, they dramatically increase 11 | the probability of your PR getting a response and getting merged. Often times, 12 | the least time consuming part of maintaining and open source project is writing 13 | code, its the process and discussions that happen around the code base that 14 | consume a majority of the maintainers' time. By spending a few moments to 15 | adhere to this template, you are not only improve the quality of your PR, you 16 | are also helping save the maintainers a considerable amount of time when trying 17 | to understand and review your changes. 18 | 19 | And remember, positive vibes are met with positive vibes. Kindness helps Free 20 | Software go round, pay it forward! 21 | --> 22 | 23 | ## Pre-Submission Checklist 24 | 25 | - [ ] Opened an issue discussing these changes before opening the PR 26 | - [ ] Ran the linter and tests via `make prepush` 27 | - [ ] Included comprehensive and convincing tests for changes 28 | 29 | ## Issues 30 | 31 | Closes: 32 | 33 | * Issue # 34 | * Issue # 35 | * Issue # 36 | 37 | > Summarize the issues that discussed these changes 38 | 39 | # Changes 40 | 41 | > What does this PR do? 42 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 60 2 | daysUntilClose: 14 3 | exemptLabels: 4 | - Critical 5 | - Serve 6 | staleLabel: Stale 7 | markComment: > 8 | This issue has been automatically marked as stale because it has not had 9 | recent activity. It will be closed if no further activity occurs. Thank you 10 | for your contributions. 11 | closeComment: > 12 | This issue has been automatically closed as stale because it has not had 13 | recent activity. 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: 7 | - master 8 | name: ci 9 | jobs: 10 | lint: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: install node v16 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: v16.x 19 | - name: install dependencies 20 | run: npm install 21 | - name: check lint 22 | run: make check-lint 23 | test: 24 | name: test node ${{ matrix.node-version }} on ${{ matrix.os }} 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | os: 29 | - ubuntu-latest 30 | node-version: 31 | - 14.x 32 | - 16.x 33 | - 18.x 34 | - 20.x 35 | runs-on: ${{matrix.os}} 36 | steps: 37 | - uses: actions/checkout@v2 38 | - name: use node ${{ matrix.node-version }} 39 | uses: actions/setup-node@v1 40 | with: 41 | node-version: ${{ matrix.node-version }} 42 | - name: install dependencies 43 | run: npm install 44 | - name: test 45 | run: make test 46 | env: 47 | TEST_SKIP_IP_V6: true 48 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - 9.x 6 | name: release-please 7 | jobs: 8 | release-please: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: GoogleCloudPlatform/release-please-action@v3.6.1 12 | with: 13 | token: ${{ secrets.GITHUB_TOKEN }} 14 | release-type: node 15 | package-name: restify 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs/*.html 3 | docs/pkg 4 | examples/todoapp/node_modules 5 | *.log 6 | *.tar.gz 7 | *.tgz 8 | build 9 | docs/*.json 10 | nbproject 11 | deps/javascriptlint 12 | deps/jsstyle 13 | package-lock.json 14 | benchmark/results 15 | .nyc_output/ 16 | coverage/ 17 | cover_html/ 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .coverage_data 2 | .dir-locals.el 3 | .gitmodules 4 | .github 5 | .travis.yml 6 | Makefile 7 | cover_html 8 | deps 9 | docs 10 | examples 11 | test 12 | tools 13 | .vscode 14 | .idea 15 | benchmark 16 | .dir-locals.el 17 | .eslintignore 18 | .eslintrc.js 19 | .gitignore 20 | .prettierignore 21 | .prettierrc 22 | .tern-project 23 | .travis.yml 24 | CONTRIBUTING.md 25 | FEATURE_REQUESTS.md 26 | *.log 27 | *.tar.gz 28 | *.tgz 29 | node_modules 30 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | cover_html 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [ 3 | "ecma5", 4 | "chai" 5 | ], 6 | "plugins": { 7 | "node": {}, 8 | "complete_strings": {}, 9 | "doc_comment": {}, 10 | "node_resolve": {} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Restify 2 | 3 | Welcome to the restify community! This document is written both for maintainers and community members! 4 | 5 | ## Issues and PRs 6 | 7 | ### Commit Messages 8 | 9 | When merging a PR, we squash and merge to keep our commit history clean. Our commit messages use the conventional changelog format (http://conventionalcommits.org/) to automagically manage semver for us. 10 | 11 | ### Labels and Templates 12 | 13 | We try to keep things organized around here. Maintainers have a finite amount of time and are often juggling multiple things in their lives. Keeping things consistent and well labeled helps reduce the amount of concentration and effort required for us to both find and carry out work on the project. Simple things like using our templates and adding the appropriate labels may only take you a few minutes, but it can save cummulative hours worth of work for maintainers trying to digest dozens of issues. 14 | 15 | ## Website 16 | 17 | ### Design 18 | 19 | The website templates are maintained at https://github.com/restify/restify.github.io and are populated from the docs directory in this repo. 20 | 21 | ### Releasing a change 22 | 23 | To update the documentaiton on the website to reflect the latest version of 5.x simply: 24 | 25 | ``` 26 | git clone --recursive git@github.com:restify/restify.github.io 27 | cd restify.github.io 28 | git submodule update --remote && git add _docs && git commit -m 'bump' && git push origin master 29 | ``` 30 | 31 | The website will automatically deploy itself with the new changes. 32 | 33 | ### Updating a documentation page 34 | 35 | To update docs, simply run: 36 | 37 | ``` 38 | make docs-build 39 | ``` 40 | 41 | ### Adding a documentation page 42 | 43 | To add a new page, simply give it a [permalink](https://github.com/restify/node-restify/blob/94fe715173ffcebd8814bed7e17a22a24fac4ae8/docs/index.md) and then update [docs.yml](https://github.com/restify/restify.github.io/blob/master/_data/docs.yml) with the new permalink. 44 | 45 | ## Running a benchmark 46 | 47 | ``` 48 | make benchmark 49 | ``` 50 | 51 | ## Cutting a release 52 | 53 | Cutting a release is currently a manual process. We use a [Conventional Changelog](http://conventionalcommits.org/) to simplify the process of managing semver on this project. Generally, the following series of commands will cut a release from the `master` branch: 54 | 55 | ``` 56 | $ git fetch 57 | $ git pull origin master # ensure you have the latest changes 58 | $ npx unleash [-p for patch, -m for minor, -M for major] --no-publish -d # do a dry run to verify 59 | $ npx unleash [-p for patch, -m for minor, -M for major] --no-publish 60 | # Unleash doesnt support 2FA, hence we use --no-publish flag here. 61 | # This ensures we have the package.json updated, changelog generated, tag created 62 | # and all the changes into origin 63 | # Next, publish to npm manually and do not forget to provide the 2FA code. 64 | $ npm publish 65 | ``` 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Mark Cavage, All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012, Joyent, Inc. All rights reserved. 3 | # 4 | # Makefile: basic Makefile for template API service 5 | # 6 | # This Makefile is a template for new repos. It contains only repo-specific 7 | # logic and uses included makefiles to supply common targets (javascriptlint, 8 | # jsstyle, restdown, etc.), which are used by other repos as well. You may well 9 | # need to rewrite most of this file, but you shouldn't need to touch the 10 | # included makefiles. 11 | # 12 | # If you find yourself adding support for new targets that could be useful for 13 | # other projects too, you should add these to the original versions of the 14 | # included Makefiles (in eng.git) so that other teams can use them too. 15 | # 16 | 17 | # 18 | # Tools 19 | # 20 | ESLINT := ./node_modules/.bin/eslint 21 | DOCUMENTATION := ./node_modules/.bin/documentation 22 | NODEUNIT := ./node_modules/.bin/nodeunit 23 | MOCHA := ./node_modules/.bin/mocha 24 | NODECOVER := ./node_modules/.bin/nyc 25 | DOCS_BUILD := ./tools/docsBuild.js 26 | NPM := npm 27 | NODE := node 28 | PRETTIER := ./node_modules/.bin/prettier 29 | 30 | # 31 | # Files 32 | # 33 | JS_FILES = '.' 34 | 35 | CLEAN_FILES += node_modules cscope.files 36 | 37 | include ./tools/mk/Makefile.defs 38 | 39 | # 40 | # Repo-specific targets 41 | # 42 | .PHONY: all 43 | all: $(NODEUNIT) $(REPO_DEPS) 44 | $(NPM) rebuild 45 | 46 | $(NODEUNIT): | $(NPM_EXEC) 47 | $(NPM) install 48 | 49 | $(NODECOVER): | $(NPM_EXEC) 50 | $(NPM) install 51 | 52 | .PHONY: cover 53 | cover: $(NODECOVER) 54 | @rm -fr ./.coverage_data 55 | $(NODECOVER) --reporter=html --reporter=text-summary --reporter=lcov $(NODEUNIT) ./test/*.js 56 | 57 | CLEAN_FILES += $(TAP) ./node_modules/nodeunit 58 | 59 | .PHONY: test 60 | test: $(NODEUNIT) 61 | $(NODEUNIT) test/*.test.js 62 | $(MOCHA) --full-trace --no-exit test/plugins/*.test.js 63 | 64 | .PHONY: docs-build 65 | docs-build: 66 | @($(NODE) $(DOCS_BUILD)) 67 | 68 | .PHONY: benchmark 69 | benchmark: 70 | @(cd ./benchmark && $(NPM) i && $(NODE) index.js) 71 | 72 | include ./tools/mk/Makefile.deps 73 | include ./tools/mk/Makefile.targ 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <!-- Please don't remove this: Grab your social icons from https://github.com/carlsednaoui/gitsocial --> 2 | 3 | <!-- display the social media buttons in your README --> 4 | 5 | [![alt text][1.1]][1] 6 | 7 | 8 | <!-- links to social media icons --> 9 | <!-- no need to change these --> 10 | 11 | <!-- icons with padding --> 12 | 13 | [1.1]: http://i.imgur.com/tXSoThF.png (twitter icon with padding) 14 | 15 | <!-- icons without padding --> 16 | 17 | [1.2]: http://i.imgur.com/wWzX9uB.png (twitter icon without padding) 18 | 19 | 20 | <!-- links to your social media accounts --> 21 | <!-- update these accordingly --> 22 | 23 | [1]: http://www.twitter.com/restifyjs 24 | 25 | <!-- Please don't remove this: Grab your social icons from https://github.com/carlsednaoui/gitsocial --> 26 | 27 |  28 | 29 | [](https://travis-ci.org/restify/node-restify) 30 | [](https://david-dm.org/restify/node-restify) 31 | [](https://david-dm.org/restify/node-restify#info=devDependencies) 32 | [](https://github.com/prettier/prettier) 33 | 34 | [restify](http://restify.com) is a framework, utilizing 35 | [connect](https://github.com/senchalabs/connect) style middleware for building 36 | REST APIs. For full details, see http://restify.com 37 | 38 | Follow restify on [![alt text][1.2]][1] 39 | 40 | # Usage 41 | 42 | ## Server 43 | ```javascript 44 | var restify = require('restify'); 45 | 46 | const server = restify.createServer({ 47 | name: 'myapp', 48 | version: '1.0.0' 49 | }); 50 | 51 | server.use(restify.plugins.acceptParser(server.acceptable)); 52 | server.use(restify.plugins.queryParser()); 53 | server.use(restify.plugins.bodyParser()); 54 | 55 | server.get('/echo/:name', function (req, res, next) { 56 | res.send(req.params); 57 | return next(); 58 | }); 59 | 60 | server.listen(8080, function () { 61 | console.log('%s listening at %s', server.name, server.url); 62 | }); 63 | ``` 64 | 65 | ## Client 66 | ```javascript 67 | var assert = require('assert'); 68 | var clients = require('restify-clients'); 69 | 70 | var client = clients.createJsonClient({ 71 | url: 'http://localhost:8080', 72 | version: '~1.0' 73 | }); 74 | 75 | client.get('/echo/mark', function (err, req, res, obj) { 76 | assert.ifError(err); 77 | console.log('Server returned: %j', obj); 78 | }); 79 | ``` 80 | 81 | # Installation 82 | ```bash 83 | $ npm install restify 84 | ``` 85 | 86 | ## Supported Node Versions 87 | 88 | Restify currently works on Node.js v14.x and v16.x. 89 | 90 | ## License 91 | 92 | The MIT License (MIT) 93 | 94 | Copyright (c) 2018 restify 95 | 96 | Permission is hereby granted, free of charge, to any person obtaining a copy of 97 | this software and associated documentation files (the "Software"), to deal in 98 | the Software without restriction, including without limitation the rights to 99 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 100 | the Software, and to permit persons to whom the Software is furnished to do so, 101 | subject to the following conditions: 102 | 103 | The above copyright notice and this permission notice shall be included in all 104 | copies or substantial portions of the Software. 105 | 106 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 107 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 108 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 109 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 110 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 111 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 112 | SOFTWARE. 113 | 114 | ## Bugs 115 | 116 | See <https://github.com/restify/node-restify/issues>. 117 | 118 | ## Other repositories 119 | 120 | - For the errors module, please go [here](https://github.com/restify/errors). 121 | 122 | 123 | ## Mailing list 124 | 125 | See the 126 | [Google group](https://groups.google.com/forum/?hl=en&fromgroups#!forum/restify) 127 | . 128 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Do not disclose vulnerabilities in public issues. Please report vulnerabilities to 6 | security@restify.com with steps to reproduce the vulnerability, and a patch to fix 7 | it if possible. 8 | -------------------------------------------------------------------------------- /benchmark/benchmarks/middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var restify = process.argv.includes('version=head') 4 | ? require('../../lib') 5 | : require('restify'); 6 | 7 | var server = restify.createServer(); 8 | var path = '/'; 9 | var port = 3000; 10 | 11 | module.exports = { 12 | url: 'http://localhost:' + port + path 13 | }; 14 | 15 | function handler(req, res, next) { 16 | next(); 17 | } 18 | 19 | for (var i = 0; i < 10; i++) { 20 | server.pre(handler); 21 | } 22 | 23 | for (var j = 0; j < 10; j++) { 24 | server.use(handler); 25 | } 26 | 27 | server.get(path, function get(req, res) { 28 | res.send('hello world'); 29 | }); 30 | 31 | if (!module.parent) { 32 | server.listen(port); 33 | } 34 | -------------------------------------------------------------------------------- /benchmark/benchmarks/response-json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var restify = process.argv.includes('version=head') 4 | ? require('../../lib') 5 | : require('restify'); 6 | 7 | var server = restify.createServer(); 8 | var path = '/'; 9 | var port = 3000; 10 | 11 | module.exports = { 12 | url: 'http://localhost:' + port + path 13 | }; 14 | 15 | server.get(path, function onRequest(req, res) { 16 | res.send({ hello: 'world' }); 17 | }); 18 | 19 | if (!module.parent) { 20 | server.listen(port); 21 | } 22 | -------------------------------------------------------------------------------- /benchmark/benchmarks/response-text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var restify = process.argv.includes('version=head') 4 | ? require('../../lib') 5 | : require('restify'); 6 | 7 | var server = restify.createServer(); 8 | var path = '/'; 9 | var port = 3000; 10 | 11 | module.exports = { 12 | url: 'http://localhost:' + port + path 13 | }; 14 | 15 | server.get(path, function onRequest(req, res) { 16 | res.send('hello world'); 17 | }); 18 | 19 | if (!module.parent) { 20 | server.listen(port); 21 | } 22 | -------------------------------------------------------------------------------- /benchmark/benchmarks/router-heavy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var restify = process.argv.includes('version=head') 4 | ? require('../../lib') 5 | : require('restify'); 6 | 7 | var server = restify.createServer(); 8 | var path = '/whiskeys/scotch/islay/lagavulin/16-years/50'; 9 | var methods = ['post', 'put', 'get', 'del', 'patch']; 10 | var _ = require('lodash'); 11 | var port = 3000; 12 | 13 | // Disabling cache: it's not fair as it aims to the worst case, when 14 | // cache hit ratio is 0%. However, it's still better than the worst 15 | // as it doesn't require extra time to maintain the LRU cache. 16 | // There is no other way to simulate 100+ different endpoint 17 | // calls with the current benchmark suite. 18 | if (!process.argv.includes('version=head')) { 19 | server.router.cache = { 20 | get: function get() { 21 | return null; 22 | }, 23 | set: function get() { 24 | return null; 25 | }, 26 | dump: function get() { 27 | return []; 28 | } 29 | }; 30 | } 31 | 32 | module.exports = { 33 | url: 'http://localhost:' + port + path 34 | }; 35 | 36 | var routes = { 37 | beers: { 38 | ale: { 39 | 'pale-ale': { 40 | 'american-pale-ale': [], 41 | 'indian-pale-ale': [] 42 | }, 43 | lambic: [], 44 | stout: { 45 | 'american-porter': [], 46 | 'imperial-stout': [], 47 | 'irish-stout': [] 48 | } 49 | }, 50 | lager: { 51 | 'german-lager': { 52 | marzen: [] 53 | }, 54 | pilsner: { 55 | 'german-pilsner': [] 56 | } 57 | } 58 | }, 59 | 60 | whiskeys: { 61 | american: { 62 | bourbon: { 63 | kentchuky: { 64 | 'jim-beam': ['jim-beam', 'bookers', 'old-crow'], 65 | 'makers-mark': ['makers-mark'], 66 | 'woodford-reserve': ['woodford-reserve'] 67 | }, 68 | tennessee: { 69 | 'jack-daniels': ['jack-daniels'] 70 | } 71 | }, 72 | rye: { 73 | 'beam-suntory': ['jim-beam-rye', 'knob-creek'] 74 | } 75 | }, 76 | irish: { 77 | 'single-malt': { 78 | bushmills: ['bushmills'], 79 | connemare: ['connemare'] 80 | }, 81 | 'single-pot': { 82 | redbreast: ['redbreast'], 83 | jameson: ['jameson-15-year'] 84 | } 85 | }, 86 | japanese: { 87 | nikka: ['coffeey-malt', 'blended', 'from-the-barrel'], 88 | hibiki: ['japanese-harmony'], 89 | yamazakura: ['blended'] 90 | }, 91 | scotch: { 92 | islay: { 93 | bruichladdich: ['25-years', 'islay-barley-2009'], 94 | octomore: ['7.2', 'islay-barley-8.3'], 95 | laphroaig: ['lore', '15-years', 'four-oak'], 96 | lagavulin: ['distillers-edition', '8-years', '16-years'] 97 | } 98 | } 99 | } 100 | }; 101 | 102 | function handler(req, res) { 103 | res.send('hello'); 104 | } 105 | 106 | function attachRoute(parent, routeConfig) { 107 | _.map(routeConfig, function map(route, routeKey) { 108 | var pathChunk = _.isString(routeKey) ? routeKey : route; 109 | var routePath = parent + '/' + pathChunk; 110 | 111 | methods.forEach(function forEach(method) { 112 | server[method](routePath, handler); 113 | }); 114 | 115 | if (_.isObject(route) || _.isArray(route)) { 116 | attachRoute(routePath, route); 117 | } 118 | if (_.isString(route)) { 119 | for (var i = 0; i <= 100; i++) { 120 | methods.forEach(function forEach(method) { 121 | server[method](routePath + '/' + i, handler); 122 | }); 123 | } 124 | } 125 | }); 126 | } 127 | 128 | attachRoute('', routes); 129 | 130 | if (!module.parent) { 131 | server.listen(port); 132 | } 133 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var inquirer = require('inquirer'); 5 | var bench = require('./lib/bench'); 6 | var stableVersion = require('restify/package.json').version; 7 | 8 | var BENCHMARKS = [ 9 | 'response-json', 10 | 'response-text', 11 | 'router-heavy', 12 | 'middleware' 13 | ]; 14 | 15 | function select(callback) { 16 | var choices = BENCHMARKS.map(function map(name) { 17 | return { 18 | name: name, 19 | checked: true 20 | }; 21 | }); 22 | 23 | choices.unshift(new inquirer.Separator(' = The usual =')); 24 | 25 | inquirer 26 | .prompt([ 27 | { 28 | type: 'checkbox', 29 | message: 'Select packages', 30 | name: 'list', 31 | choices: choices, 32 | validate: function validate(answer) { 33 | if (answer.length < 1) { 34 | return 'You must choose at least one package.'; 35 | } 36 | return true; 37 | } 38 | } 39 | ]) 40 | .then(function onPrompted(answers) { 41 | callback(answers.list); 42 | }); 43 | } 44 | 45 | inquirer 46 | .prompt([ 47 | { 48 | type: 'confirm', 49 | name: 'track', 50 | message: 'Do you want to track progress?', 51 | default: false 52 | }, 53 | { 54 | type: 'confirm', 55 | name: 'compare', 56 | message: 57 | 'Do you want to compare HEAD with the stable release (' + 58 | stableVersion + 59 | ')?', 60 | default: true 61 | }, 62 | { 63 | type: 'confirm', 64 | name: 'all', 65 | message: 'Do you want to run all benchmark tests?', 66 | default: true 67 | }, 68 | { 69 | type: 'input', 70 | name: 'connection', 71 | message: 'How many connections do you need?', 72 | default: 100, 73 | validate: function validate(value) { 74 | return ( 75 | !Number.isNaN(parseFloat(value)) || 'Please enter a number' 76 | ); 77 | }, 78 | filter: Number 79 | }, 80 | { 81 | type: 'input', 82 | name: 'pipelining', 83 | message: 'How many pipelining do you need?', 84 | default: 10, 85 | validate: function validate(value) { 86 | return ( 87 | !Number.isNaN(parseFloat(value)) || 'Please enter a number' 88 | ); 89 | }, 90 | filter: Number 91 | }, 92 | { 93 | type: 'input', 94 | name: 'duration', 95 | message: 'How long does it take?', 96 | default: 30, 97 | validate: function validate(value) { 98 | return ( 99 | !Number.isNaN(parseFloat(value)) || 'Please enter a number' 100 | ); 101 | }, 102 | filter: Number 103 | } 104 | ]) 105 | .then(function validate(opts) { 106 | if (!opts.all) { 107 | select(function onSelected(list) { 108 | bench(opts, list); 109 | }); 110 | } else { 111 | bench(opts, BENCHMARKS); 112 | } 113 | }); 114 | -------------------------------------------------------------------------------- /benchmark/lib/autocannon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var autocannon = require('autocannon'); 4 | var fs = require('fs'); 5 | var autocannonCompare = require('autocannon-compare'); 6 | var path = require('path'); 7 | 8 | var resultsDirectory = path.join(__dirname, '../results'); 9 | 10 | function writeResult(handler, version, result) { 11 | try { 12 | fs.accessSync(resultsDirectory); 13 | } catch (e) { 14 | fs.mkdirSync(resultsDirectory); 15 | } 16 | 17 | result.server = handler; 18 | 19 | var dest = path.join(resultsDirectory, handler + '-' + version + '.json'); 20 | return fs.writeFileSync(dest, JSON.stringify(result, null, 4)); 21 | } 22 | 23 | function fire(opts, handler, version, save, cb) { 24 | opts = opts || {}; 25 | opts.url = opts.url || 'http://localhost:3000'; 26 | 27 | var instance = autocannon(opts, function onResult(err, result) { 28 | if (err) { 29 | cb(err); 30 | return; 31 | } 32 | 33 | if (save) { 34 | writeResult(handler, version, result); 35 | } 36 | 37 | cb(); 38 | }); 39 | 40 | if (opts.track && save) { 41 | autocannon.track(instance); 42 | } 43 | } 44 | 45 | function compare(handler) { 46 | var resStable = require(resultsDirectory + '/' + handler + '-stable.json'); 47 | var resHead = require(resultsDirectory + '/' + handler + '-head.json'); 48 | var comp = autocannonCompare(resStable, resHead); 49 | var result = { 50 | throughput: { 51 | significant: comp.throughput.significant 52 | } 53 | }; 54 | 55 | if (comp.equal) { 56 | result.throughput.equal = true; 57 | } else if (comp.aWins) { 58 | result.throughput.equal = false; 59 | result.throughput.wins = 'stable'; 60 | result.throughput.diff = comp.throughput.difference; 61 | } else { 62 | result.throughput.equal = false; 63 | result.throughput.wins = 'head'; 64 | result.throughput.diff = autocannonCompare( 65 | resHead, 66 | resStable 67 | ).throughput.difference; 68 | } 69 | 70 | return result; 71 | } 72 | 73 | module.exports = { 74 | fire: fire, 75 | compare: compare 76 | }; 77 | -------------------------------------------------------------------------------- /benchmark/lib/bench.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var fork = require('child_process').fork; 5 | var ora = require('ora'); 6 | var path = require('path'); 7 | var autocannon = require('./autocannon'); 8 | var pipeline = require('vasync').pipeline; 9 | 10 | function runBenchmark(opts, handler, version, cb) { 11 | if (opts.track) { 12 | console.log(version.toUpperCase() + ':'); 13 | } 14 | 15 | var spinner = ora('Started ' + version + '/' + handler).start(); 16 | var modulePath = path.join(__dirname, '../benchmarks', handler); 17 | var url = require(modulePath).url; 18 | var forked = fork(modulePath, ['version=' + version]); 19 | 20 | pipeline( 21 | { 22 | funcs: [ 23 | function warm(_, callback) { 24 | spinner.color = 'magenta'; 25 | spinner.text = 26 | 'Warming ' + version + '/' + handler + ' for 5s'; 27 | 28 | var fireOpts = Object.assign({}, opts, { 29 | duration: 5, 30 | url: url 31 | }); 32 | autocannon.fire( 33 | fireOpts, 34 | handler, 35 | version, 36 | false, 37 | callback 38 | ); 39 | }, 40 | 41 | function benchmark(_, callback) { 42 | if (opts.track) { 43 | spinner.stop(); 44 | } else { 45 | spinner.color = 'yellow'; 46 | spinner.text = 47 | 'Benchmarking ' + 48 | version + 49 | '/' + 50 | handler + 51 | ' for ' + 52 | opts.duration + 53 | 's'; 54 | } 55 | 56 | var fireOpts = Object.assign({}, opts, { url: url }); 57 | autocannon.fire(fireOpts, handler, version, true, callback); 58 | } 59 | ] 60 | }, 61 | function onPipelineFinished(err) { 62 | forked.kill('SIGINT'); 63 | 64 | if (err) { 65 | spinner.fail(); 66 | cb(err); 67 | return; 68 | } 69 | 70 | spinner.text = 'Results saved for ' + version + '/' + handler; 71 | spinner.succeed(); 72 | 73 | cb(); 74 | } 75 | ); 76 | } 77 | 78 | function start(opts, list, index) { 79 | index = index || 0; 80 | 81 | // No more item 82 | if (list.length === index) { 83 | return; 84 | } 85 | 86 | var handler = list[index]; 87 | console.log('---- ' + handler + ' ----'); 88 | 89 | pipeline( 90 | { 91 | funcs: [ 92 | function head(_, callback) { 93 | runBenchmark(opts, handler, 'head', callback); 94 | }, 95 | function stable(_, callback) { 96 | if (!opts.compare) { 97 | callback(); 98 | return; 99 | } 100 | runBenchmark(opts, handler, 'stable', callback); 101 | } 102 | ] 103 | }, 104 | function onPipelineFinished(err) { 105 | if (err) { 106 | console.log(err); 107 | return; 108 | } 109 | 110 | // Compare versions 111 | if (opts.compare) { 112 | var result = autocannon.compare(handler); 113 | 114 | console.log(handler + ' throughput:'); 115 | console.log(JSON.stringify(result.throughput, null, 4) + '\n'); 116 | } 117 | 118 | // Benchmark next handler 119 | start(opts, list, ++index); 120 | } 121 | ); 122 | } 123 | 124 | module.exports = start; 125 | -------------------------------------------------------------------------------- /benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restify-benchmark", 3 | "homepage": "http://restifyjs.com", 4 | "description": "Restify benchmark", 5 | "version": "0.0.0", 6 | "private": true, 7 | "main": "index.js", 8 | "engines": { 9 | "node": ">=0.10" 10 | }, 11 | "dependencies": { 12 | "restify": "latest" 13 | }, 14 | "devDependencies": {}, 15 | "license": "MIT", 16 | "scripts": { 17 | "start": "node indec" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/_api/formatters.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Formatters API 3 | permalink: /docs/formatters-api/ 4 | --- 5 | 6 | <!-- Generated by documentation.js. Update this documentation by updating the source code. --> 7 | 8 | ### Table of Contents 9 | 10 | - [Usage][1] 11 | - [Types][2] 12 | - [formatter][3] 13 | - [Included formatters][4] 14 | - [formatText][5] 15 | - [formatJSON][6] 16 | - [formatJSONP][7] 17 | - [formatBinary][8] 18 | 19 | ## Usage 20 | 21 | Restify comes bundled with a selection of useful formatters that prepare your 22 | responses for being sent over the wire, but you are free to include your own! 23 | 24 | ```js 25 | function formatGraphQL(req, res, body) { 26 | var data = body; 27 | /* Do a thing to data */ 28 | res.setHeader('Content-Length', Buffer.byteLength(data)); 29 | return data; 30 | } 31 | 32 | var server = restify.createServer({ 33 | formatters: { 34 | 'application/graphql': formatGraphQL 35 | } 36 | }); 37 | 38 | // Your application now supports content-type 'application/graphql' 39 | ``` 40 | 41 | 42 | ## Types 43 | 44 | 45 | 46 | 47 | ### formatter 48 | 49 | Format a response for being sent over the wire 50 | 51 | Type: [Function][9] 52 | 53 | **Parameters** 54 | 55 | - `req` **[Object][10]** the request object (not used) 56 | - `res` **[Object][10]** the response object 57 | - `body` **[Object][10]** response body to format 58 | 59 | Returns **[String][11]** formatted response data 60 | 61 | ## Included formatters 62 | 63 | restify comes pre-loaded with a standard set of formatters for common 64 | use cases. 65 | 66 | 67 | ### formatText 68 | 69 | Formats the body to 'text' by invoking a toString() on the body if it 70 | exists. If it doesn't, then the response is a zero-length string. 71 | 72 | **Parameters** 73 | 74 | - `req` **[Object][10]** the request object (not used) 75 | - `res` **[Object][10]** the response object 76 | - `body` **[Object][10]** response body. If it has a toString() method this 77 | will be used to make the string representation 78 | 79 | Returns **[String][11]** data 80 | 81 | ### formatJSON 82 | 83 | JSON formatter. Will look for a toJson() method on the body. If one does not 84 | exist then a JSON.stringify will be attempted. 85 | 86 | **Parameters** 87 | 88 | - `req` **[Object][10]** the request object (not used) 89 | - `res` **[Object][10]** the response object 90 | - `body` **[Object][10]** response body 91 | 92 | Returns **[String][11]** data 93 | 94 | ### formatJSONP 95 | 96 | JSONP formatter. like JSON, but with a callback invocation. 97 | Unicode escapes line and paragraph separators. 98 | 99 | **Parameters** 100 | 101 | - `req` **[Object][10]** the request object 102 | - `res` **[Object][10]** the response object 103 | - `body` **[Object][10]** response body 104 | 105 | Returns **[String][11]** data 106 | 107 | ### formatBinary 108 | 109 | Binary formatter. 110 | 111 | **Parameters** 112 | 113 | - `req` **[Object][10]** the request object 114 | - `res` **[Object][10]** the response object 115 | - `body` **[Object][10]** response body 116 | 117 | Returns **[Buffer][12]** body 118 | 119 | [1]: #usage 120 | 121 | [2]: #types 122 | 123 | [3]: #formatter 124 | 125 | [4]: #included-formatters 126 | 127 | [5]: #formattext 128 | 129 | [6]: #formatjson 130 | 131 | [7]: #formatjsonp 132 | 133 | [8]: #formatbinary 134 | 135 | [9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function 136 | 137 | [10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object 138 | 139 | [11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String 140 | 141 | [12]: https://nodejs.org/api/buffer.html 142 | -------------------------------------------------------------------------------- /docs/api/formatters-usage.md: -------------------------------------------------------------------------------- 1 | Restify comes bundled with a selection of useful formatters that prepare your 2 | responses for being sent over the wire, but you are free to include your own! 3 | 4 | ```js 5 | function formatGraphQL(req, res, body) { 6 | var data = body; 7 | /* Do a thing to data */ 8 | res.setHeader('Content-Length', Buffer.byteLength(data)); 9 | return data; 10 | } 11 | 12 | var server = restify.createServer({ 13 | formatters: { 14 | 'application/graphql': formatGraphQL 15 | } 16 | }); 17 | 18 | // Your application now supports content-type 'application/graphql' 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/api/plugins-usage.md: -------------------------------------------------------------------------------- 1 | Restify comes bundled with a selection of useful plugins. These are accessible 2 | off of `restify.plugins` and `restify.pre`. 3 | 4 | ```js 5 | var server = restify.createServer(); 6 | server.use(restify.plugins.acceptParser(server.acceptable)); 7 | server.use(restify.plugins.authorizationParser()); 8 | server.use(restify.plugins.dateParser()); 9 | server.use(restify.plugins.queryParser()); 10 | server.use(restify.plugins.jsonp()); 11 | server.use(restify.plugins.gzipResponse()); 12 | server.use(restify.plugins.bodyParser()); 13 | server.use(restify.plugins.requestExpiry()); 14 | server.use(restify.plugins.throttle({ 15 | burst: 100, 16 | rate: 50, 17 | ip: true, 18 | overrides: { 19 | '192.168.1.1': { 20 | rate: 0, // unlimited 21 | burst: 0 22 | } 23 | } 24 | })); 25 | server.use(restify.plugins.conditionalRequest()); 26 | -------------------------------------------------------------------------------- /docs/api/request-events.md: -------------------------------------------------------------------------------- 1 | ### restifyDone 2 | 3 | After request has been fully serviced, an `restifyDone` event is fired. 4 | restify considers a request to be fully serviced when either: 5 | 6 | 1) The handler chain for a route has been fully completed 7 | 2) An error was returned to `next()`, and the corresponding error events have 8 | been fired for that error type 9 | 10 | The signature for the `restifyDone` event is as follows: 11 | 12 | ```js 13 | function(route, error) { } 14 | ``` 15 | 16 | * `route` - the route object that serviced the request 17 | * `error` - the error passed to `next()`, if applicable 18 | 19 | Note that when the server automatically responds with a 20 | `NotFound`/`MethodNotAllowed`/`VersionNotAllowed`, this event will still be 21 | fired. 22 | -------------------------------------------------------------------------------- /docs/api/request-log.md: -------------------------------------------------------------------------------- 1 | If you are using the [RequestLogger](#bundled-plugins) plugin, the child logger 2 | will be available on `req.log`: 3 | 4 | ```js 5 | function myHandler(req, res, next) { 6 | var log = req.log; 7 | 8 | log.debug({params: req.params}, 'Hello there %s', 'foo'); 9 | } 10 | ``` 11 | 12 | The child logger will inject the request's UUID in the `req._id` attribute of 13 | each log statement. Since the logger lasts for the life of the request, you can 14 | use this to correlate statements for an individual request across any number of 15 | separate handlers. -------------------------------------------------------------------------------- /docs/api/server-events.md: -------------------------------------------------------------------------------- 1 | In additional to emitting all the events from node's 2 | [http.Server](http://nodejs.org/docs/latest/api/http.html#http_class_http_server), 3 | restify servers also emit a number of additional events that make building REST 4 | and web applications much easier. 5 | 6 | ### restifyError 7 | 8 | This event is emitted following all error events as a generic catch all. It is 9 | recommended to use specific error events to handle specific errors, but this 10 | event can be useful for metrics or logging. If you use this in conjunction with 11 | other error events, the most specific event will be fired first, followed by 12 | this one: 13 | 14 | ```js 15 | server.get('/', function(req, res, next) { 16 | return next(new InternalServerError('boom')); 17 | }); 18 | 19 | server.on('InternalServer', function(req, res, err, callback) { 20 | // this will get fired first, as it's the most relevant listener 21 | return callback(); 22 | }); 23 | 24 | server.on('restifyError', function(req, res, err, callback) { 25 | // this is fired second. 26 | return callback(); 27 | }); 28 | ``` 29 | 30 | 31 | ### after 32 | 33 | After each request has been fully serviced, an `after` event is fired. This 34 | event can be hooked into to handle audit logs and other metrics. Note that 35 | flushing a response does not necessarily correspond with an `after` event. 36 | restify considers a request to be fully serviced when either: 37 | 38 | 1) The handler chain for a route has been fully completed 39 | 2) An error was returned to `next()`, and the corresponding error events have 40 | been fired for that error type 41 | 42 | The signature is for the after event is as follows: 43 | 44 | ```js 45 | function(req, res, route, error) { } 46 | ``` 47 | 48 | * `req` - the request object 49 | * `res` - the response object 50 | * `route` - the route object that serviced the request 51 | * `error` - the error passed to `next()`, if applicable 52 | 53 | Note that when the server automatically responds with a 54 | NotFound/MethodNotAllowed/VersionNotAllowed, this event will still be fired. 55 | 56 | 57 | ### pre 58 | 59 | Before each request has been routed, a `pre` event is fired. This event can be 60 | hooked into handle audit logs and other metrics. Since this event fires 61 | *before* routing has occured, it will fire regardless of whether the route is 62 | supported or not, e.g. requests that result in a `404`. 63 | 64 | The signature for the `pre` event is as follows: 65 | 66 | ```js 67 | function(req, res) {} 68 | ``` 69 | * `req` - the request object 70 | * `res` - the response object 71 | 72 | Note that when the server automatically responds with a 73 | NotFound/MethodNotAllowed/VersionNotAllowed, this event will still be fired. 74 | 75 | 76 | ### routed 77 | 78 | A `routed` event is fired after a request has been routed by the router, but 79 | before handlers specific to that route has run. 80 | 81 | The signature for the `routed` event is as follows: 82 | 83 | ```js 84 | function(req, res, route) {} 85 | ``` 86 | 87 | * `req` - the request object 88 | * `res` - the response object 89 | * `route` - the route object that serviced the request 90 | 91 | Note that this event will *not* fire if a requests comes in that are not 92 | routable, i.e. one that would result in a `404`. 93 | 94 | 95 | ### uncaughtException 96 | 97 | If the restify server was created with `handleUncaughtExceptions: true`, 98 | restify will leverage [domains](https://nodejs.org/api/domain.html) to handle 99 | thrown errors in the handler chain. Thrown errors are a result of an explicit 100 | `throw` statement, or as a result of programmer errors like a typo or a null 101 | ref. These thrown errors are caught by the domain, and will be emitted via this 102 | event. For example: 103 | 104 | ```js 105 | server.get('/', function(req, res, next) { 106 | res.send(x); // this will cause a ReferenceError 107 | return next(); 108 | }); 109 | 110 | server.on('uncaughtException', function(req, res, route, err, callback) { 111 | // this event will be fired, with the error object from above: 112 | // ReferenceError: x is not defined 113 | res.send(504, 'boom'); 114 | callback(); 115 | }); 116 | ``` 117 | 118 | If you listen to this event, you __must__: 119 | 120 | 1. send a response to the client _and_ 121 | 2. call the callback function passed as the fourth argument of the event listener 122 | 123 | This behavior is different from the standard error events. If you do not listen 124 | to this event, restify's default behavior is to call `res.send()` with the error 125 | that was thrown. 126 | 127 | The signature is for the after event is as follows: 128 | 129 | ```js 130 | function(req, res, route, error) { } 131 | ``` 132 | 133 | * `req` - the request object 134 | * `res` - the response object 135 | * `route` - the route object that serviced the request 136 | * `error` - the error passed to `next()`, if applicable 137 | 138 | ### close 139 | 140 | Emitted when the server closes. -------------------------------------------------------------------------------- /docs/config/formatters.yaml: -------------------------------------------------------------------------------- 1 | toc: 2 | - name: Usage 3 | file: ../api/formatters-usage.md 4 | - name: Types 5 | children: 6 | - formatter 7 | - name: Included formatters 8 | description: | 9 | restify comes pre-loaded with a standard set of formatters for common 10 | use cases. 11 | children: 12 | - formatText 13 | - formatJSON 14 | - formatJSONP 15 | - formatBinary 16 | -------------------------------------------------------------------------------- /docs/config/plugins.yaml: -------------------------------------------------------------------------------- 1 | toc: 2 | - name: Usage 3 | file: ../api/plugins-usage.md 4 | - name: server.pre() plugins 5 | description: | 6 | This module includes various pre plugins, which are intended to be used prior 7 | to routing of the URL. To use a plugin before routing, use the `server.pre()` 8 | method. 9 | children: 10 | - context 11 | - dedupeSlashes 12 | - pause 13 | - sanitizePath 14 | - reqIdHeaders 15 | - strictQueryParams 16 | - userAgentConnection 17 | - name: server.use() plugins 18 | children: 19 | - acceptParser 20 | - authorizationParser 21 | - dateParser 22 | - queryParser 23 | - jsonp 24 | - bodyParser 25 | - requestLogger 26 | - gzipResponse 27 | - serveStatic 28 | - serveStaticFiles 29 | - throttle 30 | - requestExpiry 31 | - inflightRequestThrottle 32 | - cpuUsageThrottle 33 | - conditionalHandler 34 | - conditionalRequest 35 | - auditLogger 36 | - metrics 37 | - name: Types 38 | children: 39 | - metrics~callback 40 | -------------------------------------------------------------------------------- /docs/config/request.yaml: -------------------------------------------------------------------------------- 1 | toc: 2 | - Request 3 | - name: Events 4 | file: ../api/server-events.md 5 | - name: Log 6 | file: ../api/request-log.md 7 | -------------------------------------------------------------------------------- /docs/config/server.yaml: -------------------------------------------------------------------------------- 1 | toc: 2 | - createServer 3 | - Server 4 | - name: Events 5 | file: ../api/server-events.md 6 | - name: Errors 7 | file: ../api/server-errors.md 8 | - name: Types 9 | children: 10 | - Server~methodOpts 11 | -------------------------------------------------------------------------------- /docs/guides/8to9guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: restify 8.x to 9.x migration guide 3 | permalink: /docs/8to9/ 4 | --- 5 | 6 | ## Introduction 7 | 8 | Restify `9.x` comes with `async/await` support, `pino` and more! 9 | 10 | ## Breaking Changes 11 | 12 | ### Drops support for Node.js `8.x` 13 | 14 | Restify requires Node.js version `>=10.0.0`. 15 | 16 | ### Async/await support 17 | 18 | `async/await` basic support for `.pre()`, `.use()` and route handlers. 19 | 20 | #### Example 21 | 22 | ```js 23 | const restify = require('restify'); 24 | 25 | const server = restify.createServer({}); 26 | 27 | server.use(async (req, res) => { 28 | req.something = await doSomethingAsync(); 29 | }); 30 | 31 | server.get('/params', async (req, res) => { 32 | const value = await asyncOperation(req.something); 33 | res.send(value); 34 | }); 35 | ``` 36 | 37 | #### Middleware API (`.pre()` and `.use()`) 38 | 39 | ```js 40 | server.use(async (req, res) => { 41 | req.something = await doSomethingAsync(); 42 | }); 43 | ``` 44 | - `fn.length === 2` (arity 2); 45 | - `fn instanceof AsyncFunction`; 46 | - if the async function resolves, it calls `next()`; 47 | - any value returned by the async function will be discarded; 48 | - if it rejects with an `Error` instance it calls `next(err)`; 49 | - if it rejects with anything else it wraps in a `AsyncError` and calls `next(err)`; 50 | 51 | #### Route handler API 52 | 53 | ```js 54 | server.get('/something', async (req, res) => { 55 | const someData = await fetchSomeDataAsync(); 56 | res.send({ data: someData }); 57 | }); 58 | ``` 59 | - `fn.length === 2` (arity 2); 60 | - `fn instanceof AsyncFunction`; 61 | - if the async function resolves without a value, it calls `next()`; 62 | - if the async function resolves with a string value, it calls `next(string)` (re-routes*); 63 | - if the async function resolves with a value other than string, it calls `next(any)`; 64 | - if it rejects with an `Error` instance it calls `next(err)`; 65 | - if it rejects with anything else it wraps in a `AsyncError` and calls `next(err)` (error-handing**); 66 | 67 | ##### (*) Note about re-routing: 68 | The `8.x` API allows re-routing when calling `next()` with a string value. If the string matches a valid route, 69 | it will re-route to the given handler. The same is valid for resolving a async function. If the value returned by 70 | the async function is a string, it will try to re-route to the given handler. 71 | 72 | ##### (**) Note about error handling: 73 | Although it is recommended to always reject with an instance of Error, in a async function it is possible to 74 | throw or reject without returning an `Error` instance or even anything at all. In such cases, the value rejected 75 | will be wrapped on a `AsyncError`. 76 | 77 | ### Handler arity check 78 | Handlers expecting 2 or fewer parameters added to a `.pre()`, `.use()` or route chain must be async functions, as: 79 | 80 | ```js 81 | server.use(async (req, res) => { 82 | req.something = await doSomethingAsync(); 83 | }); 84 | ``` 85 | 86 | Handlers expecting more than 2 parameters shouldn't be async functions, as: 87 | 88 | ````js 89 | // This middleware will be rejected and restify will throw 90 | server.use(async (req, res, next) => { 91 | doSomethingAsync(function callback(val) { 92 | req.something = val; 93 | next(); 94 | }); 95 | }); 96 | ```` 97 | 98 | ### Remove `RequestCaptureStream` 99 | 100 | Removes `RequestCaptureStream` from Restify core. 101 | 102 | ### Use `Pino` as default logger (removes dependency on `Bunyan`) 103 | 104 | [Pino](https://github.com/pinojs/pino) is well maintained, performance-focused, 105 | compatible API. It does have a few key differences from `Bunyan`: 106 | 107 | - As a performance optimization, it stores bindings a single serialized `string`, 108 | while `Bunyan` stores them as object keys; 109 | - It uses a `setter` to set the log level, `Bunyan` uses a method; 110 | - It only accepts one stream. If you need the multi-stream functionality, you 111 | must use [pino-multistream](https://github.com/pinojs/pino-multi-stream). 112 | -------------------------------------------------------------------------------- /examples/dtrace/handler-timing.d: -------------------------------------------------------------------------------- 1 | #!/usr/sbin/dtrace -s 2 | #pragma D option quiet 3 | 4 | restify*:::route-start 5 | { 6 | track[arg2] = timestamp; 7 | } 8 | 9 | 10 | restify*:::handler-start 11 | /track[arg3]/ 12 | { 13 | h[arg3, copyinstr(arg2)] = timestamp; 14 | } 15 | 16 | 17 | restify*:::handler-done 18 | /track[arg3] && h[arg3, copyinstr(arg2)]/ 19 | { 20 | 21 | @[copyinstr(arg2)] = quantize((timestamp - h[arg3, copyinstr(arg2)]) / 1000000); 22 | h[arg3, copyinstr(arg2)] = 0; 23 | } 24 | 25 | 26 | restify*:::route-done 27 | /track[arg2]/ 28 | { 29 | @[copyinstr(arg1)] = quantize((timestamp - track[arg2]) / 1000000); 30 | track[arg2] = 0; 31 | } 32 | -------------------------------------------------------------------------------- /examples/dtrace/hello.js: -------------------------------------------------------------------------------- 1 | var restify = require('../../lib'); 2 | 3 | var server = restify.createServer({ 4 | name: 'helloworld', 5 | dtrace: true 6 | }); 7 | 8 | server.use(restify.plugins.acceptParser(server.acceptable)); 9 | server.use(restify.plugins.authorizationParser()); 10 | server.use(restify.plugins.dateParser()); 11 | server.use(restify.plugins.queryParser()); 12 | server.use(restify.plugins.urlEncodedBodyParser()); 13 | 14 | server.use(function slowHandler(req, res, next) { 15 | setTimeout(function() { 16 | next(); 17 | }, 250); 18 | }); 19 | 20 | server.get( 21 | { 22 | path: '/hello/:name', 23 | name: 'GetFoo' 24 | }, 25 | function respond(req, res, next) { 26 | res.send({ 27 | hello: req.params.name 28 | }); 29 | next(); 30 | } 31 | ); 32 | 33 | server.listen(8080, function() { 34 | console.log('listening: %s', server.url); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var restify = require('../lib'); 4 | var server = restify.createServer(); 5 | 6 | server.pre(function pre(req, res, next) { 7 | console.log('pre'); 8 | next(); 9 | }); 10 | 11 | server.use(function use(req, res, next) { 12 | console.log('use'); 13 | next(); 14 | }); 15 | 16 | server.on('after', function(req, res, route, err) { 17 | console.log('after'); 18 | }); 19 | 20 | server.get( 21 | '/:userId', 22 | function onRequest(req, res, next) { 23 | console.log(req.url, '1'); 24 | next(); 25 | }, 26 | function onRequest(req, res, next) { 27 | console.log(req.url, '2'); 28 | res.send({ hello: 'world' }); 29 | next(); 30 | } 31 | ); 32 | 33 | server.listen(3001); 34 | -------------------------------------------------------------------------------- /examples/http2/http2.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var pino = require('pino'); 4 | var restify = require('../../lib'); 5 | 6 | var srv = restify.createServer({ 7 | http2: { 8 | cert: fs.readFileSync(path.join(__dirname, './keys/http2-cert.pem')), 9 | key: fs.readFileSync(path.join(__dirname, './keys/http2-key.pem')), 10 | ca: fs.readFileSync(path.join(__dirname, 'keys/http2-csr.pem')), 11 | allowHTTP1: true //allow incoming connections that do not support HTTP/2 to be downgraded to HTTP/1.x 12 | } 13 | }); 14 | 15 | srv.get('/', function(req, res, next) { 16 | res.send({ hello: 'world' }); 17 | next(); 18 | }); 19 | 20 | srv.on( 21 | 'after', 22 | restify.plugins.auditLogger({ 23 | event: 'after', 24 | body: true, 25 | log: pino( 26 | { name: 'audit' }, 27 | process.stdout 28 | ) 29 | }) 30 | ); 31 | 32 | srv.listen(8080, function() { 33 | console.log('ready on %s', srv.url); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/http2/keys/http2-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS 3 | VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY 4 | SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw 5 | OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL 6 | BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB 7 | nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x 8 | p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp 9 | gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7 10 | 5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79 11 | vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV 12 | yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j 13 | Uws6Lif3P9UbsuRiYPxMgg98wg== 14 | -----END CERTIFICATE----- 15 | 16 | -------------------------------------------------------------------------------- /examples/http2/keys/http2-csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN 3 | MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk 4 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF 5 | 3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je 6 | i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+ 7 | A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa 8 | FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb 9 | 3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC 10 | hC3dz5odyKqe4nmoofomALkBL9t4H8s= 11 | -----END CERTIFICATE REQUEST----- 12 | 13 | -------------------------------------------------------------------------------- /examples/http2/keys/http2-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV 3 | dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR 4 | GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB 5 | AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR 6 | C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6 7 | KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc 8 | FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt 9 | Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0 10 | M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv 11 | 20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx 12 | I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG 13 | ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D 14 | rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg= 15 | -----END RSA PRIVATE KEY----- 16 | 17 | -------------------------------------------------------------------------------- /examples/jsonp/jsonp.js: -------------------------------------------------------------------------------- 1 | var restify = require('../../lib'); 2 | 3 | var srv = restify.createServer(); 4 | srv.use(restify.plugins.queryParser()); 5 | srv.use(restify.plugins.jsonp()); 6 | srv.get('/', function(req, res, next) { 7 | res.send({ hello: 'world' }); 8 | next(); 9 | }); 10 | 11 | srv.listen(8080, function() { 12 | console.log('ready on %s', srv.url); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/sockio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restify-example", 3 | "version": "0.0.0", 4 | "description": "Socket.io example", 5 | "main": "sockio.js", 6 | "dependencies": { 7 | "socket.io": "^4.5.0" 8 | }, 9 | "scripts": { 10 | "start": "node sockio.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/sockio/sockio.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | var socketio = require('socket.io'); 4 | 5 | var restify = require('../../lib'); 6 | 7 | ///--- Globals 8 | 9 | var HTML = 10 | '<script src="/socket.io/socket.io.js"></script>\n' + 11 | '<script>\n' + 12 | 'var socket = io("http://localhost:8080");\n' + 13 | 'socket.on("news", function (data) {\n' + 14 | 'console.log(data);\n' + 15 | 'socket.emit("my other event", { my: "data" });\n' + 16 | '});\n' + 17 | '</script>'; 18 | 19 | ///--- Mainline 20 | 21 | var server = restify.createServer(); 22 | var io = socketio(server.server); 23 | 24 | server.get('/', function indexHTML(req, res, next) { 25 | res.setHeader('Content-Type', 'text/html'); 26 | res.setHeader('Content-Length', Buffer.byteLength(HTML)); 27 | res.writeHead(200); 28 | res.write(HTML); 29 | res.end(); 30 | next(); 31 | }); 32 | 33 | io.on('connection', function(socket) { 34 | socket.emit('news', { hello: 'world' }); 35 | socket.on('my other event', function(data) { 36 | console.log(data); 37 | }); 38 | }); 39 | 40 | server.listen(8080, function() { 41 | console.log('socket.io server listening at %s', server.url); 42 | }); 43 | -------------------------------------------------------------------------------- /examples/spdy/keys/spdy-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS 3 | VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY 4 | SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw 5 | OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL 6 | BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB 7 | nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x 8 | p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp 9 | gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7 10 | 5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79 11 | vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV 12 | yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j 13 | Uws6Lif3P9UbsuRiYPxMgg98wg== 14 | -----END CERTIFICATE----- 15 | 16 | -------------------------------------------------------------------------------- /examples/spdy/keys/spdy-csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN 3 | MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk 4 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF 5 | 3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je 6 | i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+ 7 | A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa 8 | FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb 9 | 3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC 10 | hC3dz5odyKqe4nmoofomALkBL9t4H8s= 11 | -----END CERTIFICATE REQUEST----- 12 | 13 | -------------------------------------------------------------------------------- /examples/spdy/keys/spdy-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV 3 | dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR 4 | GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB 5 | AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR 6 | C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6 7 | KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc 8 | FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt 9 | Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0 10 | M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv 11 | 20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx 12 | I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG 13 | ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D 14 | rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg= 15 | -----END RSA PRIVATE KEY----- 16 | 17 | -------------------------------------------------------------------------------- /examples/spdy/spdy.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var pino = require('pino'); 4 | var restify = require('../../lib'); 5 | 6 | var srv = restify.createServer({ 7 | spdy: { 8 | cert: fs.readFileSync(path.join(__dirname, './keys/spdy-cert.pem')), 9 | key: fs.readFileSync(path.join(__dirname, './keys/spdy-key.pem')), 10 | ca: fs.readFileSync(path.join(__dirname, 'keys/spdy-csr.pem')) 11 | } 12 | }); 13 | 14 | srv.get('/', function(req, res, next) { 15 | res.send({ hello: 'world' }); 16 | next(); 17 | }); 18 | 19 | srv.on( 20 | 'after', 21 | restify.plugins.auditLogger({ 22 | event: 'after', 23 | body: true, 24 | log: pino({name: 'audit'}) 25 | }) 26 | ); 27 | 28 | srv.listen(8080, function() { 29 | console.log('ready on %s', srv.url); 30 | }); 31 | -------------------------------------------------------------------------------- /examples/todoapp/lib/client.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Mark Cavage. All rights reserved. 2 | 3 | var util = require('util'); 4 | 5 | var assert = require('assert-plus'); 6 | var clients = require('restify-clients'); 7 | 8 | ///--- Globals 9 | 10 | var sprintf = util.format; 11 | 12 | ///--- API 13 | 14 | function TodoClient(options) { 15 | assert.object(options, 'options'); 16 | assert.object(options.log, 'options.log'); 17 | assert.optionalString(options.socketPath, 'options.socketPath'); 18 | assert.optionalString(options.url, 'options.url'); 19 | assert.optionalString(options.version, 'options.version'); 20 | 21 | var ver = options.version || '~1.0'; 22 | 23 | this.client = clients.createJSONClient({ 24 | log: options.log, 25 | name: 'TodoClient', 26 | socketPath: options.socketPath, 27 | url: options.url, 28 | version: ver 29 | }); 30 | this.log = options.log.child({ component: 'TodoClient' }, true); 31 | this.url = options.url; 32 | this.version = ver; 33 | 34 | if (options.username && options.password) { 35 | this.username = options.username; 36 | this.client.basicAuth(options.username, options.password); 37 | } 38 | } 39 | 40 | TodoClient.prototype.create = function create(task, cb) { 41 | assert.string(task, 'task'); 42 | assert.func(cb, 'callback'); 43 | 44 | this.client.post('/todo', { task: task }, function(err, req, res, obj) { 45 | if (err) { 46 | cb(err); 47 | } else { 48 | cb(null, obj); 49 | } 50 | }); 51 | }; 52 | 53 | TodoClient.prototype.list = function list(cb) { 54 | assert.func(cb, 'callback'); 55 | 56 | this.client.get('/todo', function(err, req, res, obj) { 57 | if (err) { 58 | cb(err); 59 | } else { 60 | cb(null, obj); 61 | } 62 | }); 63 | }; 64 | 65 | TodoClient.prototype.get = function get(name, cb) { 66 | assert.string(name, 'name'); 67 | assert.func(cb, 'callback'); 68 | 69 | this.client.get('/todo/' + name, function(err, req, res, obj) { 70 | if (err) { 71 | cb(err); 72 | } else { 73 | cb(null, obj); 74 | } 75 | }); 76 | }; 77 | 78 | TodoClient.prototype.update = function update(todo, cb) { 79 | assert.object(todo, 'todo'); 80 | assert.func(cb, 'callback'); 81 | 82 | this.client.put('/todo/' + todo.name, todo, function(err) { 83 | if (err) { 84 | cb(err); 85 | } else { 86 | cb(null); 87 | } 88 | }); 89 | }; 90 | 91 | TodoClient.prototype.del = function del(name, cb) { 92 | if (typeof name === 'function') { 93 | cb = name; 94 | name = ''; 95 | } 96 | assert.string(name, 'name'); 97 | assert.func(cb, 'callback'); 98 | 99 | var p = '/todo' + (name.length > 0 ? '/' + name : ''); 100 | this.client.del(p, function(err) { 101 | if (err) { 102 | cb(err); 103 | } else { 104 | cb(null); 105 | } 106 | }); 107 | }; 108 | 109 | TodoClient.prototype.toString = function toString() { 110 | var str = sprintf( 111 | '[object TodoClient<url=%s, username=%s, version=%s]', 112 | this.url, 113 | this.username || 'null', 114 | this.version 115 | ); 116 | return str; 117 | }; 118 | 119 | ///--- API 120 | 121 | module.exports = { 122 | createClient: function createClient(options) { 123 | return new TodoClient(options); 124 | } 125 | }; 126 | -------------------------------------------------------------------------------- /examples/todoapp/lib/index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Mark Cavage. All rights reserved. 2 | 3 | module.exports = { 4 | createClient: require('./client').createClient, 5 | createServer: require('./server').createServer 6 | }; 7 | -------------------------------------------------------------------------------- /examples/todoapp/main.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Mark Cavage. All rights reserved. 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var pino = require('pino'); 7 | var getopt = require('posix-getopt'); 8 | var restify = require('restify'); 9 | 10 | var todo = require('./lib'); 11 | 12 | ///--- Globals 13 | 14 | var NAME = 'todoapp'; 15 | 16 | var LOG = pino({ name: NAME }); 17 | 18 | ///--- Helpers 19 | 20 | /** 21 | * Standard POSIX getopt-style options parser. 22 | * 23 | * Some options, like directory/user/port are pretty cut and dry, but note 24 | * the 'verbose' or '-v' option afflicts the log level, repeatedly. So you 25 | * can run something like: 26 | * 27 | * node main.js -p 80 -vv 2>&1 | npx pino-pretty 28 | * 29 | * And the log level will be set to TRACE. 30 | */ 31 | function parseOptions() { 32 | var option; 33 | var opts = {}; 34 | var parser = new getopt.BasicParser('hvd:p:u:z:', process.argv); 35 | 36 | while ((option = parser.getopt()) !== undefined) { 37 | switch (option.option) { 38 | case 'd': 39 | opts.directory = path.normalize(option.optarg); 40 | break; 41 | 42 | case 'h': 43 | usage(); 44 | break; 45 | 46 | case 'p': 47 | opts.port = parseInt(option.optarg, 10); 48 | break; 49 | 50 | case 'u': 51 | opts.user = option.optarg; 52 | break; 53 | 54 | case 'v': 55 | // Allows us to set -vvv -> this little hackery 56 | // just ensures that we're never < TRACE 57 | LOG.level(Math.max(pino.levels.values.trace, LOG.level - 10)); 58 | 59 | if (LOG.level <= pino.levels.values.debug) { 60 | LOG = LOG.child({ src: true }); 61 | } 62 | break; 63 | 64 | case 'z': 65 | opts.password = option.optarg; 66 | break; 67 | 68 | default: 69 | usage('invalid option: ' + option.option); 70 | break; 71 | } 72 | } 73 | 74 | return opts; 75 | } 76 | 77 | function usage(msg) { 78 | if (msg) { 79 | console.error(msg); 80 | } 81 | 82 | var str = 83 | 'usage: ' + NAME + ' [-v] [-d dir] [-p port] [-u user] [-z password]'; 84 | console.error(str); 85 | process.exit(msg ? 1 : 0); 86 | } 87 | 88 | ///--- Mainline 89 | 90 | (function main() { 91 | var options = parseOptions(); 92 | 93 | LOG.debug(options, 'command line arguments parsed'); 94 | 95 | // First setup our 'database' 96 | var dir = path.normalize((options.directory || '/tmp') + '/todos'); 97 | 98 | try { 99 | fs.mkdirSync(dir); 100 | } catch (e) { 101 | if (e.code !== 'EEXIST') { 102 | LOG.fatal(e, 'unable to create "database" %s', dir); 103 | process.exit(1); 104 | } 105 | } 106 | 107 | var server = todo.createServer({ 108 | directory: dir, 109 | log: LOG 110 | }); 111 | 112 | // At last, let's rock and roll 113 | server.listen(options.port || 8080, function onListening() { 114 | LOG.info('listening at %s', server.url); 115 | }); 116 | })(); 117 | -------------------------------------------------------------------------------- /examples/todoapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restify-example", 3 | "version": "0.0.0", 4 | "description": "Kitchen Sink App of restify", 5 | "main": "main.js", 6 | "dependencies": { 7 | "assert-plus": "1.0.0", 8 | "pino": "^7.11.0", 9 | "posix-getopt": "^1.2.1", 10 | "restify-errors": "^8.0.2", 11 | "restify-clients": "^3.1.0", 12 | "restify": "^8.6.1" 13 | }, 14 | "devDependencies": { 15 | "mocha": "^10.0.0", 16 | "chai": "^4.3.6", 17 | "pino-pretty": "^7.6.1" 18 | }, 19 | "scripts": { 20 | "start": "node main.js 2>&1 | pino-pretty", 21 | "test": "mocha test" 22 | }, 23 | "author": "Mark Cavage", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /examples/todoapp/test/todo.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Mark Cavage. All rights reserved. 2 | 3 | var fs = require('fs'); 4 | 5 | var pino = require('pino'); 6 | var restify = require('restify'); 7 | var assert = require('chai').assert; 8 | 9 | var todo = require('../lib'); 10 | 11 | ///--- Globals 12 | 13 | var DIR = '/tmp/.todo_unit_test'; 14 | var SOCK = '/tmp/.todo_sock'; 15 | 16 | ///--- Tests 17 | 18 | describe('todoapp', function () { 19 | var CLIENT; 20 | var SERVER; 21 | 22 | before(function (done) { 23 | var log = pino({ 24 | name: 'todo_unit_test', 25 | level: process.env.LOG_LEVEL || 'info' 26 | }); 27 | 28 | fs.mkdir(DIR, function(err) { 29 | if (err && err.code !== 'EEXIST') { 30 | console.error('unable to mkdir: ' + err.stack); 31 | process.exit(1); 32 | } 33 | 34 | SERVER = todo.createServer({ 35 | directory: DIR, 36 | log: log.child({ component: 'server' }, true), 37 | noAudit: true 38 | }); 39 | 40 | assert.ok(SERVER); 41 | SERVER.listen(SOCK, function() { 42 | CLIENT = todo.createClient({ 43 | log: log.child({ component: 'client' }, true), 44 | socketPath: SOCK 45 | }); 46 | assert.ok(CLIENT); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | 52 | it('should return an empty list', function (done) { 53 | CLIENT.list(function(err, todos) { 54 | assert.ifError(err); 55 | assert.ok(todos); 56 | assert.ok(Array.isArray(todos)); 57 | 58 | if (todos) { 59 | assert.equal(todos.length, 0); 60 | } 61 | done(); 62 | }); 63 | }); 64 | 65 | it('should create a new task', function (done) { 66 | var task = 'check that unit test works'; 67 | CLIENT.create(task, function(err, todo) { 68 | assert.ifError(err); 69 | assert.ok(todo); 70 | 71 | if (todo) { 72 | assert.ok(todo.name); 73 | assert.equal(todo.task, task); 74 | } 75 | done(); 76 | }); 77 | }); 78 | 79 | it('should list and get', function (done) { 80 | CLIENT.list(function(err, todos) { 81 | assert.ifError(err); 82 | assert.ok(todos); 83 | assert.ok(Array.isArray(todos)); 84 | 85 | if (todos) { 86 | assert.equal(todos.length, 1); 87 | CLIENT.get(todos[0], function(err2, todo) { 88 | assert.ifError(err2); 89 | assert.ok(todo); 90 | done(); 91 | }); 92 | } else { 93 | done(); 94 | } 95 | }); 96 | }); 97 | 98 | it('should update', function (done) { 99 | CLIENT.list(function(err, todos) { 100 | assert.ifError(err); 101 | assert.ok(todos); 102 | assert.ok(Array.isArray(todos)); 103 | 104 | if (todos) { 105 | assert.equal(todos.length, 1); 106 | 107 | var todo = { 108 | name: todos[0], 109 | task: 'something else' 110 | }; 111 | CLIENT.update(todo, function(err2) { 112 | assert.ifError(err2); 113 | done(); 114 | }); 115 | } else { 116 | done(); 117 | } 118 | }); 119 | }); 120 | 121 | after(function(done) { 122 | CLIENT.del(function(err) { 123 | assert.ifError(err); 124 | CLIENT.client.close(); 125 | SERVER.close(function () { 126 | fs.rmdir(DIR, function(err) { 127 | assert.ifError(err); 128 | done(); 129 | }); 130 | }); 131 | }); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /lib/deprecationWarnings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function deprecationWarnings(server) { 4 | // deprecation for domains and next.ifError 5 | if (server.handleUncaughtExceptions === true) { 6 | server.log.warn( 7 | [ 8 | 'DEPRECATION WARNING: Due to deprecation of the domain module', 9 | 'in node.js, all features in restify that depend on it have', 10 | 'been deprecated as well.', 11 | 'This includes `handleUncaughtExceptions` and', 12 | '`next.ifError()`. They will continue to work in 5.x, but', 13 | 'consider them unsupported and likely to be removed', 14 | 'from future versions of restify.' 15 | ].join(' ') 16 | ); 17 | } 18 | } 19 | 20 | module.exports = deprecationWarnings; 21 | -------------------------------------------------------------------------------- /lib/dtrace.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | ///--- Globals 4 | 5 | 'use strict'; 6 | 7 | var ID = 0; 8 | var MAX_INT = Math.pow(2, 32) - 1; 9 | 10 | var PROBES = { 11 | // server_name, route_name, id, method, url, headers (json) 12 | 'route-start': ['char *', 'char *', 'int', 'char *', 'char *', 'json'], 13 | 14 | // server_name, route_name, handler_name, id 15 | 'handler-start': ['char *', 'char *', 'char *', 'int'], 16 | 17 | // server_name, route_name, handler_name, id 18 | 'handler-done': ['char *', 'char *', 'char *', 'int'], 19 | 20 | // server_name, route_name, id, statusCode, headers (json) 21 | 'route-done': ['char *', 'char *', 'int', 'int', 'json'], 22 | 23 | // Client probes 24 | // method, url, headers, id 25 | 'client-request': ['char *', 'char *', 'json', 'int'], 26 | 27 | // id, statusCode, headers 28 | 'client-response': ['int', 'int', 'json'], 29 | 30 | // id, Error.toString() 31 | 'client-error': ['id', 'char *'] 32 | }; 33 | var PROVIDER; 34 | 35 | ///--- API 36 | 37 | // eslint-disable-next-line wrap-iife 38 | module.exports = (function exportStaticProvider() { 39 | if (!PROVIDER) { 40 | try { 41 | var dtrace = require('dtrace-provider'); 42 | PROVIDER = dtrace.createDTraceProvider('restify'); 43 | } catch (e) { 44 | PROVIDER = { 45 | fire: function fire() {}, 46 | enable: function enable() {}, 47 | addProbe: function addProbe() { 48 | var p = { 49 | fire: function fire() {} 50 | }; 51 | return p; 52 | }, 53 | removeProbe: function removeProbe() {}, 54 | disable: function disable() {} 55 | }; 56 | } 57 | 58 | PROVIDER._rstfy_probes = {}; 59 | 60 | Object.keys(PROBES).forEach(function forEach(p) { 61 | var args = PROBES[p].splice(0); 62 | args.unshift(p); 63 | 64 | var probe = PROVIDER.addProbe.apply(PROVIDER, args); 65 | PROVIDER._rstfy_probes[p] = probe; 66 | }); 67 | 68 | PROVIDER.enable(); 69 | 70 | PROVIDER.nextId = function nextId() { 71 | if (++ID >= MAX_INT) { 72 | ID = 1; 73 | } 74 | 75 | return ID; 76 | }; 77 | } 78 | 79 | return PROVIDER; 80 | })(); 81 | -------------------------------------------------------------------------------- /lib/errorTypes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var errors = require('restify-errors'); 4 | 5 | // This allows Restify to work with restify-errors v6+ 6 | module.exports = { 7 | RequestCloseError: errors.makeConstructor('RequestCloseError'), 8 | RouteMissingError: errors.makeConstructor('RouteMissingError'), 9 | AsyncError: errors.makeConstructor('AsyncError') 10 | }; 11 | -------------------------------------------------------------------------------- /lib/formatters/binary.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | ///--- Exports 6 | 7 | /** 8 | * Binary formatter. 9 | * 10 | * @public 11 | * @function formatBinary 12 | * @param {Object} req - the request object 13 | * @param {Object} res - the response object 14 | * @param {Object} body - response body 15 | * @returns {Buffer} body 16 | */ 17 | function formatBinary(req, res, body) { 18 | if (!Buffer.isBuffer(body)) { 19 | body = new Buffer(body.toString()); 20 | } 21 | 22 | res.setHeader('Content-Length', body.length); 23 | 24 | return body; 25 | } 26 | 27 | module.exports = formatBinary; 28 | -------------------------------------------------------------------------------- /lib/formatters/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | ///--- Exports 5 | 6 | /** 7 | * Format a response for being sent over the wire 8 | * 9 | * @public 10 | * @typedef {Function} formatter 11 | * @param {Object} req - the request object (not used) 12 | * @param {Object} res - the response object 13 | * @param {Object} body - response body to format 14 | * @returns {String} formatted response data 15 | */ 16 | 17 | module.exports = { 18 | 'application/javascript; q=0.1': require('./jsonp'), 19 | 'application/json; q=0.4': require('./json'), 20 | 'text/plain; q=0.3': require('./text'), 21 | 'application/octet-stream; q=0.2': require('./binary') 22 | }; 23 | -------------------------------------------------------------------------------- /lib/formatters/json.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var errors = require('restify-errors'); 6 | 7 | ///--- Exports 8 | 9 | /** 10 | * JSON formatter. Will look for a toJson() method on the body. If one does not 11 | * exist then a JSON.stringify will be attempted. 12 | * 13 | * @public 14 | * @function formatJSON 15 | * @param {Object} req - the request object (not used) 16 | * @param {Object} res - the response object 17 | * @param {Object} body - response body 18 | * @returns {String} data 19 | */ 20 | function formatJSON(req, res, body) { 21 | var data = 'null'; 22 | if (body !== undefined) { 23 | try { 24 | data = JSON.stringify(body); 25 | } catch (e) { 26 | throw new errors.InternalServerError( 27 | { cause: e, info: { formatter: 'json' } }, 28 | 'could not format response body' 29 | ); 30 | } 31 | } 32 | 33 | // Setting the content-length header is not a formatting feature and should 34 | // be separated into another module 35 | res.setHeader('Content-Length', Buffer.byteLength(data)); 36 | 37 | return data; 38 | } 39 | 40 | module.exports = formatJSON; 41 | -------------------------------------------------------------------------------- /lib/formatters/jsonp.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | ///--- Exports 6 | 7 | /** 8 | * JSONP formatter. like JSON, but with a callback invocation. 9 | * Unicode escapes line and paragraph separators. 10 | * 11 | * @public 12 | * @function formatJSONP 13 | * @param {Object} req - the request object 14 | * @param {Object} res - the response object 15 | * @param {Object} body - response body 16 | * @returns {String} data 17 | */ 18 | function formatJSONP(req, res, body) { 19 | if (!body) { 20 | res.setHeader('Content-Length', 0); 21 | return null; 22 | } 23 | 24 | if (Buffer.isBuffer(body)) { 25 | body = body.toString('base64'); 26 | } 27 | 28 | var _cb = req.query.callback || req.query.jsonp; 29 | var data; 30 | 31 | if (_cb) { 32 | data = 33 | 'typeof ' + 34 | _cb + 35 | " === 'function' && " + 36 | _cb + 37 | '(' + 38 | JSON.stringify(body) + 39 | ');'; 40 | } else { 41 | data = JSON.stringify(body); 42 | } 43 | 44 | data = data.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); 45 | 46 | res.setHeader('Content-Length', Buffer.byteLength(data)); 47 | return data; 48 | } 49 | 50 | module.exports = formatJSONP; 51 | -------------------------------------------------------------------------------- /lib/formatters/text.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * Formats the body to 'text' by invoking a toString() on the body if it 7 | * exists. If it doesn't, then the response is a zero-length string. 8 | * 9 | * @public 10 | * @function formatText 11 | * @param {Object} req - the request object (not used) 12 | * @param {Object} res - the response object 13 | * @param {Object} body - response body. If it has a toString() method this 14 | * will be used to make the string representation 15 | * @returns {String} data 16 | */ 17 | function formatText(req, res, body) { 18 | // if body is null, default to empty string 19 | var data = ''; 20 | 21 | data = body.toString(); 22 | 23 | // TODO: setting the content-length header is not a formatting 24 | // feature and should be separated into another module 25 | res.setHeader('Content-Length', Buffer.byteLength(data)); 26 | return data; 27 | } 28 | 29 | module.exports = formatText; 30 | -------------------------------------------------------------------------------- /lib/helpers/chainComposer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | var Chain = require('../chain'); 5 | var _ = require('lodash'); 6 | 7 | module.exports = composeHandlerChain; 8 | 9 | /** 10 | * Builds a function with the signature of a handler 11 | * function(req,resp,callback). 12 | * which internally executes the passed in array of handler function as a chain. 13 | * 14 | * @param {Array} [handlers] - handlers Array of 15 | * function(req,resp,callback) handlers. 16 | * @param {Object} [options] - options Optional option object that is 17 | * passed to Chain. 18 | * @returns {Function} Handler function that executes the handler chain when run 19 | */ 20 | function composeHandlerChain(handlers, options) { 21 | var chain = new Chain(options); 22 | if (_.isArray(handlers)) { 23 | handlers = _.flattenDeep(handlers); 24 | handlers.forEach(function(handler) { 25 | chain.add(handler); 26 | }); 27 | } else { 28 | chain.add(handlers); 29 | } 30 | 31 | return function handlerChain(req, resp, callback) { 32 | chain.run(req, resp, callback); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /lib/http_date.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * Takes an instance of a date object, formats it UTC 7 | * e.g., Wed, 17 Jun 2015 01:30:26 GMT 8 | * 9 | * @public 10 | * @function httpDate 11 | * @param {Object} now - a date object 12 | * @returns {String} formatted dated object 13 | */ 14 | module.exports = function httpDate(now) { 15 | if (!now) { 16 | now = new Date(); 17 | } 18 | 19 | return now.toUTCString(); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert-plus'); 6 | var errors = require('restify-errors'); 7 | 8 | var pino = require('pino'); 9 | var Router = require('./router'); 10 | var Server = require('./server'); 11 | var shallowCopy = require('./utils').shallowCopy; 12 | 13 | var InternalError = errors.InternalError; 14 | 15 | /** 16 | * A restify server object is the main interface through which you will register 17 | * routes and handlers for incoming requests. 18 | * 19 | * @public 20 | * @function createServer 21 | * @param {Object} [options] - an options object 22 | * @param {String} [options.name="restify"] - Name of the server. 23 | * @param {Boolean} [options.dtrace=false] - enable DTrace support 24 | * @param {Router} [options.router=new Router(opts)] - Router 25 | * @param {Object} [options.log=pino({name:options.name || "restify"})] 26 | * - [pino](https://github.com/pinojs/pino) instance. 27 | * @param {String} [options.url] - Once listen() is called, this will be filled 28 | * in with where the server is running. 29 | * @param {String|Buffer} [options.certificate] - If you want to create an HTTPS 30 | * server, pass in a PEM-encoded certificate and key. 31 | * @param {String|Buffer} [options.key] - If you want to create an HTTPS server, 32 | * pass in a PEM-encoded certificate and key. 33 | * @param {Object} [options.formatters] - Custom response formatters for 34 | * `res.send()`. 35 | * @param {Boolean} [options.handleUncaughtExceptions=false] - When true restify 36 | * will use a domain to catch and respond to any uncaught 37 | * exceptions that occur in its handler stack. 38 | * Comes with significant negative performance impact. 39 | * @param {Object} [options.spdy] - Any options accepted by 40 | * [node-spdy](https://github.com/indutny/node-spdy). 41 | * @param {Object} [options.http2] - Any options accepted by 42 | * [http2.createSecureServer](https://nodejs.org/api/http2.html). 43 | * @param {Boolean} [options.handleUpgrades=false] - Hook the `upgrade` event 44 | * from the node HTTP server, pushing `Connection: Upgrade` requests through the 45 | * regular request handling chain. 46 | * @param {Boolean} [options.onceNext=false] - Prevents calling next multiple 47 | * times 48 | * @param {Boolean} [options.strictNext=false] - Throws error when next() is 49 | * called more than once, enabled onceNext option 50 | * @param {Object} [options.httpsServerOptions] - Any options accepted by 51 | * [node-https Server](http://nodejs.org/api/https.html#https_https). 52 | * If provided the following restify server options will be ignored: 53 | * spdy, ca, certificate, key, passphrase, rejectUnauthorized, requestCert and 54 | * ciphers; however these can all be specified on httpsServerOptions. 55 | * @param {Boolean} [options.noWriteContinue=false] - prevents 56 | * `res.writeContinue()` in `server.on('checkContinue')` when proxing 57 | * @param {Boolean} [options.ignoreTrailingSlash=false] - ignore trailing slash 58 | * on paths 59 | * @param {Boolean} [options.strictFormatters=true] - enables strict formatters 60 | * behavior: a formatter matching the response's content-type is required. If 61 | * not found, the response's content-type is automatically set to 62 | * 'application/octet-stream'. If a formatter for that content-type is not 63 | * found, sending the response errors. 64 | * @example 65 | * var restify = require('restify'); 66 | * var server = restify.createServer(); 67 | * 68 | * server.listen(8080, function () { 69 | * console.log('ready on %s', server.url); 70 | * }); 71 | * @returns {Server} server 72 | */ 73 | function createServer(options) { 74 | assert.optionalObject(options, 'options'); 75 | 76 | var opts = shallowCopy(options || {}); 77 | var server; 78 | 79 | // empty string should override default value. 80 | opts.name = opts.hasOwnProperty('name') ? opts.name : 'restify'; 81 | opts.log = opts.log || pino({ name: opts.name || 'restify' }); 82 | opts.router = opts.router || new Router(opts); 83 | 84 | server = new Server(opts); 85 | 86 | if (opts.handleUncaughtExceptions) { 87 | server.on('uncaughtException', function onUncaughtException( 88 | req, 89 | res, 90 | route, 91 | e 92 | ) { 93 | if ( 94 | this.listeners('uncaughtException').length > 1 || 95 | res.headersSent 96 | ) { 97 | return false; 98 | } 99 | 100 | res.send(new InternalError(e, e.message || 'unexpected error')); 101 | return true; 102 | }); 103 | } 104 | 105 | return server; 106 | } 107 | 108 | ///--- Exports 109 | 110 | module.exports.logger = pino; 111 | module.exports.createServer = createServer; 112 | module.exports.formatters = require('./formatters'); 113 | module.exports.plugins = require('./plugins'); 114 | module.exports.pre = require('./plugins').pre; 115 | module.exports.helpers = { compose: require('./helpers/chainComposer') }; 116 | -------------------------------------------------------------------------------- /lib/plugins/accept.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert-plus'); 6 | var mime = require('mime'); 7 | 8 | var NotAcceptableError = require('restify-errors').NotAcceptableError; 9 | 10 | /** 11 | * Parses the `Accept` header, and ensures that the server can respond to what 12 | * the client asked for. In almost all cases passing in `server.acceptable` is 13 | * all that's required, as that's an array of content types the server knows 14 | * how to respond to (with the formatters you've registered). If the request is 15 | * for a non-handled type, this plugin will return a `NotAcceptableError` (406). 16 | * 17 | * Note you can get the set of types allowed from a restify server by doing 18 | * `server.acceptable`. 19 | * 20 | * @public 21 | * @function acceptParser 22 | * @throws {NotAcceptableError} 23 | * @param {String[]} accepts - array of accept types. 24 | * @returns {Function} restify handler. 25 | * @example 26 | * server.use(restify.plugins.acceptParser(server.acceptable)); 27 | */ 28 | function acceptParser(accepts) { 29 | var acceptable = accepts; 30 | 31 | if (!Array.isArray(acceptable)) { 32 | acceptable = [acceptable]; 33 | } 34 | assert.arrayOfString(acceptable, 'acceptable'); 35 | 36 | acceptable = acceptable 37 | .filter(function filter(a) { 38 | return a; 39 | }) 40 | .map(function map(a) { 41 | return a.indexOf('/') === -1 ? mime.getType(a) : a; 42 | }) 43 | .filter(function filter(a) { 44 | return a; 45 | }); 46 | 47 | var e = new NotAcceptableError('Server accepts: ' + acceptable.join()); 48 | 49 | function parseAccept(req, res, next) { 50 | if (req.accepts(acceptable)) { 51 | return next(); 52 | } 53 | return next(e); 54 | } 55 | 56 | return parseAccept; 57 | } 58 | 59 | module.exports = acceptParser; 60 | -------------------------------------------------------------------------------- /lib/plugins/authorization.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var httpSignature = require('http-signature'); 6 | var errors = require('restify-errors'); 7 | 8 | ///--- Globals 9 | 10 | var InvalidHeaderError = errors.InvalidHeaderError; 11 | 12 | var OPTIONS = { 13 | algorithms: [ 14 | 'rsa-sha1', 15 | 'rsa-sha256', 16 | 'rsa-sha512', 17 | 'dsa-sha1', 18 | 'hmac-sha1', 19 | 'hmac-sha256', 20 | 'hmac-sha512' 21 | ] 22 | }; 23 | 24 | ///--- Helpers 25 | 26 | function parseBasic(string) { 27 | var decoded; 28 | var index; 29 | var pieces; 30 | 31 | decoded = new Buffer(string, 'base64').toString('utf8'); 32 | 33 | if (!decoded) { 34 | throw new InvalidHeaderError('Authorization header invalid'); 35 | } 36 | 37 | index = decoded.indexOf(':'); 38 | 39 | if (index === -1) { 40 | pieces = [decoded]; 41 | } else { 42 | pieces = [decoded.slice(0, index), decoded.slice(index + 1)]; 43 | } 44 | 45 | if (!pieces || typeof pieces[0] !== 'string') { 46 | throw new InvalidHeaderError('Authorization header invalid'); 47 | } 48 | 49 | // Allows for usernameless authentication 50 | if (!pieces[0]) { 51 | pieces[0] = null; 52 | } 53 | 54 | // Allows for passwordless authentication 55 | if (!pieces[1]) { 56 | pieces[1] = null; 57 | } 58 | 59 | return { 60 | username: pieces[0], 61 | password: pieces[1] 62 | }; 63 | } 64 | 65 | function parseSignature(request, options) { 66 | var opts = options || {}; 67 | opts.algorithms = OPTIONS.algorithms; 68 | 69 | try { 70 | return httpSignature.parseRequest(request, options); 71 | } catch (e) { 72 | throw new InvalidHeaderError( 73 | 'Authorization header invalid: ' + e.message 74 | ); 75 | } 76 | } 77 | 78 | /** 79 | * Parses out the `Authorization` header as best restify can. 80 | * Currently only HTTP Basic Auth and 81 | * [HTTP Signature](https://github.com/joyent/node-http-signature) 82 | * schemes are supported. 83 | * 84 | * @public 85 | * @function authorizationParser 86 | * @throws {InvalidArgumentError} 87 | * @param {Object} [options] - an optional options object that is 88 | * passed to http-signature 89 | * @returns {Function} Handler 90 | * @example 91 | * <caption> 92 | * Subsequent handlers will see `req.authorization`, which looks like above. 93 | * 94 | * `req.username` will also be set, and defaults to 'anonymous'. If the scheme 95 | * is unrecognized, the only thing available in `req.authorization` will be 96 | * `scheme` and `credentials` - it will be up to you to parse out the rest. 97 | * </caption> 98 | * { 99 | * scheme: "<Basic|Signature|...>", 100 | * credentials: "<Undecoded value of header>", 101 | * basic: { 102 | * username: $user 103 | * password: $password 104 | * } 105 | * } 106 | */ 107 | function authorizationParser(options) { 108 | function parseAuthorization(req, res, next) { 109 | req.authorization = {}; 110 | req.username = 'anonymous'; 111 | 112 | if (!req.headers.authorization) { 113 | return next(); 114 | } 115 | 116 | var pieces = req.headers.authorization.split(' ', 2); 117 | 118 | if (!pieces || pieces.length !== 2) { 119 | var e = new InvalidHeaderError('BasicAuth content is invalid.'); 120 | return next(e); 121 | } 122 | 123 | req.authorization.scheme = pieces[0]; 124 | req.authorization.credentials = pieces[1]; 125 | 126 | try { 127 | switch (pieces[0].toLowerCase()) { 128 | case 'basic': 129 | req.authorization.basic = parseBasic(pieces[1]); 130 | req.username = req.authorization.basic.username; 131 | break; 132 | 133 | case 'signature': 134 | req.authorization.signature = parseSignature(req, options); 135 | req.username = req.authorization.signature.keyId; 136 | break; 137 | 138 | default: 139 | break; 140 | } 141 | } catch (e2) { 142 | return next(e2); 143 | } 144 | 145 | return next(); 146 | } 147 | 148 | return parseAuthorization; 149 | } 150 | 151 | module.exports = authorizationParser; 152 | -------------------------------------------------------------------------------- /lib/plugins/date.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert-plus'); 6 | var errors = require('restify-errors'); 7 | 8 | ///--- Globals 9 | 10 | var InvalidHeaderError = errors.InvalidHeaderError; 11 | var RequestExpiredError = errors.RequestExpiredError; 12 | 13 | var BAD_MSG = 'Date header is invalid'; 14 | var OLD_MSG = 'Date header %s is too old'; 15 | 16 | ///--- API 17 | 18 | /** 19 | * Parses out the HTTP Date header (if present) and checks for clock skew. 20 | * If the header is invalid, a `InvalidHeaderError` (`400`) is returned. 21 | * If the clock skew exceeds the specified value, 22 | * a `RequestExpiredError` (`400`) is returned. 23 | * Where expired means the request originated at a time 24 | * before (`$now - $clockSkew`). 25 | * The default clockSkew allowance is 5m (thanks 26 | * Kerberos!) 27 | * 28 | * @public 29 | * @function dateParser 30 | * @throws {RequestExpiredError} 31 | * @throws {InvalidHeaderError} 32 | * @param {Number} [clockSkew=300] - allowed clock skew in seconds. 33 | * @returns {Function} restify handler. 34 | * @example 35 | * // Allows clock skew of 1m 36 | * server.use(restify.plugins.dateParser(60)); 37 | */ 38 | function dateParser(clockSkew) { 39 | var normalizedClockSkew = clockSkew || 300; 40 | assert.number(normalizedClockSkew, 'normalizedClockSkew'); 41 | 42 | normalizedClockSkew = normalizedClockSkew * 1000; 43 | 44 | function parseDate(req, res, next) { 45 | if (!req.headers.date) { 46 | return next(); 47 | } 48 | 49 | var e; 50 | var date = req.headers.date; 51 | var log = req.log; 52 | 53 | try { 54 | var now = Date.now(); 55 | var sent = new Date(date).getTime(); 56 | 57 | if (log.trace()) { 58 | log.trace( 59 | { 60 | allowedSkew: normalizedClockSkew, 61 | now: now, 62 | sent: sent 63 | }, 64 | 'Checking clock skew' 65 | ); 66 | } 67 | 68 | if (now - sent > normalizedClockSkew) { 69 | e = new RequestExpiredError(OLD_MSG, date); 70 | return next(e); 71 | } 72 | } catch (err) { 73 | log.trace( 74 | { 75 | err: err 76 | }, 77 | 'Bad Date header: %s', 78 | date 79 | ); 80 | 81 | e = new InvalidHeaderError(BAD_MSG, date); 82 | return next(e); 83 | } 84 | 85 | return next(); 86 | } 87 | 88 | return parseDate; 89 | } 90 | 91 | module.exports = dateParser; 92 | -------------------------------------------------------------------------------- /lib/plugins/fieldedTextBodyParser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var csv = require('csv'); 8 | var assert = require('assert-plus'); 9 | 10 | ///--- API 11 | 12 | /** 13 | * Returns a plugin that will parse the HTTP request body if the 14 | * contentType is `text/csv` or `text/tsv`. 15 | * 16 | * @public 17 | * @function fieldedTextParser 18 | * @param {Object} options - an options object 19 | * @returns {Function} Handler 20 | */ 21 | function fieldedTextParser(options) { 22 | assert.optionalObject(options, 'options'); 23 | 24 | function parseFieldedText(req, res, next) { 25 | // save original body on req.rawBody and req._body 26 | req.rawBody = req._body = req.body; 27 | 28 | var contentType = req.getContentType(); 29 | 30 | if ( 31 | (contentType !== 'text/csv' && 32 | contentType !== 'text/tsv' && 33 | contentType !== 'text/tab-separated-values') || 34 | !req.body 35 | ) { 36 | next(); 37 | return; 38 | } 39 | 40 | var hDelimiter = req.headers['x-content-delimiter']; 41 | var hEscape = req.headers['x-content-escape']; 42 | var hQuote = req.headers['x-content-quote']; 43 | var hColumns = req.headers['x-content-columns']; 44 | 45 | var delimiter = contentType === 'text/tsv' ? '\t' : ','; 46 | delimiter = hDelimiter ? hDelimiter : delimiter; 47 | var escape = hEscape ? hEscape : '\\'; 48 | var quote = hQuote ? hQuote : '"'; 49 | var columns = hColumns ? hColumns : true; 50 | 51 | var parserOptions = { 52 | delimiter: delimiter, 53 | quote: quote, 54 | escape: escape, 55 | columns: columns 56 | }; 57 | 58 | csv.parse(req.body, parserOptions, function parse(err, parsedBody) { 59 | if (err) { 60 | return next(err); 61 | } 62 | 63 | // Add an "index" property to every row 64 | parsedBody.forEach(function forEach(row, index) { 65 | row.index = index; 66 | }); 67 | req.body = parsedBody; 68 | return next(); 69 | }); 70 | } 71 | 72 | return parseFieldedText; 73 | } 74 | 75 | module.exports = fieldedTextParser; 76 | -------------------------------------------------------------------------------- /lib/plugins/formBodyParser.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert-plus'); 6 | var querystring = require('qs'); 7 | 8 | var bodyReader = require('./bodyReader'); 9 | var errors = require('restify-errors'); 10 | 11 | ///--- Globals 12 | 13 | var MIME_TYPE = 'application/x-www-form-urlencoded'; 14 | 15 | ///--- API 16 | 17 | /** 18 | * Returns a plugin that will parse the HTTP request body IFF the 19 | * contentType is application/x-www-form-urlencoded. 20 | * 21 | * If req.params already contains a given key, that key is skipped and an 22 | * error is logged. 23 | * 24 | * @public 25 | * @function urlEncodedBodyParser 26 | * @param {Object} options - an option sobject 27 | * @returns {Function} Handler 28 | */ 29 | function urlEncodedBodyParser(options) { 30 | var opts = options || {}; 31 | assert.object(opts, 'opts'); 32 | 33 | var override = opts.overrideParams; 34 | 35 | function parseUrlEncodedBody(req, res, next) { 36 | // save original body on req.rawBody and req._body 37 | req.rawBody = req._body = req.body; 38 | 39 | if (req.getContentType() !== MIME_TYPE || !req.body) { 40 | next(); 41 | return; 42 | } 43 | 44 | try { 45 | var params = querystring.parse(req.body); 46 | 47 | if (opts.mapParams === true) { 48 | var keys = Object.keys(params); 49 | keys.forEach(function forEach(k) { 50 | var p = req.params[k]; 51 | 52 | if (p && !override) { 53 | return; 54 | } 55 | req.params[k] = params[k]; 56 | }); 57 | } 58 | 59 | req.body = params; 60 | } catch (e) { 61 | next(new errors.InvalidContentError(e.message)); 62 | return; 63 | } 64 | 65 | req.log.trace('req.params now: %j', req.params); 66 | next(); 67 | } 68 | 69 | var chain = []; 70 | 71 | if (!opts.bodyReader) { 72 | chain.push(bodyReader(opts)); 73 | } 74 | chain.push(parseUrlEncodedBody); 75 | return chain; 76 | } 77 | 78 | module.exports = urlEncodedBodyParser; 79 | -------------------------------------------------------------------------------- /lib/plugins/fullResponse.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var crypto = require('crypto'); 6 | var httpDate = require('./utils/httpDate'); 7 | var hrTimeDurationInMs = require('./utils/hrTimeDurationInMs'); 8 | 9 | ///--- API 10 | 11 | function setHeaders(req, res) { 12 | var hash; 13 | var now = new Date(); 14 | 15 | if (!res.getHeader('Connection')) { 16 | res.setHeader('Connection', req.isKeepAlive() ? 'Keep-Alive' : 'close'); 17 | } 18 | 19 | if (res._data && !res.getHeader('Content-MD5')) { 20 | hash = crypto.createHash('md5'); 21 | hash.update(res._data); 22 | res.setHeader('Content-MD5', hash.digest('base64')); 23 | } 24 | 25 | if (!res.getHeader('Date')) { 26 | res.setHeader('Date', httpDate(now)); 27 | } 28 | 29 | if (res.etag && !res.getHeader('Etag')) { 30 | res.setHeader('Etag', res.etag); 31 | } 32 | 33 | if (!res.getHeader('Server')) { 34 | res.setHeader('Server', res.serverName); 35 | } 36 | 37 | if (res.version && !res.getHeader('Api-Version')) { 38 | res.setHeader('Api-Version', res.version); 39 | } 40 | 41 | if (!res.getHeader('Request-Id')) { 42 | res.setHeader('Request-Id', req.getId()); 43 | } 44 | 45 | if (!res.getHeader('Response-Time')) { 46 | // we cannot use req._timeFlushed here as 47 | // the response is not flushed yet 48 | res.setHeader( 49 | 'Response-Time', 50 | hrTimeDurationInMs(req._timeStart, process.hrtime()) 51 | ); 52 | } 53 | } 54 | 55 | /** 56 | * handles disappeared CORS headers. 57 | * https://github.com/restify/node-restify/issues/284 58 | * 59 | * @public 60 | * @function fullResponse 61 | * @returns {Function} Handler 62 | */ 63 | function fullResponse() { 64 | function restifyResponseHeaders(req, res, next) { 65 | res.once('header', function onceHeader() { 66 | // Restify 1.0 compatibility 67 | if (res.defaultResponseFormatters) { 68 | res.defaultResponseFormatters(res._data); 69 | } 70 | 71 | res.emit('beforeSend', res._data, res._body); 72 | 73 | // end backwards-compatibility 74 | return setHeaders(req, res); 75 | }); 76 | 77 | return next(); 78 | } 79 | 80 | return restifyResponseHeaders; 81 | } 82 | 83 | ///--- Exports 84 | 85 | module.exports = fullResponse; 86 | -------------------------------------------------------------------------------- /lib/plugins/gzip.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var zlib = require('zlib'); 6 | 7 | var assert = require('assert-plus'); 8 | 9 | /** 10 | * @private 11 | * @function _writeHead 12 | * @param {Function} originalFunction - originalFunction 13 | * @returns {undefined} no return value 14 | */ 15 | function _writeHead(originalFunction) { 16 | this.removeHeader('Content-Length'); 17 | var argsLength = arguments.length; 18 | var args = new Array(argsLength - 1); 19 | 20 | for (var i = 1; i < argsLength; i++) { 21 | args[i - 1] = arguments[i]; 22 | } 23 | originalFunction.apply(this, args); 24 | } 25 | 26 | ///--- API 27 | 28 | /** 29 | * If the client sends an `accept-encoding: gzip` header (or one with an 30 | * appropriate q-val), then the server will automatically gzip all 31 | * response data. 32 | * Note that only `gzip` is supported, as this is most widely supported by 33 | * clients in the wild. 34 | * This plugin will overwrite some of the internal streams, so any 35 | * calls to `res.send`, `res.write`, etc., will be compressed. A side effect is 36 | * that the `content-length` header cannot be known, and so 37 | * `transfer-encoding: chunked` will *always* be set when this is in effect. 38 | * This plugin has no impact if the client does not send 39 | * `accept-encoding: gzip`. 40 | * 41 | * https://github.com/restify/node-restify/issues/284 42 | * 43 | * @public 44 | * @function gzipResponse 45 | * @param {Object} [opts] - an options object, see: zlib.createGzip 46 | * @returns {Function} Handler 47 | * @example 48 | * server.use(restify.plugins.gzipResponse()); 49 | */ 50 | function gzipResponse(opts) { 51 | assert.optionalObject(opts, 'options'); 52 | 53 | function gzip(req, res, next) { 54 | if (!req.acceptsEncoding('gzip')) { 55 | next(); 56 | return; 57 | } 58 | 59 | var gz = zlib.createGzip(opts); 60 | 61 | gz.on('data', res.write.bind(res)); 62 | gz.once('end', res.end.bind(res)); 63 | gz.on('drain', res.emit.bind(res, 'drain')); 64 | 65 | var origWrite = res.write; 66 | var origEnd = res.end; 67 | var origWriteHead = res.writeHead; 68 | res.handledGzip = function _handledGzip() { 69 | res.write = origWrite; 70 | res.end = origEnd; 71 | res.writeHead = origWriteHead; 72 | }; 73 | 74 | res.write = gz.write.bind(gz); 75 | res.end = gz.end.bind(gz); 76 | 77 | res.writeHead = _writeHead.bind(res, res.writeHead); 78 | res.setHeader('Content-Encoding', 'gzip'); 79 | next(); 80 | } 81 | 82 | return gzip; 83 | } 84 | 85 | ///--- Exports 86 | 87 | module.exports = gzipResponse; 88 | -------------------------------------------------------------------------------- /lib/plugins/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | ///--- Exports 6 | 7 | module.exports = { 8 | acceptParser: require('./accept'), 9 | auditLogger: require('./audit'), 10 | authorizationParser: require('./authorization'), 11 | bodyParser: require('./bodyParser'), 12 | bodyReader: require('./bodyReader'), 13 | conditionalHandler: require('./conditionalHandler'), 14 | conditionalRequest: require('./conditionalRequest'), 15 | cpuUsageThrottle: require('./cpuUsageThrottle.js'), 16 | dateParser: require('./date'), 17 | fullResponse: require('./fullResponse'), 18 | gzipResponse: require('./gzip'), 19 | inflightRequestThrottle: require('./inflightRequestThrottle'), 20 | jsonBodyParser: require('./jsonBodyParser'), 21 | jsonp: require('./jsonp'), 22 | multipartBodyParser: require('./multipartBodyParser'), 23 | oauth2TokenParser: require('./oauth2TokenParser'), 24 | queryParser: require('./query'), 25 | metrics: require('./metrics'), 26 | requestExpiry: require('./requestExpiry'), 27 | requestLogger: require('./requestLogger'), 28 | serveStatic: require('./static'), 29 | serveStaticFiles: require('./staticFiles'), 30 | throttle: require('./throttle'), 31 | urlEncodedBodyParser: require('./formBodyParser'), 32 | 33 | pre: { 34 | context: require('./pre/context'), 35 | dedupeSlashes: require('./pre/dedupeSlashes'), 36 | pause: require('./pre/pause'), 37 | reqIdHeaders: require('./pre/reqIdHeaders'), 38 | sanitizePath: require('./pre/prePath'), 39 | strictQueryParams: require('./pre/strictQueryParams'), 40 | userAgentConnection: require('./pre/userAgent') 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /lib/plugins/inflightRequestThrottle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert-plus'); 4 | var ServiceUnavailableError = require('restify-errors').ServiceUnavailableError; 5 | 6 | /** 7 | * The `inflightRequestThrottle` module allows you to specify an upper limit to 8 | * the maximum number of inflight requests your server is able to handle. This 9 | * is a simple heuristic for protecting against event loop contention between 10 | * requests causing unacceptable latencies. 11 | * 12 | * The custom error is optional, and allows you to specify your own response 13 | * and status code when rejecting incoming requests due to too many inflight 14 | * requests. It defaults to `503 ServiceUnavailableError`. 15 | * 16 | * This plugin should be registered as early as possibly in the middleware stack 17 | * using `pre` to avoid performing unnecessary work. 18 | * 19 | * @public 20 | * @function inflightRequestThrottle 21 | * @param {Object} opts - configure this plugin 22 | * @param {Number} opts.limit - maximum number of inflight requests the server 23 | * will handle before returning an error 24 | * @param {Error} opts.err - A restify error used as a response when the 25 | * inflight request limit is exceeded 26 | * @param {Function} opts.server - the instance of the restify server this 27 | * plugin will throttle. 28 | * @returns {Function} middleware to be registered on server.pre 29 | * @example 30 | * var errors = require('restify-errors'); 31 | * var restify = require('restify'); 32 | * 33 | * var server = restify.createServer(); 34 | * const options = { limit: 600, server: server }; 35 | * options.res = new errors.InternalServerError(); 36 | * server.pre(restify.plugins.inflightRequestThrottle(options)); 37 | */ 38 | function inflightRequestThrottle(opts) { 39 | // Scrub input and populate our configuration 40 | assert.object(opts, 'opts'); 41 | assert.number(opts.limit, 'opts.limit'); 42 | assert.object(opts.server, 'opts.server'); 43 | assert.func(opts.server.inflightRequests, 'opts.server.inflightRequests'); 44 | 45 | if (opts.err !== undefined && opts.err !== null) { 46 | assert.ok(opts.err instanceof Error, 'opts.err must be an error'); 47 | assert.optionalNumber(opts.err.statusCode, 'opts.err.statusCode'); 48 | } 49 | 50 | var plugin = {}; 51 | plugin._err = opts.err || new ServiceUnavailableError('resource exhausted'); 52 | plugin._limit = opts.limit; 53 | plugin._server = opts.server; 54 | 55 | function onRequest(req, res, next) { 56 | var inflightRequests = plugin._server.inflightRequests(); 57 | 58 | if (inflightRequests > plugin._limit) { 59 | req.log.trace( 60 | { 61 | plugin: 'inflightRequestThrottle', 62 | inflightRequests: inflightRequests, 63 | limit: plugin._limit 64 | }, 65 | 'maximum inflight requests exceeded, rejecting request' 66 | ); 67 | return next(plugin._err); 68 | } 69 | 70 | return next(); 71 | } 72 | 73 | return onRequest; 74 | } 75 | 76 | module.exports = inflightRequestThrottle; 77 | -------------------------------------------------------------------------------- /lib/plugins/jsonBodyParser.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert-plus'); 6 | var errors = require('restify-errors'); 7 | 8 | var bodyReader = require('./bodyReader'); 9 | var regex = require('./utils/regex'); 10 | 11 | ///--- API 12 | 13 | /** 14 | * Parses json body from the request. 15 | * 16 | * @public 17 | * @function jsonBodyParser 18 | * @param {Object} options - an options object 19 | * @throws {InvalidContentError} on bad input 20 | * @returns {Function} Handler 21 | */ 22 | function jsonBodyParser(options) { 23 | assert.optionalObject(options, 'options'); 24 | var opts = options || {}; 25 | 26 | var override = opts.overrideParams; 27 | 28 | function parseJson(req, res, next) { 29 | // save original body on req.rawBody and req._body 30 | req.rawBody = req._body = req.body; 31 | 32 | var contentType = req.getContentType(); 33 | 34 | // check for empty body first, don't pay regex tax unless necessary. 35 | // for content type, check for exact match and any of the *+json types 36 | if ( 37 | !req.body || 38 | (contentType !== 'application/json' && 39 | !regex.jsonContentType.test(contentType)) 40 | ) { 41 | return next(); 42 | } 43 | 44 | var params; 45 | 46 | try { 47 | params = JSON.parse(req.body, opts.reviver); 48 | } catch (e) { 49 | return next( 50 | new errors.InvalidContentError( 51 | '%s', 52 | 'Invalid JSON: ' + e.message 53 | ) 54 | ); 55 | } 56 | 57 | if (opts.mapParams === true) { 58 | if (Array.isArray(params)) { 59 | // if req.params exists, we have url params. we can't map an 60 | // array safely onto req.params, throw an error. 61 | if ( 62 | req.params && 63 | Object.keys(req.params).length > 0 && 64 | !(req.params instanceof Array) 65 | ) { 66 | return next( 67 | new errors.InternalServerError( 68 | 'Cannot map POST body of [Array array] onto ' + 69 | 'req.params' 70 | ) 71 | ); 72 | } 73 | req.params = params; 74 | } else if (typeof params === 'object' && params !== null) { 75 | // else, try to merge the objects 76 | Object.keys(params).forEach(function forEach(k) { 77 | var p = req.params[k]; 78 | 79 | if (p && !override) { 80 | return; 81 | } 82 | req.params[k] = params[k]; 83 | }); 84 | } else { 85 | // otherwise, do a wholesale stomp, no need to merge one by one. 86 | req.params = params || req.params; 87 | } 88 | } 89 | 90 | req.body = params; 91 | 92 | return next(); 93 | } 94 | 95 | var chain = []; 96 | 97 | if (!opts.bodyReader) { 98 | chain.push(bodyReader(opts)); 99 | } 100 | chain.push(parseJson); 101 | return chain; 102 | } 103 | 104 | module.exports = jsonBodyParser; 105 | -------------------------------------------------------------------------------- /lib/plugins/jsonp.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var qs = require('qs'); 6 | 7 | ///--- API 8 | 9 | /** 10 | * Parses the jsonp callback out of the request. 11 | * Supports checking the query string for `callback` or `jsonp` and ensuring 12 | * that the content-type is appropriately set if JSONP params are in place. 13 | * There is also a default `application/javascript` formatter to handle this. 14 | * 15 | * You *should* set the `queryParser` plugin to run before this, but if you 16 | * don't this plugin will still parse the query string properly. 17 | * 18 | * @public 19 | * @function jsonp 20 | * @returns {Function} Handler 21 | * @example 22 | * var server = restify.createServer(); 23 | * server.use(restify.plugins.jsonp()); 24 | */ 25 | function jsonp() { 26 | function _jsonp(req, res, next) { 27 | var q = req.getQuery(); 28 | 29 | // If the query plugin wasn't used, we need to hack it in now 30 | if (typeof q === 'string') { 31 | req.query = qs.parse(q); 32 | } 33 | 34 | if (req.query.callback || req.query.jsonp) { 35 | res.setHeader('Content-Type', 'application/javascript'); 36 | } 37 | 38 | next(); 39 | } 40 | 41 | return _jsonp; 42 | } 43 | 44 | module.exports = jsonp; 45 | -------------------------------------------------------------------------------- /lib/plugins/oauth2TokenParser.js: -------------------------------------------------------------------------------- 1 | /* 2 | oauth2TokenParser - Parser oauth2 tokens from the authorization header 3 | or BODY of the request 4 | 5 | If parsing from the BODY there is adependency on the bodyParser plugin: 6 | 7 | server.use(plugins.bodyParser()); 8 | server.use(plugins.oauth2TokenParser()); 9 | 10 | 11 | */ 12 | 'use strict'; 13 | 14 | var errors = require('restify-errors'); 15 | 16 | /* 17 | 18 | Parses the header for the authorization: bearer 19 | 20 | */ 21 | function parseHeader(req) { 22 | if (req.headers && req.headers.authorization) { 23 | var credentialsIndex = 1; 24 | var parts = req.headers.authorization.split(' '); 25 | var partsExpectedLength = 2; 26 | var schemeIndex = 0; 27 | 28 | if (parts.length === partsExpectedLength) { 29 | var credentials = parts[credentialsIndex]; 30 | var scheme = parts[schemeIndex]; 31 | 32 | if (/^Bearer$/i.test(scheme)) { 33 | return credentials; 34 | } 35 | } 36 | } 37 | 38 | return null; 39 | } 40 | 41 | /** 42 | * Returns a plugin that will parse the client's request for an OAUTH2 43 | access token 44 | * 45 | * Subsequent handlers will see `req.oauth2`, which looks like: 46 | * 47 | * ```js 48 | * { 49 | * oauth2: { 50 | accessToken: 'mF_9.B5f-4.1JqM&p=q' 51 | } 52 | * } 53 | * ``` 54 | * 55 | * @public 56 | * @function oauth2TokenParser 57 | * @throws {InvalidArgumentError} 58 | * @param {Object} options - an options object 59 | * @returns {Function} Handler 60 | */ 61 | function oauth2TokenParser(options) { 62 | function parseOauth2Token(req, res, next) { 63 | req.oauth2 = { accessToken: null }; 64 | 65 | var tokenFromHeader = parseHeader(req); 66 | 67 | if (tokenFromHeader) { 68 | req.oauth2.accessToken = tokenFromHeader; 69 | } 70 | 71 | var tokenFromBody = null; 72 | 73 | if (typeof req.body === 'object') { 74 | tokenFromBody = req.body.access_token; 75 | } 76 | 77 | // more than one method to transmit the token in each request 78 | // is not allowed - return 400 79 | if (tokenFromBody && tokenFromHeader) { 80 | // eslint-disable-next-line new-cap 81 | return next( 82 | new errors.makeErrFromCode(400, 'multiple tokens disallowed') 83 | ); 84 | } 85 | 86 | if ( 87 | tokenFromBody && 88 | req.contentType().toLowerCase() === 89 | 'application/x-www-form-urlencoded' 90 | ) { 91 | req.oauth2.accessToken = tokenFromBody; 92 | } 93 | 94 | return next(); 95 | } 96 | 97 | return parseOauth2Token; 98 | } 99 | 100 | module.exports = oauth2TokenParser; 101 | -------------------------------------------------------------------------------- /lib/plugins/pre/context.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Restify. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert-plus'); 6 | 7 | ///--- API 8 | 9 | /** 10 | * This plugin creates `req.set(key, val)` and `req.get(key)` methods for 11 | * setting and retrieving request specific data. 12 | * 13 | * @public 14 | * @function context 15 | * @returns {Function} Handler 16 | * @example 17 | * server.pre(restify.plugins.pre.context()); 18 | * server.get('/', [ 19 | * function(req, res, next) { 20 | * req.set(myMessage, 'hello world'); 21 | * return next(); 22 | * }, 23 | * function two(req, res, next) { 24 | * res.send(req.get(myMessage)); // => sends 'hello world' 25 | * return next(); 26 | * } 27 | * ]); 28 | */ 29 | function ctx() { 30 | return function context(req, res, next) { 31 | var data = {}; 32 | 33 | /** 34 | * Set context value by key 35 | * Requires the context plugin. 36 | * 37 | * @public 38 | * @memberof Request 39 | * @instance 40 | * @function req.set 41 | * @param {String} key - key 42 | * @param {*} value - value 43 | * @returns {undefined} no return value 44 | */ 45 | req.set = function set(key, value) { 46 | assert.string(key, 'key must be string'); 47 | 48 | if (key === '') { 49 | assert.fail('key must not be empty string'); 50 | } 51 | data[key] = value; 52 | }; 53 | 54 | /** 55 | * Get context value by key. 56 | * Requires the context plugin. 57 | * 58 | * @public 59 | * @memberof Request 60 | * @instance 61 | * @function req.get 62 | * @param {String} key - key 63 | * @returns {*} value stored in context 64 | */ 65 | req.get = function get(key) { 66 | assert.string(key, 'key must be string'); 67 | 68 | if (key === '') { 69 | assert.fail('key must not be empty string'); 70 | } 71 | return data[key]; 72 | }; 73 | 74 | /** 75 | * Get all context 76 | * Requires the context plugin. 77 | * 78 | * @public 79 | * @memberof Request 80 | * @instance 81 | * @function req.getAll 82 | * @returns {*} value stored in context 83 | */ 84 | req.getAll = function getAll() { 85 | return data; 86 | }; 87 | 88 | return next(); 89 | }; 90 | } 91 | 92 | ///--- Exports 93 | 94 | module.exports = ctx; 95 | -------------------------------------------------------------------------------- /lib/plugins/pre/dedupeSlashes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This plugin deduplicates extra slashes found in the URL. This can help with 5 | * malformed URLs that might otherwise get misrouted. 6 | * 7 | * @public 8 | * @function dedupeSlashes 9 | * @returns {Function} Handler 10 | * @example 11 | * server.pre(restify.plugins.pre.dedupeSlashes()); 12 | * server.get('/hello/:one', function(req, res, next) { 13 | * res.send(200); 14 | * return next(); 15 | * }); 16 | * 17 | * // the server will now convert requests to /hello//jake => /hello/jake 18 | */ 19 | function createDedupeSlashes() { 20 | return function dedupeSlashes(req, res, next) { 21 | req.url = req.url.replace(/(\/)\/+/g, '$1'); 22 | return next(); 23 | }; 24 | } 25 | 26 | module.exports = createDedupeSlashes; 27 | -------------------------------------------------------------------------------- /lib/plugins/pre/pause.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | ///--- Helpers 6 | 7 | /** 8 | * @private 9 | * @function pauseStream 10 | * @param {Stream} stream - the stream to pause 11 | * @returns {undefined} no return value 12 | */ 13 | function pauseStream(stream) { 14 | function _buffer(chunk) { 15 | stream.__buffered.push(chunk); 16 | } 17 | 18 | function _catchEnd(chunk) { 19 | stream.__rstfyEnded = true; 20 | } 21 | 22 | stream.__rstfyEnded = false; 23 | stream.__rstfyPaused = true; 24 | stream.__buffered = []; 25 | stream.on('data', _buffer); 26 | stream.once('end', _catchEnd); 27 | stream.pause(); 28 | 29 | stream._resume = stream.resume; 30 | stream.resume = function _rstfy_resume() { 31 | if (!stream.__rstfyPaused) { 32 | return; 33 | } 34 | 35 | stream.removeListener('data', _buffer); 36 | stream.removeListener('end', _catchEnd); 37 | 38 | stream.__buffered.forEach(stream.emit.bind(stream, 'data')); 39 | stream.__buffered.length = 0; 40 | 41 | stream._resume(); 42 | stream.resume = stream._resume; 43 | 44 | if (stream.__rstfyEnded) { 45 | stream.emit('end'); 46 | } 47 | }; 48 | } 49 | 50 | /** 51 | * This pre handler fixes issues with node hanging when an `asyncHandler` is 52 | * used prior to `bodyParser`. 53 | * https://github.com/restify/node-restify/issues/287 54 | * https://github.com/restify/node-restify/issues/409 55 | * https://github.com/restify/node-restify/wiki/1.4-to-2.0-Migration-Tips 56 | * 57 | * @public 58 | * @function pause 59 | * @returns {Function} Handler 60 | */ 61 | function pause() { 62 | function prePause(req, res, next) { 63 | pauseStream(req); 64 | next(); 65 | } 66 | 67 | return prePause; 68 | } 69 | 70 | ///--- Exports 71 | 72 | module.exports = pause; 73 | -------------------------------------------------------------------------------- /lib/plugins/pre/prePath.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | ///--- Helpers 6 | 7 | /** 8 | * Cleans up sloppy URLs on the request object, like /foo////bar/// to /foo/bar. 9 | * 10 | * @private 11 | * @function strip 12 | * @param {Object} path - a url path to clean up 13 | * @returns {String} cleaned path 14 | */ 15 | function strip(path) { 16 | var cur; 17 | var next; 18 | var str = ''; 19 | 20 | for (var i = 0; i < path.length; i++) { 21 | cur = path.charAt(i); 22 | 23 | if (i !== path.length - 1) { 24 | next = path.charAt(i + 1); 25 | } 26 | 27 | if (cur === '/' && (next === '/' || (next === '?' && i > 0))) { 28 | continue; 29 | } 30 | 31 | str += cur; 32 | } 33 | 34 | return str; 35 | } 36 | 37 | /** 38 | * Cleans up sloppy URLs on the request object, 39 | * like `/foo////bar///` to `/foo/bar`. 40 | * 41 | * @public 42 | * @function sanitizePath 43 | * @returns {Function} Handler 44 | */ 45 | function sanitizePath() { 46 | function _sanitizePath(req, res, next) { 47 | req.url = strip(req.url); 48 | next(); 49 | } 50 | 51 | return _sanitizePath; 52 | } 53 | 54 | ///--- Exports 55 | 56 | module.exports = sanitizePath; 57 | -------------------------------------------------------------------------------- /lib/plugins/pre/reqIdHeaders.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert-plus'); 4 | 5 | var DEFAULT_HEADERS = ['request-id', 'x-request-id']; 6 | 7 | /** 8 | * This plugin pulls the value from an incoming request header and uses it 9 | * as the value of the request id. Subsequent calls to `req.id()` 10 | * will return the header values. 11 | * 12 | * @public 13 | * @function reqIdHeaders 14 | * @param {Object} opts - an options object 15 | * @param {String[]} opts.headers - array of headers from where to pull existing 16 | * request id headers. Lookup precedence 17 | * is left to right (lowest index first) 18 | * @returns {Function} Handler 19 | */ 20 | function createReqIdHeaders(opts) { 21 | assert.object(opts, 'opts'); 22 | assert.arrayOfString(opts.headers, 'opts.headers'); 23 | 24 | var headers = opts.headers.concat(DEFAULT_HEADERS); 25 | 26 | return function reqIdHeaders(req, res, next) { 27 | for (var i = 0; i < headers.length; i++) { 28 | var val = req.header(headers[i]); 29 | 30 | if (val) { 31 | req.id(val); 32 | break; 33 | } 34 | } 35 | 36 | return next(); 37 | }; 38 | } 39 | 40 | ///--- Exports 41 | 42 | module.exports = createReqIdHeaders; 43 | -------------------------------------------------------------------------------- /lib/plugins/pre/strictQueryParams.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var BadRequestError = require('restify-errors').BadRequestError; 4 | var assert = require('assert-plus'); 5 | 6 | ///--- API 7 | 8 | /** 9 | * Prevents `req.urls` non-strict key-value query params 10 | * 11 | * The Request-URI is transmitted in the format specified in section 3.2.1. 12 | * If the Request-URI is encoded using the "% HEX HEX" encoding [42], 13 | * the origin server MUST decode the Request-URI 14 | * in order to properly interpret the request. 15 | * Servers SHOULD respond to invalid Request-URIs 16 | * with an appropriate status code. 17 | * 18 | * part of Hypertext Transfer Protocol -- HTTP/1.1 | 5.1.2 Request-URI 19 | * RFC 2616 Fielding, et al. 20 | * 21 | * @public 22 | * @function strictQueryParams 23 | * @param {Object} [options] - an options object 24 | * @param {String} [options.message] - a custom error message 25 | * default value: 26 | * "Url query params does not meet strict format" 27 | * @returns {Function} Handler 28 | */ 29 | function strictQueryParams(options) { 30 | var opts = options || {}; 31 | assert.optionalObject(opts, 'options'); 32 | assert.optionalString(opts.message, 'options.message'); 33 | 34 | function _strictQueryParams(req, res, next) { 35 | var keyValQParams = !/(\&(?!(\w+=\w+)))/.test(req.url); 36 | 37 | if (!keyValQParams) { 38 | var msg = opts.message 39 | ? opts.message 40 | : 'Url query params does not meet strict format'; 41 | return next(new BadRequestError(msg)); 42 | } 43 | 44 | return next(); 45 | } 46 | 47 | return _strictQueryParams; 48 | } 49 | 50 | ///--- Exports 51 | 52 | module.exports = strictQueryParams; 53 | -------------------------------------------------------------------------------- /lib/plugins/pre/userAgent.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert-plus'); 6 | 7 | ///--- API 8 | 9 | /** 10 | * This basically exists for `curl`. `curl` on `HEAD` requests usually 11 | * just sits there and hangs, unless you explicitly set 12 | * Connection:close. And in general, you probably want to set 13 | * Connection: close to curl anyway. 14 | * 15 | * Also, because curl spits out an annoying message to stderr about 16 | * remaining bytes if content-length is set, this plugin also drops 17 | * the `content-length` header (some user agents handle it and want it, 18 | * curl does not). 19 | * 20 | * To be slightly more generic, the options block takes a user 21 | * agent regexp, however. 22 | * 23 | * @public 24 | * @function userAgentConnection 25 | * @param {Object} [options] - an options object 26 | * @param {RegExp} [options.userAgentRegExp=/^curl.+/] - matching any 27 | * user-agents applicable 28 | * @returns {Function} Handler 29 | */ 30 | function userAgentConnection(options) { 31 | var opts = options || {}; 32 | assert.optionalObject(opts, 'options'); 33 | assert.optionalObject(opts.userAgentRegExp, 'options.userAgentRegExp'); 34 | 35 | var re = opts.userAgentRegExp; 36 | 37 | if (!re) { 38 | re = /^curl.+/; 39 | } 40 | 41 | function handleUserAgent(req, res, next) { 42 | var ua = req.headers['user-agent']; 43 | 44 | if (ua && re.test(ua)) { 45 | res.setHeader('Connection', 'close'); 46 | 47 | if (req.method === 'HEAD') { 48 | res.once( 49 | 'header', 50 | res.removeHeader.bind(res, 'content-length') 51 | ); 52 | } 53 | } 54 | 55 | next(); 56 | } 57 | 58 | return handleUserAgent; 59 | } 60 | 61 | module.exports = userAgentConnection; 62 | -------------------------------------------------------------------------------- /lib/plugins/query.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var qs = require('qs'); 6 | var assert = require('assert-plus'); 7 | 8 | /** 9 | * Parses the HTTP query string (i.e., `/foo?id=bar&name=mark`). 10 | * If you use this, the parsed content will always be available in `req.query`, 11 | * additionally params are merged into `req.params`. 12 | * You can disable by passing in `mapParams: false` in the options object. 13 | * 14 | * Many options correspond directly to option defined for the underlying 15 | * [`qs.parse`](https://github.com/ljharb/qs). 16 | * 17 | * @public 18 | * @function queryParser 19 | * @param {Object} [options] - an options object 20 | * @param {Object} [options.mapParams=true] - disable passing 21 | * @param {Boolean} [options.mapParams=false] - Copies parsed query parameters 22 | * into`req.params`. 23 | * @param {Boolean} [options.overrideParams=false] - Only applies when if 24 | * mapParams true. 25 | * When true, will stomp on req.params field when existing value is found. 26 | * @param {Boolean} [options.allowDots=false] - Transform `?foo.bar=baz` to a 27 | * nested object: `{foo: {bar: 'baz'}}`. 28 | * @param {Number} [options.arrayLimit=20] - Only transform `?a[$index]=b` 29 | * to an array if `$index` is less than `arrayLimit`. 30 | * @param {Number} [options.depth=5] - The depth limit for parsing 31 | * nested objects, e.g. `?a[b][c][d][e][f][g][h][i]=j`. 32 | * @param {Number} [options.parameterLimit=1000] - Maximum number of query 33 | * params parsed. Additional params are silently dropped. 34 | * @param {Boolean} [options.parseArrays=true] - Whether to parse 35 | * `?a[]=b&a[1]=c` to an array, e.g. `{a: ['b', 'c']}`. 36 | * @param {Boolean} [options.plainObjects=false] - Whether `req.query` is a 37 | * "plain" object -- does not inherit from `Object`. 38 | * This can be used to allow query params whose names collide with Object 39 | * methods, e.g. `?hasOwnProperty=blah`. 40 | * @param {Boolean} [options.strictNullHandling=false] - If true, `?a&b=` 41 | * results in `{a: null, b: ''}`. Otherwise, `{a: '', b: ''}`. 42 | * @returns {Function} Handler 43 | * @example 44 | * server.use(restify.plugins.queryParser({ mapParams: false })); 45 | */ 46 | function queryParser(options) { 47 | var opts = options || {}; 48 | assert.object(opts, 'opts'); 49 | 50 | function parseQueryString(req, res, next) { 51 | if (!req.getQuery()) { 52 | req.query = {}; 53 | return next(); 54 | } 55 | 56 | req.query = qs.parse(req.getQuery(), opts); 57 | 58 | if (opts.mapParams === true) { 59 | Object.keys(req.query).forEach(function forEach(k) { 60 | if (req.params[k] && !opts.overrideParams) { 61 | return; 62 | } 63 | req.params[k] = req.query[k]; 64 | }); 65 | } 66 | 67 | return next(); 68 | } 69 | 70 | return parseQueryString; 71 | } 72 | 73 | module.exports = queryParser; 74 | -------------------------------------------------------------------------------- /lib/plugins/requestLogger.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | var assert = require('assert-plus'); 6 | 7 | var shallowCopy = require('./utils/shallowCopy'); 8 | 9 | ///--- API 10 | 11 | /** 12 | * Sets up a child [logger](https://github.com/pinojs/pino) logger with 13 | * the current request id filled in, along with any other parameters you define. 14 | * 15 | * You can pass in no options to this, in which case only the request id will be 16 | * appended, and no serializers appended (this is also the most performant); the 17 | * logger created at server creation time will be used as the parent logger. 18 | * This logger can be used normally, with [req.log](#request-api). 19 | * 20 | * This plugin does _not_ log each individual request. Use the Audit Logging 21 | * plugin or a custom middleware for that use. 22 | * 23 | * @public 24 | * @function requestLogger 25 | * @param {Object} [options] - an options object 26 | * @param {Array} [options.headers] - A list of headers to transfer from 27 | * the request to top level props on the log. 28 | * @param {Object} [options.properties] - A set of key-values to pass to the child logger 29 | * @param {Object} [options.serializers] - Override serializers to use in the child logger 30 | * @param {Object} [options.log] - A logger to use as a fallback if req.log is missing 31 | * @param {String} [options.requestIdFieldName] - The name of the request id property attached 32 | * to log lines. Defaults to "req_id". 33 | * @returns {Function} Handler 34 | * @example 35 | * server.use(restify.plugins.requestLogger({ 36 | * properties: { 37 | * foo: 'bar' 38 | * }, 39 | * serializers: {...} 40 | * })); 41 | */ 42 | function requestLogger(options) { 43 | assert.optionalObject(options); 44 | var opts = options || {}; 45 | 46 | var props; 47 | 48 | if (opts.properties) { 49 | props = shallowCopy(opts.properties); 50 | } else { 51 | props = {}; 52 | } 53 | 54 | if (opts.serializers) { 55 | props.serializers = opts.serializers; 56 | } 57 | 58 | var headersToCopy = opts.headers || []; 59 | const requestIdFieldName = opts.requestIdFieldName || 'req_id'; 60 | 61 | return function logger(req, res, next) { 62 | if (!req.log && !opts.log) { 63 | next(); 64 | return; 65 | } 66 | 67 | var log = req.log || opts.log; 68 | 69 | props[requestIdFieldName] = req.getId(); 70 | 71 | headersToCopy.forEach(function forEach(k) { 72 | if (req.headers[k]) { 73 | props[k] = req.headers[k]; 74 | } 75 | }); 76 | const childOptions = {}; 77 | if (props.serializers) { 78 | childOptions.serializers = props.serializers; 79 | } 80 | req.log = log.child(props, childOptions); 81 | 82 | if (props[requestIdFieldName]) { 83 | delete props[requestIdFieldName]; 84 | } 85 | 86 | next(); 87 | }; 88 | } 89 | 90 | ///--- Exports 91 | 92 | module.exports = requestLogger; 93 | -------------------------------------------------------------------------------- /lib/plugins/utils/hrTimeDurationInMs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var NS_PER_SEC = 1e9; 4 | var MS_PER_NS = 1e6; 5 | 6 | /** 7 | * Get duration in milliseconds from two process.hrtime() 8 | 9 | * @function hrTimeDurationInMs 10 | * @param {Array} startTime - [seconds, nanoseconds] 11 | * @param {Array} endTime - [seconds, nanoseconds] 12 | * @returns {Number|null} durationInMs 13 | */ 14 | function hrTimeDurationInMs(startTime, endTime) { 15 | if (!Array.isArray(startTime) || !Array.isArray(endTime)) { 16 | return null; 17 | } 18 | 19 | var secondDiff = endTime[0] - startTime[0]; 20 | var nanoSecondDiff = endTime[1] - startTime[1]; 21 | var diffInNanoSecond = secondDiff * NS_PER_SEC + nanoSecondDiff; 22 | 23 | return Math.round(diffInNanoSecond / MS_PER_NS); 24 | } 25 | 26 | module.exports = hrTimeDurationInMs; 27 | -------------------------------------------------------------------------------- /lib/plugins/utils/httpDate.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * Takes an instance of a date object, formats it UTC 7 | * e.g., Wed, 17 Jun 2015 01:30:26 GMT. 8 | * 9 | * @public 10 | * @function httpDate 11 | * @param {Object} now - a date object 12 | * @returns {String} formatted dated object 13 | */ 14 | module.exports = function httpDate(now) { 15 | return now.toUTCString(); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/plugins/utils/regex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | jsonContentType: new RegExp('^application/[a-zA-Z.]+\\+json') 5 | }; 6 | -------------------------------------------------------------------------------- /lib/plugins/utils/shallowCopy.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * Return a shallow copy of the given object 7 | * 8 | * @public 9 | * @function shallowCopy 10 | * @param {Object} obj - the object to copy 11 | * @returns {Object} the new copy of the object 12 | */ 13 | function shallowCopy(obj) { 14 | if (!obj) { 15 | return obj; 16 | } 17 | var copy = {}; 18 | Object.keys(obj).forEach(function forEach(k) { 19 | copy[k] = obj[k]; 20 | }); 21 | return copy; 22 | } 23 | 24 | ///--- Exports 25 | 26 | module.exports = shallowCopy; 27 | -------------------------------------------------------------------------------- /lib/routerRegistryRadix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert-plus'); 4 | var FindMyWay = require('find-my-way'); 5 | var Chain = require('./chain'); 6 | 7 | /** 8 | * Radix tree based router registry backed by `find-my-way` 9 | * 10 | * @class RouterRegistryRadix 11 | * @public 12 | * @param {Object} options - an options object 13 | * @param {Object} [options.ignoreTrailingSlash] - ignore trailing slash on 14 | * paths 15 | */ 16 | function RouterRegistryRadix(options) { 17 | this._findMyWay = new FindMyWay(options); 18 | this._routes = {}; 19 | } 20 | 21 | /** 22 | * Adds a route. 23 | * 24 | * @public 25 | * @memberof Router 26 | * @instance 27 | * @function add 28 | * @param {Object} route - an route object 29 | * @param {String} route.name - name of the route 30 | * @param {String} route.method - HTTP method 31 | * @param {String} route.path - any String accepted by 32 | * [find-my-way](https://github.com/delvedor/find-my-way) 33 | * @param {Chain} route.chain - Chain instance 34 | * @returns {Boolean} true 35 | */ 36 | RouterRegistryRadix.prototype.add = function add(route) { 37 | assert.object(route, 'route'); 38 | assert.string(route.method, 'route.method'); 39 | assert.string(route.path, 'path'); 40 | assert.ok(route.chain instanceof Chain, 'route.chain'); 41 | 42 | this._findMyWay.on( 43 | route.method, 44 | route.path, 45 | function onRoute(req, res, next) { 46 | route.chain.run(req, res, next); 47 | }, 48 | { 49 | route: route 50 | } 51 | ); 52 | 53 | this._routes[route.name] = route; 54 | 55 | return route; 56 | }; 57 | 58 | /** 59 | * Removes a route. 60 | * 61 | * @public 62 | * @memberof RouterRegistryRadix 63 | * @instance 64 | * @function remove 65 | * @param {String} name - the route name 66 | * @returns {Object|undefined} removed route if found 67 | */ 68 | RouterRegistryRadix.prototype.remove = function remove(name) { 69 | assert.string(name, 'name'); 70 | 71 | // check for route 72 | var route = this._routes[name]; 73 | if (!route) { 74 | return undefined; 75 | } 76 | 77 | // remove from registry 78 | this._findMyWay.off(route.method, route.path); 79 | delete this._routes[name]; 80 | 81 | return route; 82 | }; 83 | 84 | /** 85 | * Registry for route 86 | * 87 | * @public 88 | * @memberof RouterRegistryRadix 89 | * @instance 90 | * @function Registry 91 | * @param {String} method - method 92 | * @param {String} pathname - pathname 93 | * @returns {Chain|undefined} handler or undefined 94 | */ 95 | RouterRegistryRadix.prototype.lookup = function lookup(method, pathname) { 96 | assert.string(method, 'method'); 97 | assert.string(pathname, 'pathname'); 98 | 99 | var fmwRoute = this._findMyWay.find(method, pathname); 100 | 101 | // Not found 102 | if (!fmwRoute) { 103 | return undefined; 104 | } 105 | 106 | // Call handler chain 107 | return { 108 | route: fmwRoute.store.route, 109 | params: fmwRoute.params, 110 | handler: fmwRoute.handler 111 | }; 112 | }; 113 | 114 | /** 115 | * Get registry 116 | * 117 | * @public 118 | * @memberof RouterRegistryRadix 119 | * @instance 120 | * @function toString 121 | * @returns {String} stringified RouterRegistryRadix 122 | */ 123 | RouterRegistryRadix.prototype.get = function get() { 124 | return this._routes; 125 | }; 126 | 127 | /** 128 | * toString() serialization. 129 | * 130 | * @public 131 | * @memberof RouterRegistryRadix 132 | * @instance 133 | * @function toString 134 | * @returns {String} stringified RouterRegistryRadix 135 | */ 136 | RouterRegistryRadix.prototype.toString = function toString() { 137 | return this._findMyWay.prettyPrint(); 138 | }; 139 | 140 | module.exports = RouterRegistryRadix; 141 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * Return a shallow copy of the given object; 7 | * 8 | * @public 9 | * @function shallowCopy 10 | * @param {Object} obj - the object to copy 11 | * @returns {Object} the new copy of the object 12 | */ 13 | function shallowCopy(obj) { 14 | if (!obj) { 15 | return obj; 16 | } 17 | var copy = {}; 18 | Object.keys(obj).forEach(function forEach(k) { 19 | copy[k] = obj[k]; 20 | }); 21 | return copy; 22 | } 23 | 24 | /** 25 | * Merges two query parameter objects. Merges to array 26 | * if the same key is encountered. 27 | * 28 | * @public 29 | * @function mergeQs 30 | * @param {Object} obj1 - first qs object 31 | * @param {Object} obj2 - second qs object 32 | * @returns {Object} the merged object 33 | */ 34 | function mergeQs(obj1, obj2) { 35 | var merged = shallowCopy(obj1) || {}; 36 | 37 | // defend against null cause null is an object. yay js. 38 | if (obj2 && typeof obj2 === 'object') { 39 | Object.keys(obj2).forEach(function forEach(key) { 40 | // if we already have this key and it isn't an array, 41 | // make it one array of the same element. 42 | if (merged.hasOwnProperty(key) && !(merged[key] instanceof Array)) { 43 | merged[key] = [merged[key]]; 44 | 45 | // push the new value down 46 | merged[key].push(obj2[key]); 47 | } else { 48 | // otherwise just set it 49 | merged[key] = obj2[key]; 50 | } 51 | }); 52 | } 53 | 54 | return merged; 55 | } 56 | 57 | ///--- Exports 58 | 59 | module.exports = { 60 | shallowCopy: shallowCopy, 61 | mergeQs: mergeQs 62 | }; 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Mark Cavage <mcavage@gmail.com>", 3 | "contributors": [ 4 | "Adam Argo", 5 | "Alex Liu", 6 | "Alexander Olsson", 7 | "Andrew Robinson", 8 | "Andrew Sliwinski", 9 | "Anro Robinson", 10 | "Armin Tamzarian", 11 | "Asa Ayers", 12 | "Bastiaan Marinus van de Weerd", 13 | "Ben Doerr", 14 | "Ben Hale", 15 | "Ben Howes", 16 | "Ben Hutchison", 17 | "Benjamine Coe", 18 | "Benjamin Urban", 19 | "Blake VanLandingham", 20 | "Brian Pin", 21 | "Bryan Donovan", 22 | "Bryce Kahle", 23 | "Christopher Cannell", 24 | "Clément Désiles", 25 | "Colin O'Brien", 26 | "Corbin Uselton", 27 | "Diego Torres", 28 | "Domenic Denicola", 29 | "Domikik Lessel", 30 | "Dominic Barnes", 31 | "Erik Kristensen", 32 | "Falco Nogatz", 33 | "Gergely Nemeth", 34 | "Guillaume Chauvet", 35 | "Ifiok Idiang", 36 | "Isaac Schlueter", 37 | "Jacob Quatier", 38 | "James O'Cull", 39 | "James Womack", 40 | "Jonathan Dahan", 41 | "Josh Clulow", 42 | "Jorge Serrano", 43 | "Jason Ghent", 44 | "Khaja Naquiuddin", 45 | "Lou Sacco", 46 | "Matt Smillie", 47 | "Mattijs Spierings", 48 | "Micah Ransdell", 49 | "Michal Moskal", 50 | "Michael Paulson", 51 | "Mike Williams", 52 | "Nathanael Anderson", 53 | "Patrick Mooney", 54 | "Paul Bouzakis", 55 | "Pedro Palazón", 56 | "Quentin Buathier", 57 | "Richardo Stuven", 58 | "Scott Turnquest", 59 | "Shaun Berryman", 60 | "Steve Mason", 61 | "Tim Kuijsten", 62 | "Trent Mick", 63 | "Tuure Kanuisto", 64 | "Will Prater", 65 | "Yunong Xiao", 66 | "Zachary Snow" 67 | ], 68 | "name": "restify", 69 | "homepage": "http://restify.com", 70 | "description": "REST framework", 71 | "keywords": [ 72 | "REST", 73 | "framework", 74 | "express", 75 | "DTrace" 76 | ], 77 | "version": "11.2.0", 78 | "repository": { 79 | "type": "git", 80 | "url": "git://github.com/restify/node-restify.git" 81 | }, 82 | "bugs": { 83 | "url": "https://github.com/restify/node-restify/issues" 84 | }, 85 | "main": "lib/index.js", 86 | "directories": { 87 | "lib": "./lib" 88 | }, 89 | "bin": { 90 | "report-latency": "./bin/report-latency" 91 | }, 92 | "engines": { 93 | "node": ">=10.0.0" 94 | }, 95 | "dependencies": { 96 | "assert-plus": "^1.0.0", 97 | "csv": "^6.2.2", 98 | "escape-regexp-component": "^1.0.2", 99 | "ewma": "^2.0.1", 100 | "find-my-way": "^7.6.0", 101 | "formidable": "^1.2.1", 102 | "http-signature": "^1.3.6", 103 | "lodash": "^4.17.11", 104 | "lru-cache": "^7.14.1", 105 | "mime": "^3.0.0", 106 | "negotiator": "^0.6.2", 107 | "once": "^1.4.0", 108 | "pidusage": "^3.0.2", 109 | "pino": "^8.7.0", 110 | "qs": "^6.7.0", 111 | "restify-errors": "^8.0.2", 112 | "semver": "^7.3.8", 113 | "send": "^0.18.0", 114 | "spdy": "^4.0.0", 115 | "uuid": "^9.0.0", 116 | "vasync": "^2.2.0" 117 | }, 118 | "optionalDependencies": { 119 | "dtrace-provider": "~0.8" 120 | }, 121 | "devDependencies": { 122 | "autocannon": "^4.0.0", 123 | "autocannon-compare": "^0.4.0", 124 | "chai": "^4.2.0", 125 | "coveralls": "^3.0.3", 126 | "documentation": "^11.0.0", 127 | "eslint": "^5.16.0", 128 | "eslint-config-prettier": "^4.3.0", 129 | "eslint-plugin-jsdoc": "^3.15.1", 130 | "eslint-plugin-prettier": "^3.1.0", 131 | "glob": "^7.1.4", 132 | "inquirer": "^3.3.0", 133 | "mkdirp": "^0.5.1", 134 | "mocha": "^7.1.1", 135 | "nodeunit": "^0.11.3", 136 | "nyc": "^15.0.0", 137 | "ora": "^1.3.0", 138 | "pre-commit": "^1.2.2", 139 | "prettier": "^1.17.1", 140 | "proxyquire": "^1.8.0", 141 | "restify-clients": "^2.6.6", 142 | "rimraf": "^2.6.3", 143 | "sinon": "^7.5.0", 144 | "validator": "^7.2.0", 145 | "watershed": "^0.4.0" 146 | }, 147 | "license": "MIT", 148 | "scripts": { 149 | "test": "make prepush" 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "handle-callback-err": [ 0 ] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/chainComposer.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | if (require.cache[__dirname + '/lib/helper.js']) { 5 | delete require.cache[__dirname + '/lib/helper.js']; 6 | } 7 | var helper = require('./lib/helper.js'); 8 | 9 | ///--- Globals 10 | 11 | var test = helper.test; 12 | var composer = require('../lib/helpers/chainComposer'); 13 | 14 | test('chainComposer creates a valid chain for a handler array ', function(t) { 15 | var counter = 0; 16 | var handlers = []; 17 | handlers.push(function(req, res, next) { 18 | counter++; 19 | next(); 20 | }); 21 | 22 | handlers.push(function(req, res, next) { 23 | counter++; 24 | next(); 25 | }); 26 | 27 | var chain = composer(handlers); 28 | chain( 29 | { 30 | startHandlerTimer: function() {}, 31 | endHandlerTimer: function() {}, 32 | connectionState: function() { 33 | return ''; 34 | } 35 | }, 36 | {}, 37 | function() { 38 | t.equal(counter, 2); 39 | t.done(); 40 | } 41 | ); 42 | }); 43 | 44 | test('chainComposer creates a valid chain for a single handler', function(t) { 45 | var counter = 0; 46 | var handlers = function(req, res, next) { 47 | counter++; 48 | next(); 49 | }; 50 | 51 | var chain = composer(handlers); 52 | chain( 53 | { 54 | startHandlerTimer: function() {}, 55 | endHandlerTimer: function() {}, 56 | connectionState: function() { 57 | return ''; 58 | } 59 | }, 60 | {}, 61 | function() { 62 | t.equal(counter, 1); 63 | t.done(); 64 | } 65 | ); 66 | }); 67 | -------------------------------------------------------------------------------- /test/formatter-optional.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | var restifyClients = require('restify-clients'); 5 | 6 | var restify = require('../lib'); 7 | 8 | if (require.cache[__dirname + '/lib/helper.js']) { 9 | delete require.cache[__dirname + '/lib/helper.js']; 10 | } 11 | var helper = require('./lib/helper.js'); 12 | 13 | ///--- Globals 14 | 15 | var after = helper.after; 16 | var before = helper.before; 17 | var test = helper.test; 18 | 19 | var CLIENT; 20 | var LOCALHOST; 21 | var PORT = process.env.UNIT_TEST_PORT || 0; 22 | var SERVER; 23 | 24 | ///--- Tests 25 | 26 | before(function(callback) { 27 | try { 28 | SERVER = restify.createServer({ 29 | handleUncaughtExceptions: true, 30 | log: helper.getLog('server'), 31 | strictFormatters: false 32 | }); 33 | SERVER.listen(PORT, '127.0.0.1', function() { 34 | PORT = SERVER.address().port; 35 | CLIENT = restifyClients.createJsonClient({ 36 | url: 'http://127.0.0.1:' + PORT, 37 | dtrace: helper.dtrace, 38 | retry: false 39 | }); 40 | LOCALHOST = 'http://' + '127.0.0.1:' + PORT; 41 | callback(); 42 | }); 43 | } catch (e) { 44 | console.error(e.stack); 45 | process.exit(1); 46 | } 47 | }); 48 | 49 | after(function(callback) { 50 | try { 51 | SERVER.close(callback); 52 | CLIENT.close(); 53 | } catch (e) { 54 | console.error(e.stack); 55 | process.exit(1); 56 | } 57 | }); 58 | 59 | test('send 200 on formatter missing and strictFormatters false', function(t) { 60 | // When server is passed "strictFormatters: false" at creation time, 61 | // res.send still sends a successful response even when a formatter is 62 | // not set up for a specific content-type. 63 | SERVER.get('/11', function handle(req, res, next) { 64 | res.header('content-type', 'application/hal+json'); 65 | res.send(200, JSON.stringify({ hello: 'world' })); 66 | return next(); 67 | }); 68 | 69 | CLIENT.get(LOCALHOST + '/11', function(err, _, res) { 70 | t.ifError(err); 71 | t.equal(res.statusCode, 200); 72 | t.equal(res.headers['content-type'], 'application/hal+json'); 73 | t.end(); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved. 2 | 3 | 'use strict'; 4 | /* eslint-disable func-names */ 5 | 6 | var httpDate = require('../lib/http_date'); 7 | 8 | if (require.cache[__dirname + '/lib/helper.js']) { 9 | delete require.cache[__dirname + '/lib/helper.js']; 10 | } 11 | var helper = require('./lib/helper.js'); 12 | 13 | ///--- Globals 14 | 15 | var test = helper.test; 16 | 17 | ///--- Tests 18 | 19 | test('httpDate', function(t) { 20 | var d = httpDate(); 21 | var regex = /\w{3}, \d{1,2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT/; 22 | t.ok(regex.test(d)); 23 | t.end(); 24 | }); 25 | -------------------------------------------------------------------------------- /test/keys/http2-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS 3 | VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY 4 | SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw 5 | OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL 6 | BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB 7 | nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x 8 | p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp 9 | gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7 10 | 5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79 11 | vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV 12 | yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j 13 | Uws6Lif3P9UbsuRiYPxMgg98wg== 14 | -----END CERTIFICATE----- 15 | 16 | -------------------------------------------------------------------------------- /test/keys/http2-csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN 3 | MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk 4 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF 5 | 3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je 6 | i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+ 7 | A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa 8 | FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb 9 | 3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC 10 | hC3dz5odyKqe4nmoofomALkBL9t4H8s= 11 | -----END CERTIFICATE REQUEST----- 12 | 13 | -------------------------------------------------------------------------------- /test/keys/http2-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV 3 | dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR 4 | GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB 5 | AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR 6 | C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6 7 | KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc 8 | FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt 9 | Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0 10 | M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv 11 | 20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx 12 | I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG 13 | ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D 14 | rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg= 15 | -----END RSA PRIVATE KEY----- 16 | 17 | -------------------------------------------------------------------------------- /test/lib/helper.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Mark Cavage. All rights reserved. 2 | // 3 | // Just a simple wrapper over nodeunit's exports syntax. Also exposes 4 | // a common logger for all tests. 5 | // 6 | 7 | 'use strict'; 8 | /* eslint-disable func-names */ 9 | 10 | var domain = require('domain'); 11 | 12 | var pino = require('pino'); 13 | var once = require('once'); 14 | 15 | ///--- Exports 16 | 17 | module.exports = { 18 | after: function after(teardown) { 19 | module.parent.exports.tearDown = function _teardown(callback) { 20 | var d = domain.create(); 21 | var self = this; 22 | 23 | d.once('error', function(err) { 24 | console.error('after: uncaught error\n', err.stack); 25 | process.exit(1); 26 | }); 27 | 28 | d.run(function() { 29 | teardown.call(self, once(callback)); 30 | }); 31 | }; 32 | }, 33 | 34 | before: function before(setup) { 35 | module.parent.exports.setUp = function _setup(callback) { 36 | var d = domain.create(); 37 | var self = this; 38 | 39 | d.once('error', function(err) { 40 | console.error('before: uncaught error\n' + err.stack); 41 | process.exit(1); 42 | }); 43 | 44 | d.run(function() { 45 | setup.call(self, once(callback)); 46 | }); 47 | }; 48 | }, 49 | 50 | test: function test(name, tester) { 51 | module.parent.exports[name] = function _(t) { 52 | var d = domain.create(); 53 | var self = this; 54 | 55 | d.once('error', function(err) { 56 | t.ifError(err); 57 | t.end(); 58 | }); 59 | 60 | d.add(t); 61 | d.run(function() { 62 | t.end = once(function() { 63 | t.done(); 64 | }); 65 | t.notOk = function notOk(ok, message) { 66 | return t.ok(!ok, message); 67 | }; 68 | 69 | tester.call(self, t); 70 | }); 71 | }; 72 | }, 73 | 74 | getLog: function(name, streams, level) { 75 | return pino( 76 | { 77 | level: process.env.LOG_LEVEL || level || 'fatal', 78 | name: name || process.argv[1], 79 | serializers: pino.stdSerializers 80 | }, 81 | streams || process.stdout 82 | ); 83 | }, 84 | 85 | get dtrace() { 86 | return true; 87 | }, 88 | 89 | sleep: function sleep(timeInMs) { 90 | return new Promise(function sleepPromise(resolve) { 91 | setTimeout(function timeout() { 92 | resolve(); 93 | }, timeInMs); 94 | }); 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /test/lib/server-withDisableUncaughtException.js: -------------------------------------------------------------------------------- 1 | // A simple node process that will start a restify server with the 2 | // uncaughtException handler disabled. Responds to a 'serverPortRequest' message 3 | // and sends back the server's bound port number. 4 | 5 | 'use strict'; 6 | /* eslint-disable func-names */ 7 | 8 | var restify = require('../../lib'); 9 | 10 | function main() { 11 | var port = process.env.UNIT_TEST_PORT || 0; 12 | var server = restify.createServer({ handleUncaughtExceptions: false }); 13 | server.get('/', function(req, res, next) { 14 | throw new Error('Catch me!'); 15 | }); 16 | server.listen(0, function() { 17 | port = server.address().port; 18 | console.log('port: ', port); 19 | 20 | process.on('message', function(msg) { 21 | if (msg.task !== 'serverPortRequest') { 22 | process.send({ error: 'Unexpected message: ' + msg }); 23 | return; 24 | } 25 | process.send({ task: 'serverPortResponse', port: port }); 26 | }); 27 | }); 28 | } 29 | 30 | if (require.main === module) { 31 | main(); 32 | } 33 | -------------------------------------------------------------------------------- /test/lib/streamRecorder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stream = require('stream'); 4 | 5 | class StreamRecorder extends stream.Writable { 6 | constructor(options) { 7 | options = options || {}; 8 | super(options); 9 | this.flushRecords(); 10 | } 11 | 12 | _write(chunk, encoding, callback) { 13 | const record = JSON.parse(chunk.toString()); 14 | this.records.push(record); 15 | callback(); 16 | } 17 | 18 | flushRecords() { 19 | this.records = []; 20 | } 21 | } 22 | 23 | module.exports = StreamRecorder; 24 | -------------------------------------------------------------------------------- /test/plugins/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | env: { 3 | mocha: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/plugins/accept.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | // external requires 5 | var assert = require('chai').assert; 6 | var restify = require('../../lib/index.js'); 7 | var restifyClients = require('restify-clients'); 8 | 9 | // local files 10 | var helper = require('../lib/helper'); 11 | 12 | // local globals 13 | var SERVER; 14 | var CLIENT; 15 | var PORT; 16 | 17 | describe('accept parser', function() { 18 | before(function(done) { 19 | SERVER = restify.createServer({ 20 | dtrace: helper.dtrace, 21 | log: helper.getLog('server') 22 | }); 23 | 24 | SERVER.use(restify.plugins.acceptParser(SERVER.acceptable)); 25 | 26 | SERVER.get('/', function respond(req, res, next) { 27 | res.send(); 28 | next(); 29 | }); 30 | 31 | SERVER.listen(0, '127.0.0.1', function() { 32 | PORT = SERVER.address().port; 33 | CLIENT = restifyClients.createJsonClient({ 34 | url: 'http://127.0.0.1:' + PORT, 35 | dtrace: helper.dtrace, 36 | retry: false 37 | }); 38 | 39 | done(); 40 | }); 41 | }); 42 | 43 | after(function(done) { 44 | CLIENT.close(); 45 | SERVER.close(done); 46 | }); 47 | 48 | it('accept ok', function(done) { 49 | CLIENT.get('/', function(err, _, res) { 50 | assert.ifError(err); 51 | assert.equal(res.statusCode, 200); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('accept not ok (406)', function(done) { 57 | var opts = { 58 | path: '/', 59 | headers: { 60 | accept: 'foo/bar' 61 | } 62 | }; 63 | 64 | CLIENT.get(opts, function(err, _, res) { 65 | assert.ok(err); 66 | assert.equal(err.name, 'NotAcceptableError'); 67 | assert.equal(res.statusCode, 406); 68 | done(); 69 | }); 70 | }); 71 | 72 | it('GH-1619: should fire NotAcceptable event on server', function(done) { 73 | var opts = { 74 | path: '/', 75 | headers: { 76 | accept: 'foo/bar' 77 | } 78 | }; 79 | var evtFired = false; 80 | 81 | SERVER.on('NotAcceptable', function(req, res, err, cb) { 82 | evtFired = true; 83 | return cb(); 84 | }); 85 | 86 | CLIENT.get(opts, function(err, _, res) { 87 | assert.ok(err); 88 | assert.equal(err.name, 'NotAcceptableError'); 89 | assert.equal(res.statusCode, 406); 90 | assert.isTrue(evtFired); 91 | return done(); 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /test/plugins/authorization.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | // external requires 5 | var assert = require('chai').assert; 6 | var restify = require('../../lib/index.js'); 7 | var restifyClients = require('restify-clients'); 8 | 9 | // local files 10 | var helper = require('../lib/helper'); 11 | 12 | // local globals 13 | var SERVER; 14 | var CLIENT; 15 | var PORT; 16 | 17 | describe('authorization parser', function() { 18 | before(function(done) { 19 | SERVER = restify.createServer({ 20 | dtrace: helper.dtrace, 21 | log: helper.getLog('server') 22 | }); 23 | 24 | SERVER.use(restify.plugins.authorizationParser()); 25 | 26 | SERVER.get('/', function respond(req, res, next) { 27 | res.send(); 28 | next(); 29 | }); 30 | 31 | SERVER.listen(0, '127.0.0.1', function() { 32 | PORT = SERVER.address().port; 33 | CLIENT = restifyClients.createJsonClient({ 34 | url: 'http://127.0.0.1:' + PORT, 35 | dtrace: helper.dtrace, 36 | retry: false 37 | }); 38 | 39 | done(); 40 | }); 41 | }); 42 | 43 | after(function(done) { 44 | CLIENT.close(); 45 | SERVER.close(done); 46 | }); 47 | 48 | it('should accept basic authorization', function(done) { 49 | var authz = 'Basic ' + new Buffer('user:secret').toString('base64'); 50 | var opts = { 51 | path: '/', 52 | headers: { 53 | authorization: authz 54 | } 55 | }; 56 | CLIENT.get(opts, function(err, _, res) { 57 | assert.ifError(err); 58 | assert.equal(res.statusCode, 200); 59 | done(); 60 | }); 61 | }); 62 | 63 | it('should reject basic authorization', function(done) { 64 | var opts = { 65 | path: '/', 66 | headers: { 67 | authorization: 'Basic ' 68 | } 69 | }; 70 | CLIENT.get(opts, function(err, _, res) { 71 | assert.ok(err); 72 | assert.equal(err.name, 'InvalidHeaderError'); 73 | assert.equal(res.statusCode, 400); 74 | done(); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/plugins/context.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | // external requires 5 | var assert = require('chai').assert; 6 | var restify = require('../../lib/index.js'); 7 | var restifyClients = require('restify-clients'); 8 | 9 | // local files 10 | var helper = require('../lib/helper'); 11 | 12 | // local globals 13 | var SERVER; 14 | var CLIENT; 15 | var PORT; 16 | 17 | describe('accept parser', function() { 18 | before(function(done) { 19 | SERVER = restify.createServer({ 20 | dtrace: helper.dtrace, 21 | log: helper.getLog('server') 22 | }); 23 | 24 | SERVER.use(restify.plugins.pre.context()); 25 | 26 | SERVER.listen(0, '127.0.0.1', function() { 27 | PORT = SERVER.address().port; 28 | CLIENT = restifyClients.createJsonClient({ 29 | url: 'http://127.0.0.1:' + PORT, 30 | dtrace: helper.dtrace, 31 | retry: false 32 | }); 33 | 34 | done(); 35 | }); 36 | }); 37 | 38 | after(function(done) { 39 | CLIENT.close(); 40 | SERVER.close(done); 41 | }); 42 | 43 | it('should use context', function(done) { 44 | SERVER.get('/', [ 45 | function one(req, res, next) { 46 | req.set('foo', { 47 | a: 1 48 | }); 49 | return next(); 50 | }, 51 | function two(req, res, next) { 52 | assert.deepEqual(req.get('foo'), { 53 | a: 1 54 | }); 55 | req.get('foo').b = 2; 56 | req.set('bar', [1]); 57 | return next(); 58 | }, 59 | function three(req, res, next) { 60 | assert.deepEqual(req.get('foo'), { 61 | a: 1, 62 | b: 2 63 | }); 64 | assert.deepEqual(req.get('bar'), [1]); 65 | 66 | assert.deepEqual(req.getAll(), { 67 | foo: { 68 | a: 1, 69 | b: 2 70 | }, 71 | bar: [1] 72 | }); 73 | 74 | res.send(); 75 | return next(); 76 | } 77 | ]); 78 | 79 | CLIENT.get('/', function(err, _, res) { 80 | assert.ifError(err); 81 | assert.equal(res.statusCode, 200); 82 | return done(); 83 | }); 84 | }); 85 | 86 | it('should not share context', function(done) { 87 | SERVER.get('/1', function one(req, res, next) { 88 | // ensure we don't get context from previous request 89 | assert.equal(req.get('foo', null)); 90 | res.end(); 91 | return next(); 92 | }); 93 | 94 | CLIENT.get('/1', function(err, _, res) { 95 | assert.ifError(err); 96 | assert.equal(res.statusCode, 200); 97 | return done(); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/plugins/cpuUsageThrottle.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | var assert = require('chai').assert; 5 | var proxyquire = require('proxyquire'); 6 | var restify = require('../../lib/index.js'); 7 | var restifyClients = require('restify-clients'); 8 | var pidusage = require('pidusage'); 9 | 10 | // Allow tests to set the CPU usage returned by pidUsage 11 | var CPU = 50; 12 | 13 | var cpuUsageThrottle = proxyquire('../../lib/plugins/cpuUsageThrottle.js', { 14 | pidusage: function(pid, cb) { 15 | return cb(null, { cpu: CPU }); 16 | } 17 | }); 18 | 19 | var MR = Math.random; 20 | describe('cpuUsageThrottle', function() { 21 | var plugin; 22 | 23 | before('Setup: stub math.random', function(done) { 24 | Math.random = function() { 25 | return 0; 26 | }; 27 | done(); 28 | }); 29 | 30 | it('Unit: Should shed load', function(done) { 31 | var opts = { limit: 0, interval: 500 }; 32 | plugin = cpuUsageThrottle(opts); 33 | function next(cont) { 34 | assert(cont instanceof Error, 'Should call next with error'); 35 | assert.equal(cont.statusCode, 503, 'Defaults to 503 status'); 36 | done(); 37 | } 38 | plugin({}, {}, next); 39 | }); 40 | 41 | it('Unit: Should let request through when not under load', function(done) { 42 | var opts = { interval: 500, limit: 0.9 }; 43 | plugin = cpuUsageThrottle(opts); 44 | function next(cont) { 45 | assert.isUndefined(cont, 'Should call next'); 46 | done(); 47 | } 48 | plugin({}, {}, next); 49 | }); 50 | 51 | it('Unit: Update should update state', function(done) { 52 | var opts = { 53 | max: 1, 54 | limit: 0.9, 55 | halfLife: 50, 56 | interval: 50 57 | }; 58 | plugin = cpuUsageThrottle(opts); 59 | opts = { 60 | max: 0.5, 61 | limit: 0.1, 62 | halfLife: 1000, 63 | interval: 1000 64 | }; 65 | plugin.update(opts); 66 | assert.equal(plugin.state.limit, opts.limit, 'opts.limit'); 67 | assert.equal(plugin.state.max, opts.max, 'opts.max'); 68 | assert.equal(plugin.state.halfLife, opts.halfLife, 'opts.halfLife'); 69 | assert.equal(plugin.state.interval, opts.interval, 'opts.interval'); 70 | done(); 71 | }); 72 | 73 | it('Unit: Should have proper name', function(done) { 74 | var opts = { 75 | max: 1, 76 | limit: 0.9, 77 | halfLife: 50, 78 | interval: 50 79 | }; 80 | plugin = cpuUsageThrottle(opts); 81 | assert.equal(plugin.name, 'cpuUsageThrottle'); 82 | done(); 83 | }); 84 | 85 | it('Unit: Should report proper lag', function(done) { 86 | var opts = { max: 1, limit: 0.9, halfLife: 50, interval: 50 }; 87 | var dn = Date.now; 88 | var now = 0; 89 | // First timer will be 0, all future timers will be interval 90 | Date.now = function() { 91 | return (now++ > 0) * opts.interval; 92 | }; 93 | plugin = cpuUsageThrottle(opts); 94 | Date.now = dn; 95 | assert.equal(plugin.state.lag, 0); 96 | done(); 97 | }); 98 | 99 | it('Integration: Should shed load', function(done) { 100 | var server = restify.createServer(); 101 | var client = { 102 | close: function() {} 103 | }; 104 | var opts = { interval: 500, limit: 0 }; 105 | plugin = cpuUsageThrottle(opts); 106 | server.pre(plugin); 107 | server.get('/foo', function(req, res, next) { 108 | res.send(200); 109 | next(); 110 | }); 111 | server.listen(0, '127.0.0.1', function() { 112 | client = restifyClients.createJsonClient({ 113 | url: 'http://127.0.0.1:' + server.address().port, 114 | retry: false 115 | }); 116 | client.get({ path: '/foo' }, function(e, _, res) { 117 | assert(e, 'Second request is shed'); 118 | assert.equal( 119 | res.statusCode, 120 | 503, 121 | 'Default shed status code returned' 122 | ); 123 | clearTimeout(plugin._timeout); 124 | // we should close the server else mocha wont exit 125 | server.close(); 126 | done(); 127 | }); 128 | }); 129 | }); 130 | 131 | it('Integration: pidusage should report CPU usage', function(done) { 132 | assert.isFunction(pidusage, 'pidusage can be invoked'); 133 | pidusage(process.pid, function(e, stat) { 134 | assert.ifError(e); 135 | assert.isObject(stat); 136 | assert.isNumber(stat.cpu); 137 | pidusage.clear(); 138 | done(); 139 | }); 140 | }); 141 | 142 | afterEach(function(done) { 143 | if (plugin) { 144 | plugin.close(); 145 | } 146 | plugin = undefined; 147 | done(); 148 | }); 149 | 150 | after('Teardown: Reset Math.random', function(done) { 151 | Math.random = MR; 152 | done(); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /test/plugins/dedupeSlashes.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | // external requires 5 | var assert = require('chai').assert; 6 | var restify = require('../../lib/index.js'); 7 | var restifyClients = require('restify-clients'); 8 | 9 | // local files 10 | var helper = require('../lib/helper'); 11 | 12 | // local globals 13 | var SERVER; 14 | var CLIENT; 15 | var PORT; 16 | 17 | describe('dedupe forward slashes in URL', function() { 18 | before(function(done) { 19 | SERVER = restify.createServer({ 20 | dtrace: helper.dtrace, 21 | log: helper.getLog('server') 22 | }); 23 | 24 | SERVER.pre(restify.plugins.pre.dedupeSlashes()); 25 | 26 | SERVER.get('/foo/bar/', function respond(req, res, next) { 27 | res.send(req.url); 28 | next(); 29 | }); 30 | 31 | SERVER.listen(0, '127.0.0.1', function() { 32 | PORT = SERVER.address().port; 33 | CLIENT = restifyClients.createJsonClient({ 34 | url: 'http://127.0.0.1:' + PORT, 35 | dtrace: helper.dtrace, 36 | retry: false 37 | }); 38 | 39 | done(); 40 | }); 41 | }); 42 | 43 | after(function(done) { 44 | CLIENT.close(); 45 | SERVER.close(done); 46 | }); 47 | 48 | it('should not remove single slashes', function(done) { 49 | CLIENT.get('/foo/bar/', function(err, _, res, data) { 50 | assert.ifError(err); 51 | assert.equal(res.statusCode, 200); 52 | assert.equal(data, '/foo/bar/'); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('should remove duplicate slashes', function(done) { 58 | CLIENT.get('//////foo///bar///////', function(err, _, res, data) { 59 | assert.ifError(err); 60 | assert.equal(res.statusCode, 200); 61 | assert.equal(data, '/foo/bar/'); 62 | done(); 63 | }); 64 | }); 65 | 66 | // eslint-disable-next-line 67 | it('should remove duplicate slashes including trailing slashes', function(done) { 68 | CLIENT.get('//foo//bar//', function(err, _, res, data) { 69 | assert.ifError(err); 70 | assert.equal(res.statusCode, 200); 71 | assert.equal(data, '/foo/bar/'); 72 | done(); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/plugins/files/data-csv.txt: -------------------------------------------------------------------------------- 1 | field1,field2,field3 2 | 1,2,3 3 | 3,2,1 4 | "a","b","c" 5 | "\"c","b","a" -------------------------------------------------------------------------------- /test/plugins/files/data-tsv.txt: -------------------------------------------------------------------------------- 1 | field1 field2 field3 2 | 1 2 3 3 | 3 2 1 -------------------------------------------------------------------------------- /test/plugins/files/object-csv.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "field1": "1", "field2": "2", "field3": "3", "index": 0 }, 3 | { "field1": "3", "field2": "2", "field3": "1", "index": 1 }, 4 | { "field1": "a", "field2": "b", "field3": "c", "index": 2 }, 5 | { "field1": "\"c", "field2": "b", "field3": "a", "index": 3 } 6 | ] -------------------------------------------------------------------------------- /test/plugins/files/object-tsv.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "field1": "1", "field2": "2", "field3": "3", "index": 0 }, 3 | { "field1": "3", "field2": "2", "field3": "1", "index": 1 } 4 | ] -------------------------------------------------------------------------------- /test/plugins/gzip.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | // external requires 5 | var assert = require('chai').assert; 6 | var restify = require('../../lib/index.js'); 7 | var restifyClients = require('restify-clients'); 8 | 9 | // local files 10 | var helper = require('../lib/helper'); 11 | 12 | // local globals 13 | var SERVER; 14 | var CLIENT; 15 | var PORT; 16 | 17 | describe('gzip parser', function() { 18 | beforeEach(function(done) { 19 | SERVER = restify.createServer({ 20 | dtrace: helper.dtrace, 21 | log: helper.getLog('server') 22 | }); 23 | 24 | SERVER.listen(0, '127.0.0.1', function() { 25 | PORT = SERVER.address().port; 26 | CLIENT = restifyClients.createJsonClient({ 27 | url: 'http://127.0.0.1:' + PORT, 28 | dtrace: helper.dtrace, 29 | retry: false 30 | }); 31 | 32 | done(); 33 | }); 34 | }); 35 | 36 | afterEach(function(done) { 37 | CLIENT.close(); 38 | SERVER.close(done); 39 | }); 40 | 41 | it('should gzip response', function(done) { 42 | SERVER.use(restify.plugins.gzipResponse()); 43 | 44 | SERVER.get('/gzip/:id', function(req, res, next) { 45 | res.send({ 46 | hello: 'world' 47 | }); 48 | next(); 49 | }); 50 | 51 | var opts = { 52 | path: '/gzip/foo', 53 | headers: { 54 | 'Accept-Encoding': 'gzip' 55 | } 56 | }; 57 | CLIENT.get(opts, function(err, _, res, obj) { 58 | assert.ifError(err); 59 | assert.deepEqual({ hello: 'world' }, obj); 60 | done(); 61 | }); 62 | }); 63 | 64 | it('gzip large response', function(done) { 65 | var testResponseSize = 65536 * 3; 66 | var TestStream = function() { 67 | this.readable = true; 68 | this.sentSize = 0; 69 | this.totalSize = testResponseSize; 70 | this.interval = null; 71 | }; 72 | require('util').inherits(TestStream, require('stream')); 73 | TestStream.prototype.resume = function() { 74 | var self = this; 75 | 76 | if (!this.interval) { 77 | this.interval = setInterval(function() { 78 | var chunkSize = Math.min( 79 | self.totalSize - self.sentSize, 80 | 65536 81 | ); 82 | 83 | if (chunkSize > 0) { 84 | var chunk = new Array(chunkSize + 1); 85 | chunk = chunk.join('a'); 86 | self.emit('data', chunk); 87 | self.sentSize += chunkSize; 88 | } else { 89 | self.emit('data', '"}'); 90 | self.emit('end'); 91 | self.pause(); 92 | } 93 | }, 1); 94 | } 95 | }; 96 | 97 | TestStream.prototype.pause = function() { 98 | clearInterval(this.interval); 99 | this.interval = null; 100 | }; 101 | 102 | var bodyStream = new TestStream(); 103 | 104 | SERVER.use(restify.plugins.gzipResponse()); 105 | SERVER.get('/gzip/:id', function(req, res, next) { 106 | bodyStream.resume(); 107 | res.write('{"foo":"'); 108 | bodyStream.pipe(res); 109 | bodyStream.on('end', function() { 110 | next(); 111 | }); 112 | }); 113 | 114 | var opts = { 115 | path: '/gzip/foo', 116 | headers: { 117 | 'Accept-Encoding': 'gzip' 118 | } 119 | }; 120 | CLIENT.get(opts, function(err, _, res, obj) { 121 | assert.ifError(err); 122 | var expectedResponse = { 123 | foo: new Array(testResponseSize + 1).join('a') 124 | }; 125 | assert.deepEqual(expectedResponse, obj); 126 | done(); 127 | }); 128 | }); 129 | 130 | it('gzip body json ok', function(done) { 131 | SERVER.use(restify.plugins.gzipResponse()); 132 | SERVER.use( 133 | restify.plugins.queryParser({ 134 | mapParams: true 135 | }) 136 | ); 137 | SERVER.use( 138 | restify.plugins.bodyParser({ 139 | mapParams: true 140 | }) 141 | ); 142 | SERVER.post('/body/:id', function(req, res, next) { 143 | assert.equal(req.params.id, 'foo'); 144 | assert.equal(req.params.name, 'markc'); 145 | assert.equal(req.params.phone, '(206) 555-1212'); 146 | res.send(); 147 | next(); 148 | }); 149 | 150 | var obj = { 151 | phone: '(206) 555-1212', 152 | name: 'somethingelse' 153 | }; 154 | CLIENT.gzip = {}; 155 | CLIENT.post('/body/foo?name=markc', obj, function(err, _, res) { 156 | assert.ifError(err); 157 | assert.ok(res); 158 | 159 | if (res) { 160 | assert.equal(res.statusCode, 200); 161 | } 162 | done(); 163 | }); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /test/plugins/inflightRequestThrottle.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | var assert = require('chai').assert; 5 | var restify = require('../../lib/index.js'); 6 | var restifyClients = require('restify-clients'); 7 | var inflightRequestThrottle = restify.plugins.inflightRequestThrottle; 8 | 9 | function fakeServer(count) { 10 | return { 11 | inflightRequests: function() { 12 | return count; 13 | } 14 | }; 15 | } 16 | 17 | describe('inlfightRequestThrottle', function() { 18 | it('Unit: Should shed load', function(done) { 19 | var logged = false; 20 | var opts = { server: fakeServer(10), limit: 1 }; 21 | var plugin = inflightRequestThrottle(opts); 22 | function send(body) { 23 | assert(logged, 'Should have emitted a log'); 24 | assert.equal(body.statusCode, 503, 'Defaults to 503 status'); 25 | assert(body instanceof Error, 'Defaults to error body'); 26 | done(); 27 | } 28 | function next(err) { 29 | assert.equal(err.name, 'ServiceUnavailableError'); 30 | done(); 31 | } 32 | function trace() { 33 | logged = true; 34 | } 35 | var log = { trace: trace }; 36 | var fakeReq = { log: log }; 37 | plugin(fakeReq, { send: send }, next); 38 | }); 39 | 40 | it('Unit: Should support custom response', function(done) { 41 | var server = fakeServer(10); 42 | var err = new Error('foo'); 43 | var opts = { server: server, limit: 1, err: err }; 44 | var plugin = inflightRequestThrottle(opts); 45 | function send(body) { 46 | assert.equal(body, err, 'Overrides body'); 47 | } 48 | function next(nextErr) { 49 | assert.equal(err, nextErr); 50 | done(); 51 | } 52 | var fakeReq = { log: { trace: function() {} } }; 53 | plugin(fakeReq, { send: send }, next); 54 | }); 55 | 56 | it('Unit: Should let request through when not under load', function(done) { 57 | var opts = { server: fakeServer(1), limit: 2 }; 58 | var plugin = inflightRequestThrottle(opts); 59 | function send() { 60 | assert(false, 'Should not call send'); 61 | } 62 | function next(cont) { 63 | assert.isUndefined(cont, 'Should call next'); 64 | done(); 65 | } 66 | var fakeReq = { log: { trace: function() {} } }; 67 | plugin(fakeReq, { send: send }, next); 68 | }); 69 | 70 | it('Integration: Should shed load', function(done) { 71 | var server = restify.createServer(); 72 | var client = { 73 | close: function() {} 74 | }; 75 | var isDone = false; 76 | var to; 77 | function finish() { 78 | if (isDone) { 79 | return null; 80 | } 81 | clearTimeout(to); 82 | isDone = true; 83 | client.close(); 84 | server.close(); 85 | return done(); 86 | } 87 | to = setTimeout(finish, 2000); 88 | var err = new Error('foo'); 89 | err.statusCode = 555; 90 | var opts = { server: server, limit: 1, err: err }; 91 | server.pre(inflightRequestThrottle(opts)); 92 | var RES; 93 | server.get('/foo', function(req, res, next) { 94 | if (RES) { 95 | res.send(999); 96 | } else { 97 | RES = res; 98 | } 99 | }); 100 | server.listen(0, '127.0.0.1', function() { 101 | client = restifyClients.createJsonClient({ 102 | url: 'http://127.0.0.1:' + server.address().port, 103 | retry: false 104 | }); 105 | client.get({ path: '/foo' }, function(e, _, res) { 106 | assert( 107 | e === null || e === undefined, 108 | 'First request isnt shed' 109 | ); 110 | assert.equal(res.statusCode, 200, '200 returned on success'); 111 | finish(); 112 | }); 113 | client.get({ path: '/foo' }, function(e, _, res) { 114 | assert(e, 'Second request is shed'); 115 | assert.equal( 116 | e.name, 117 | 'InternalServerError', 118 | 'Default err returned' 119 | ); 120 | assert.equal( 121 | res.statusCode, 122 | 555, 123 | 'Default shed status code returned' 124 | ); 125 | 126 | if (RES) { 127 | RES.send(200); 128 | } 129 | }); 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /test/plugins/reqIdHeaders.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | // external modules 5 | var assert = require('chai').assert; 6 | var restify = require('../../lib/index.js'); 7 | var restifyClients = require('restify-clients'); 8 | var validator = require('validator'); 9 | 10 | // internal files 11 | var helper = require('../lib/helper'); 12 | 13 | describe('request id headers', function() { 14 | var SERVER; 15 | var CLIENT; 16 | var PORT; 17 | 18 | beforeEach(function(done) { 19 | SERVER = restify.createServer({ 20 | dtrace: helper.dtrace, 21 | log: helper.getLog('server') 22 | }); 23 | 24 | SERVER.pre( 25 | restify.plugins.pre.reqIdHeaders({ 26 | headers: ['x-req-id-a', 'x-req-id-b'] 27 | }) 28 | ); 29 | 30 | SERVER.listen(0, '127.0.0.1', function() { 31 | PORT = SERVER.address().port; 32 | CLIENT = restifyClients.createJsonClient({ 33 | url: 'http://127.0.0.1:' + PORT, 34 | dtrace: helper.dtrace, 35 | retry: false 36 | }); 37 | return done(); 38 | }); 39 | }); 40 | 41 | afterEach(function(done) { 42 | CLIENT.close(); 43 | SERVER.close(function() { 44 | CLIENT = null; 45 | SERVER = null; 46 | return done(); 47 | }); 48 | }); 49 | 50 | it('GH-1086: should reuse request id when available', function(done) { 51 | SERVER.get('/1', function(req, res, next) { 52 | // the 12345 value is set when the client is created. 53 | assert.ok(req.headers.hasOwnProperty('x-req-id-a')); 54 | assert.equal(req.getId(), req.headers['x-req-id-a']); 55 | res.send('hello world'); 56 | return next(); 57 | }); 58 | 59 | // create new client since we new specific headers 60 | CLIENT = restifyClients.createJsonClient({ 61 | url: 'http://127.0.0.1:' + PORT, 62 | headers: { 63 | 'x-req-id-a': 12345 64 | } 65 | }); 66 | 67 | CLIENT.get('/1', function(err, req, res, data) { 68 | assert.ifError(err); 69 | assert.equal(data, 'hello world'); 70 | return done(); 71 | }); 72 | }); 73 | 74 | it('GH-1086: should use second request id when available', function(done) { 75 | SERVER.get('/1', function(req, res, next) { 76 | assert.ok(req.headers.hasOwnProperty('x-req-id-b')); 77 | assert.equal(req.getId(), req.headers['x-req-id-b']); 78 | res.send('hello world'); 79 | return next(); 80 | }); 81 | 82 | // create new client since we new specific headers 83 | CLIENT = restifyClients.createJsonClient({ 84 | url: 'http://127.0.0.1:' + PORT, 85 | headers: { 86 | 'x-req-id-b': 678910 87 | } 88 | }); 89 | 90 | CLIENT.get('/1', function(err, req, res, data) { 91 | assert.ifError(err); 92 | assert.equal(data, 'hello world'); 93 | return done(); 94 | }); 95 | }); 96 | 97 | // eslint-disable-next-line 98 | it('GH-1086: should use default uuid request id if none provided', function(done) { 99 | SERVER.get('/1', function(req, res, next) { 100 | assert.ok(req.getId()); 101 | assert.ok(validator.isUUID(req.getId())); 102 | res.send('hello world'); 103 | return next(); 104 | }); 105 | 106 | // create new client since we new specific headers 107 | CLIENT = restifyClients.createJsonClient({ 108 | url: 'http://127.0.0.1:' + PORT 109 | }); 110 | 111 | CLIENT.get('/1', function(err, req, res, data) { 112 | assert.ifError(err); 113 | assert.equal(data, 'hello world'); 114 | return done(); 115 | }); 116 | }); 117 | 118 | it('GH-1086: empty request id should be ignored', function(done) { 119 | SERVER.get('/1', function(req, res, next) { 120 | assert.ok(req.headers.hasOwnProperty('x-req-id-b')); 121 | assert.equal(req.getId(), req.headers['x-req-id-b']); 122 | res.send('hello world'); 123 | return next(); 124 | }); 125 | 126 | // create new client since we new specific headers 127 | CLIENT = restifyClients.createJsonClient({ 128 | url: 'http://127.0.0.1:' + PORT, 129 | headers: { 130 | 'x-req-id-a': '', 131 | 'x-req-id-b': 12345 132 | } 133 | }); 134 | 135 | CLIENT.get('/1', function(err, req, res, data) { 136 | assert.ifError(err); 137 | assert.equal(data, 'hello world'); 138 | return done(); 139 | }); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /test/plugins/testStaticFiles/docs/doc.md: -------------------------------------------------------------------------------- 1 | #This is doc.md -------------------------------------------------------------------------------- /test/plugins/testStaticFiles/docs/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="UTF-8" /> 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 | <meta http-equiv="X-UA-Compatible" content="ie=edge" /> 7 | <title>Document</title> 8 | </head> 9 | <body> 10 | <h1>testStaticFiles/docs/index.html</h1> 11 | </body> 12 | </html> 13 | -------------------------------------------------------------------------------- /test/plugins/testStaticFiles/file1.txt: -------------------------------------------------------------------------------- 1 | This is file1.txt -------------------------------------------------------------------------------- /test/plugins/testStaticFiles/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="UTF-8" /> 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 | <meta http-equiv="X-UA-Compatible" content="ie=edge" /> 7 | <title>Document</title> 8 | </head> 9 | <body> 10 | <h1>testStaticFiles/index.html</h1> 11 | </body> 12 | </html> 13 | -------------------------------------------------------------------------------- /test/plugins/testStaticFiles/special/$_$/bad (file).txt: -------------------------------------------------------------------------------- 1 | This is a very badly named file. -------------------------------------------------------------------------------- /test/plugins/userAgent.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | // core requires 5 | var child_process = require('child_process'); 6 | var http = require('http'); 7 | 8 | // external requires 9 | var assert = require('chai').assert; 10 | var restify = require('../../lib/index.js'); 11 | 12 | // local files 13 | var helper = require('../lib/helper'); 14 | 15 | // local globals 16 | var SERVER; 17 | var SERVER_PORT; 18 | var SERVER_ADDRESS = '127.0.0.1'; 19 | var SERVER_ENDPOINT; 20 | var TEST_ENDPOINT; 21 | var TEST_RESPONSE_DATA = 'foobar'; 22 | var TEST_RESPONSE_DATA_LENGTH = TEST_RESPONSE_DATA.length; 23 | var TEST_PATH = '/test/userAgent'; 24 | 25 | describe('userAgent pre-route handler', function() { 26 | beforeEach(function(done) { 27 | SERVER = restify.createServer({ 28 | dtrace: helper.dtrace, 29 | log: helper.getLog('server') 30 | }); 31 | 32 | // Enable the user agent pre-route handler, since this is the component 33 | // under test. 34 | SERVER.use(restify.plugins.pre.userAgentConnection()); 35 | 36 | SERVER.head('/test/:name', function(req, res, next) { 37 | // Explicitly set Content-Length response header so that we can test 38 | // for its removal (or lack thereof) by the userAgentConnection 39 | // pre-route handler in tests below. 40 | res.setHeader('Content-Length', TEST_RESPONSE_DATA_LENGTH); 41 | res.send(200, TEST_RESPONSE_DATA); 42 | next(); 43 | }); 44 | 45 | SERVER.listen(0, SERVER_ADDRESS, function() { 46 | SERVER_PORT = SERVER.address().port; 47 | SERVER_ENDPOINT = SERVER_ADDRESS + ':' + SERVER_PORT; 48 | TEST_ENDPOINT = SERVER_ENDPOINT + TEST_PATH; 49 | done(); 50 | }); 51 | }); 52 | 53 | afterEach(function(done) { 54 | SERVER.close(done); 55 | }); 56 | 57 | // By default, the userAgentConnection pre-route handler must: 58 | // 59 | // 1. set the 'connection' header to 'close' 60 | // 61 | // 2. remove the content-length header from the response 62 | // 63 | // when a HEAD request is handled and the client's user agent is curl. 64 | it('sets proper headers for HEAD requests from curl', function(done) { 65 | var CURL_CMD = ['curl', '-sS', '-i', TEST_ENDPOINT, '-X', 'HEAD'].join( 66 | ' ' 67 | ); 68 | 69 | child_process.exec(CURL_CMD, function onExec(err, stdout, stderr) { 70 | assert.ifError(err); 71 | 72 | var lines = stdout.split(/\n/); 73 | 74 | var contentLengthHeaderNotPresent = lines.every( 75 | function checkContentLengthNotPresent(line) { 76 | return /Content-Length:.*/.test(line) === false; 77 | } 78 | ); 79 | var connectionCloseHeaderPresent = lines.some( 80 | function checkConnectionClosePresent(line) { 81 | return /Connection: close/.test(line); 82 | } 83 | ); 84 | 85 | assert.ok(contentLengthHeaderNotPresent); 86 | assert.ok(connectionCloseHeaderPresent); 87 | 88 | done(); 89 | }); 90 | }); 91 | 92 | // When handling a HEAD request, and if the client's user agent is not curl, 93 | // the userAgentConnection should not remove the content-length header from 94 | // the response, and it should not replace the value of the 'connection' 95 | // header by 'close'. 96 | // eslint-disable-next-line 97 | it('sets proper headers for HEAD requests from non-curl clients', function(done) { 98 | var req = http.request( 99 | { 100 | hostname: SERVER_ADDRESS, 101 | port: SERVER_PORT, 102 | path: TEST_PATH, 103 | method: 'HEAD', 104 | headers: { 105 | 'user-agent': 'foobar', 106 | connection: 'keep-alive' 107 | } 108 | }, 109 | function onResponse(res) { 110 | var responseHeaders = res.headers; 111 | 112 | assert.ok(responseHeaders.hasOwnProperty('content-length')); 113 | assert.equal(responseHeaders.connection, 'keep-alive'); 114 | 115 | // destroy the socket explicitly now since the request was 116 | // explicitly requesting to not destroy the socket by setting 117 | // its connection header to 'keep-alive'. 118 | req.abort(); 119 | 120 | done(); 121 | } 122 | ); 123 | 124 | req.on('error', function onReqError(err) { 125 | assert.ifError(err); 126 | done(); 127 | }); 128 | 129 | req.end(); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/plugins/utilsHrTimeDurationInMs.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | var assert = require('chai').assert; 5 | var hrTimeDurationInMs = require('../../lib/plugins/utils/hrTimeDurationInMs'); 6 | 7 | describe('utils #hrTimeDurationInMs', function() { 8 | it('should return with duration', function() { 9 | var startTime = [0, 0]; 10 | var endTime = [1, 1e6]; 11 | 12 | var duration = hrTimeDurationInMs(startTime, endTime); 13 | 14 | assert.equal(duration, 1001); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/routerRegistryRadix.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | var RouterRegistryRadix = require('../lib/routerRegistryRadix'); 5 | var Chain = require('../lib/chain'); 6 | 7 | if (require.cache[__dirname + '/lib/helper.js']) { 8 | delete require.cache[__dirname + '/lib/helper.js']; 9 | } 10 | var helper = require('./lib/helper.js'); 11 | 12 | ///--- Globals 13 | 14 | var test = helper.test; 15 | 16 | function getTestRoute(opts) { 17 | var chain = new Chain(); 18 | var name = opts.method + '-' + opts.path; 19 | name = name.replace(/\W/g, '').toLowerCase(); 20 | 21 | return { 22 | name: name, 23 | method: opts.method, 24 | path: opts.path, 25 | spec: opts, 26 | chain: chain 27 | }; 28 | } 29 | 30 | ///--- Tests 31 | 32 | test('adds a route', function(t) { 33 | var registry = new RouterRegistryRadix(); 34 | registry.add(getTestRoute({ method: 'GET', path: '/' })); 35 | registry.add(getTestRoute({ method: 'POST', path: '/' })); 36 | registry.add(getTestRoute({ method: 'GET', path: '/ab' })); 37 | 38 | t.deepEqual(Object.keys(registry.get()), ['get', 'post', 'getab']); 39 | 40 | t.done(); 41 | }); 42 | 43 | test('removes a route', function(t) { 44 | var registry = new RouterRegistryRadix(); 45 | 46 | // Mount 47 | registry.add(getTestRoute({ method: 'GET', path: '/a' })); 48 | registry.add(getTestRoute({ method: 'POST', path: '/b' })); 49 | t.deepEqual(Object.keys(registry.get()), ['geta', 'postb']); 50 | 51 | // Unmount 52 | var route = registry.remove('geta'); 53 | t.ok(route); 54 | t.equal(route.name, 'geta'); 55 | 56 | // Removes from registry 57 | t.deepEqual(Object.keys(registry.get()), ['postb']); 58 | 59 | t.end(); 60 | }); 61 | 62 | test('lookups a route', function(t) { 63 | var registry = new RouterRegistryRadix(); 64 | var route = getTestRoute({ method: 'GET', path: '/a/:b' }); 65 | registry.add(route); 66 | 67 | var result = registry.lookup('GET', '/a/b'); 68 | 69 | t.deepEqual(result, { 70 | route: route, 71 | params: { b: 'b' }, 72 | handler: result.handler 73 | }); 74 | 75 | t.done(); 76 | }); 77 | 78 | test('get registered routes', function(t) { 79 | var registry = new RouterRegistryRadix(); 80 | registry.add(getTestRoute({ method: 'GET', path: '/' })); 81 | registry.add(getTestRoute({ method: 'GET', path: '/a' })); 82 | registry.add(getTestRoute({ method: 'GET', path: '/a/b' })); 83 | registry.add(getTestRoute({ method: 'POST', path: '/' })); 84 | 85 | t.deepEqual(Object.keys(registry.get()), ['get', 'geta', 'getab', 'post']); 86 | t.end(); 87 | }); 88 | 89 | test('toString()', function(t) { 90 | var registry = new RouterRegistryRadix(); 91 | registry.add(getTestRoute({ method: 'GET', path: '/' })); 92 | registry.add(getTestRoute({ method: 'GET', path: '/a' })); 93 | registry.add(getTestRoute({ method: 'GET', path: '/a/b' })); 94 | registry.add(getTestRoute({ method: 'POST', path: '/' })); 95 | 96 | t.deepEqual( 97 | registry.toString(), 98 | // prettier-ignore 99 | '└── / (GET, POST)\n' + 100 | ' └── a (GET)\n' + 101 | ' └── /b (GET)\n' 102 | ); 103 | t.end(); 104 | }); 105 | 106 | test('toString() with ignoreTrailingSlash', function(t) { 107 | var registry = new RouterRegistryRadix({ ignoreTrailingSlash: true }); 108 | registry.add(getTestRoute({ method: 'GET', path: '/' })); 109 | registry.add(getTestRoute({ method: 'GET', path: '/a' })); 110 | registry.add(getTestRoute({ method: 'GET', path: '/a/b' })); 111 | registry.add(getTestRoute({ method: 'POST', path: '/' })); 112 | 113 | t.deepEqual( 114 | registry.toString(), 115 | // prettier-ignore 116 | '└── / (GET, POST)\n' + 117 | ' └── a (GET)\n' + 118 | ' └── /b (GET)\n' 119 | ); 120 | t.end(); 121 | }); 122 | -------------------------------------------------------------------------------- /test/serverHttp2.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | var path = require('path'); 5 | var fs = require('fs'); 6 | var http2; 7 | 8 | // http2 module is not available < v8.4.0 (only with flag <= 8.8.0) 9 | try { 10 | http2 = require('http2'); 11 | } catch (err) { 12 | console.log('HTTP2 module is not available'); 13 | console.log( 14 | 'Node.js version >= v8.8.8 required, current: ' + process.versions.node 15 | ); 16 | return; 17 | } 18 | 19 | var restify = require('../lib'); 20 | 21 | if (require.cache[__dirname + '/lib/helper.js']) { 22 | delete require.cache[__dirname + '/lib/helper.js']; 23 | } 24 | var helper = require('./lib/helper.js'); 25 | 26 | ///--- Globals 27 | 28 | var after = helper.after; 29 | var before = helper.before; 30 | var test = helper.test; 31 | 32 | var CERT = fs.readFileSync(path.join(__dirname, './keys/http2-cert.pem')); 33 | var KEY = fs.readFileSync(path.join(__dirname, './keys/http2-key.pem')); 34 | var CA = fs.readFileSync(path.join(__dirname, 'keys/http2-csr.pem')); 35 | 36 | var PORT = process.env.UNIT_TEST_PORT || 0; 37 | var CLIENT; 38 | var SERVER; 39 | 40 | ///--- Tests 41 | 42 | before(function(cb) { 43 | try { 44 | SERVER = restify.createServer({ 45 | dtrace: helper.dtrace, 46 | handleUncaughtExceptions: true, 47 | http2: { 48 | cert: CERT, 49 | key: KEY, 50 | ca: CA 51 | }, 52 | log: helper.getLog('server') 53 | }); 54 | SERVER.listen(PORT, '127.0.0.1', function() { 55 | PORT = SERVER.address().port; 56 | CLIENT = http2.connect('https://127.0.0.1:' + PORT, { 57 | rejectUnauthorized: false 58 | }); 59 | 60 | cb(); 61 | }); 62 | } catch (e) { 63 | console.error(e.stack); 64 | process.exit(1); 65 | } 66 | }); 67 | 68 | after(function(cb) { 69 | try { 70 | CLIENT.destroy(); 71 | SERVER.close(function() { 72 | CLIENT = null; 73 | SERVER = null; 74 | cb(); 75 | }); 76 | } catch (e) { 77 | console.error(e.stack); 78 | process.exit(1); 79 | } 80 | }); 81 | 82 | test('get (path only)', function(t) { 83 | SERVER.get('/foo/:id', function echoId(req, res, next) { 84 | t.ok(req.params); 85 | t.equal(req.params.id, 'bar'); 86 | t.equal(req.isUpload(), false); 87 | res.json({ hello: 'world' }); 88 | next(); 89 | }); 90 | 91 | var req = CLIENT.request({ 92 | ':path': '/foo/bar', 93 | ':method': 'GET' 94 | }); 95 | 96 | req.on('response', function(headers, flags) { 97 | var data = ''; 98 | t.equal(headers[':status'], 200); 99 | 100 | req.on('data', function(chunk) { 101 | data += chunk; 102 | }); 103 | req.on('end', function() { 104 | t.deepEqual(JSON.parse(data), { hello: 'world' }); 105 | t.end(); 106 | }); 107 | }); 108 | req.on('error', function(err) { 109 | t.ifError(err); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable func-names */ 3 | 4 | var mergeQs = require('../lib/utils').mergeQs; 5 | 6 | if (require.cache[__dirname + '/lib/helper.js']) { 7 | delete require.cache[__dirname + '/lib/helper.js']; 8 | } 9 | var helper = require('./lib/helper.js'); 10 | 11 | ///--- Globals 12 | 13 | var test = helper.test; 14 | 15 | test('merge qs', function(t) { 16 | var qs1 = mergeQs(undefined, { a: 1 }); 17 | t.deepEqual(qs1, { a: 1 }); 18 | 19 | var qs2 = mergeQs({ a: 1 }, null); 20 | t.deepEqual(qs2, { a: 1 }); 21 | 22 | var qs3 = mergeQs({ a: 1 }, { a: 2 }); 23 | t.deepEqual(qs3, { a: [1, 2] }); 24 | 25 | var qs4 = mergeQs({ a: 1 }, { b: 2 }); 26 | t.deepEqual(qs4, { a: 1, b: 2 }); 27 | 28 | var qs5 = mergeQs(null, null); 29 | t.deepEqual(qs5, {}); 30 | 31 | t.done(); 32 | }); 33 | -------------------------------------------------------------------------------- /tools/mk/Makefile.defs: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # Copyright (c) 2012, Joyent, Inc. All rights reserved. 4 | # 5 | # Makefile.defs: common defines. 6 | # 7 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 8 | # into other repos as-is without requiring any modifications. If you find 9 | # yourself changing this file, you should instead update the original copy in 10 | # eng.git and then update your repo to use the new version. 11 | # 12 | # This makefile defines some useful defines. Include it at the top of 13 | # your Makefile. 14 | # 15 | # Definitions in this Makefile: 16 | # 17 | # TOP The absolute path to the project directory. The top dir. 18 | # BRANCH The current git branch. 19 | # TIMESTAMP The timestamp for the build. This can be set via 20 | # the TIMESTAMP envvar (used by MG-based builds). 21 | # STAMP A build stamp to use in built package names. 22 | # 23 | 24 | TOP := $(shell pwd) 25 | 26 | # 27 | # Mountain Gorilla-spec'd versioning. 28 | # See "Package Versioning" in MG's README.md: 29 | # <https://mo.joyent.com/mountain-gorilla/blob/master/README.md#L139-200> 30 | # 31 | # Need GNU awk for multi-char arg to "-F". 32 | _AWK := $(shell (which gawk >/dev/null && echo gawk) \ 33 | || (which nawk >/dev/null && echo nawk) \ 34 | || echo awk) 35 | BRANCH := $(shell git log -n 1 --pretty=%d HEAD | $(_AWK) '{print $NF}' | sed -e 's/.$//' | cut -d/ -f2) 36 | ifeq ($(TIMESTAMP),) 37 | TIMESTAMP := $(shell date -u "+%Y%m%dT%H%M%SZ") 38 | endif 39 | _GITDESCRIBE := g$(shell git describe --all --long --dirty | $(_AWK) -F'-g' '{print $NF}') 40 | STAMP := $(BRANCH)-$(TIMESTAMP)-$(_GITDESCRIBE) 41 | -------------------------------------------------------------------------------- /tools/mk/Makefile.deps: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # Copyright (c) 2012, Joyent, Inc. All rights reserved. 4 | # 5 | # Makefile.deps: Makefile for including common tools as dependencies 6 | # 7 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 8 | # into other repos as-is without requiring any modifications. If you find 9 | # yourself changing this file, you should instead update the original copy in 10 | # eng.git and then update your repo to use the new version. 11 | # 12 | # This file is separate from Makefile.targ so that teams can choose 13 | # independently whether to use the common targets in Makefile.targ and the 14 | # common tools here. 15 | # 16 | 17 | 18 | # 19 | # restdown 20 | # 21 | RESTDOWN_EXEC ?= deps/restdown/bin/restdown 22 | RESTDOWN ?= python $(RESTDOWN_EXEC) 23 | $(RESTDOWN_EXEC): | deps/restdown/.git 24 | --------------------------------------------------------------------------------