XML وSOAP

لغة XML (Extensible Markup Language)، التي تعرّفها توصية XML 1.0 الصادرة عن W3C، صيغة نصية تحفظ البيانات في عناصر مسمّاة ومتداخلة مثل <orderId>ORD-1042</orderId>. وSOAP بروتوكول مراسلة مبني عليها: كل طلب وكل استجابة وثيقة XML تُسمّى المغلف (Envelope)، ويصف ملف WSDL عمليات الخدمة وشكل XML الدقيق الذي تقبله كل عملية.

مثال: لمعرفة حالة طلب شراء، يرسل تطبيق العميل (Client) بطريقة POST مغلفاً يحمل جسمه العنصر GetOrderStatus ورقم الطلب، فيتلقى مغلفاً فيه الحالة أو عطل SOAP (SOAP Fault) يوضح ما الخطأ. وبخلاف REST، تُسمّى العملية داخل الرسالة، لا بالطريقة (Method) وعنوان URI.

لماذا يهم

ينتشر SOAP في التكامل بين المؤسسات وفي الأنظمة المؤسسية طويلة العمر، مثل واجهات المصارف والدفع والتأمين والخدمات الحكومية والاتصالات، وكثيراً ما يعمل خلف واجهة حديثة. وقد يوقف عيبٌ فيه عملية أعمال كاملة، مثل تأكيد دفعة.

كما أن أعطال XML وSOAP تختلف عن أعطال JSON عبر REST. فعلامة & واحدة غير مُهرَّبة تجعل الرسالة كلها غير مقروءة، والعنصر في مساحة أسماء خاطئة مجهول وإن بدا اسمه صحيحاً، ويعيد SOAP 1.1 رمز HTTP 500 حتى للمدخلات الخاطئة. ومن يقرأ رموز الحالة وحدها يفوّت عيوباً ويختلق عيوباً غير موجودة.

أساسيات XML: سلامة البنية والصلاحية ومساحات الأسماء

العنصر (Element) وسم بداية ثم محتوى ثم وسم نهاية، مثل <total>249.90</total>، ويمكن كتابة العنصر الخالي من المحتوى بالشكل <item/>. والسمة (Attribute) زوج name="value" داخل وسم البداية، مثل currency="SAR" في الوثيقة أدناه. ولوثيقة XML عنصر جذري (Root) واحد بالضبط، وكل عنصر يُغلق ويتداخل تداخلاً صحيحاً، والأسماء حساسة لحالة الأحرف، فـ <orderId> و<OrderId> عنصران مختلفان. وقيم السمات تُكتب بين علامتي اقتباس، ولا يتكرر اسم السمة في العنصر الواحد، ولا معنى لترتيب السمات.

اتباع هذه القواعد يجعل الوثيقة سليمة البنية (Well-formed)؛ ويعدّ XML 1.0 أي مخالفة خطأً فادحاً (Fatal error)، فيرفض المحلل (Parser) الرسالة كلها بدلاً من إصلاحها كما تفعل المتصفحات مع HTML. والوثيقة الصالحة (Valid) تطابق مخططاً أيضاً (ويعرّف XML 1.0 ذلك لملفات DTD)؛ وتستخدم خدمات الويب XML Schema (XSD) لتحديد العناصر الإلزامية وترتيبها وأنواعها وقيمها المسموحة.

  • تهريب الأحرف الخاصة (Escaping): لا يجوز أن يظهر < و& كما هما في النص؛ اكتب &lt; و&amp; (ويعرّف XML مسبقاً أيضاً &gt; و&quot; و&apos;). ومقطع CDATA، أي <![CDATA[...]]>، يحمل نصاً دون تهريب.
  • ترميز الأحرف (Encoding): يحدده التصريح الاختياري <?xml version="1.0" encoding="UTF-8"?>، والبايتات التي لا تطابقه تشوّه الأسماء العربية أو الهندية أو تؤدي إلى رفض الرسالة.
  • مساحات الأسماء (Namespaces): xmlns:ord="urn:example:orders" يربط بادئة بعنوان URI. ويُعرَّف العنصر بالعنوان مع اسمه المحلي، فالبادئة لا تهم، والاسم الصحيح في مساحة أسماء خاطئة مجهول.
