Etiqueta: SOAP

  • Cómo enviar  encabezados SOAP desde PHP

    Cómo enviar encabezados SOAP desde PHP

    El protocolo SOAP, a pesar de lo que indica su nombre, es de todo menos sencillo.

    Principalmente, su complejidad deriva del hecho de estar basado en XML, aunque no es lo único que tiene.

    En teoría, es un protocolo super flexible. En la realidad… un dolor de cabeza importante.

    En PHP existen varias implementaciones que intentan simplificar un poco el problema.

    Las veces que me ha tocado enfrentarme a SOAP las clases SoapClient y SoapServer han estado a la altura, aunque cuando aparecieron los encabezados no fue tan sencillo.

    Tomemos como ejemplo este servicio web que define el siguiente WSDL:

    <?xml version="1.0" encoding="utf-8"?>
    <wsdl:definitions xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="https://sccnlp.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" targetNamespace="https://sccnlp.com/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
      <wsdl:types>
        <s:schema elementFormDefault="qualified" targetNamespace="https://sccnlp.com/">
          <s:element name="registrarNombradas">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="1" maxOccurs="1" name="rutEmpresa" type="s:int" />
                <s:element minOccurs="0" maxOccurs="1" name="nombradas" type="tns:ArrayOfNombrada" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="ArrayOfNombrada">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="Nombrada" nillable="true" type="tns:Nombrada" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="Nombrada">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="fechaInicioNombrada" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="idPuerto" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idTurno" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="idNave" nillable="true" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idLocacion" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="trabajadores" type="tns:ArrayOfTrabajadorNombrada" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="ArrayOfTrabajadorNombrada">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="TrabajadorNombrada" nillable="true" type="tns:TrabajadorNombrada" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="TrabajadorNombrada">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="idContrato" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idLabor" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idFuncion" type="s:int" />
            </s:sequence>
          </s:complexType>
          <s:element name="registrarNombradasResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="registrarNombradasResult" type="tns:NombradaCreada" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="NombradaCreada">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="Estado" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="Mensaje" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="nombradaCreadaDetalle" type="tns:ArrayOfNombradaCreadaDetalle" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="ArrayOfNombradaCreadaDetalle">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="NombradaCreadaDetalle" nillable="true" type="tns:NombradaCreadaDetalle" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="NombradaCreadaDetalle">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="id" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="error" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idEstado" type="s:int" />
            </s:sequence>
          </s:complexType>
          <s:element name="UserCredentials" type="tns:UserCredentials" />
          <s:complexType name="UserCredentials">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="userName" nillable="true" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="password" nillable="true" type="s:string" />
            </s:sequence>
            <s:anyAttribute />
          </s:complexType>
          <s:element name="modificarNombradas">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="1" maxOccurs="1" name="rutEmpresa" type="s:int" />
                <s:element minOccurs="0" maxOccurs="1" name="lista" type="tns:ArrayOfTrabajadorNombradaEdicion" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="ArrayOfTrabajadorNombradaEdicion">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="TrabajadorNombradaEdicion" nillable="true" type="tns:TrabajadorNombradaEdicion" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="TrabajadorNombradaEdicion">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="idNombrada" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idContrato" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="idContratoNuevo" nillable="true" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idLabor" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idFuncion" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="activo" type="s:boolean" />
              <s:element minOccurs="1" maxOccurs="1" name="rutTrabajador" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="dvTrabajador" nillable="true" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="pasaporte" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="extensionJornada" type="s:boolean" />
            </s:sequence>
          </s:complexType>
          <s:element name="modificarNombradasResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="modificarNombradasResult" type="tns:NombradaCreada" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:element name="getResolucionNombrada">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="1" maxOccurs="1" name="idNombrada" type="s:int" />
                <s:element minOccurs="1" maxOccurs="1" name="rutEmpresa" type="s:int" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:element name="getResolucionNombradaResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="getResolucionNombradaResult" type="tns:NombradaResolucion" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="NombradaResolucion">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="Estado" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="Mensaje" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="nombradaResolucionDetalle" type="tns:NombradaResolucionDetalle" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="NombradaResolucionDetalle">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="id" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idEmpresa" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaInicioNombrada" nillable="true" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaFinNombrada" nillable="true" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="idTurno" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="Turno" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idNave" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="Nave" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idLocacion" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="lugar" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="posicion" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaCreacion" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="activo" nillable="true" type="s:boolean" />
              <s:element minOccurs="1" maxOccurs="1" name="idEstadoNombrada" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="glosaEstadoNombrada" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="trabajadores" type="tns:ArrayOfTrabajadorResolucion" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="ArrayOfTrabajadorResolucion">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="TrabajadorResolucion" nillable="true" type="tns:TrabajadorResolucion" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="TrabajadorResolucion">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="idNombrada" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idContrato" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idContratoNuevo" nillable="true" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idTrabajador" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="rutTrabajador" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="dvTrabajador" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="pasaporte" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="nombres" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="apellidos" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idLabor" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idFuncion" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="estadoTrabajador" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="activo" type="s:boolean" />
              <s:element minOccurs="1" maxOccurs="1" name="idEstadoTrabajador" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="extensionJornada" type="s:boolean" />
              <s:element minOccurs="1" maxOccurs="1" name="horasExtras" nillable="true" type="s:double" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaCreacion" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaFinNombradaTrabajador" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaModificacion" nillable="true" type="s:dateTime" />
            </s:sequence>
          </s:complexType>
          <s:element name="consultarNombrada">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="1" maxOccurs="1" name="rutEmpresa" type="s:int" />
                <s:element minOccurs="1" maxOccurs="1" name="filtro" nillable="true" type="tns:FiltroNombrada" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="FiltroNombrada">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="idNombrada" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="fechaInicio" nillable="true" type="s:dateTime" />
              <s:element minOccurs="0" maxOccurs="1" name="idLabor" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="idNave" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="idLocacion" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="rutTrabajador" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="dvTrabajador" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="pasaporte" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="idEstado" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="idFuncion" nillable="true" type="s:int" />
            </s:sequence>
          </s:complexType>
          <s:element name="consultarNombradaResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="consultarNombradaResult" type="tns:NombradaConsultaMuellaje" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="NombradaConsultaMuellaje">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="Estado" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="Mensaje" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="consultaDetalle" type="tns:ArrayOfNombradaOutMuellaje" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="ArrayOfNombradaOutMuellaje">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="NombradaOutMuellaje" nillable="true" type="tns:NombradaOutMuellaje" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="NombradaOutMuellaje">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="idNombrada" nillable="true" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaInicioNombrada" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaFinNombrada" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="idNave" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="nombreNave" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idPuerto" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="nombrePuerto" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idLocacion" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="lugar" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="posicion" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idTurno" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="glosaTurno" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idEstadoNombrada" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="glosaEstadoNombrada" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="rutMuellaje" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="nombreMuellaje" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="rutConcesionaria" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="nombreConcesionario" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="NOMBRE_INSTALACION" type="s:string" />
            </s:sequence>
          </s:complexType>
          <s:element name="eliminarNombrada">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="1" maxOccurs="1" name="rutEmpresa" type="s:int" />
                <s:element minOccurs="1" maxOccurs="1" name="idNombrada" type="s:int" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:element name="eliminarNombradaResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="eliminarNombradaResult" type="tns:Respuesta" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="Respuesta">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="Estado" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="Mensaje" type="s:string" />
            </s:sequence>
          </s:complexType>
          <s:element name="consultarNombradaByConcesionaria">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="1" maxOccurs="1" name="rutEmpresa" type="s:int" />
                <s:element minOccurs="1" maxOccurs="1" name="filtro" nillable="true" type="tns:FiltroNombrada" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:element name="consultarNombradaByConcesionariaResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="consultarNombradaByConcesionariaResult" type="tns:NombradaConsultaConcesionario" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="NombradaConsultaConcesionario">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="Estado" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="Mensaje" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="consultaDetalle" type="tns:ArrayOfNombradaOutConcesionario" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="ArrayOfNombradaOutConcesionario">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="NombradaOutConcesionario" nillable="true" type="tns:NombradaOutConcesionario" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="NombradaOutConcesionario">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="idNombrada" nillable="true" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaInicioNombrada" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="fechaFinNombrada" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="idNave" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="nombreNave" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idLocacion" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="lugar" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idPuerto" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="nombrePuerto" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idTurno" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="glosaTurno" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="posicion" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idEstadoNombrada" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="glosaEstadoNombrada" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="rutMuellaje" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="nombreMuellaje" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idLabor" nillable="true" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="idFuncion" nillable="true" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="rutConcesionaria" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="nombreConcesionaria" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="trabajadores" type="tns:ArrayOfTrabajador" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="ArrayOfTrabajador">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="Trabajador" nillable="true" type="tns:Trabajador" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="Trabajador">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="idContrato" nillable="true" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="rutTrabajador" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="dvTrabajador" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="pasaporteTrabajador" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="nombresTrabajador" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="apellidoPaternoTrabajador" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idLabor" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="glosaLabor" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="idFuncion" nillable="true" type="s:int" />
              <s:element minOccurs="0" maxOccurs="1" name="glosaFuncion" type="s:string" />
              <s:element minOccurs="0" maxOccurs="1" name="glosaEstadoTrabajador" type="s:string" />
              <s:element minOccurs="1" maxOccurs="1" name="activoTrabajador" nillable="true" type="s:boolean" />
            </s:sequence>
          </s:complexType>
          <s:element name="verificarTrabajadorPortuario">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="1" maxOccurs="1" name="rutEmpresa" type="s:int" />
                <s:element minOccurs="1" maxOccurs="1" name="rutTrabajador" type="s:int" />
                <s:element minOccurs="0" maxOccurs="1" name="dvTrabajador" type="s:string" />
                <s:element minOccurs="1" maxOccurs="1" name="fecha" type="s:dateTime" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:element name="verificarTrabajadorPortuarioResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="verificarTrabajadorPortuarioResult" type="tns:Respuesta" />
              </s:sequence>
            </s:complexType>
          </s:element>
        </s:schema>
      </wsdl:types>
      <wsdl:message name="registrarNombradasSoapIn">
        <wsdl:part name="parameters" element="tns:registrarNombradas" />
      </wsdl:message>
      <wsdl:message name="registrarNombradasSoapOut">
        <wsdl:part name="parameters" element="tns:registrarNombradasResponse" />
      </wsdl:message>
      <wsdl:message name="registrarNombradasUserCredentials">
        <wsdl:part name="UserCredentials" element="tns:UserCredentials" />
      </wsdl:message>
      <wsdl:message name="modificarNombradasSoapIn">
        <wsdl:part name="parameters" element="tns:modificarNombradas" />
      </wsdl:message>
      <wsdl:message name="modificarNombradasSoapOut">
        <wsdl:part name="parameters" element="tns:modificarNombradasResponse" />
      </wsdl:message>
      <wsdl:message name="modificarNombradasUserCredentials">
        <wsdl:part name="UserCredentials" element="tns:UserCredentials" />
      </wsdl:message>
      <wsdl:message name="getResolucionNombradaSoapIn">
        <wsdl:part name="parameters" element="tns:getResolucionNombrada" />
      </wsdl:message>
      <wsdl:message name="getResolucionNombradaSoapOut">
        <wsdl:part name="parameters" element="tns:getResolucionNombradaResponse" />
      </wsdl:message>
      <wsdl:message name="getResolucionNombradaUserCredentials">
        <wsdl:part name="UserCredentials" element="tns:UserCredentials" />
      </wsdl:message>
      <wsdl:message name="consultarNombradaSoapIn">
        <wsdl:part name="parameters" element="tns:consultarNombrada" />
      </wsdl:message>
      <wsdl:message name="consultarNombradaSoapOut">
        <wsdl:part name="parameters" element="tns:consultarNombradaResponse" />
      </wsdl:message>
      <wsdl:message name="consultarNombradaUserCredentials">
        <wsdl:part name="UserCredentials" element="tns:UserCredentials" />
      </wsdl:message>
      <wsdl:message name="eliminarNombradaSoapIn">
        <wsdl:part name="parameters" element="tns:eliminarNombrada" />
      </wsdl:message>
      <wsdl:message name="eliminarNombradaSoapOut">
        <wsdl:part name="parameters" element="tns:eliminarNombradaResponse" />
      </wsdl:message>
      <wsdl:message name="eliminarNombradaUserCredentials">
        <wsdl:part name="UserCredentials" element="tns:UserCredentials" />
      </wsdl:message>
      <wsdl:message name="consultarNombradaByConcesionariaSoapIn">
        <wsdl:part name="parameters" element="tns:consultarNombradaByConcesionaria" />
      </wsdl:message>
      <wsdl:message name="consultarNombradaByConcesionariaSoapOut">
        <wsdl:part name="parameters" element="tns:consultarNombradaByConcesionariaResponse" />
      </wsdl:message>
      <wsdl:message name="consultarNombradaByConcesionariaUserCredentials">
        <wsdl:part name="UserCredentials" element="tns:UserCredentials" />
      </wsdl:message>
      <wsdl:message name="verificarTrabajadorPortuarioSoapIn">
        <wsdl:part name="parameters" element="tns:verificarTrabajadorPortuario" />
      </wsdl:message>
      <wsdl:message name="verificarTrabajadorPortuarioSoapOut">
        <wsdl:part name="parameters" element="tns:verificarTrabajadorPortuarioResponse" />
      </wsdl:message>
      <wsdl:message name="verificarTrabajadorPortuarioUserCredentials">
        <wsdl:part name="UserCredentials" element="tns:UserCredentials" />
      </wsdl:message>
      <wsdl:portType name="NombradasSoap">
        <wsdl:operation name="registrarNombradas">
          <wsdl:input message="tns:registrarNombradasSoapIn" />
          <wsdl:output message="tns:registrarNombradasSoapOut" />
        </wsdl:operation>
        <wsdl:operation name="modificarNombradas">
          <wsdl:input message="tns:modificarNombradasSoapIn" />
          <wsdl:output message="tns:modificarNombradasSoapOut" />
        </wsdl:operation>
        <wsdl:operation name="getResolucionNombrada">
          <wsdl:input message="tns:getResolucionNombradaSoapIn" />
          <wsdl:output message="tns:getResolucionNombradaSoapOut" />
        </wsdl:operation>
        <wsdl:operation name="consultarNombrada">
          <wsdl:input message="tns:consultarNombradaSoapIn" />
          <wsdl:output message="tns:consultarNombradaSoapOut" />
        </wsdl:operation>
        <wsdl:operation name="eliminarNombrada">
          <wsdl:input message="tns:eliminarNombradaSoapIn" />
          <wsdl:output message="tns:eliminarNombradaSoapOut" />
        </wsdl:operation>
        <wsdl:operation name="consultarNombradaByConcesionaria">
          <wsdl:input message="tns:consultarNombradaByConcesionariaSoapIn" />
          <wsdl:output message="tns:consultarNombradaByConcesionariaSoapOut" />
        </wsdl:operation>
        <wsdl:operation name="verificarTrabajadorPortuario">
          <wsdl:input message="tns:verificarTrabajadorPortuarioSoapIn" />
          <wsdl:output message="tns:verificarTrabajadorPortuarioSoapOut" />
        </wsdl:operation>
      </wsdl:portType>
      <wsdl:binding name="NombradasSoap" type="tns:NombradasSoap">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="registrarNombradas">
          <soap:operation soapAction="https://sccnlp.com/registrarNombradas" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
            <soap:header message="tns:registrarNombradasUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="modificarNombradas">
          <soap:operation soapAction="https://sccnlp.com/modificarNombradas" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
            <soap:header message="tns:modificarNombradasUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="getResolucionNombrada">
          <soap:operation soapAction="https://sccnlp.com/getResolucionNombrada" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
            <soap:header message="tns:getResolucionNombradaUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="consultarNombrada">
          <soap:operation soapAction="https://sccnlp.com/consultarNombrada" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
            <soap:header message="tns:consultarNombradaUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="eliminarNombrada">
          <soap:operation soapAction="https://sccnlp.com/eliminarNombrada" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
            <soap:header message="tns:eliminarNombradaUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="consultarNombradaByConcesionaria">
          <soap:operation soapAction="https://sccnlp.com/consultarNombradaByConcesionaria" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
            <soap:header message="tns:consultarNombradaByConcesionariaUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="verificarTrabajadorPortuario">
          <soap:operation soapAction="https://sccnlp.com/verificarTrabajadorPortuario" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
            <soap:header message="tns:verificarTrabajadorPortuarioUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
      </wsdl:binding>
      <wsdl:binding name="NombradasSoap12" type="tns:NombradasSoap">
        <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="registrarNombradas">
          <soap12:operation soapAction="https://sccnlp.com/registrarNombradas" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
            <soap12:header message="tns:registrarNombradasUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="modificarNombradas">
          <soap12:operation soapAction="https://sccnlp.com/modificarNombradas" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
            <soap12:header message="tns:modificarNombradasUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="getResolucionNombrada">
          <soap12:operation soapAction="https://sccnlp.com/getResolucionNombrada" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
            <soap12:header message="tns:getResolucionNombradaUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="consultarNombrada">
          <soap12:operation soapAction="https://sccnlp.com/consultarNombrada" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
            <soap12:header message="tns:consultarNombradaUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="eliminarNombrada">
          <soap12:operation soapAction="https://sccnlp.com/eliminarNombrada" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
            <soap12:header message="tns:eliminarNombradaUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="consultarNombradaByConcesionaria">
          <soap12:operation soapAction="https://sccnlp.com/consultarNombradaByConcesionaria" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
            <soap12:header message="tns:consultarNombradaByConcesionariaUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="verificarTrabajadorPortuario">
          <soap12:operation soapAction="https://sccnlp.com/verificarTrabajadorPortuario" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
            <soap12:header message="tns:verificarTrabajadorPortuarioUserCredentials" part="UserCredentials" use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
      </wsdl:binding>
      <wsdl:service name="Nombradas">
        <wsdl:port name="NombradasSoap" binding="tns:NombradasSoap">
          <soap:address location="https://sccnlpservices-piloto.dirtrab.cl/Servicios/Nombradas.asmx" />
        </wsdl:port>
        <wsdl:port name="NombradasSoap12" binding="tns:NombradasSoap12">
          <soap12:address location="https://sccnlpservices-piloto.dirtrab.cl/Servicios/Nombradas.asmx" />
        </wsdl:port>
      </wsdl:service>
    </wsdl:definitions>

    Con esta información puedes armar un script como este:

    <?php
    
    $url = "https://sccnlpservices-piloto.dirtrab.cl/Servicios/Nombradas.asmx?WSDL";
    $client = new SoapClient($url, ["trace" => 1, "exception" => 0]);
    print_r($client->__getFunctions());

    Y, al correrlo obtendrás algo como:

    Array
    (
        [0] => registrarNombradasResponse registrarNombradas(registrarNombradas $parameters)
        [1] => modificarNombradasResponse modificarNombradas(modificarNombradas $parameters)
        [2] => getResolucionNombradaResponse getResolucionNombrada(getResolucionNombrada $parameters)
        [3] => consultarNombradaResponse consultarNombrada(consultarNombrada $parameters)
        [4] => eliminarNombradaResponse eliminarNombrada(eliminarNombrada $parameters)
        [5] => consultarNombradaByConcesionariaResponse consultarNombradaByConcesionaria(consultarNombradaByConcesionaria $parameters)
        [6] => verificarTrabajadorPortuarioResponse verificarTrabajadorPortuario(verificarTrabajadorPortuario $parameters)
        [7] => registrarNombradasResponse registrarNombradas(registrarNombradas $parameters)
        [8] => modificarNombradasResponse modificarNombradas(modificarNombradas $parameters)
        [9] => getResolucionNombradaResponse getResolucionNombrada(getResolucionNombrada $parameters)
        [10] => consultarNombradaResponse consultarNombrada(consultarNombrada $parameters)
        [11] => eliminarNombradaResponse eliminarNombrada(eliminarNombrada $parameters)
        [12] => consultarNombradaByConcesionariaResponse consultarNombradaByConcesionaria(consultarNombradaByConcesionaria $parameters)
        [13] => verificarTrabajadorPortuarioResponse verificarTrabajadorPortuario(verificarTrabajadorPortuario $parameters)
    )

    Ahora bien, digamos que quieres ejecutar una función como consultarNombrada.

    Podrías hacerlo de esta forma:

    $response = $client->getResolucionNombrada([ 'idNombrada' => 1, 'rutEmpresa' => 2 ]);

    Pero, al correrlo obtendrás un error similar a:

    SOAP Fault:
    Fault Code: soap:Server
    Fault String: System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> System.NullReferenceException: Object reference not set to an instance of an object.
       at Services.Servicios.Nombradas.getResolucionNombrada(Int32 idNombrada, Int32 rutEmpresa) in C:\VSTS-Apl-Agent\_work\48\s\SCCNLP_Services\Services\Servicios\Nombradas.asmx.cs:line 144
       --- End of inner exception stack trace ---

    ¿Qué ha ocurrido? ¿Es un problema del lado del webservice?

    No. Simplemente, no configuraste la autenticación, por lo tanto, la llamada falla.

    Si miras nuevamente el WSDL verás esta definición:

    <soap:header message="tns:registrarNombradasUserCredentials" part="UserCredentials" use="literal"/>

    Que indica que, como parte de la llamada, es necesario especificar un encabezado llamado tns:registrarNombradasUserCredentials

    Este elemento usa la definición que aparece al comienzo (En la parte de los tipos):

    <s:complexType name="UserCredentials">
     <s:sequence>
      <s:element minOccurs="1" maxOccurs="1" name="userName" nillable="true" type="s:string"/>
      <s:element minOccurs="1" maxOccurs="1" name="password" nillable="true" type="s:string"/>
     </s:sequence>
     <s:anyAttribute/>
    </s:complexType>

    Es decir que, para que la llamada tenga éxito, debes incluir la información de autenticación (userName y password) como un encabezado SOAP.

    Puedes lograr esto agregando lo siguiente a tu código:

    $client->__setSoapHeaders([
                new SoapHeader(
                        "https://sccnlp.com/",
                        "UserCredentials",
                        [
                                "userName" => "XXXX",
                                "password" => "YYYY",
                        ]
                )
        ]);

    Y, asumiendo que tengas datos correctos para realizar la autenticación y que tu usuario tenga los permisos requeridos, obtendrás la información que buscas.

    Algo interesante de hacer este ajuste de esta forma es que los datos de autenticación estarán disponibles para todas las subsiguientes llamadas al servicio.

  • Consumir un WebService SOAP con certificado digital con PHP

    Consumir un WebService SOAP con certificado digital con PHP

    Una lectora del newsletter de Leeway Academy, me escribe lo siguiente:

    Necesito consumir un webservice con php utilizando certificado para autenticarse, recibí .pfx y lo convertí en .pem, obtuve cert.pem y key.pem.

    A primera vista parece algo sencillo, ¿cierto? Se trata de conectarse a un servidor del que ya sabemos su URL, tenemos los certificados… ¿qué podría salir mal?

    Claro que, como siempre que se trata de usar SOAP, las cosas no son tan fáciles como aparentan.

    Para empezar, hay que decidir qué herramientas usaremos para realizar la conexión (cURL, file_get_contents, Guzzle…).

    Después está el tema de cómo especificar el certificado… y por último, qué hacer con la respuesta del servidor una vez la hayamos obtenido.

    Porque, no olvidemos que, siendo un WebService SOAP, la respuesta es un XML. Pero no un XML cualquiera, un XML diseñado según las especificaciones de SOAP.

    Y, por si faltaba algo, lo más probable es que no se trate de una sola petición, no. Para poder hacer algo más que sólo conectarnos al servidor, seguramente tengamos que hacer unas cuantas.

    Juntemos todo eso y tenemos un buen dolor de cabeza por delante.

    Bueno… no necesariamente. Las cosas pueden ser bastante más sencillas si tenemos claro qué hacer.

    Empecemos aclarando algunos conceptos.

    Qué es un certificado digital

    Un certificado digital es un mecanismo que permite a una computadora asegurarse de que su contraparte en una comunicación digital es efectivamente quien dice ser.

    Más en concreto: cuando se realiza una comunicación web, típicamente existen dos actores, el cliente y el servidor.

    El cliente envía un mensaje codificado en HTTP y el servidor le responde de la misma forma.

    El problema es que, en ocasiones, puede haber algún intermediario no deseado (Lo que se conoce como ataque de man-in-the-middle):

    Cuando se debe enviar información sensible, es muy importante para el emisor poder determinar que quien recibe esa información es, efectivamente, quien dice ser.

    Y ¿cómo puede asegurarse la identidad de la contraparte? Precisamente, con un certificado digital.

    Los certificados digitales son emitidos por entidades en las que el emisor confía (Autoridades de Certificación), algo similar al DNI que otorgan los entes gubernamentales a las personas.

    De modo que, mediante criptografía, es posible determinar si un certificado presentado por una parte está avalado por alguna autoridad competente.

    Todo esto pasa de forma transparente cada vez que navegás hacia una página que comienza con https en lugar de http.

    Ese es el caso más común: como cliente no querés enviar tus datos de tarjeta de crédito a un servidor a menos que tengas la certeza de que estás comunicándote con quien creés que estás haciéndolo.

    Pues bien, lo mismo puede ocurrir a la inversa. Es decir, un servidor puede negarse a establecer comunicación con un cliente del cual no conoce su identidad.

    ¿Cómo puede el cliente dar fé de su identidad? Del mismo modo, ofreciéndole al servidor un certificado avalado por alguna autoridad en la que él confíe.

    Ese es el caso que estamos tratando en este artículo.

    Cómo especificar el certificado digital en una llamada SOAP

    Ahora sí, habiendo despejado los temas más teóricos, pasemos a la práctica.

    Asumiendo que contamos con el archivo de certificado (.pem) y el de clave privada (.key), podemos realizar la conexión directa, usando cURL, con el servidor de esta forma:

    $ch = curl_init(); 
    curl_setopt($ch, CURLOPT_URL, $url); 
    curl_setopt($ch, CURLOPT_VERBOSE, 1); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); 
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); 
    curl_setopt($ch, CURLOPT_FAILONERROR, 1); 
    curl_setopt($ch, CURLOPT_SSLCERT, __DIR__ . '/client.crt'); 
    curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM'); 
    curl_setopt($ch, CURLOPT_SSLKEY, __DIR__ . '/client.key'); 
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
    curl_setopt($ch, CURLOPT_POSTFIELDS, $requestXml);
    $ret = curl_exec($ch);

    Algo bastante laborioso por cierto.

    Una alternativa ligeramente más sencilla es usar file_get_contents en combinación con stream_context_create para manejar la parte de los certificados:

    $ret = file_get_contents($url, false, stream_context_create(
    [
            'ssl' => [
                'local_cert' => __DIR__ . '/client.crt',
                'local_pk' => __DIR__ . '/client.key',
            ]
        ]
    );

    El problema es que nos encontraremos, a la vuelta, con algo como:

    <?xml version="1.0" encoding="UTF-8"?>
    <soapenv:Envelope
        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <soapenv:Body>
            <deudasDeudorResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
                <deudasDeudorReturn href="#id0"/>
            </deudasDeudorResponse>
            <multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns1:DeudasDeudorClient"
                xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
                xmlns:ns1="urn:consultadeudasxinstWs.bcu.gub.uy">
                <deudas soapenc:arrayType="ns1:DeudaInstitucionClient[5]" xsi:type="soapenc:Array">
                    <deudas href="#id1"/>
                    <deudas href="#id2"/>
                    <deudas href="#id3"/>
                    <deudas href="#id4"/>
                    <deudas href="#id5"/></deudas>
                <deudor href="#id6"/>
                <periodo href="#id7"/>
                <tipoDeCambio href="#id8"/>
            </multiRef>        
        </soapenv:Body>
    </soapenv:Envelope>

    Es decir, tendremos que interpretar este XML para poder procesarlo efectivamente.

    Cómo interpretar el XML que retorna el WebService

    Si bien es perfectamente posible hacerlo una herramienta como SimpleXML o, directamente parseándolo mediante expresiones regulares, la verdad es que cualquiera de estos métodos es sumamente ineficiente y propenso a errores.

    De hecho, si el archivo contiene algo como <wsdl:import location="http://localhost:8080/soapservice/services/quoteService?wsdl=RandomQuote.wsdl" namespace="http://examples.javacodegeeks.com/"> vamos a tener que hacer otra llamada, otra vez especificando los parámetros de seguridad y así sucesivamente.

    Un modo mucho mejor es utilizar la clase SoapClient, la cual permite especificar, entre otras, cómo conectarse al servidor remoto:

    $client = new SoapClient($url,
            [
                'stream_context' => stream_context_create(
                    [
                        'ssl' => [
                            'local_cert' => __DIR__ . '/client.crt',
                            'local_pk' => __DIR__ . '/client.key',
                        ]
                    ]
                ),
            ]);

    A partir de aquí ya es posible ejecutar cualquier servicio disponible en el WebService o, si lo necesitás, ver qué operaciones te ofrece.

  • Cómo enviar un archivo a un WebService SOAP desde PHP

    Cómo enviar un archivo a un WebService SOAP desde PHP

    Henry, un suscriptor del newsletter de Leeway Academy, me envía este mensaje:

    Buenos dias.

    Soy desarrollador php pero tengo este problema ya que este wsdl (https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm) no me da mucha info.

    Debo enviar un archivo xml tal cual, como archivo, con muchas etiquetas, en fin ya lo tengo resuelto. Una vez que tengo el ARCHIVO, cómo lo adjunto a este servicio para luego obtener el archivo de respuesta y leerlo?.

    No veo en los parametros de SoapClient cómo adjuntarlo o la forma de enviarlo.

    Si este es el formato correcto ( o no )

    $xml='<.......... ';  (TODAS LAS ETIQUETAS QUE NECESITO ENVIAR)
    
    $texto='<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.sigad.sunat.gob.pe">
       <soapenv:Header/>
       <soapenv:Body>
          <ser:recibirArchivo>
             <!--Optional:-->
             <numeroTransaccion>?</numeroTransaccion>
             <!--Optional:-->
             <informacionArchivo>$xml</informacionArchivo>
          </ser:recibirArchivo>
       </soapenv:Body>
    </soapenv:Envelope>'
    
    $texto = mb_convert_encoding($texto, "UTF-8");  //  CON Y SIN ESTA CONVERSION
    
    $location_URL = 'https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm?wsdl';
    $action='urn:recibirArchivo';
    $options = array(
    // Stuff for development.
    'location' => $location_URL,
    // 'uri'      => 'https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm',
    'uri'  => 'https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm?wsdl',
    'username' => "XXXX",
    'password' =>"YYYY",
    'trace' => 1
    );

    AQUI ME ESTANCO

    envio: $order_return = $client->__doRequest($texto,$location_URL,$action,0);

    y recibo de respuesta

    <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
       <S:Body>
          <ns0:Fault xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.w3.org/2003/05/soap-envelope">
             <faultcode>ns0:Server</faultcode>
             <faultstring>javax.xml.ws.soap.SOAPFaultException</faultstring>
          </ns0:Fault>
       </S:Body>
    </S:Envelope>

    Entonces no se la forma correcta de enviarlo.

    Saludos y por favor ayuda.

    Pasemos un poco en limpio el problema que tiene Henry.

    Para empezar, el WSDL al que se refiere es este:

    <?xml version='1.0' encoding='UTF-8'?>
    <!-- Published by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.3.0-b170407.2038 svn-revision#2eaca54d17a59d265c6fe886b7fd0027836c766c. -->
    <!-- Generated by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.3.0-b170407.2038 svn-revision#2eaca54d17a59d265c6fe886b7fd0027836c766c. -->
    <definitions
        xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
        xmlns:wsp="http://www.w3.org/ns/ws-policy"
        xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy"
        xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
        xmlns:tns="http://service.receptor.tecnologia.sunat.gob.pe/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://service.receptor.tecnologia.sunat.gob.pe/" name="ReceptorService.htm">
        <import namespace="http://services.sigad.sunat.gob.pe" location="https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm?wsdl=1"/>
        <binding
            xmlns:ns1="http://services.sigad.sunat.gob.pe" name="ReceptorWebServiceServiceImplPortBinding" type="ns1:ReceptorService.htm">
            <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
            <operation name="recibirArchivo">
                <soap:operation soapAction="urn:recibirArchivo"/>
                <input>
                    <soap:body use="literal"/>
                </input>
                <output>
                    <soap:body use="literal"/>
                </output>
                <fault name="Exception">
                    <soap:fault name="Exception" use="literal"/>
                </fault>
            </operation>
            <operation name="realizarConsulta">
                <soap:operation soapAction="urn:realizarConsulta"/>
                <input>
                    <soap:body use="literal"/>
                </input>
                <output>
                    <soap:body use="literal"/>
                </output>
                <fault name="Exception">
                    <soap:fault name="Exception" use="literal"/>
                </fault>
            </operation>
        </binding>
        <service name="ReceptorService.htm">
            <port name="ReceptorWebServiceServiceImplPort" binding="tns:ReceptorWebServiceServiceImplPortBinding">
                <soap:address location="https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm"/>
            </port>
        </service>
    </definitions>

    Y según parece, lo que está intentando lograr Henry es enviar un archivo a un WebService de la agencia tributaria de Perú (Sunat).

    Nunca antes me había tocado tratar con este WS, de modo que, antes de pasar al tema específico, realicé algunas pruebas.

    Entender el WebService

    El primer paso para resolver un problema como este es comprender qué tenemos del otro lado.

    Nada mejor para empezar que usar una herramienta como cURL para descargar el archivo WSDL. Usando el comando curl https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm\?wsdl obtuve:

    curl: (60) SSL certificate problem: unable to get local issuer certificate<br>More details here: https://curl.haxx.se/docs/sslcerts.html
    curl failed to verify the legitimacy of the server and therefore could not
    establish a secure connection to it. To learn more about this situation and
    how to fix it, please visit the web page mentioned above.

    No exactamente lo que esperaba… parece que me falta alguna verificación SSL.

    Para saltearme este chequeo agrego el modificador -k al comando original, quedando curl -k https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm\?wsdl y ahí sí puedo descargar el WSDL y usar un simple script basado en SoapClient para ver qué me ofrece este servicio de un modo algo más simple:

    <?php
    
    if ($argc < 2) {
        die('Specify the WSDL URI');
    }
    
    $client = new SoapClient($argv[1],
        [
            'stream_context' => stream_context_create([
                    "ssl" => [
                        "verify_peer" => false,
                        "verify_peer_name" => false,
                    ]]),
        ]
    );
    echo "Functions available:" . PHP_EOL;
    echo "====================" . PHP_EOL;
    print_r($client->__getFunctions());

    Al ejecutar php describe_ws.php https://test.sunat.gob.pe:444/ol-ad-itseida-ws/ReceptorService.htm\?wsdl obtengo:

    Functions available:
    ====================
    Array
    (
        [0] => recibirArchivoResponse recibirArchivo(recibirArchivo $parameters)
        [1] => realizarConsultaResponse realizarConsulta(realizarConsulta $parameters)
    )

    Es decir, tenemos dos métodos disponibles para invocar: recibirArchivo y realizarConsulta.

    En particular, el que nos interesa en este caso es el primero.

    Ahora sería interesante ver qué son exactamente los tipos recibirArchivo y recibirArchivoResponse.

    Para ello le agregué un par de líneas al script:

    echo "Types available:" . PHP_EOL;
    echo "====================" . PHP_EOL;
    print_r($client->__getTypes());

    Y al correr nuevamente el scritp obtengo, además de lo que ya tenía:

    Types available:
    ====================
    Array
    (
        [0] => struct Exception {
     string message;
    }
        [1] => struct realizarConsulta {
     string parametrosConsulta;
    }
        [2] => struct realizarConsultaResponse {
     base64Binary realizarConsultaResultado;
     respuestaConsultaBean respuesta;
    }
        [3] => struct recibirArchivo {
     string numeroTransaccion;
     base64Binary informacionArchivo;
    }
        [4] => struct recibirArchivoResponse {
     acuseRecibo recibirArchivoResultado;
    }
        [5] => struct respuestaConsultaBean {
     string descripcion;
     string tipo;
    }
        [6] => struct acuseRecibo {
     string anhoEnvio;
     string documentoEmisor;
     string fechaRecepcion;
     string hashDocumento;
     errorAcuse listaErrores;
     errorAcuse listaWarning;
     string numeroOrden;
     string ticketEnvio;
    }
        [7] => struct errorAcuse {
     string codigo;
     string descripcion;
     string tipo;
    }
    )

    Bien, ahora vemos entonces que, para invocar al servicio de recibirArchivo se requiere enviar un string (numeroTransaccion) y un dato de tipo base64Binary (informacionArchivo).

    Esto ya me da alguna pista de lo que puede haber fallado en el caso de Henry… sigamos investigando.

    Qué es base64Binary

    Si vemos la definición en la especificación de SOAP encontramos lo siguiente:

    [Definition:]  base64Binary represents Base64-encoded arbitrary binary data. The ·value space· of base64Binary is the set of finite-length sequences of binary octets. For base64Binary data the entire binary stream is encoded using the Base64 Alphabet in [RFC 2045].

    Lo que esto significa en Español es que los datos del archivo deben ser enviados como una cadena codificada utilizando base64. De hecho, esto tiene bastante sentido ya que SOAP se monta sobre XML, un estándar basado en texto, de modo que algunos datos binarios podrían generar problemas.

    Envío de un archivo binario a un WebService SOAP

    Me temo que, como no tengo los datos de acceso al Sunat no voy a poder dar un ejemplo real pero dejaré uno que se parezca lo suficiente como para que pueda entenderse el mecanismo a utilizar.

    Armé un pequeño programita en Java basado en lo que se muestra en este artículo.

    Para correrlo uso este comando:

    java1.8 -Dfile.encoding=UTF-8 -jar ./base64_server.jar

    Y, a partir de ahí puedo consultar el WSDL a través de la URL http://localhost:9898/?wsdl, lo que me da:

    <!--  Published by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.2.9-b130926.1035 svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e.  -->
    <!--  Generated by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.2.9-b130926.1035 svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e.  -->
    <definitions
        xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
        xmlns:wsp="http://www.w3.org/ns/ws-policy"
        xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy"
        xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
        xmlns:tns="http://soap.leewayweb.com/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://soap.leewayweb.com/" name="FileTransfererImplService">
        <types>
            <xsd:schema>
                <xsd:import namespace="http://soap.leewayweb.com/" schemaLocation="http://localhost:9898/?xsd=1"/>
            </xsd:schema>
        </types>
        <message name="upload">
            <part name="parameters" element="tns:upload"/>
        </message>
        <message name="uploadResponse">
            <part name="parameters" element="tns:uploadResponse"/>
        </message>
        <message name="IOException">
            <part name="fault" element="tns:IOException"/>
        </message>
        <portType name="FileTransfererImpl">
            <operation name="upload">
                <input wsam:Action="http://soap.leewayweb.com/FileTransfererImpl/uploadRequest" message="tns:upload"/>
                <output wsam:Action="http://soap.leewayweb.com/FileTransfererImpl/uploadResponse" message="tns:uploadResponse"/>
                <fault message="tns:IOException" name="IOException" wsam:Action="http://soap.leewayweb.com/FileTransfererImpl/upload/Fault/IOException"/>
            </operation>
        </portType>
        <binding name="FileTransfererImplPortBinding" type="tns:FileTransfererImpl">
            <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
            <operation name="upload">
                <soap:operation soapAction=""/>
                <input>
                    <soap:body use="literal"/>
                </input>
                <output>
                    <soap:body use="literal"/>
                </output>
                <fault name="IOException">
                    <soap:fault name="IOException" use="literal"/>
                </fault>
            </operation>
        </binding>
        <service name="FileTransfererImplService">
            <port name="FileTransfererImplPort" binding="tns:FileTransfererImplPortBinding">
                <soap:address location="http://localhost:9898/"/>
            </port>
        </service>
    </definitions>

    Y usando el mismo script que antes vemos que para invocarlo desde PHP tenemos los siguientes métodos:

    Functions available:
    ====================
    Array
    (
        [0] => uploadResponse upload(upload $parameters)
    )
    Types available:
    ====================
    Array
    (
        [0] => struct upload {
     string arg0;
     base64Binary arg1;
    }
        [1] => struct uploadResponse {
    }
        [2] => struct IOException {
     string message;
    }
    )

    Bastante parecido al caso que estoy analizando.

    Pues bien, lo que resta entonces es ver cómo enviar un archivo binario al servidor. Todo el truco pasa por enviar el contenido del archivo como una cadena de caracteres (Del encoding base64 se encarga SoapClient).

    Una función útil para esto es file_get_contents. Puede usarse de esta forma:

    <?php
    
    $client = new SoapClient('http://localhost:9898/?wsdl');
    
    $contents = file_get_contents($argv[1]);
    echo "Sending ".$contents.PHP_EOL;
    try {
    	$client->upload([
    		'arg0' => basename($argv[1]), 
    		'arg1' => $contents,
    	]);
    	echo "Sent!".PHP_EOL;
    } catch (Exception $e) {
    	var_dump($e);
    }

    Si se guarda el script como send_file.php puede ejecutarse con cualquier archivo binario mediante php send_file.php /ruta/al/archivo/binario y luego se podrá ver cómo se encuentra una copia en /ruta/al/programa/java/uploads.

    Si querés probarlo en tu computadora podés descargar el archivo .jar de acá.

    Advertencia: no usar con archivos grandes

    Este mecanismo puede funcionar bien bajo el supuesto de que los archivos sean pequeños. Si este no fuera el caso, las limitaciones de HTTP pueden volverse un cuello de botella difícil de superar.

    En todo caso, si buscas enviar archivos grandes no dejes de leer este artículo.

  • Cómo generar el WSDL de un Webservice PHP

    Un suscriptor del newsletter de Leeway Academy me envía el siguiente correo:

    Hola. muy bueno los mails, concuerdo con lo de los errores del otro día.

    Pero estoy atrapado en un problema.  Genere un Web service, me anda bárbaro, pero la gente que va a consumirlo en un sistema hecho en C# no puede procesar el WSDL que generé, me tiran errores.

    Lo estoy haciendo a mano.

    Hay alguna herramienta que se pueda usar?

    Muchas gracias.

    Ouch. Que temita que son los archivos WSDL, ¿no?

    Para dar un poquito más de contexto, se trata de un webservice de tipo SOAP desarrollado usando PHP.

    En general, el protocolo SOAP, aunque su nombre incluya la palabra Simple, dista bastante de serlo.

    Los XML que hay que enviar y recibir aportan bastante a la confusión general.

    Claro que, si se utilizan las herramientas adecuadas todo se vuelve mucho más sencillo.

    Yendo al problema que dio origen a este post, la clave está en «Lo estoy haciendo a mano.«.

    En línea general, es siempre preferible usar alguna librería ya hecha ya que, seguramente estará ampliamente testeada.

    En php disponemos de buenas herramientas para trabajar con XML pero para SOAP en particular pueden ser un poco rústicas. Mejor usar otra más específica: las clases provistas por la extensión Soap, en particular para este caso SoapServer.

    Con este par de herramientas tenemos cubierto el 80% de nuestras necesidades. Falta un detalle nomás: SoapServer no tiene la capacidad (Al menos hasta la versión 8.1 de php) de generar el archivo de definición del WebService: el WSDL.

    ¿Significa eso que tenemos que hacerlo a mano?

    Claro que es una posibilidad, se puede generar un archivo como este:

    <definitions name="Greetings" targetNamespace="http://localhost:8000/greetings_server.php" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://localhost:8000/greetings_server.php" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/">
       <types>
          <xsd:schema targetNamespace="http://localhost:8000/greetings_server.php"/>
       </types>
       <portType name="GreetingsPort">
          <operation name="sayHello">
             <documentation>sayHello</documentation>
             <input message="tns:sayHelloIn"/>
             <output message="tns:sayHelloOut"/>
          </operation>
          <operation name="sayGoodBye">
             <documentation>sayGoodBye</documentation>
             <input message="tns:sayGoodByeIn"/>
             <output message="tns:sayGoodByeOut"/>
          </operation>
       </portType>
       <binding name="GreetingsBinding" type="tns:GreetingsPort">
          <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
          <operation name="sayHello">
             <soap:operation soapAction="http://localhost:8000/greetings_server.php#sayHello"/>
             <input>
                <soap:body use="literal" namespace="http://localhost:8000/greetings_server.php"/>
             </input>
             <output>
                <soap:body use="literal" namespace="http://localhost:8000/greetings_server.php"/>
             </output>
          </operation>
          <operation name="sayGoodBye">
             <soap:operation soapAction="http://localhost:8000/greetings_server.php#sayGoodBye"/>
             <input>
                <soap:body use="literal" namespace="http://localhost:8000/greetings_server.php"/>
             </input>
             <output>
                <soap:body use="literal" namespace="http://localhost:8000/greetings_server.php"/>
             </output>
          </operation>
       </binding>
       <service name="GreetingsService">
          <port name="GreetingsPort" binding="tns:GreetingsBinding">
             <soap:address location="http://localhost:8000/greetings_server.php"/>
          </port>
       </service>
       <message name="sayHelloIn">
          <part name="name" type="xsd:string"/>
       </message>
       <message name="sayHelloOut">
          <part name="return" type="xsd:string"/>
       </message>
       <message name="sayGoodByeIn">
          <part name="name" type="xsd:string"/>
       </message>
       <message name="sayGoodByeOut">
          <part name="return" type="xsd:string"/>
       </message>
    </definitions>

    Y ciertamente el uso de SimpleXML será de gran ayuda… pero para hacerlo exitosamente habrá que conocer bien los detalles del protocolo SOAP y el manejo de Namespaces XML.

    ¿No existe acaso una librería que lo haga todo?

    Librerías para generar WSDL a partir de PHP

    Esa misma pregunta me hice yo, así que fui a buscar y encontré algunas opciones:

    Los probé todos (y algunos más que no recuerdo en este momento) y ninguno funcionaba al 100%.

    Algunos son tan viejos que ni tienen mantenimiento y han quedado obsoletos.

    Otros resolvían parcialmente el problema.

    Estaba a punto de darme por vencido cuando finalmente apareció una luz al final del túnel: la clase AutoDiscover del paquete SOAP de Laminas.

    A partir de ahí todo se volvió cuesta abajo.

    Uso de Laminas Soap\AutoDiscover

    Partiendo de este código que tenía para ejemplificar:

    <?php
    
    class GreetingsServer
    {
        function sayHello(string $name): string
        {
            return "Hello $name!";
        }
    
        function sayGoodBye(string $name): string
        {
            return "Goodbye $name!";
        }
    }
    
    $server = new SoapServer(__DIR__.'/grettings.wsdl');
    
    $server->setClass(GreetingServer::class);
    $server->handle();
    

    Le agregué la dependencia usando composer:

    composer require laminas/laminas-soap

    Luego se trata de discriminar el caso de que se esté solicitando el wsdl o no. Basta con un simple if:

    if (array_key_exists('wsdl', $_GET)) {
        header('Content-Type: application/wsdl+xml');
        die($wsdl);
    }

    Y ahora viene la parte interesante… ¿de dónde sale el contenido de $wsdl?

    Precisamente, de usar un objeto Laminas\Soap\AutoDiscover:

    $wsdl = (new AutoDiscover())
        ->setClass(GreetingsServer::class)
        ->setUri(SERVER_URI)
        ->setServiceName('Greetings')
        ->setOperationBodyStyle([
            'use' => 'literal'
        ])
        ->setBindingStyle(
            [
                'style' => 'rpc'
            ])
        ->generate()
        ->toXml();

    En el caso de que no se pida el wsdl (Es decir, que el QueryString sea diferente de ?wsdl), se debe crear el servidor usando un archivo ya generado:

    $server = new SoapServer(__DIR__ . '/greetings.wsdl');
    $server->setClass(GreetingsServer::class);
    $server->handle();

    ¿Cómo se genera ese archivo? Una forma muy simple es hacer un request a este mismo servicio y guardarlo en el mismo directorio:

    curl http://localhost:8000/greetings_server.php\?wsdl > greetings.wsdl

    En mi caso dice localhost:8000 porque es ahí donde está levantado el WebService pero esta URL debería cambiar según dónde tengas todo montado.

    Y listo, con esto se logra el bendito archivo WSDL que otros pueden consumir.

    Si querés ver el código completo podés descargarlo de GitHub.

    Ejemplo de clientes

    El ejemplo no estaría completo sin ver el cliente, ¿cierto?

    Empecemos por una versión en PHP:

    <?php
    
    $client = new SoapClient(
        'http://localhost:8000/greetings_server.php?wsdl',
        [
            'soap_version' => SOAP_1_2,
            'cache_wsdl' => WSDL_CACHE_NONE,
        ]);
    
    if ($argc === 1) {
        echo "Service description:" . PHP_EOL;
        echo "--------------------" . PHP_EOL;
        echo "Functions:" . PHP_EOL;
        print_r($client->__getFunctions());
        echo "=====" . PHP_EOL;
        echo "Types:" . PHP_EOL;
        print_r($client->__getTypes());
    } elseif ($argv[1] === "h") {
        echo $client->sayHello($argv[2]);
    } elseif ($argv[1] === "g") {
        echo $client->sayGoodBye($argv[2]);
    }

    Con este pequeño script de línea de comandos es posible ver la definición del webservice y ejecutar ambos servicios como si se tratara de llamadas locales.

    Y ahora, para ver un ejemplo algo más jugoso, veamos un script de python que consume este webservice:

    from zeep import Client
    import sys
    
    client = Client('http://localhost:8000/greetings_server.php?wsdl')
    
    cmd = sys.argv[1]
    
    if cmd == "h":
        print (client.service.sayHello(sys.argv[2]))
    else:
        print (client.service.sayGoodBye(sys.argv[2]))

    Usando el comando python3.8 client.py h Mauro obtenemos Hello Mauro! y con python3.8 client.py g Mauro obtenemos Goodbye Mauro!

    Y así vemos cómo es posible integrar dos aplicaciones desarrolladas en dos lenguajes diferentes usando un WebService SOAP.

    Cómo manejar el cambio de definición del WebService

    Es muy probable que, durante el desarrollo al menos, la definición del servicio cambie y, por lo tanto, el WSDL también debería cambiar.

    Teniendo en cuenta que el WSDL es un archivo estático, basta con refrescarlo cada vez que cambie el servicio (o con una periodicidad razonable).

    Esto se puede lograr a través de, entre otras:

    Ahora sí, dicho esto, se acabó el misterio de los archivos WSDL

  • Cómo autenticar usuarios en WebServices SOAP usando PHP

    Cómo autenticar usuarios en WebServices SOAP usando PHP

    Muchas organizaciones (Especialmente gubernamentales) optan por exponer sus servicios web mediante el protocolo SOAP.

    Para hacer uso de dichos servicios es necesario consumirlos.

    Existen algunas ocasiones en las que no basta con conocer la URL del servicio, también es necesario realizar algún tipo de autenticación para obtener el resultado buscado.

    Autenticación HTTP en WebServices SOAP

    El método más simple de autenticación es el propio de HTTP.

    Si este es el caso, basta con generar una URL del estilo http://usuario@password:dominio/web_service para poder acceder.

    El principal problema de este método es su poca seguridad, ya que las credenciales viajan en cada pedido, por lo tanto, es bastante poco frecuente su uso en servicios web.

    En aplicaciones web normales podrías llegar a encontrártelo (o incluso podrías querer implementarlo).

    API-Key en WebServices SOAP

    Una segunda forma de realizar autenticación es aquella basada en las API-Keys.

    En este escenario, el proveedor del servicio debe decirte cuál es tu clave, la cual deberás enviar mediante algún encabezado como parte de tu petición.

    Para ello, asumiendo que utilices la clase SoapClient, deberás crear un nuevo contexto en el que basar tus peticiones, algo como:

    $soapclient = new SoapClient($wsdl, [ 
        'stream_context' => stream_context_create([ 
         'http'=> [ 
          'header' => "X-Api-Key: 1234abcd"    
         ] 
        ]) 
    ]); 

    Este esquema es algo más seguro, siempre y cuando se realice la comunicación a través de SSL.

    Autenticación via encabezados SOAP

    Otro esquema que suele utilizarse es el de la autenticación mediante encabezados SOAP.

    Este método permite que las credenciales viajen como parte del mensaje enviado lo cual puede ser deseable para evitar depender del protocolo subyacente (HTTP en la mayoría de los casos).

    Para lograrlo debes usar el método __setSoapHeaders y la clase SoapHeader.

    Ejemplo:

    $header = new SoapHeader(
                       $namespace,
                       'UserCredentials'
                       [
                             $UserID,
                             $Pwd
                       ]
    );
    
    $client->__setSoapHeaders($header);

    Claro que tanto el namespace como el nombre y estructura exacta del encabezado deberás validarlo contra el archivo WSDL del servicio al que te quieras conectar, pero la adaptación es simple.

    Autenticación vía WSSE

    Por último hay que mencionar un protocolo especial de seguridad para servicios web: WSSE (o WS-Security).

    Este protocolo es bastante complejo ya que incluye, entre otros, firmas digitales.

    Desafortunadamente, a la fecha no existe una implementación nativa de PHP para este tipo de autenticación, con lo cual no queda mucha opción que crear la nuestra o usar alguna desarrollada por un tercero.

    El punto clave aquí es hacer algunos toques a los encabezados que enviaremos.

    Para ello una buena opción es extender la clase SoapHeader de esta forma:

    class WsseAuthHeader extends SoapHeader 
    {
        private $wss_ns = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd';
    
        function __construct($user, $pass, $ns = null) 
        {
            if ($ns) {
                $this->wss_ns = $ns;
            }
    
           $auth = new stdClass();
           $auth->Username = new SoapVar($user, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns); 
           $auth->Password = new SoapVar($pass, XSD_STRING, NULL, $this->wss_ns, NULL, $this->wss_ns);
    
           $username_token = new stdClass();
           $username_token->UsernameToken = new SoapVar($auth, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns); 
    
           $security_sv = new SoapVar(
               new SoapVar($username_token, SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'UsernameToken', $this->wss_ns),
               SOAP_ENC_OBJECT, NULL, $this->wss_ns, 'Security', $this->wss_ns);
           parent::__construct($this->wss_ns, 'Security', $security_sv, true);
        }
    }

    Y luego simplemente se trata de agregar este nuevo encabezado a nuestro cliente:

    $client->__setSoapHeaders(new WsseAuthHeader( $UserID, $PWD));

    Y a partir de aquí ya es posible consumir los servicios con normalidad.

  • Cómo exponer un WebService SOAP con PHP

    Cómo exponer un WebService SOAP con PHP

    ¿Qué es un WebService?

    Ya en el artículo sobre cliente SOAP di una pequeña definición de un WebService (Una más exhaustiva está en el curso de WebServices con PHP), por si no lo leíste te lo cuento:

    Un WebService es una pequeña aplicación web diseñada para interactuar con otras aplicaciones (en lugar de hacerlo con personas).

    Las dos aplicaciones que se comunican toman el rol de:

    1. Servidor: quien expone el servicio
    2. Cliente: quien lo consume

    ¿Qué es SOAP?

    SOAP es un protocolo de intercambio de información basado en XML.

    ¿Cómo se implementa en PHP?

    Ahora que estamos claros con las definiciones veamos un ejemplo:

    server.php:

    <?php
    
    class MiClase
    {
     public function saludar()
     {
     return 'Hola ' . func_get_args()[0] . PHP_EOL;
     }
    }
    
    try {
     $server = new SoapServer(
     null,
     [
     'uri'=> 'http://localhost:8080/soap_server.php',
     ]
     );
    
     $server->setClass('MiClase');
     $server->handle();
    } catch (SOAPFault $f) {
     print $f->faultstring;
    }

    cliente.php

    <?php
    
    $client = new SoapClient(null, array(
          'location' => "http://localhost:8080/server.php",
          'uri'      => "http://localhost:8080/server.php",
          'trace'    => 1 ));
    
    try {
    	echo $return = $client->__soapCall("saludar", ["mundo!" ] );
    } catch ( SOAPFault $e ) {
    	echo $e->getMessage().PHP_EOL;
    }
    

    Para que todo esto tenga sentido, primero necesitamos tener un webserver levantado en localhost:8080. Para hacerlo simple, usemos el servidor incorporado al intérprete de PHP:

    php -S localhost:8080 &

    Y entonces, al ejecutar php soap_client.php veremos:

    Hola mundo!

    Si en lugar de publicar este script (server.php) en nuestro localhost lo subiéramos a un servidor accesible públicamente, cualquier aplicación conectarse a este servicio e invocar nuestro método saludar.

    Puedes utilizar un archivo WSDL para darle más robustez al servicio (y hacerlo descubrible también), pero por el momento tienes todo lo necesario para permitir a otras aplicaciones interactuar con la tuya a través de un WebService SOAP.

    ¡Feliz integración! 🙂

  • Cómo consumir un WebService SOAP con PHP

    Cómo consumir un WebService SOAP con PHP

    Qué son los WebServices

    Los WebServices son un mecanismo muy útil para integrar aplicaciones a través del protocolo HTTP, y de ese modo, aprovechar las capacidades de terceros dentro de nuestras propias aplicaciones.

    Un ejemplo muy común es de las pasarelas de pago, como ser PayPal o MercadoPago.

    Se basan siempre en la existencia de dos procesos:

    • El cliente (Consumidor)
    • El servidor (Productor)

    A nivel técnico existen dos operaciones que pueden realizarse a través de WebServices:

    1. Consumirlos
    2. Exponerlos

    Uno de los protocolos que pueden utilizar los WebServices es SOAP (Otro muy común es REST).

    Consumirlos usando PHP es bastante simple, para ello se utiliza la clase SoapClient.

    Cómo obtener la localización del visitante usando su IP

    Para este ejemplo usaremos el WebService de cdyne.com para obtener información geográfica en base a la IP buscada.

    <?php
    
    $url = "http://ws.cdyne.com/ip2geo/ip2geo.asmx?wsdl";
    
    try {
     $client = new SoapClient($url, [ "trace" => 1 ] );
     $result = $client->ResolveIP( [ "ipAddress" => $argv[1], "licenseKey" => "0" ] );
    
     print_r($result);
    } catch ( SoapFault $e ) {
     echo $e->getMessage();
    }
    
    
    echo PHP_EOL;

    En este caso, este script debería ser corrido desde CLI.

    Por ejemplo, si lo guardás como «ws.php», al ejecutar php ws.php 210.45.151.101 obtendrás la salida:

    stdClass Object
    (
     [ResolveIPResult] => stdClass Object
     (
     [City] => Huainan
     [StateProvince] => 01
     [Country] => China
     [Organization] => 
     [Latitude] => 32.6264
     [Longitude] => 116.9969
     [AreaCode] => 0
     [TimeZone] => 
     [HasDaylightSavings] => 
     [Certainty] => 90
     [RegionName] => 
     [CountryCode] => CN
     )
    )

    Como podrás observar, la respuesta del método ResolveIP es un objeto de tipo StdClass.

    StdClass es una clase genérica de PHP (Algo medio raro y, casi diría que un abuso de la naturaleza interpretada del lenguaje). Esta clase no tiene métodos ni propiedades definidas, pero sirve como una especie de contenedor al que se le puede asignar arbitrariamente todo lo que uno quiera (En rigor de verdad, esto puede hacerse con cualquier clase de PHP, sólo que es preferible no hacerlo).

    Básicamente, al construir el cliente a partir de la definición de un WSDL están disponibles todos los servicios expuestos como métodos propios (como si estuviesen accesibles en forma local, a pesar de que la verdadera llamada es remota).

    Es interesante notar esto, si ves la línea $result = $client->ResolveIP( [ "ipAddress" => $argv[1], "licenseKey" => "0" ] ); podrás notar que se está invocando al método ResolveIP sobre un objeto de clase SoapClient.

    La clase SoapClient es una clase estándar de PHP, mientras que el método ResolveIP sólo tiene sentido dentro de este WebService. Si te estás preguntando cómo puede una clase estándar reconocer métodos desconocidos te diría que deberías darle una mirada al tema de los métodos mágicos de PHP (O tomar el curso de PHP Orientado a Objetos).

    Como te imaginarás, si existe la clase SoapClient… debe existir la clase SoapServer (Tema de otro post).

    En el curso de PHP WebServices estudiamos este tema en mayor profundidad, mientras tanto, si te quedó alguna duda podés dejarla en un comentario.