Ejecutar un servicio WCF desde PHP

En un proyecto reciente he tenido que crear un web service con WCF para que sea consumido por un programa cliente escrito en PHP.

Por supuesto, el servicio lo he programado con .NET. En concreto he realizado un servicio de WCF (Windows Communication Foundation) con enlace por http hospedado en un servidor IIS. Es decir, un web service.

Se da el caso de que el programa es un servicio especifico para una empresa diferente a la que trabajo yo, donde desarrollan exclusivamente en PHP. Yo hice mi progroma tranquilamente, el enlace lo defini como wsHttpBinding (estandart actual), y lo probe con un cliente .NET y con un cliente Java - SoapUI -, funcionando a la perfeccion en ambos casos.

Pero el servicio era para ser consumido desde PHP ... Como me gusta probar bien las cosas - por mucho que los web service sean estandares maravillosos y que si funciona con un cliente .NET o Java debe funcionar en PHP, Phyton o RoR -, me descargue mi Aptana para probar el servicio. Realice un sencillo programilla para probar mi servicio y no son pocos los problemas que encontre.

Para trabajar con PHP y web services tenemos dos posibilidades:

  1. La librería nuSOAP - se trata de una librería open source que debemos descargar y referenciar en nuestro proyecto.
  2. Soporte nativo de PHP para web service. Es un soporte experimental y solo está disponible para version 5 o superior.

Mi primera opcion fue la librería nuSOAP - es la que usaba la otra empresa y eso de "experimental" en el soporte nativo me daba "yuyu", mucho "yuyu".

Con la librería nuSOAP no fui capaz de enviar parametros de tipo complejo - es decir clases como parámetros del web service -, asi que finalmente utilice el soporte nativo de PHP 5. 

Como no son pocos los aspectos a tener en cuenta vamos a ver un ejemplo completo que funciona. La idea es simple, vamos a crear un servicio para guardar datos de "personas" en archivos Xml.

He utilizado Visual Studio 2008 - .NET Framework 3.5 SP1 -  y Aptana con PHP 5 como entornos de desarrollo.

El servicio de WCF

Lo primero debemos crear con Visual Studio una nueva Solucion en blanco. Agregamos ahora un proyecto de tipo "Web - Servicio de WCF" al que llamamos WcfPersonasService.

El servicio va a ser muy simple, con un único método para añadir una persona , al que llamamos AddPersona y que recibe como único parámetro un objeto Persona.

Lo primero definimos la interface del servicio:

 

[ServiceContract]

public interface IWcfPersonasService

{

[OperationContract]

AddPersonaResult AddPersona(Persona persona);

}

Hemos decorado la iterface con ServiceContract y el método con OperationContract para que se exponga correctamente en la fachada del servicio.