<?xml version="1.0" encoding="UTF-8"?>
<order id="ORD-1042" currency="SAR">
  <customer>Smith &amp; Sons</customer>
  <item sku="A-100" qty="2"/>
  <note><![CDATA[Leave at <b>reception</b>]]></note>
  <total>249.90</total>
</order>

جرّبها: مدقّق XML جرّبها: منسّق XML

مغلف SOAP وربطه بـ HTTP

رسالة SOAP وثيقة XML عنصرها الجذري Envelope. وتحمل الترويسة الاختيارية Header بيانات عن الرسالة مثل معرّف الطلب أو رمز الأمان (Security token)، ويحمل الجسم الإلزامي Body عنصر الطلب أو الاستجابة، أو عطلاً. وكتلة الترويسة الموسومة بـ mustUnderstand التي يعجز المستقبِل عن معالجتها يجب أن تُنتج عطل MustUnderstand.

تُرسَل الطلبات عادةً بطريقة POST إلى عنوان نقطة نهاية (Endpoint) واحد تشترك فيه كل العمليات. ويختلف إصدارا SOAP في مساحة أسماء المغلف وفي الربط بـ HTTP، لذا يفشل الطلب المبني لأحد الإصدارين عند إرساله إلى خدمة لا تدعم إلا الإصدار الآخر.

طلب SOAP 1.1 في طبقتين: ترويسات HTTP تحمل نوع الوسائط وSOAPAction، ومساحة أسماء مغلف XML تحدد الإصدار، مع Header اختيارية وBody إلزامي يسمّي العملية.

سطر طلب HTTP والترويسات

الجزء 1:POST /services/orders HTTP/1.1
Host: api.example.com
الجزء 2:Content-Type: text/xml; charset=utf-8
الجزء 3:SOAPAction: "urn:example:orders/GetOrderStatus"

جسم XML: مغلف SOAP

الجزء 4:<?xml version="1.0" encoding="UTF-8"?>
الجزء 5:<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               الجزء 6:xmlns:ord="urn:example:orders">
  الجزء 7:<soap:Header>
    <ord:RequestId>7f3c-0091</ord:RequestId>
  </soap:Header>
  الجزء 8:<soap:Body>
    الجزء 9:<ord:GetOrderStatus>
      الجزء 10:<ord:orderId>ORD-1042</ord:orderId>
    </ord:GetOrderStatus>
  </soap:Body>
</soap:Envelope>
  1. 1عادةً POST إلى نقطة نهاية واحدةكل العمليات تشترك في هذا العنوان، والعنصر داخل Body هو ما يسمّي العملية
  2. 2نوع الوسائط في SOAP 1.1يستخدم SOAP 1.2 النوع application/soap+xml
  3. 3ترويسة SOAPAction، في SOAP 1.1 فقطيجب أن يرسلها العميل بالقيمة الواردة في ربط WSDL، ويستخدم SOAP 1.2 بدلاً منها المعامل action
  4. 4تصريح XMLاختياري، ويحدد ترميز الأحرف
  5. 5العنصر Envelope في مساحة أسماء SOAPمساحة الأسماء تحدد الإصدار، وغير المدعومة منها تحصل على VersionMismatch
  6. 6مساحة أسماء الخدمةهي targetNamespace في المخطط، ويجب أن تكون عناصر العملية فيها
  7. 7الترويسة Header، اختياريةبيانات عن الرسالة مثل معرّف الطلب؛ والكتلة الموسومة mustUnderstand إما أن تُعالَج وإما أن يُردّ عليها بعطل
  8. 8الجسم Body، إلزاميعنصر الطلب أو الاستجابة، أو Fault
  9. 9عنصر العمليةاسمه ومساحة أسمائه يحددان العملية المطلوبة
  10. 10معامل يُفحص وفق مخطط XSDإلزامي، ويجب أن يطابق ORD- متبوعاً بـ⁦1⁩ إلى ⁦8⁩ أرقام
  • SOAP 1.1 (مذكرة W3C Note صدرت عام ⁦2000⁩): مساحة الأسماء http://schemas.xmlsoap.org/soap/envelope/، وContent-Type: text/xml، وترويسة SOAPAction إلزامية يحدد WSDL قيمتها لكل عملية.
  • SOAP 1.2 (توصية W3C منذ ⁦2003⁩): مساحة الأسماء http://www.w3.org/2003/05/soap-envelope، وContent-Type: application/soap+xml، والإجراء (Action)، إن وُجد، معامل باسم action في هذه الترويسة.
