تصحيح الأخطاءقراءة 6 دقائق

كيفية إصلاح خطأ ERR_INVALID_URL في Node.js

يظهر خطأ ERR_INVALID_URL في Node.js عندما يستقبل مُنشئ URL أو الدالة url.parse() سلسلة نصية ليست عنوان URL صالحًا. ومن أكثر الأسباب شيوعًا غياب البروتوكول، ووجود رموز خاصة غير مُرمَّزة، وترميز percent-encoding غير سليم. ويمكن إصلاحه عبر التحقق من صحة المُدخلات وترميز الرموز الخاصة قبل التحليل.

ما الذي يُسبِّب خطأ ERR_INVALID_URL؟

يُطلَق خطأ ERR_INVALID_URL (بصيغته الرسمية TypeError [ERR_INVALID_URL]: Invalid URL) من قِبَل Node.js عندما يستقبل مُنشئ URL أو الدالة القديمة url.parse() سلسلة نصية لا يمكن تحليلها كعنوان URL صالح. وهذا الخطأ شائع عند معالجة مُدخلات المستخدم، أو تحليل ملفات الإعدادات، أو التعامل مع عناوين URL قادمة من مصادر خارجية.

يتّبع مُنشئ URL معيار WHATWG URL، وهو أكثر صرامة من التحليل الأقدم المبني على معايير RFC. فلكي تُقبَل السلسلة النصية كعنوان URL صالح، يجب أن تتضمّن مخططًا صالحًا (مثل https:)، وسلطة صالحة (اسم مضيف)، ومكوّنات مسار واستعلام مُنسَّقة بشكل سليم.

// هذا يُطلق خطأ ERR_INVALID_URL
try {
  const url = new URL('not-a-url');
} catch (err) {
  console.log(err.code);    // 'ERR_INVALID_URL'
  console.log(err.message); // 'Invalid URL: not-a-url'
  console.log(err.input);   // 'not-a-url'
}

الأسباب الشائعة وطرق إصلاحها

السبب الأول: غياب البروتوكول/المخطط. يتطلّب مُنشئ URL وجود مخطط مثل https:// أو http://. فالسلاسل النصية مثل example.com/path أو www.example.com ستفشل.

// يفشل: لا يوجد بروتوكول
new URL('example.com/path');  // ERR_INVALID_URL

// الإصلاح: أضف البروتوكول
new URL('https://example.com/path');  // يعمل!

// الإصلاح: أضف البروتوكول إن كان مفقودًا
function ensureProtocol(urlString) {
  if (!/^https?:\/\//i.test(urlString)) {
    return 'https://' + urlString;
  }
  return urlString;
}
new URL(ensureProtocol('example.com/path'));  // يعمل!

السبب الثاني: رموز خاصة غير مُرمَّزة. لا يُسمح باستخدام رموز مثل المسافات، والأقواس المعقوفة، والشرطات العمودية، وبعض رموز Unicode داخل عناوين URL دون ترميزها.

// يفشل: مسافات ورموز خاصة غير مُرمَّزة
new URL('https://example.com/my file.pdf');    // ERR_INVALID_URL
new URL('https://example.com/path?q=a b');     // قد يفشل في بعض الإصدارات

// الإصلاح: رمِّز الأجزاء التي تُسبِّب المشكلة
const filename = encodeURIComponent('my file.pdf');
new URL('https://example.com/' + filename);    // يعمل!

// الإصلاح: استخدم encodeURI لعنوان URL كامل يحتوي على مسافات
const rawUrl = 'https://example.com/my file.pdf';
new URL(encodeURI(rawUrl));  // يعمل!

السبب الثالث: ترميز percent-encoding غير سليم. إذا احتوى عنوان URL على علامة نسبة مئوية لا يتبعها رقمان ستّ عشريان بالضبط، فسيرفضه المُحلِّل.

// يفشل: ترميز percent-encoding غير سليم
new URL('https://example.com/100%done');       // ERR_INVALID_URL
new URL('https://example.com/path?q=50%');     // ERR_INVALID_URL

// الإصلاح: رمِّز علامة النسبة المئوية المنفردة إلى %25
function fixPercentSigns(urlString) {
  return urlString.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');
}
new URL(fixPercentSigns('https://example.com/100%done'));
// يعمل! يصبح عنوان URL هو https://example.com/100%25done

السبب الرابع: مُدخلات فارغة أو null. كما أنّ تمرير سلسلة نصية فارغة أو null أو undefined إلى مُنشئ URL يُطلق هذا الخطأ أيضًا.

// يفشل: مُدخلات فارغة أو null
new URL('');           // ERR_INVALID_URL
new URL(null);         // ERR_INVALID_URL
new URL(undefined);    // ERR_INVALID_URL

// الإصلاح: تحقّق من صحة المُدخلات قبل التحليل
function parseUrl(input) {
  if (!input || typeof input !== 'string') {
    return null;
  }
  try {
    return new URL(input);
  } catch {
    return null;
  }
}

السبب الخامس: عناوين URL نسبية دون عنوان أساسي. يعامل مُنشئ URL الوسيط الأول على أنه عنوان URL مطلق افتراضيًا. أما عناوين URL النسبية مثل /path/to/page فتتطلّب عنوان URL أساسيًا كوسيط ثانٍ.

// يفشل: عنوان URL نسبي دون عنوان أساسي
new URL('/api/users');  // ERR_INVALID_URL

// الإصلاح: وفِّر عنوان URL أساسيًا
new URL('/api/users', 'https://example.com');
// يعمل! ← https://example.com/api/users

// نمط مفيد لعملاء API
const BASE_URL = 'https://api.example.com';
const endpoint = new URL('/v2/users?page=1', BASE_URL);
console.log(endpoint.href);
// "https://api.example.com/v2/users?page=1"

