Question
· Oct 14, 2021

How could we use MTOM to send a zip with a csv inside?

First of all thanks for your time reading this question, and thanks for your help

 

We have the following use case: we would need to send with MTOM a zip which contains a csv

 

We have read:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

 

We would need to replicate how SOAPUI does send a zip with a csv as an attachment in a SOAP request, using Ensemble

Being the raw tags as follows:

 

Could you help us, or point us, to the needed documentation, code examples, or Ensemble projects, to study, understand, and develop, this behaviour? 💭

Are there some code examples, or documentantion that could help us? 💭

 

Thanks for your time, replies and help

$ZV: Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2017.2.1 (Build 801_3_18358U) Tue Jul 24 2018 16:36:10 EDT
Discussion (2)1
Log in or sign up to continue

We have been developing the Web Service following the official documentation:

Using MTOM for Attachments. Webclient

Specifically we have followed an example of a web service client where MTOMRequired property has been activated, and we have also changed the data types to %Stream.

We have written the following WebService:

Class WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP Extends %SOAP.WebClient [ ProcedureBlock ]
{

// Parameter LOCATION = "https://regvacube.sns.gob.es/regvacu/ws/FicheroVacuService";

/// This is the URL used to access the web service.
/// This is the namespace used by the Service
Parameter NAMESPACE = "http://ws.regvacuWs.ms.es/regvacu/ws/FicheroVacuService";

///  20/09/21 Cambiamos a 0, con el objetivo de quitar el xsi:type
Parameter OUTPUTTYPEATTRIBUTE = 0;

/// Determines handling of Security header.
Parameter SECURITYIN = "ALLOW";

/// This is the name of the Service
Parameter SERVICENAME = "FicheroVacuService";

// Parameter SOAPVERSION = 1.2;

Parameter SOAPVERSION = 1.1;

/// This is the SOAP version supported by the service.
Parameter MTOMREQUIRED = 1;

// Method cargarFichero(fichero As %xsd.base64Binary(REQUIRED=1), ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]

// Method cargarFichero(fichero As %GlobalBinaryStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]

/// 15 10 21 Edu explica que el fichero, el .zip con un .csv necesitamos enviarlo SIN CODIFICAR
/// quitamos %GlobalBinaryStream y ponemos %GlobalCharacterStream
///
Method cargarFichero(fichero As %GlobalCharacterStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
  //Header - Addresing
 set addressing = ..crearAddressing()
 
 set addressing.Action = "cargarFichero"
 
 set ..AddressingOut                = addressing
 set ..AddressingOut.mustUnderstand = "1"

 //Firma el XML (mensaje SOAP)
 //do ..crearSignature()
 
 set ..MTOMRequired=1
 
 //24 09 21 para añadir parametro name en cabecera content type
 //set ..ContentType="application/octet-stream; name=nombre"
 
 //28 09 21 probamos a ajustarlo
 //set ..ContentType="application/octet-stream; charset=latin1"
 set ..ContentType="application/xhtml+xml; charset=latin1"
 
 /*
     27 09 21 con el objetivo de poner parametro name en cabecera content type
     Genera excepcion:      ERROR #5001: <INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1
 */
 //set ..HttpRequest.ContentType="application/octet-stream; name=nombre"
 //do ..HttpRequest.SetHeader("name","nombre")
 
 Quit ..WebMethod("cargarFichero","CargarFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero",.fichero,.ccaaId,.tipoFichero)
}

Method infoFichero(ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), infoFichero As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(REQUIRED=1), Output estado As EsquemasDatos.HistoriaClinica.tns.EstadoFicheroType(REQUIRED=1)) As %xsd.base64Binary(XMLNAME="fichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
 set ..MTOMRequired=1    
 Quit ..WebMethod("infoFichero","InfoFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/infoFichero",.ccaaId,.infoFichero,.estado)
}

Method crearAddressing() As %SOAP.Addressing.Properties
{
    set IPRedSanitaria = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","IPRedSanitaria")
     set puertoRespuestas = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","PuertoRespuestasSSL")
     set ReplyTo = ##class(%SOAP.Addressing.EndpointReference).%New()
     set ReplyTo.Address = "http://www.w3.org/2005/08/addressing/anonymous"
     //set ReplyTo.Address = "https://"_IPRedSanitaria_":"_puertoRespuestas_"/csp/SNS/Servicios.ProgramasAsistenciales.SIFCOv02r00.cls"
     set MessageId = ##class(Util.FuncionesComunes).getUID()
     
     set addressing = ##class(%SOAP.Addressing.Properties).%New()
     set addressing.MessageId = MessageId
     set addressing.Destination = ..Location
     set addressing.ReplyEndpoint = ReplyTo    
          
     Quit addressing
}

Method crearSignature() As %XML.Security.Signature
{
       //Generamos el Binary Security Token a partir del mcertificado
     set x509alias = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","aliasCertMSSSI")
    set pwd = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","pwdCertMSSSI")
    set cred = ##class(%SYS.X509Credentials).GetByAlias(x509alias,pwd)
    set token = ##class(%SOAP.Security.BinarySecurityToken).CreateX509Token(cred)
    
    //Creamos la firma
    //set sig1=##class(%XML.Security.Signature).CreateX509(token,,$$$KeyInfoX509IssuerSerial)
    //set sig2=##class(%XML.Security.Signature).CreateX509(token,$$$SOAPWSIncludeSoapBody,$$$SOAPWSReferenceDirect)
    set sig2=##class(%XML.Security.Signature).CreateX509(token,$$$SOAPWSIncludeDefault,$$$SOAPWSReferenceDirect)
    
    //do sig1.SetSignatureMethod($$$SOAPWSrsasha1)
    do sig2.SetSignatureMethod($$$SOAPWSrsasha1)
    //do sig1.SetDigestMethod($$$SOAPWSsha1)
    do sig2.SetDigestMethod($$$SOAPWSsha1)
    
    //Creamos la referencia al id del token generado a partir de la firma
    //set algorithm=$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSc14n
    set reference=##class(%XML.Security.Reference).Create(token.Id)
    do sig2.AddReference(reference)
    
    //Crear TimeStamp
    Set timestamp=##class(%SOAP.Security.Timestamp).Create()
    
    //Se une
    //do ..SecurityOut.AddElement(sig1)
    do ..SecurityOut.AddToken(token)
    do ..SecurityOut.AddElement(sig2)    
    Do ..SecurityOut.AddToken(timestamp)
}

}

The target system, asks us to implement a WebService with MTOM to send the zip with a csv inside, as follows:

When importing the WSDL of the target system, the header of the "cargarFichero" (loadFile) method was generated with "fichero" (file) as a "%xsd.base64Binary".

We changed file to "%GlobalCharacterStream".

This way the file, the csv, is sent unencoded, inside a CDATA

Being the response of the target system:

10/15/2021 08:57:24 *********************
Input to Web client with SOAP action = http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero
--MIME_Boundary
Content-ID: <root.message@cxf.apache.org>
Content-Type: application/xop+xml; type="text/xml"; charset=utf-8
Content-Transfer-Encoding: 8bit

        <?xml version="1.0" encoding="UTF-8"?>
        <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
            <soap:Body>
                <soap:Fault>
                    <faultcode>soap:Server</faultcode>
                    <faultstring>Name must not be null</faultstring>
                </soap:Fault>
            </soap:Body>
        </soap:Envelope>

--MIME_Boundary--

---------------
Validate Security header: action=http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero, MethodName=cargarFichero
**** SOAP client return error. method=cargarFichero, action=http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero
     ERROR #6248: La respuesta de SOAP es un error de SOAP: faultcode=Server
faultstring=Name must not be null
faultactor=
detail=

However, as we can see in the first image, the target system would need, would require, that we send the file encoded in binary, since it says:

Content-Transfer-Encoding: binary

When we adapt the file to be "%GlobalBinaryStream" we see the following trace, where it is encoded in binary:

When sending binary encoded, the target system also responds:

09/28/2021 16:56:43 *********************
Input to Web client with SOAP action = http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero
--MIME_Boundary
Content-ID: <root.message@cxf.apache.org>
Content-Type: application/xop+xml; type="text/xml"; charset=utf-8
Content-Transfer-Encoding: 8bit

            <?xml version="1.0" encoding="UTF-8"?>
            <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
                <soap:Body>
                    <soap:Fault>
                        <faultcode>soap:Server</faultcode>
                        <faultstring>Name must not be null</faultstring>
                    </soap:Fault>
                </soap:Body>
            </soap:Envelope>

--MIME_Boundary--

---------------
Validate Security header: action=http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero, MethodName=cargarFichero
**** SOAP client return error. method=cargarFichero, action=http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero
     ERROR #6248: La respuesta de SOAP es un error de SOAP: faultcode=Server
faultstring=Name must not be null
faultactor=
detail=

In short, the target system needs from us:

"add the "name" parameter inside the "Content-Type" header when attaching the file. Your client should generate this parameter to avoid getting this error."

Being the full comparison between what is sent by ensemble (without the "name" parameter, therefore incorrect) on the left; and what is generated by the SoapUI (with the name parameter, correct), on the right:

➡️ Please could you point us to examples, documentation, projects, code, that we can use as a reference to investigate, research and complete the development? 💭

We have also investigated the following answers:

https://community.intersystems.com/post/add-parameter-name-inside-conten...

Thank you very much for your time, reading and responding.

Thanks for your help.

We may need to add the Content-Disposition header in the request and there specify the file name.

In the example we had originally made with SoapUI, that header had this value:

Content-Disposition: attachment; name="application.zip"

Fine Tuning a WebClient tells you how you can specify headers in your web client using the SetHttpHeader() method that inherits from %SOAP.WebClient.

Following the explanation in the documentation, we have added:

do ..SetHttpHeader("Content-Disposition","application.zip")

We have written the line before:

Quit ..WebMethod("cargarFichero","CargarFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero",.fichero,.ccaaId,.tipoFichero)

When capturing the LOGSOAP we notice that it is left without including the Content-Disposition header and the name application.zip in what we send:

Why could it be that when writing the line, it is left without including the new Content-Disposition header and the application.zip file name? 💭🤔
 

Also, in other tests, we have written 3 additional ways, to try to solve it:

set ..ContentType="application/octet-stream; name=nombre"

Using the above line, we don't see that it adds the "name" parameter.

We tried the following line and it would generate an Ensemble exception:

set ..HttpRequest.ContentType="application/octet-stream; name=nombre"

ERROR #5001: <INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1

and using the next line:

do ..HttpRequest.SetHeader("name","nombre")

It gives us exception as well:

<INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1

The complete class we have written of the Web Service is:

