├── .gitattributes ├── .github ├── dependabot.yml ├── stale.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── eslint.config.js ├── package.json ├── plugin.js ├── test ├── https │ ├── fastify.cert │ └── fastify.key └── tests.test.js └── types ├── fastify-url-data.d.ts └── fastify-url-data.test-d.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically convert line endings 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "monthly" 13 | open-pull-requests-limit: 10 14 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 15 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - "discussion" 8 | - "feature request" 9 | - "bug" 10 | - "help wanted" 11 | - "plugin suggestion" 12 | - "good first issue" 13 | # Label to use when marking an issue as stale 14 | staleLabel: stale 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: > 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: false 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | - 'v*' 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | pull_request: 13 | paths-ignore: 14 | - 'docs/**' 15 | - '*.md' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | test: 22 | permissions: 23 | contents: write 24 | pull-requests: write 25 | uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5 26 | with: 27 | license-check: true 28 | lint: true 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # .tap output 27 | .tap 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Docusaurus cache and generated files 111 | .docusaurus 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | 135 | # Vim swap files 136 | *.swp 137 | 138 | # macOS files 139 | .DS_Store 140 | 141 | # Clinic 142 | .clinic 143 | 144 | # lock files 145 | bun.lockb 146 | package-lock.json 147 | pnpm-lock.yaml 148 | yarn.lock 149 | 150 | # editor files 151 | .vscode 152 | .idea 153 | 154 | #tap files 155 | .tap/ 156 | 157 | # 0x 158 | .__browserify_string_empty.js 159 | profile-* 160 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fastify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @fastify/url-data 2 | 3 | [![CI](https://github.com/fastify/fastify-url-data/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify-url-data/actions/workflows/ci.yml) 4 | [![NPM version](https://img.shields.io/npm/v/@fastify/url-data.svg?style=flat)](https://www.npmjs.com/package/@fastify/url-data) 5 | [![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) 6 | 7 | A plugin for [Fastify](https://fastify.dev/) that adds support for getting raw 8 | URL information from the request. 9 | 10 | ## Install 11 | ``` 12 | npm i @fastify/url-data 13 | ``` 14 | 15 | ### Compatibility 16 | | Plugin version | Fastify version | 17 | | ---------------|-----------------| 18 | | `>=6.x` | `^5.x` | 19 | | `^5.x` | `^4.x` | 20 | | `>=3.x <5.x` | `^3.x` | 21 | | `^2.x` | `^2.x` | 22 | | `^2.x` | `^1.x` | 23 | 24 | 25 | Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin 26 | in the table above. 27 | See [Fastify's LTS policy](https://github.com/fastify/fastify/blob/main/docs/Reference/LTS.md) for more details. 28 | 29 | ## Example 30 | 31 | ```js 32 | const fastify = require('fastify')() 33 | 34 | fastify.register(require('@fastify/url-data')) 35 | 36 | fastify.get('/foo', (req, reply) => { 37 | const urlData = req.urlData() 38 | req.log.info(urlData.path) // '/foo' 39 | req.log.info(urlData.query) // 'a=b&c=d' 40 | req.log.info(urlData.host) // '127.0.0.1' 41 | req.log.info(urlData.port) // 8080 42 | 43 | // if you just need single data: 44 | req.log.info(req.urlData('path')) // '/foo' 45 | 46 | reply.send({hello: 'world'}) 47 | }) 48 | 49 | // GET: 'http://127.0.0.1:8080/foo?a=b&c=d 50 | ``` 51 | 52 | ## License 53 | 54 | Licensed under [MIT](./LICENSE). 55 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('neostandard')({ 4 | ignores: require('neostandard').resolveIgnoresFromGitignore(), 5 | ts: true 6 | }) 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fastify/url-data", 3 | "version": "6.0.3", 4 | "description": "A Fastify plugin to provide access to URI components", 5 | "main": "plugin.js", 6 | "type": "commonjs", 7 | "types": "types/fastify-url-data.d.ts", 8 | "scripts": { 9 | "lint": "eslint", 10 | "lint:fix": "eslint --fix", 11 | "test": "npm run test:unit && npm run test:typescript", 12 | "test:unit": "c8 --100 node --test", 13 | "test:typescript": "tsd" 14 | }, 15 | "precommit": [ 16 | "lint", 17 | "test" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git+ssh://git@github.com/fastify/fastify-url-data.git" 22 | }, 23 | "keywords": [ 24 | "fastify" 25 | ], 26 | "author": "James Sumners ", 27 | "contributors": [ 28 | { 29 | "name": "Matteo Collina", 30 | "email": "hello@matteocollina.com" 31 | }, 32 | { 33 | "name": "Manuel Spigolon", 34 | "email": "behemoth89@gmail.com" 35 | }, 36 | { 37 | "name": "Aras Abbasi", 38 | "email": "aras.abbasi@gmail.com" 39 | }, 40 | { 41 | "name": "Frazer Smith", 42 | "email": "frazer.dev@icloud.com", 43 | "url": "https://github.com/fdawgs" 44 | } 45 | ], 46 | "license": "MIT", 47 | "bugs": { 48 | "url": "https://github.com/fastify/fastify-url-data/issues" 49 | }, 50 | "homepage": "https://github.com/fastify/fastify-url-data#readme", 51 | "funding": [ 52 | { 53 | "type": "github", 54 | "url": "https://github.com/sponsors/fastify" 55 | }, 56 | { 57 | "type": "opencollective", 58 | "url": "https://opencollective.com/fastify" 59 | } 60 | ], 61 | "devDependencies": { 62 | "@fastify/pre-commit": "^2.1.0", 63 | "@types/node": "^22.0.0", 64 | "c8": "^10.1.3", 65 | "eslint": "^9.17.0", 66 | "fastify": "^5.0.0", 67 | "h2url": "^0.2.0", 68 | "neostandard": "^0.12.0", 69 | "tsd": "~0.32.0" 70 | }, 71 | "dependencies": { 72 | "fast-uri": "^3.0.0", 73 | "fastify-plugin": "^5.0.0" 74 | }, 75 | "publishConfig": { 76 | "access": "public" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const { parse } = require('fast-uri') 5 | 6 | function fastifyUrlData (fastify, _options, next) { 7 | fastify.decorateRequest('urlData', function (key) { 8 | const scheme = this.headers[':scheme'] ? this.headers[':scheme'] : this.protocol 9 | const host = this.hostname 10 | const port = this.port 11 | const path = this.headers[':path'] || this.raw.url 12 | const urlData = parse(`${scheme}://${host}${port ? ':' + port : ''}${path}`) 13 | if (key) return urlData[key] 14 | return urlData 15 | }) 16 | next() 17 | } 18 | 19 | module.exports = fp(fastifyUrlData, { 20 | fastify: '5.x', 21 | name: '@fastify/url-data' 22 | }) 23 | module.exports.default = fastifyUrlData 24 | module.exports.fastifyUrlData = fastifyUrlData 25 | -------------------------------------------------------------------------------- /test/https/fastify.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDBzCCAe+gAwIBAgIJALbQMeb7k/WqMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV 3 | BAMMD3d3dy5mYXN0aWZ5Lm9yZzAeFw0xNzAyMDcyMDE5NDJaFw0yNzAyMDUyMDE5 4 | NDJaMBoxGDAWBgNVBAMMD3d3dy5mYXN0aWZ5Lm9yZzCCASIwDQYJKoZIhvcNAQEB 5 | BQADggEPADCCAQoCggEBAKtfXzDMmU+n3A7oVVOiqp6Z5cgu1t+qgj7TadwXONvO 6 | RZvuOcE8BZpM9tQEDE5XEIdcszDx0tWKHHSobgZAxDaEuK1PMhh/RTNvw1KzYJFm 7 | 2G38mqgm11JUni87xmIFqpgJfeCApHnWUv+3/npuQniOoVSL13jdXEifeFM8onQn 8 | R73TVDyvMOjljTulMo0n9V8pYhVSzPnm2uxTu03p5+HosQE2bU0QKj7k8/8dwRVX 9 | EqnTtbLoW+Wf7V2W3cr/UnfPH8JSaBWTqct0pgXqYIqOSTiWQkO7pE69mGPHrRlm 10 | 7+whp4WRriTacB3Ul+Cbx28wHU+D83ver4A8LKGVDSECAwEAAaNQME4wHQYDVR0O 11 | BBYEFHVzTr/tNziIUrR75UHXXA84yqmgMB8GA1UdIwQYMBaAFHVzTr/tNziIUrR7 12 | 5UHXXA84yqmgMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKVSdGeF 13 | vYcZOi0TG2WX7O3tSmu4G4nGxTldFiEVF89G0AU+HhNy9iwKXQLjDB7zMe/ZKbtJ 14 | cQgc6s8eZWxBk/OoPD1WNFGstx2EO2kRkSUBKhwnOct7CIS5X+NPXyHx2Yi03JHX 15 | unMA4WaHyo0dK4vAuali4OYdQqajNwL74avkRIxXFnZQeHzaq6tc6gX+ryB4dDSr 16 | tYn46Lo14D5jH6PtZ8DlGK+jIzM4IE7TEp2iv0CgaTU4ryt/SHPnLxfwZUpl7gSO 17 | EqkMAy3TlRMpv0oXM2Vh/CsyJzq2P/nY/O3bolsashSPWo9WsQTH4giYVA51ZVDK 18 | lGksQD+oWpfa3X0= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/https/fastify.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAq19fMMyZT6fcDuhVU6KqnpnlyC7W36qCPtNp3Bc4285Fm+45 3 | wTwFmkz21AQMTlcQh1yzMPHS1YocdKhuBkDENoS4rU8yGH9FM2/DUrNgkWbYbfya 4 | qCbXUlSeLzvGYgWqmAl94ICkedZS/7f+em5CeI6hVIvXeN1cSJ94UzyidCdHvdNU 5 | PK8w6OWNO6UyjSf1XyliFVLM+eba7FO7Tenn4eixATZtTRAqPuTz/x3BFVcSqdO1 6 | suhb5Z/tXZbdyv9Sd88fwlJoFZOpy3SmBepgio5JOJZCQ7ukTr2YY8etGWbv7CGn 7 | hZGuJNpwHdSX4JvHbzAdT4Pze96vgDwsoZUNIQIDAQABAoIBAG278ys/R8he1yVg 8 | lgqo9ZH7P8zwWTz9ZMsv+vAomor9SUtwvuDCO2AzejYGpY6gZ4AV1tQ3dOaxukjk 9 | 9Rbh8AJs+AhZ1t0i2b/3B95z6BkS/vFmt+2GeYhJkMT0BLMNp9AU+9p+5VLy71C5 10 | k6T3525k/l8x8HZ/YDFMk/LQt8GhvM6A3J3BNElKraiDVO6ZIWgQQ5wiefJkApo1 11 | BsptHNTx83FbnkEbAahmOR8PfKcRdKY/mZDM2WrlfoU2uwVzPV0/KdYucpsfg2et 12 | jb5bdJzcvZDuDF4GsPi1asCSC1c403R0XGuPFW9TiBuOPxbfhYK2o60yTggX6H2X 13 | 39WBc/ECgYEA3KNGgXEWzDSLpGciUisP+MzulOdQPawBTUHNykpQklEppnZbNWCX 14 | 07dv6uasnp0pFHG4WlhZJ4+IQBpZH6xAVy9y68PvN7IDYdgMiEiYPSyqQu0rvJGa 15 | 2ZR79SHDokZ8K5oofocC839RzleNRqWqxIwhHt29sxVs73kvml6OQm0CgYEAxtbA 16 | zbQwf6DXtFwutSgfOLgdXQK72beBdyeTcpUGbkonl5xHSbtz0CFmRpKiPnXfgg4W 17 | GXlTrqlYF/o048B7dU9+jCKY5DXx1Yzg/EFisEIClad3WXMhNOz1vBYVH6xU3Zq1 18 | YuYr5dcqiCWDv89e6Y6WJOhwIDZi6RqikD2EJQUCgYEAnWSAJFCnIa8OOo4z5oe/ 19 | kg2m2GQWUphEKXeatQbEaUwquQvPTsmEJUzDMr+xPkkAiAwDpbdGijkSyh/Bmh2H 20 | nGpFwbf5CzMaxI6ZihK3P1SAdNO5koAQBcytjJW0eCtt4rDK2E+5pDgcBGVia5Y8 21 | to78BYfLDlhnaIF7mtR/CRUCgYEAvGCuzvOcUv4F/eirk5NMaQb9QqYZZD2XWVTU 22 | O2T2b7yvX9J+M1t1cESESe4X6cbwlp1T0JSCdGIZhLXWL8Om80/52zfX07VLxP6w 23 | FCy6G7SeEDxVNRh+6E5qzOO65YP17vDoUacxBZJgyBWKiUkkaW9dzd+sgsgj0yYZ 24 | xz+QlyUCgYEAxdNWQnz0pR5Rt2dbIedPs7wmiZ7eAe0VjCdhMa52IyJpejdeB6Bn 25 | Es+3lkHr0Xzty8XlQZcpbswhM8UZRgPVoBvvwQdQbv5yV+LdUu69pLM7InsdZy8u 26 | opPY/+q9lRdJt4Pbep3pOWYeLP7k5l4vei2vOEMHRjHnoqM5etSb6RU= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/tests.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('node:fs') 4 | const join = require('node:path').join 5 | const { test } = require('node:test') 6 | const Fastify = require('fastify') 7 | const plugin = require('..') 8 | 9 | const urlHost = 'localhost' 10 | const urlForwardedHost = 'example.com' 11 | const urlPath = '/one' 12 | const urlQuery = 'a=b&c=d' 13 | const httpScheme = 'http' 14 | const httpsScheme = 'https' 15 | 16 | test('parses a full URI', async (t) => { 17 | t.plan(11) 18 | const fastify = Fastify() 19 | 20 | fastify 21 | .register(plugin) 22 | .after((err) => { 23 | t.assert.ifError(err) 24 | }) 25 | 26 | fastify.get(urlPath, (req, reply) => { 27 | const uriData = req.urlData() 28 | t.assert.deepStrictEqual(uriData.host, urlHost) 29 | t.assert.deepStrictEqual(uriData.port, port) 30 | t.assert.deepStrictEqual(uriData.path, urlPath) 31 | t.assert.deepStrictEqual(uriData.query, urlQuery) 32 | t.assert.deepStrictEqual(uriData.scheme, httpScheme) 33 | t.assert.deepStrictEqual(req.urlData('host'), urlHost) 34 | t.assert.deepStrictEqual(req.urlData('port'), port) 35 | t.assert.deepStrictEqual(req.urlData('path'), urlPath) 36 | t.assert.deepStrictEqual(req.urlData('query'), urlQuery) 37 | t.assert.deepStrictEqual(req.urlData('scheme'), httpScheme) 38 | reply.send() 39 | }) 40 | 41 | await fastify.listen({ port: 0 }) 42 | const port = fastify.server.address().port 43 | fastify.server.unref() 44 | 45 | await fetch(`http://${urlHost}:${port}${urlPath}?${urlQuery}#foo`) 46 | 47 | t.after(() => fastify.close()) 48 | }) 49 | 50 | test('parses a full URI in HTTP2', async (t) => { 51 | t.plan(11) 52 | 53 | const h2url = require('h2url') 54 | 55 | const fastify = Fastify({ 56 | http2: true, 57 | https: { 58 | key: fs.readFileSync(join(__dirname, 'https', 'fastify.key')), 59 | cert: fs.readFileSync(join(__dirname, 'https', 'fastify.cert')) 60 | } 61 | }) 62 | 63 | fastify 64 | .register(plugin) 65 | .after((err) => { 66 | t.assert.ifError(err) 67 | }) 68 | 69 | fastify.get(urlPath, (req, reply) => { 70 | const uriData = req.urlData() 71 | t.assert.deepStrictEqual(uriData.host, urlHost) 72 | t.assert.deepStrictEqual(uriData.port, port) 73 | t.assert.deepStrictEqual(uriData.path, urlPath) 74 | t.assert.deepStrictEqual(uriData.query, urlQuery) 75 | t.assert.deepStrictEqual(uriData.scheme, httpsScheme) 76 | t.assert.deepStrictEqual(req.urlData('host'), urlHost) 77 | t.assert.deepStrictEqual(req.urlData('port'), port) 78 | t.assert.deepStrictEqual(req.urlData('path'), urlPath) 79 | t.assert.deepStrictEqual(req.urlData('query'), urlQuery) 80 | t.assert.deepStrictEqual(req.urlData('scheme'), httpsScheme) 81 | reply.send() 82 | }) 83 | 84 | await fastify.listen({ port: 0 }) 85 | const port = fastify.server.address().port 86 | fastify.server.unref() 87 | 88 | await h2url.concat({ url: `https://${urlHost}:${port}${urlPath}?${urlQuery}#foo` }) 89 | 90 | t.after(() => fastify.close()) 91 | }) 92 | 93 | test('parses a full URI using X-Forwarded-Host when trustProxy is set', async (t) => { 94 | t.plan(11) 95 | const fastify = Fastify({ trustProxy: true }) // Setting trustProxy true will use X-Forwarded-Host header if set 96 | 97 | fastify 98 | .register(plugin) 99 | .after((err) => { 100 | t.assert.ifError(err) 101 | }) 102 | 103 | fastify.get(urlPath, (req, reply) => { 104 | const uriData = req.urlData() 105 | t.assert.deepStrictEqual(uriData.host, urlForwardedHost) 106 | t.assert.deepStrictEqual(uriData.port, port) 107 | t.assert.deepStrictEqual(uriData.path, urlPath) 108 | t.assert.deepStrictEqual(uriData.query, urlQuery) 109 | t.assert.deepStrictEqual(uriData.scheme, httpScheme) 110 | t.assert.deepStrictEqual(req.urlData('host'), urlForwardedHost) 111 | t.assert.deepStrictEqual(req.urlData('port'), port) 112 | t.assert.deepStrictEqual(req.urlData('path'), urlPath) 113 | t.assert.deepStrictEqual(req.urlData('query'), urlQuery) 114 | t.assert.deepStrictEqual(req.urlData('scheme'), httpScheme) 115 | reply.send() 116 | }) 117 | 118 | await fastify.listen({ port: 0 }) 119 | const port = fastify.server.address().port 120 | fastify.server.unref() 121 | 122 | await fetch(`http://${urlHost}:${fastify.server.address().port}${urlPath}?${urlQuery}#foo`, { headers: { 'X-Forwarded-Host': `${urlForwardedHost}:${port}` } }) 123 | 124 | t.after(() => fastify.close()) 125 | }) 126 | 127 | test('parses a full URI ignoring X-Forwarded-Host when trustProxy is not set', async (t) => { 128 | t.plan(11) 129 | const fastify = Fastify() 130 | 131 | fastify 132 | .register(plugin) 133 | .after((err) => { 134 | t.assert.ifError(err) 135 | }) 136 | 137 | fastify.get(urlPath, (req, reply) => { 138 | const uriData = req.urlData() 139 | t.assert.deepStrictEqual(uriData.host, urlHost) 140 | t.assert.deepStrictEqual(uriData.port, port) 141 | t.assert.deepStrictEqual(uriData.path, urlPath) 142 | t.assert.deepStrictEqual(uriData.query, urlQuery) 143 | t.assert.deepStrictEqual(uriData.scheme, httpScheme) 144 | t.assert.deepStrictEqual(req.urlData('host'), urlHost) 145 | t.assert.deepStrictEqual(req.urlData('port'), port) 146 | t.assert.deepStrictEqual(req.urlData('path'), urlPath) 147 | t.assert.deepStrictEqual(req.urlData('query'), urlQuery) 148 | t.assert.deepStrictEqual(req.urlData('scheme'), httpScheme) 149 | reply.send() 150 | }) 151 | 152 | await fastify.listen({ port: 0 }) 153 | const port = fastify.server.address().port 154 | fastify.server.unref() 155 | 156 | await fetch(`http://${urlHost}:${fastify.server.address().port}${urlPath}?${urlQuery}#foo`, { headers: { 'X-Forwarded-Host': `${urlForwardedHost}:${port}` } }) 157 | 158 | t.after(() => fastify.close()) 159 | }) 160 | 161 | test('should parse path without a port specified', async (t) => { 162 | t.plan(2) 163 | const fastify = Fastify() 164 | fastify 165 | .register(plugin) 166 | 167 | fastify.get('/', (req, reply) => { 168 | const path = req.urlData('path') 169 | reply.send('That worked, path is ' + path) 170 | }) 171 | 172 | const res = await fastify.inject({ url: '/', headers: { host: 'localhost' } }) 173 | t.assert.deepStrictEqual(res.statusCode, 200) 174 | t.assert.deepStrictEqual(res.body, 'That worked, path is /') 175 | }) 176 | -------------------------------------------------------------------------------- /types/fastify-url-data.d.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginCallback } from 'fastify' 2 | import { URIComponent } from 'fast-uri' 3 | 4 | type FastifyUrlData = FastifyPluginCallback 5 | 6 | declare module 'fastify' { 7 | interface FastifyRequest { 8 | urlData(target: K): URIComponent[K] 9 | urlData(): URIComponent 10 | } 11 | } 12 | 13 | declare namespace fastifyUrlData { 14 | export const fastifyUrlData: FastifyUrlData 15 | export { fastifyUrlData as default } 16 | } 17 | 18 | declare function fastifyUrlData (...params: Parameters): ReturnType 19 | export = fastifyUrlData 20 | -------------------------------------------------------------------------------- /types/fastify-url-data.test-d.ts: -------------------------------------------------------------------------------- 1 | import fastify from 'fastify' 2 | import { expectType } from 'tsd' 3 | import urlData from '..' 4 | 5 | const server = fastify() 6 | 7 | server.register(urlData) 8 | 9 | server.get('/data', (req, reply) => { 10 | console.log(req.urlData) 11 | expectType(req.urlData().path) 12 | expectType(req.urlData().host) 13 | expectType(req.urlData().port) 14 | expectType(req.urlData().query) 15 | 16 | expectType(req.urlData('path')) 17 | expectType(req.urlData('port')) 18 | 19 | reply.send({ msg: 'ok' }) 20 | }) 21 | 22 | server.listen({ port: 3030 }) 23 | 24 | server.inject({ 25 | method: 'GET', 26 | url: '/data' 27 | }) 28 | --------------------------------------------------------------------------------