Como podemos ver el método AddPersona recibe un único paramétro de tipo Persona  y devuelve un AddPersonaResult  - todos tipos complejos (clases), por lo que no podemos utilizar nuSOAP - o al menos yo no he sido capaz :-(

Ahora necesitamos las clases necesarias para el servicio, Persona - que a su vez requiere de Documento - y AddPersonaResult .

 

[DataContract]

public class Persona

{

[DataMember]

public string Nombre { get; set; }

 

[DataMember]

public string PrimerApellido { get; set; }

 

[DataMember]

public string SegundoApellido { get; set; }

 

[DataMember]

public Documento Documento { get; set; }

}

 

[DataContract]

public class AddPersonaResult

{

[DataMember]

public string ReturnCode { get; set; }

 

[DataMember]

public string Mensaje { get; set; }

}

 

[DataContract]

public class Documento

{

[DataMember]

public string Tipo { get; set; }

[DataMember]

public string Numero { get; set; }

[DataMember]

public string Letra { get; set; }

}

Decoramos las clases DataContract y DataMember para que se expongan correctamente a través del servicio.

La implementacion.

A continuacion escribimos nuestra clase para implementar la interface que acabamos de definir. No nos complicaremos mucho, simplemente guardaremos los datos que recibimos en un fichero xml. Tendremos en cuenta que hemos renombrado los ficheros de Service1 a WcfPersonasService.

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

using PersonasWCFService;

using System.IO;

using System.Xml.Serialization;

using System.Xml;

 

namespace WcfPersonasService

{

public class WcfPersonasService : IWcfPersonasService

{

public AddPersonaResult AddPersona(Persona persona)

{

try

{

string path =

String.Format("C:\\temp\\Persona{0}.xml", Guid.NewGuid());

using (FileStream fs = new FileStream(path, FileMode.CreateNew))

{

XmlSerializer serializador =

new XmlSerializer(persona.GetType());

serializador.Serialize(fs, persona);

fs.Close();

return new AddPersonaResult { ReturnCode = "OK" };

}

}

catch (Exception)

{

return new AddPersonaResult {

ReturnCode = "ERR",

Mensaje="Se ha producido un error" };

}

}

}

}

Como queremos publicar el servicio en un servidor web IIS, necesitamos el archivo WcfPersonasService.svc - que Visual Studio debe haber creado cuando hemos creado el proyecto de tipo "Web , servicio de WCF".

 

<%@ ServiceHost Language="C#"

Service="WcfPersonasService.WcfPersonasService"

CodeBehind="WcfPersonasService.svc.cs" %>

La configuracion siempre es un punto delicado en WCF , el archivo de configuracion no es más que in web.config clásico con la seccion system.serviceModel.

Lo primero recordar que es un EndPoint, se trata de un concepto básico e imprescindible en WCF. Un EndPoint es cada uno de los extremos que intervienen en una comunicacion. El EndPoint define tres elementos básicos de la comunicacion:

  • Adress - la direccion URI donde se expone el servicio.
  • Binding - el enlace que vamos a utilizar. Este parametro define el canal de la comunicacion.
  • Contract - la interface, es decir las operaciones que vamos a poder realizar.

Al conjunto de estas tres propiedades se las conoces como el "ABC" de WCF, en alusion a las iniciales de Adress, Binding y Contract. En realidad el EndPoint dispone de más parametros pero no dejan de ser especificaciones de estos tres.

Es importante destacar que PHP no es compatible con la version actual de SOAP por lo que nuestro binding debe ser forzosamente basicHttpBinding - que se correspondría con SOAP 1.1 - y no wsHttpBinding - SOAP 1.2-

Configuraremos nuestro servidor de la siguiente manera.

 

<system.serviceModel>

<bindings>

<basicHttpBinding>

<binding name="HttpBinding" />

</basicHttpBinding>

</bindings>

<services>

<service

behaviorConfiguration="WcfPersonasService.DefaultBehavior"

name="WcfPersonasService.WcfPersonasService">

<endpoint name="HttpPersonasService"

address="http://localhost:12730/WcfPersonasService.svc"

binding="basicHttpBinding"

contract="WcfPersonasService.IWcfPersonasService"

bindingConfiguration="HttpBinding" />

<endpoint name="mex"

address="mex"

binding="mexHttpBinding"

contract="IMetadataExchange" />

</service>

</services>

<behaviors>

<serviceBehaviors>

<behavior name="WcfPersonasService.DefaultBehavior">

<serviceMetadata httpGetEnabled="true" />

<serviceDebug includeExceptionDetailInFaults="false" />

</behavior>

</serviceBehaviors>

</behaviors>

</system.serviceModel>

Existe un segundo EndPoint, el llamado "mex" que se encarga de proporcionar los metadatos y que añade WCF por defecto.

El cliente .NET

Ahora creamos una aplicacion de Windows forms para probar nuestro servicio. AgrEgamos un nuevo proyecto a la solucion de Visual Studio, de tipo formualrios de windows.

Ahora necesitamos agragar la referencia al servicio. Sobre el explorador de Soluciones, agregamos una referencia al servicio , que en nuestro caso está ubicado en http://localhost:12730/WcfPersonasService.svc.

Cuando añadimos la referencia, Visual Studio crea una clase "proxy" para trabajar con el servicio. Solo tenemos que instanciar esta clase - WcfPersonasServiceClient - e invocar al método AddPersona para ejecutar el servicio.

Si configuramos Visual Studio para ver los archivos ocultos podremos ver el código que se genera al añadir una referencia al servicio.

Ahora volvemos al formulario por defecto de la aplicacion - Form1 -, añadimos un boton y lo programamos con el siguiente código.

 

private void button1_Click(object sender, EventArgs e)

{

AddPersonaResult res = null;

using (WcfPersonasServiceClient client = new WcfPersonasServiceClient())

{

Persona persona = new Persona();

persona.Nombre = "Pedro";

persona.PrimerApellido = "Herrarte";

persona.SegundoApellido = "Sanchez";

 

persona.Documento =

new Documento { Tipo = "NIF",

Numero = "00000000",

Letra = "T" };

 

res = client.AddPersona(persona);

}

MessageBox.Show(res.ReturnCode);

}

El código es muy sencillo: instanciamos el proxy, asignamos las propiedades y ejecutamos el método AddPersona.

El cliente de .NET es otro extremo de la configuracion ... por lo tanto tenemos que configurar el EndPoint correspondiente. Esto se hace al agragar una nueva "Service Reference"

El archivo de configuracion:

 

<system.serviceModel>

<bindings>

<basicHttpBinding>

<binding name="HttpPersonasService"

closeTimeout="00:01:00"

openTimeout="00:01:00"

receiveTimeout="00:10:00"

sendTimeout="00:01:00"

allowCookies="false"

bypassProxyOnLocal="false"

hostNameComparisonMode="StrongWildcard"

maxBufferSize="65536"

maxBufferPoolSize="524288"

maxReceivedMessageSize="65536"

messageEncoding="Text"

textEncoding="utf-8"

transferMode="Buffered"

useDefaultWebProxy="true">

<readerQuotas maxDepth="32"

maxStringContentLength="8192"

maxArrayLength="16384"

maxBytesPerRead="4096"

maxNameTableCharCount="16384" />

<security mode="None">

<transport

clientCredentialType="None"

proxyCredentialType="None"

realm="" />

<message clientCredentialType="UserName"

algorithmSuite="Default" />

</security>

</binding>

</basicHttpBinding>

</bindings>

<client>

<endpoint

address="http://localhost:12730/WcfPersonasService.svc"

binding="basicHttpBinding"

contract="PersonasServiceReference.IWcfPersonasService"

bindingConfiguration="HttpPersonasService"

name="HttpPersonasService" />

</client>

</system.serviceModel>

Tener toda la configuracion está en el archivo externo completamente desacoplado del código. WCF.

El cliente PHP

Por fin el úlitmo eslabon de la cadena. El cliente de PHP.

Abrimos nuestro editor Aptana y creamos un nuevo proyecto de PHP. Antes de nada tendremos que tener en cuenta:

  • Solo vamos a poder consumir el servicio si este se expone a través de basicHttpBinding. PHP no es compatible con el estandar SOAP 1.2. 
  • Con la librería nuSOAP no he consguido invocar métodos con tipos complejos (clases) como parámetros.

Bien, el proceso no es complejo - cuando se sabe como!. Los pasos a seguir son:

  • Crear una instancia de la clase SoapClient.Necesita de la dirección de descripcion del servicio (WSDL)
  • Representar los tipos complejos como arrays.
  • Envolver todo en un nuevo array y llamar al método. Hay que tener en cuenta que el nombre de este array debe coincidir con el nombre del parámetro en el servicio - en caso contrario se recibirá un valor null en el servicio.

El código de la página es el siguiente:

 

<?php

$wsdl = "http://localhost:12730/WcfPersonasService.svc?wsdl";

$soapClient = new SoapClient($wsdl);

$documento = array('Tipo' => 'NIF',

'Numero' => '000000000001',

'Letra' => 'K');

$persona = array ('Nombre' => 'Pedro',

'PrimerApellido' => 'Herrarte',

'SegundoApellido' => 'PHP',

'Documento' => $documento);

 

$retval = $soapClient->AddPersona(array('persona' => $persona));

$code = $retval->AddPersonaResult->ReturnCode;

 

if ($code == "OK") {

echo "<h1>Proceso correcto</h1>";

}

else {

echo "<h1>Se ha producido error</h1>";

echo "<h2>" + $retval->AddPersonaResult->Message + "<h2>";

}

?>

Con esto estaremos consumiendo un servicio de WCF desde PHP. Objetivo cumplido.

Consideraciones sobre el rendimiento y conclusiones.

Me llama muchisimo la atención el hecho del escasisimo soporte que ofrece PHP para trabajar con web services, las lista de quejas es larga:

  • Soporte experimental en las clases nativas.
  • Compatibilidad reducida. Solo es compatible con SOAP 1.1
  • Trabajo basado en arrays, es decir: no tipado y susceptible a errores.

Pero donde de verdad PHP falla es en el rendimiento. Cada vez que neceistemos invocar al web service, es necesario llamar antes al wsdl. Esto quiere decir que cada vez que quiero ejecutar el método "AddPersona",PHP pide antes al servcio la clase Persona, la clase Documento,  ... y finalmente ejecuta el método. Esto reduce al mínimo el rendimiento y "mata" toda posibilidad de escalabilidad del sistema.

Vamos que no me hace ninguna gracia.

Saludos, DJK

 

Pedro  Herrarte  Sánchez
Ejecutar un servicio WCF desde PHP
Pedro Herrarte Sánchez

Pedro Herrarte, es consultor independiente, ofreciendo servicios de consultoría, análisis, desarrollo y formación. Posee mas de diez años de experiencia trabajando para las principales empresas de España. Es especialista en tecnologías .NET, entornos Web (ASP.NET, ASP.NET MVC,jQuery, HTML5), bases de datos (SQL Server y ORACLE) e integración de sistemas. Es experto en desarrollo (C#, VB.Net, T-SQL, PL/SQL, , ASP, CGI , C, Pro*C, Java, Essbase, Vignette, PowerBuilder y Visual Basic ...) y bases de datos (SQL Server y ORACLE). Pedro es MCP y MAP 2012, es fundador, diseñador y programador de www.devjoker.com..
Fecha de alta:17/09/2009
Última actualizacion:17/09/2009
Visitas totales:17270
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com