Class WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP Extends %SOAP.WebClient [ ProcedureBlock ]
{

// Parameter LOCATION = "https://regvacube.sns.gob.es/regvacu/ws/FicheroVacuService";

/// This is the URL used to access the web service.
/// This is the namespace used by the Service
Parameter NAMESPACE = "http://ws.regvacuWs.ms.es/regvacu/ws/FicheroVacuService";

///  20/09/21 Cambiamos a 0, con el objetivo de quitar el xsi:type
Parameter OUTPUTTYPEATTRIBUTE = 0;

/// Determines handling of Security header.
Parameter SECURITYIN = "ALLOW";

/// This is the name of the Service
Parameter SERVICENAME = "FicheroVacuService";

// Parameter SOAPVERSION = 1.2;

Parameter SOAPVERSION = 1.1;

/// This is the SOAP version supported by the service.
Parameter MTOMREQUIRED = 1;

// Method cargarFichero(fichero As %xsd.base64Binary(REQUIRED=1), ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]

// Method cargarFichero(fichero As %GlobalBinaryStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]

/// 15 10 21 Edu explica que el fichero, el .zip con un .csv necesitamos enviarlo SIN CODIFICAR
/// quitamos %GlobalBinaryStream y ponemos %GlobalCharacterStream
///
Method cargarFichero(fichero As %GlobalCharacterStream, ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), tipoFichero As EsquemasDatos.HistoriaClinica.tns.TipoFicheroType(REQUIRED=1)) As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(XMLNAME="responseFichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
  //Header - Addresing
 set addressing = ..crearAddressing()
 
 set addressing.Action = "cargarFichero"
 
 set ..AddressingOut                = addressing
 set ..AddressingOut.mustUnderstand = "1"

 //Firma el XML (mensaje SOAP)
 //do ..crearSignature()
 
 set ..MTOMRequired=1
 
 //24 09 21 para añadir parametro name en cabecera content type
 //set ..ContentType="application/octet-stream; name=nombre"
 
 //28 09 21 probamos a ajustarlo
 //set ..ContentType="application/octet-stream; charset=latin1"
 //set ..ContentType="application/xhtml+xml; charset=latin1"
 
 /*
     27 09 21 con el objetivo de poner parametro name en cabecera content type
     Genera excepcion:      ERROR #5001: <INVALID OREF>zcargarFichero+16^WSCLIENTE.HistoriaClinica.FicheroVacuServiceSOAP.1
 */
 //set ..HttpRequest.ContentType="application/octet-stream; name=nombre"
 //do ..HttpRequest.SetHeader("name","nombre")
 
 
 /*
 15 10 21 seguimos la indicacion de Alberto Fuentes:
     ➕ añadir la cabecera Content-Disposition en la petición y ahí especificar el nombre del archivo.
     https://es.community.intersystems.com/post/%C2%BFc%C3%B3mo-podr%C3%ADamos-usar-mtom-para-enviar-un-zip-con-un-csv-adentro#comment-169536
     https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSOAP_cli_details#GSOAP_cli_details_http_headers
 */
 do ..SetHttpHeader("Content-Disposition","application.zip")
 
 Quit ..WebMethod("cargarFichero","CargarFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/cargarFichero",.fichero,.ccaaId,.tipoFichero)
}

Method infoFichero(ccaaId As EsquemasDatos.HistoriaClinica.tns.CCAAIdType(REQUIRED=1), infoFichero As EsquemasDatos.HistoriaClinica.tns.InfoFicheroType(REQUIRED=1), Output estado As EsquemasDatos.HistoriaClinica.tns.EstadoFicheroType(REQUIRED=1)) As %xsd.base64Binary(XMLNAME="fichero") [ Final, ProcedureBlock = 1, SoapBindingStyle = document, SoapBodyUse = literal, WebMethod ]
{
 set ..MTOMRequired=1    
 Quit ..WebMethod("infoFichero","InfoFicheroVacuRequest").Invoke($this,"http://ws.regvacuWs.ms.es/FicheroVacu/infoFichero",.ccaaId,.infoFichero,.estado)
}

Method crearAddressing() As %SOAP.Addressing.Properties
{
    set IPRedSanitaria = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","IPRedSanitaria")
     set puertoRespuestas = ##class(Util.TablasMaestras).getValorMaestra("PARAMETROS","PuertoRespuestasSSL")
     set ReplyTo = ##class(%SOAP.Addressing.EndpointReference).%New()
     set ReplyTo.Address = "http://www.w3.org/2005/08/addressing/anonymous"
     //set ReplyTo.Address = "https://"_IPRedSanitaria_":"_puertoRespuestas_"/csp/SNS/Servicios.ProgramasAsistenciales.SIFCOv02r00.cls"
     set MessageId = ##class(Util.FuncionesComunes).getUID()
     
     set addressing = ##class(%SOAP.Addressing.Properties).%New()
     set addressing.MessageId = MessageId
     set addressing.Destination = ..Location
     set addressing.ReplyEndpoint = ReplyTo    
          
     Quit addressing
}

}

How could we understand, debug, tune, and resolve together, this situation?

If you could give us some guidance, we would appreciate it 🙇🙏🙏🙏🙏🙏

How would you recommend us to follow?

What documentation do we need to study, read, understand, to complete this part?

Thanks for your answers and time reading this question