مشکلات URL و URLSearchParams

نوشته رو در وبلاگ یاvar بخونید: https://yavarjs.ir/posts/url-urlsearchparams

بررسی تفاوت‌های ریزی که حین کار با URLها باعث به وجود آمدن باگ‌های غیرمنتظره می‌شود.

همه چیز از یک باگ شروع شد

کار با URL‌ها در JavaScript و Node.js باید ساده باشد، اما یک باگ اخیر در پروژه ما، من را به دنیایی از جزئیات پیچیده در API‌های URL و URLSearchParams برد. در این پست، به بررسی این جزئیات و مشکلات احتمالی آن‌ها در کد و نحوه اجتناب از آن‌ها خواهیم پرداخت.

مشکل: مدیریت URL با Axios

ما این مشکل را هنگام تولید URL‌ها و اضافه کردن امضا‌های هش به آن‌ها پیدا کردیم. پارامترهای کوئری به طور یک‌پارچه percent-encode نمی‌شدند که منجر به رفتار غیرمنتظره و امضا‌های هش اشتباه می‌شد.

واضح شد که تعامل بین اشیای URL و URLSearchParams نیاز به دقت بیشتری دارد.

مشکل شماره 1: تفاوت بین URL.search و ()URLSearchParams.toString

اولین شگفتی تفاوت بین URL.search و ()URLSearchParams.toString بود.

هنگام استفاده از searchParams. برای تغییر URL، دقت کنید، زیرا طبق مشخصات WHATWG ، شیء URLSearchParams از قوانین متفاوتی برای تعیین اینکه کدام کاراکترها باید percent-encode شوند استفاده می‌کند. به عنوان مثال، شیء URL کاراکتر تیلد ASCII (~) را percent-encode نمی‌کند، در حالی که URLSearchParams همیشه آن را encode می‌کند.

// Example 1
const url = new URL(&quothttps://example.com?param=foo bar&quot)
console.log(url.search) // prints param=foo%20bar
console.log(url.searchParams.toString()) // prints ?param=foo+bar

// Example 2
const myURL = new URL(&quothttps://example.org/abc?foo=~bar&quot)
console.log(myURL.search) // prints ?foo=~bar
// Modify the URL via searchParams...
myURL.searchParams.sort()
console.log(myURL.search) // prints ?foo=%7Ebar

در پروژه ما، لازم بود به طور صریح ()url.search = url.searchParams.toString را دوباره اختصاص دهیم تا اطمینان حاصل شود که رشته کوئری به طور یکنواخت encode شده است.

مشکل شماره 2: چالش علامت بعلاوه

یکی دیگر از نکات ظریف این است که چگونه URLSearchParams با کاراکترهای + برخورد می‌کند. به طور پیش‌فرض، URLSearchParams کاراکتر + را به عنوان فضای خالی تفسیر می‌کند که ممکن است هنگام encode داده‌های باینری یا رشته‌های Base64 منجر به خرابی داده‌ها شود.

const params = new URLSearchParams(&quotbin=E+AXQB+A&quot)
console.log(params.get(&quotbin&quot)) // &quotE AXQB A&quot

یک راه حل این است که قبل از افزودن مقادیر به URLSearchParams از encodeURIComponent استفاده کنید:

params.append(&quotbin&quot, encodeURIComponent(&quotE+AXQB+A&quot))

جزئیات بیشتر در مستندات MDN موجود است.

مشکل شماره 3: URLSearchParams.get در مقابل ()URLSearchParams.toString

یکی دیگر از جزئیات ظریف زمانی به وجود می‌آید که خروجی‌های URLSearchParams.get و URLSearchParams.toString را مقایسه می‌کنید. به عنوان مثال:

const params = new URLSearchParams(&quot?key=value&key=other&quot)
console.log(params.get(&quotkey&quot)) // &quotvalue&quot (اولین مورد)
console.log(params.toString()) // &quotkey=value&key=other&quot (همه موارد سریالایز شده)

در سناریوهای چند مقداری، get فقط اولین مقدار را برمی‌گرداند، در حالی که toString همه را سریالایز می‌کند.

راه‌حل در کد ما

در پروژه ما، مشکل را با اختصاص صریح search حل کردیم:

url.search = url.searchParams.toString()
url.searchParams.set(
  &quothash&quot,
  cryptography.createSha256HmacBase64UrlSafe(url.href, SECRET_KEY ?? &quot&quot)
)

این اطمینان حاصل کرد که تمام پارامترهای کوئری قبل از اضافه کردن مقدار hash به درستی encode شده بودند.

ماژول querystring در Node.js

رابط کاربری WHATWG URLSearchParams و ماژول querystring هدف مشابهی دارند، اما هدف ماژول querystring عمومی‌تر است، زیرا امکان سفارشی‌سازی کاراکترهای جداکننده (& و =) را فراهم می‌کند. از سوی دیگر، API URLSearchParams به طور خاص برای رشته‌های کوئری URL طراحی شده است.

ماژول querystring از URLSearchParams کارآمدتر است اما یک API استاندارد نیست. از URLSearchParams زمانی استفاده کنید که عملکرد بحرانی نیست یا وقتی سازگاری با کد مرورگر مطلوب است.

هنگام استفاده از URLSearchParams برخلاف ماژول querystring، کلیدهای تکراری به صورت آرایه مجاز نیستند. آرایه‌ها با استفاده از ()array.toString سریالایز می‌شوند که به سادگی همه عناصر آرایه را با کاما جدا می‌کند.

const params = new URLSearchParams({
  user: &quotabc&quot,
  query: [&quotfirst&quot, &quotsecond&quot],
})
console.log(params.getAll(&quotquery&quot))
// Prints [ 'first,second' ]
console.log(params.toString())
// Prints 'user=abc&query=first%2Csecond'

با ماژول querystring، رشته کوئری 'foo=bar&abc=xyz&abc=123' به این صورت پارس می‌شود:

{
  &quotfoo&quot: &quotbar&quot,
  &quotabc&quot: [&quotxyz&quot, &quot123&quot]
}

نکات کلیدی

  1. هنگام استفاده از URLSearchParams به نحوه مدیریت کاراکترهای خاص (مانند ~) و فضاهای خالی توجه کنید. در صورت نیاز از encodeURIComponent استفاده کنید.
  2. تفاوت بین URL.search، URLSearchParams.get و URLSearchParams.toString را برای جلوگیری از رفتار غیرمنتظره درک کنید.
  3. در Node.js از ماژول querystring استفاده کنید اگر می‌خواهید پارامترهای کوئری تکراری را به عنوان یک آرایه پارس کنید.

منبع: Pitfalls of URL and URLSearchParams in JavaScript از وبلاگ Software Alchemist