您可能在不知不觉中以不安全的方式编写 URL

你能发现这段代码中的错误吗?

const url = `https://builder.io/api/v2/content
  ?model=${model}&locale=${locale}?query.text=${text}`

const res = await fetch(url)

至少有三个!

我们将在下面分解它们:

常见问题 #1:不正确的分隔符

带有额外 `?` 的 URL 字符串

哎呀!这当然是一个新手错误,但很容易错过,即使经过 10 年的 JS 开发,我也在我自己的代码中发现了这个错误。

根据我的经验,一个常见的罪魁祸首是在编辑或移动代码之后。例如,您有一个结构正确的 URL,然后将一个片段从一个片段复制到另一个片段,然后错过了参数分隔符的错误排序。

连接时也会发生这种情况。例如:

url = url   '?foo=bar'

但是等等,原来的url可能有一个查询参数。好的,所以这应该是:

url = url   '&foo=bar'

但是等等,如果原来url 没有查询参数那么现在这是错误的。啊。

常见问题 #2:忘记编码

带有参数但未编码的 URL 字符串

啊。model并且locale可能不需要编码,因为它们是 URL 安全值,但我并没有停下来思考text可以是所有类型的文本,包括空格和特殊字符,这会给我们带来问题。

因此,也许我们会矫枉过正,让事情变得更加安全:

const url = `https://builder.io/api/v2/content
  ?model=${
    encodeURIComponent(model)
  }&locale=${
    encodeURIComponent(locale)
  }&query.text=${
    encodeURIComponent(text)
  }`

但事情感觉有点......丑陋。

常见问题 #3:意外的空白字符

带有意外空白字符的 URL 字符串

钱币。为了将这个长 URL 分成多行,我们不小心在 URL 中包含了换行符和额外的空格,这将导致无法按预期进行抓取。

我们现在可以正确地分解字符串,但我们变得更加混乱和难以阅读:

const url = `https://builder.io/api/v2/content`
    `?model=${
    encodeURIComponent(model)
  }&locale=${
    encodeURIComponent(locale)
  }&query.text=${
    encodeURIComponent(text)
  }`

仅仅为了使构建一个 URL 正确就需要很多。下一次我们是否会记住这一切,尤其是在截止日期即将到来且我们需要尽快发布新功能或修复时?

一定有更好的方法。

乔伊的动图,朋友们说“一定有更好的方法!”

URL营救的构造函数

解决上述挑战的更清洁、更安全的解决方案是使用URL 构造函数

const url = new URL('https://builder.io/api/v2/content')

url.searchParams.set('model', model)
url.searchParams.set('locale', locale)
url.searchParams.set('text', text)
  
const res = await fetch(url.toString())

这为我们解决了几件事:

  • 分隔符总是正确的(?对于第一个参数,以及之后的参数)。
  • 所有参数都自动编码。
  • 为长 URL 跨多行时没有额外空白字符的风险。

修改网址

对于我们正在修改 URL 但不知道当前状态的情况,它也非常有用。

例如,而不是有这个问题:

url  = (url.includes('?') ? '&' : '?')   'foo=bar'

我们可以改为:

// Assuming `url` is a URL
url.searchParams.set('foo', 'bar')

// Or if URL is a string
const structuredUrl = new URL(url)
structuredUrl.searchParams.set('foo', 'bar')
url = structuredUrl.toString()

同样,你也可以写URL的其他部分:

const url = new URL('https://builder.io')

url.pathname = '/blog'      // Update the path
url.hash = '#featured'      // Update the hash
url.host = 'www.builder.io' // Update the host

url.toString()              // https://www.builder.io/blog#featured

读取 URL 值

现在,“我只想在没有库的情况下从当前 URL 读取查询参数”这个由来已久的问题得到了解决。

const pageParam = new URL(location.href).searchParams.get('page')

或者例如更新当前 URL:

const url = new URL(location.href)
const currentPage = Number(url.searchParams.get('page'))
url.searchParams.set('page', String(currentPage   1))
location.href = url.toString()

但这不仅限于浏览器。它也可以在 Node.js 中使用

const http = require('node:http');

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `https://${req.headers.host}`)
  // Read path, query, etc...
});

以及 Deno:

import { serve } from "https://deno.land/std/http/mod.ts";
async function reqHandler(req: Request) {
  const url = new URL(req.url)
  // Read path, query, etc...
  return new Response();
}
serve(reqHandler, { port: 8000 });

要知道的 URL 属性

URL 实例支持您已经在浏览器中使用的所有属性,例如 onwindow.location或 anchor 元素,所有这些您都可以读写

const url = new URL('https://builder.io/blog?page=1');

url.protocol // https:
url.host     // builder.io
url.pathname // /blog
url.search   // ?page=1
url.href     // https://builder.io/blog?page=1
url.origin   // https://builder.io
url.searchParams.get('page') // 1

或者,一目了然:

URL 和指向每个段的箭头的图表,例如“主机名”与“哈希”等≠的位置。

URLSearchParams方法要知道

URLSearchParams对象可在URL实例上访问,url.searchParams支持许多方便的方法:

searchParams.has(name)

检查搜索参数是否包含给定名称:

url.searchParams.has('page') // true

searchParams.get(name)

获取给定参数的值:

url.searchParams.get('page') // '1'

searchParams.getAll(name)

获取为参数提供的所有值。如果您允许同名的多个值,这很方便,例如&page=1&page=2

url.searchParams.getAll('page') // ['1']

searchParams.set(name, value)

设置参数的值:

url.searchParams.set('page', '1')

searchParams.append(name, value)

附加一个参数——如果你可能多次支持同一个参数,这很有用,比如&page=1&page=2

url.searchParams.append('page', '2')

searchParams.delete(name)

从 URL 中完全删除一个参数:

url.searchParams.delete('page')

陷阱

要知道的一大陷阱是传递给 URL 构造函数的所有 URL 都必须是绝对的。

例如,这将引发错误:

new URL('/blog') // ERROR!

您可以通过提供原点作为第二个参数来解决这个问题,如下所示:

new URL('/blog', 'https://builder.io')

或者,如果您真的只需要使用 URL 部分,  如果您只需要使用相对 URL 的查询参数,则可以直接使用URLSearchParams :

const params = new URLSearchParams('page=1')
params.set('page=2')
params.toString()

URLSearchParams 还有一个优点,那就是它也可以将键值对的对象作为其输入:

const params = new URLSearchParams({
  page: 1,
  text: 'foobar',
})
params.set('page=2')
params.toString()

浏览器和运行时支持

new URL支持所有现代浏览器,以及 Node.js 和 Deno!(来源

浏览器支持表 - 您可以在上面的“源”链接中找到它。

使用您的组件进行可视化构建

Builder.io是一个无头 CMS,可让您直接 在现有站点拖放 组件。

// Dynamically render your components
export function MyPage({ json }) {
  return 
}

registerComponents([MyHero, MyProducts])

分享

推特
领英
Facebook

现代 JavaScript 中更安全的 URL 读写
标签: