Oct 14, 2011

SOAP Messages with Attachments

A héten futottam bele egy feladatba [problémába :)], amelynél webszolgáltatáson keresztül, nem csupán xml adatstruktúrát, de csatolt állományokat is fel kellett dolgoznom.
Egy report szerverről (java alapú) volt szó, amelynek ha elküldünk egy megfelelően felépített xml-t - mint paramétert - SOAP-on keresztül, akkor az annak megfelelő dokumentumot legenerálja, majd úgyszintén webszolgáltatáson keresztül szolgáltatja vissza. Ki lehet találni, hogy az elküldésnél még minden rendben volt, viszont amikor a válasz üzenet visszaérkezett, akkor jelentkezett a - igazából várt  - probléma.
Tegyük fel, hogy  pdf kiterjesztésben kérem el a dokumentumot. Ilyenkor a válasz üzenet multipart/related tartalom típussal (Content-Type) fog érkezni. Röviden ez azt jelenti (részletesebben itt), hogy bár egy üzenet érkezik válaszként, az több különálló részre lesz osztva. A Content-Type fejlécben megadott boundary értékével lesznek az egyes részek elválasztva, amelyek külön-külön fejlécekkel és egyedi azonosítóval (Content-Id) rendelkeznek. A boundary információ mellett - related esetben - egy start attribútumot is találunk, értéke egy Content-Id-ra való hivatkozás. Azaz a start jelöli ki - esetünkben - a SOAP envelope-ot. Az, hogy az envelope-on belül milyen hivatkozások vannak a csatolt állományokra, minket most nem érdekel, csak a különböző részeket szeretnénk feldolgozni. Amikor éppen nem az envelope-ot dolgozzuk fel, akkor figyelni kell a Content-Transfer-Encoding fejlécet, mert ez lehet bináris, de akár base64 kódolású is. A leírtak nagyrészt (A kód közel véglegesnek tekinthető, de még vár rá néhány teszteset) lefedik a SOAP Messages with Attachments fogalmát.

Nézzük egy (kölcsönzött) példát a kapott válaszüzenetre:
MIME-Version: 1.0
Content-Type: Multipart/Related; boundary=MIME_boundary; type=text/xml;
        start="<http://claiming-it.com/claim061400a.xml>"
Content-Description: This is the optional message description.

--MIME_boundary
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 8bit
Content-ID: <http://claiming-it.com/claim061400a.xml>
Content-Location: http://claiming-it.com/claim061400a.xml


<?xml version='1.0' ?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
...
<theSignedForm href="http://claiming-it.com/claim061400a.tiff"/>
...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

--MIME_boundary
Content-Type: image/tiff
Content-Transfer-Encoding: binary
Content-ID: <http://claiming-it.com/claim061400a.tiff>
Content-Location: http://claiming-it.com/claim061400a.tiff

...binary TIFF image...
--MIME_boundary--


Egy ehhez hasonló üzenetet kellene tehát feldolgoznunk, hogy utána az egyes részek kényelmesen lekérhetőek legyenek. A gond viszont az, hogy a natív PHP SoapClient osztály az ilyen típusú üzeneteket nem támogatja, így másfelé kell nézelődnünk.
A problémára két megoldást találtam. Kibővítem a meglévő SoapClient osztályt, vagy egy kész könyvtárat használok (sem NuSOAP-ban, sem ZendFwSoap-ban nem találtam a feature-t [Ha valaki tud wso2-höz hasonló könyvtárat, kommentben kérem ossza meg]). A kész könyvtár a WSO2 Web Service Framework for PHP lenne, amely nagyon sok funkciót tud, köztük az SwA helyett ajánlott MTOM-ot (ez a kettő lényegében megegyezik, csupán az MTOM előrehaladottabb a csatolások belinkelésével).
A kibővítés mellett döntöttem, egyrészt mert a jövőre tekintve is elegendőnek tűnik a majd lentebb látható megoldás, másrészt a kódegység tartása (natív PHP SOAP osztályokat használok az érintett projektben) végett az összes SOAP-al kapcsolatos kódrészt át kellett volna írni, a könytárnak megfelelőre.
Mostmár rátérhetünk a gyakorlati részre, a kódra. Itt nem szeretnék hosszas magyarázatokba bocsátkozni, a lényeg annyi, hogy a SoapClient osztály __doRequest metódusát fogjuk felülírni. Ez az a metódus amely elküldi a kérést, majd visszatér a válasszal. Nekünk akkor kell beavatkoznunk, ha több részes üzenetet fedezünk fel a válaszban, egyébként az eddigi működést támogatjuk. Lekéréskor az összes részt, vagy csupán egyet is elkérhetünk. Egy résznek van fejléc része, amely kulcs-érték párokat tartalmaz és van tartalma, ezenkívül meg tudja mondani magáról, hogy ő maga-e az envelope.


2 comments:

  1. Kösz! Belefutottam én is ilyenbe, két apró bővítés hozzá:

    * 123 sorban (regexp escapelés + application/xop+xml típus kezelés)

    preg_match('/Content-Type: Multipart/Related;.*type="text/xml";/i', $this->__getLastResponseHeaders()) === 1 &&

    helyett:

    preg_match('/Content-Type: Multipart\/Related;.*type="(text\/xml|application\/xop\+xml)";/i', $this->__getLastResponseHeaders()) === 1 &&

    * 165-ös sorban (nagybetűvel volt nálam az ID ezért elhasalt)

    if ($this->mParts[$n_parts]->header['Content-Id'] === $start[1])

    helyett:

    $header_keys = join(",", array_keys($this->mParts[$n_parts]->header));

    if (preg_match('/.*(Content-id).*/i', $header_keys, $m) && isset($m[1]) && $this->mParts[$n_parts]->header[$m[1]] === $start[1])

    ReplyDelete
  2. Szia.

    A xop-ot nem is értem miért nem tettem bele ct lehetőségként. Köszi a hasznos kiegészítéseket!

    ReplyDelete