النسخة النصية
POST /services/orders HTTP/1.1
Host: api.example.com
Content-Type: text/xml; charset=utf-8
SOAPAction: "urn:example:orders/GetOrderStatus"

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:ord="urn:example:orders">
  <soap:Header>
    <ord:RequestId>7f3c-0091</ord:RequestId>
  </soap:Header>
  <soap:Body>
    <ord:GetOrderStatus>
      <ord:orderId>ORD-1042</ord:orderId>
    </ord:GetOrderStatus>
  </soap:Body>
</soap:Envelope>

جرّبها: مختبِر طلبات SOAP

WSDL: عقد خدمة SOAP

وثيقة WSDL (Web Services Description Language) هي العقد المقروء آلياً لخدمة SOAP، ويُعتاد نشرها على عنوان نقطة النهاية مع إضافة ?wsdl. وما زال WSDL 1.1 (مذكرة من W3C صدرت عام ⁦2001⁩) واسع الاستخدام؛ ويغيّر WSDL 2.0 (عام ⁦2007⁩) اسم portType إلى interface واسم port إلى endpoint. ولـ WSDL 1.1 خمسة عناصر في المستوى الأعلى، مذكورة أدناه.

ومخطط XSD داخل types هو أدق جزء في أساس الاختبار (Test basis). ففي المخطط الوارد في نهاية هذا القسم، orderId إلزامي (القيمة الافتراضية لـ minOccurs هي ⁦1⁩) ويجب أن يطابق ORD- متبوعاً بـ⁦1⁩ إلى ⁦8⁩ أرقام، لأن أنماط XSD تطابق القيمة كاملة. وincludeItems اختياري لا يقبل إلا true أو false أو 1 أو 0؛ ويفرض xs:sequence الترتيب؛ وtargetNamespace هي مساحة الأسماء التي يجب أن تستخدمها عناصر الطلب.

  • types: تعريفات XML Schema لعناصر الطلب والاستجابة والأعطال.
  • message: البيانات التي ترسلها كل عملية أو تستقبلها.
  • portType: العمليات بمدخلاتها ومخرجاتها وأعطالها المعلنة.
  • binding: الربط، أي إصدار SOAP، ونمط document أو rpc، وقيمة soapAction لكل عملية.
  • service: عنوان المنفذ (Port)، أي عنوان URL الذي ترسل إليه الطلبات.
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="urn:example:orders"
           elementFormDefault="qualified">
  <xs:element name="GetOrderStatus">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="orderId">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:pattern value="ORD-[0-9]{1,8}"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
        <xs:element name="includeItems" type="xs:boolean" minOccurs="0"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

أعطال SOAP: أخطاء ذات بنية محددة

الطلب الذي يتعذر تنفيذه يحصل على عنصر Fault داخل Body. وفي SOAP 1.1 يحتوي على faultcode وfaultstring، وfaultactor اختيارياً، وعنصر detail الذي يجب أن يوجد إذا تعذرت معالجة محتوى Body، ويحمل بيانات خاصة بالتطبيق مثل أعطال الأعمال المعلنة في WSDL. ويسمّيها SOAP 1.2 Code (فيه Value وSubcode اختياري) وReason وNode وRole وDetail، والإلزامي منها Code وReason فقط. تحقّق من رمز العطل أولاً:

  • Client في 1.1 أو Sender في 1.2: الرسالة خاطئة، أي معطوبة البنية أو غير صالحة أو تحمل قيمة خاطئة، وستفشل مجدداً إذا أُعيد إرسالها دون تغيير.
  • Server في 1.1 أو Receiver في 1.2: الرسالة سليمة لكن المعالجة فشلت، كأن تكون قاعدة البيانات متوقفة. وقد تنجح لاحقاً.
  • VersionMismatch: مساحة أسماء مغلف غير مدعومة. MustUnderstand: لم تُفهم كتلة ترويسة إلزامية. ويضيف SOAP 1.2 الرمز DataEncodingUnknown.
  • رمز حالة HTTP: يفرض SOAP 1.1 الرمز 500 لكل عطل، بما فيها أعطال Client؛ ويستخدم SOAP 1.2 الرمز 400 لأعطال Sender و500 لبقيتها. لذا فإن 500 بعد مدخلات خاطئة صحيح في SOAP 1.1؛ ورمز العطل هو الحَكَم.