كيفية التحقق من صحة عناوين URL قبل التحليل

إنّ أكثر الطرق موثوقية للتحقق من صحة عنوان URL في Node.js هي استخدام مُنشئ URL داخل كتلة try-catch. فلا توجد دالة مدمجة باسم URL.isValid() (اعتبارًا من Node.js 22)، ولذلك يُعدّ التقاط الخطأ هو النهج المعتمد.

// أداة تحقق بسيطة من عناوين URL
function isValidUrl(string) {
  try {
    new URL(string);
    return true;
  } catch {
    return false;
  }
}

console.log(isValidUrl('https://example.com'));  // true
console.log(isValidUrl('not-a-url'));            // false
console.log(isValidUrl(''));                     // false

// أداة تحقق مع بروتوكولات مسموح بها
function isValidHttpUrl(string) {
  try {
    const url = new URL(string);
    return url.protocol === 'http:' || url.protocol === 'https:';
  } catch {
    return false;
  }
}

console.log(isValidHttpUrl('https://example.com'));     // true
console.log(isValidHttpUrl('ftp://example.com'));       // false
console.log(isValidHttpUrl('javascript:alert(1)'));     // false

نمط آمن لتحليل عناوين URL

إليك نمطًا متينًا لتحليل عناوين URL يتعامل مع جميع حالات الخطأ الشائعة، ويُرجع كائن URL مُحلَّلًا أو رسالة خطأ ذات معنى.

class UrlParser {
  static parse(input, base) {
    // التحقق من صحة المُدخلات
    if (!input || typeof input !== 'string') {
      return { ok: false, error: 'Input must be a non-empty string' };
    }

    // إزالة المسافات الزائدة
    const trimmed = input.trim();

    // إصلاح المشكلات الشائعة
    let urlString = trimmed;

    // أضف البروتوكول إن كان مفقودًا
    if (/^[a-zA-Z0-9]/.test(urlString) && !urlString.includes('://')) {
      urlString = 'https://' + urlString;
    }

    // إصلاح علامات النسبة المئوية المنفردة
    urlString = urlString.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');

    try {
      const url = base ? new URL(urlString, base) : new URL(urlString);

      // اختياري: التقييد على البروتوكولات الآمنة
      if (!['http:', 'https:'].includes(url.protocol)) {
        return { ok: false, error: 'Unsupported protocol: ' + url.protocol };
      }

      return { ok: true, url };
    } catch (err) {
      return { ok: false, error: err.message };
    }
  }
}

// الاستخدام
const result = UrlParser.parse('example.com/path?q=hello world');
if (result.ok) {
  console.log(result.url.href);
} else {
  console.error(result.error);
}

أفضل الممارسات للوقاية

سيساعدك اتّباع هذه الممارسات على تجنُّب أخطاء ERR_INVALID_URL في تطبيقات Node.js لديك، وبناء معالجة أكثر متانة لعناوين URL.

  • غلِّف تحليل عناوين URL دائمًا داخل try-catch. لا تفترض أبدًا أنّ سلسلة عنوان URL صالحة، خصوصًا عندما تأتي من مُدخلات المستخدم، أو متغيرات البيئة، أو قواعد البيانات، أو واجهات API الخارجية.
  • استخدم واجهة URL بدلًا من دمج السلاسل النصية. ابنِ عناوين URL باستخدام مُنشئ URL وURLSearchParams بدلًا من دمج السلاسل النصية. فالواجهة تتولّى الترميز تلقائيًا.
  • تحقّق من صحة متغيرات البيئة عند بدء التشغيل. إذا كان تطبيقك يقرأ عناوين URL من متغيرات البيئة أو ملفات الإعدادات، فتحقّق من صحتها عند بدء تشغيل التطبيق، لا عند استخدامها لأول مرة.
  • رمِّز مُدخلات المستخدم قبل تضمينها في عناوين URL. استخدم دائمًا encodeURIComponent() لقيم معاملات الاستعلام، وencodeURI() لعناوين URL الكاملة التي يوفّرها المستخدمون.
  • استخدم أداة بناء عناوين URL للحالات المعقّدة. لبناء عناوين URL ذات أجزاء ديناميكية كثيرة، أنشئ دالة مساعدة أو صنفًا يتولّى الترميز بشكل متّسق.
  • سجِّل المُدخل الأصلي عند حدوث الأخطاء. عندما تلتقط خطأ ERR_INVALID_URL، سجِّل المُدخل الذي تسبّب فيه (مع تعقيمه إن كان قد يحتوي على بيانات حسّاسة) للمساعدة في تصحيح المشكلة.
// ممارسة جيدة: تحقّق من صحة عناوين URL في الإعدادات عند بدء التشغيل
const requiredUrls = ['API_BASE_URL', 'AUTH_SERVER_URL', 'WEBHOOK_URL'];

for (const envVar of requiredUrls) {
  const value = process.env[envVar];
  if (!value) {
    throw new Error(envVar + ' environment variable is required');
  }
  try {
    new URL(value);
  } catch {
    throw new Error(envVar + ' is not a valid URL: ' + value);
  }
}

// ممارسة جيدة: استخدم واجهة URL لبناء عناوين URL
function buildApiUrl(endpoint, params) {
  const url = new URL(endpoint, process.env.API_BASE_URL);
  for (const [key, value] of Object.entries(params)) {
    url.searchParams.set(key, String(value));
  }
  return url.toString();
}

const searchUrl = buildApiUrl('/api/search', {
  q: 'Node.js & Express',
  page: 1
});
// "https://api.example.com/api/search?q=Node.js+%26+Express&page=1"

مقالات ذات صلة

جرّب أدواتنا المجانية