HTTP/1.1 500 Internal Server Error
Content-Type: text/xml; charset=utf-8

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <faultcode>soap:Client</faultcode>
      <faultstring>orderId must match ORD- followed by 1 to 8 digits</faultstring>
      <detail>
        <ord:ValidationError xmlns:ord="urn:example:orders">
          <ord:field>orderId</ord:field>
          <ord:value>1042</ord:value>
        </ord:ValidationError>
      </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

مثال تطبيقي: اختبار العملية GetOrderStatus

تعمل خدمة الطلبات بالإصدار SOAP 1.1 على بيئة اختبار يملكها الفريق. ويستخرج المختبِر الفحوص من WSDL ومخططه، لكل فحص نتيجة متوقعة، ويتأكد من سلامة بنية كل مغلف يكتبه يدوياً قبل إرساله (أداة «مدقّق XML» تفحص سلامة البنية، لا المطابقة لمخطط XSD).

ويتحقق المختبِر من الاستجابات بلغة XPath، وهي لغة استعلام لوثائق XML: فالتعبير //ord:status يختار كل عنصر status في مساحة الأسماء التي تربطها أداة الاختبار بالبادئة ord، أياً كانت البادئة التي تستخدمها الاستجابة نفسها. ولا يقارن المختبِر الاستجابة نصاً بنص أبداً.

  • طلب صالح للرقم ORD-1042: رمز HTTP 200، وعنصر GetOrderStatusResponse في مساحة أسماء الخدمة صالح وفق XSD.
  • طلب شراء غير موجود ORD-99999999: العطل الذي يعلنه WSDL، مثل OrderNotFound، لا 200 بجسم فارغ.
  • رسالة معطوبة البنية، مثل </ord:orderID> لإغلاق <ord:orderId>: عطل Client، ولا صفحة HTML أبداً. ولا يحتوي أي faultstring على تتبّع مكدس (Stack trace) أو أسماء أصناف (Classes) أو SQL أو مسارات ملفات.
  • رسالة غير صالحة: orderId محذوف، أو 1042 بلا البادئة، أو ORD- دون أي رقم أو متبوعاً بتسعة أرقام (خارج الحدين ⁦1⁩ و⁦8⁩ مباشرة)، أو includeItems بالقيمة yes، أو تبديل ترتيب العنصرين: عطل Client يسمّي الحقل. وقبول أيٍّ منها يعني أن الخدمة لا تتحقق من مدخلاتها؛ فاسأل هل هذا مقصود.
  • المغلف: مغلف بمساحة أسماء SOAP 1.2، مرسَل مع text/xml، يحصل على VersionMismatch؛ وقيمة SOAPAction التي لا تطابق الجسم لا تؤدي أبداً إلى تنفيذ عملية أخرى.
  • سطر <!DOCTYPE> قبل المغلف ينبغي أن يُرفض بعطل: فإصدارا SOAP كلاهما يمنعان تصريح نوع الوثيقة (Document type declaration) في الرسالة، والمحلل الذي يقبله قد يكون معرَّضاً لهجمات الكيانات الخارجية في XML (XXE). لا تُجرِ مثل هذه الفحوص إلا على أنظمة لديك إذن باختبارها.

جرّبها: مختبِر طلبات SOAP جرّبها: مدقّق XML

أخطاء شائعة

  • الحكم على استجابة SOAP من رمز حالة HTTP: ففي SOAP 1.1 قد يكون 500 هو الرد الصحيح على مدخلات خاطئة، وقد يحمل 200 خطأً أُعيد بيانات عادية.
  • مقارنة XML نصاً بنص أو التحقق من البادئات مثل ns2:؛ فالرسائل المتكافئة قد تختلف في ترتيب السمات والمسافات البادئة والبادئات.
  • إرسال XML مكتوب يدوياً دون التأكد من سلامة بنيته، ثم الإبلاغ عن عطل Client الصحيح على أنه عيب.
  • اختبار المغلفات الصالحة فقط بدلاً من مخالفة كل عنصر إلزامي وكل نوع وكل نمط (Pattern) وكل قائمة قيم مسموحة (Enumeration)، وترتيب العناصر.
  • افتراض أن الخادم يتحقق من الطلبات وفق XSD. فهذا خيار في الإعدادات: أرسل رسالة غير صالحة وانظر.

من منظور ضمان الجودة

في مشروع SOAP ابدأ من WSDL كما تبدأ من وثيقة OpenAPI: اكتب قائمة العمليات وأعطالها المعلنة، واقرأ XSD لمعرفة العناصر الإلزامية والأنواع والحدود. واتفق مبكراً على إصدار SOAP المستخدم، وهل تُفحص الطلبات وفق المخطط، وأي أخطاء الأعمال تعود أعطالاً لا استجابات عادية.

احفظ مغلفات الطلبات بيانات اختبار ذات إصدارات، مغلفاً لكل فحص. وفي تقرير الخطأ اذكر نقطة النهاية وإصدار SOAP وقيمة SOAPAction ومغلفَي الطلب والاستجابة كاملين ورمز حالة HTTP: ففي XML كثيراً ما يكون حرف واحد خاطئ أو مساحة أسماء خاطئة هو العيب كله.

تمرين عملي

راجع خمس استجابات من خدمة SOAP 1.1

المطلوب

أرسل زميلك خمسة طلبات إلى العملية CreateCustomer في خدمة SOAP 1.1 على بيئة الاختبار، ودوّن ما أُرسل (sent) وما عاد (got). قرّر لكل محاولة (Run) هل السلوك صحيح أم عيب، ولماذا.

المادة
Operation: CreateCustomer   SOAP 1.1   test environment
Schema:    name   xs:string, maxLength 50, required
           email  xs:string, required
WSDL:      declared fault DuplicateEmail

Run 1  sent: <cus:name>Smith &amp; Sons</cus:name>, valid email
       got:  200, <cus:name>Smith &amp; Sons</cus:name> in the response
Run 2  sent: <cus:Email>sara@example.com</cus:Email>  (no <cus:email>)
       got:  200, customer C-381 created with no email
Run 3  sent: valid request, email already registered
       got:  500, faultcode soap:Client, detail <cus:DuplicateEmail/>
Run 4  sent: valid request while the database is stopped
       got:  500, faultcode soap:Client, faultstring "Invalid request"
Run 5  sent: envelope without </soap:Body>
       got:  500, Content-Type text/html, page shows a Java stack trace
الخطوات
  1. اكتب لكل محاولة رمز HTTP ورمز العطل والجسم المتوقعة من المخطط وWSDL وقواعد SOAP 1.1.
  2. صنّف كل محاولة: صحيحة أو عيب، واكتب لكل عيب عنوان تقرير خطأ من سطر واحد.
إظهار الإجابة النموذجية
الإجابة النموذجية

المحاولة ⁦1⁩: صحيحة. المتوقع 200 مع إنشاء العميل: & مهرَّبة إلى &amp; في XML وعادت دون تغيير، فالاسم المخزَّن هو Smith & Sons.

المحاولة ⁦2⁩: عيب. أسماء XML حساسة لحالة الأحرف، فـ <cus:Email> عنصر مجهول والعنصر الإلزامي email مفقود؛ وكان الواجب 500 مع عطل Client يسمّي email، فالخدمة لا تتحقق من الطلبات وفق مخططها. العنوان: «CreateCustomer ينشئ عميلاً بلا البريد الإلزامي».

المحاولة ⁦3⁩: صحيحة. يعيد SOAP 1.1 الرمز 500 لكل عطل، وClient مع التفاصيل DuplicateEmail المعلنة يخبر المستدعي بما يجب إصلاحه.

المحاولة ⁦4⁩: عيب. الطلب صالح، فالمتوقع 500 مع عطل Server؛ وعطل Client يطلب من المستدعين تغيير رسالة صحيحة ويوقف إعادة محاولاتهم. العنوان: «CreateCustomer يعيد عطل Client عند توقف قاعدة البيانات».

المحاولة ⁦5⁩: عيب. XML المعطوب يجب أن يحصل على 500 وعطل Client داخل مغلف SOAP يُرسَل بالنوع text/xml؛ وصفحة HTML تعطّل عملاء SOAP، وتتبّع المكدس يكشف تفاصيل داخلية. العنوان: «CreateCustomer يردّ على XML المعطوب بصفحة HTML فيها تتبّع مكدس».

أسئلة شائعة

هل يستحق SOAP التعلّم إذا كنت أعرف REST؟

نعم، إذا كنت ستختبر تكاملاً بين مؤسسات أو أنظمة مؤسسية طويلة العمر، حيث ما زال SOAP شائعاً. ومعظم ما تعلّمته في REST ينتقل معك، كالعقود والاختبارات السلبية وتقارير الأخطاء الدقيقة، ويضيف SOAP صرامة XML وعقد WSDL رسمياً وأعطالاً ذات بنية محددة.

ما الفرق بين XML سليم البنية وXML صالح؟

سليم البنية يعني أن الوثيقة تتبع قواعد صياغة XML: جذر واحد، وعناصر مغلقة ومتداخلة تداخلاً صحيحاً، وسمات بين علامتي اقتباس، وأحرف خاصة مهرَّبة. وصالح يعني أنها سليمة البنية وتطابق مخططاً أيضاً، مثل XSD في ملف WSDL. ويجب على المحلل رفض الوثيقة غير سليمة البنية، وقد تكون الوثيقة سليمة البنية وغير صالحة في آن واحد.

هل أستطيع اختبار خدمة SOAP بأداة REST؟

نعم. طلب SOAP هو طلب HTTP بطريقة POST وجسمه XML، فأي أداة ترسل جسماً خاماً مع ترويسات مخصصة تفي بالغرض. عليك أن تضبط Content-Type بنفسك، وSOAPAction في SOAP 1.1. وتضيف أدوات SOAP المتخصصة تسهيلات مثل بناء الطلبات من WSDL.

كيف أعرف هل تستخدم الخدمة SOAP 1.1 أم 1.2؟

انظر إلى الربط (Binding) في WSDL: ربط SOAP 1.1 يستخدم مساحة الأسماء http://schemas.xmlsoap.org/wsdl/soap/، وربط SOAP 1.2 يستخدم http://schemas.xmlsoap.org/wsdl/soap12/. وقد تقدّم الخدمة الإصدارين بمنفذ (Port) لكل منهما. وفي طلب يعمل، تخبرك بذلك أيضاً مساحة أسماء المغلف وقيمة Content-Type (text/xml أو application/soap+xml).

المراجع

تدرّب بأدوات متقن

أرسل مغلّفات SOAP 1.1 / 1.2 مع ترويسات SOAPAction واعرض استجابة XML منسّقة.

اختبار واجهات API

التحقق من سلامة بنية XML والإبلاغ عن الأخطاء مع رقم السطر والعمود.

التنسيق

تنسيق XML بشكل أنيق مع مسافة بادئة قابلة للتخصيص، وإعادة تصغيره عند الحاجة.

التنسيق

تحويل XML إلى JSON مع الحفاظ على السمات والعقد النصية وتحويل العناصر المتكررة إلى مصفوفات.

JSON والبيانات

للتعمّق أكثر

تقدّمك في هذا الموضوع

جزء من مسار تعلم ضمان الجودة