viernes, 21 de septiembre de 2012

WS con gsoap a mysql

Hoy Quiero escribir con respecto a como crear un servicio web en C bajo el estándar SOAP utilizando GSOAP y que este conecte a una base de datos mysql. Lo anterior, debido a que busque información en la WEB y no encontré mucha documentación y/o ejemplos prácticos.

Esto puede servir para crear aplicaciones del tipo bpel y que la inteligencia de negocio sea por el lado del WS y estos se puedan mezclar.

En este ejemplo, se muestra el desarrollo y ejecución de dos servicios web básicos uno que cargue datos y el otro que lea datos desde una una tabla mysql, ademas de dos script que hacen las veces de cliente para los WS.

Requerimientos.

1) Linux y gcc instalado
2) Librerias de desarrollo mysql.
3) mysql
4) gsoap (yo utilizo la version 2.7)
5) PHP5 instalado para hacer los clientes.
6) Apache instalado con soporte cgi activo

Comenzamos.

1) Crear una tabla simple

CREATE TABLE `basededatos`.`alumnos` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`run` int(10) NOT NULL,
`nombre` varchar(45) NOT NULL,
`apellido1` varchar(45) NOT NULL,
`apellido2` varchar(45) NOT NULL,
`fec_nacim` date DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `run` (`run`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='tabla alumnos'

2) Con la tabla creada, procedemos a crear dos servicios web una para realizar insert y otro para select

a) Servicio de insert

Crear un archivo CreaAlumno.h

//gsoap ns service name: CreaAlumno
//gsoap ns service namespace: urn:CreaAlumno
//gsoap ns service location: http://localhost/cgi-bin/CreaAlumno.cgi
//gsoap ns schema namespace: urn:CreaAlumno

struct Datainput{
char *run;
char *nombre;
char *apellido1;
char *apellido2;
char *fec_nacim;
};

struct ns__getInfoResponse{
char *recepcion;
};

int ns__getInfo(struct Datainput *transaccion, struct ns__getInfoResponse *result_soap);

Crear un archivo CreaAlumno.c

#include "soapH.h" /* get the gSOAP-generated definitions */
#include "CreaAlumno.nsmap" /* get the gSOAP-generated namespace bindings */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <mysql/mysql.h>

#define PATH "/var/log/CreaAlumno/"
#define server "localhost"
#define user "root"
#define password ""
#define database "basededatos"
#define tabla "alumnos"

int main()
{
struct soap soap;
soap_init(&soap);

time_t tiempo = time(0);
struct tm *tlocal = localtime(&tiempo);
char fechaActual[128];
strftime(fechaActual,128,"%Y%m%d",tlocal);

char fechaHora[128];
strftime(fechaHora,128,"%Y%m%d%H%M%S",tlocal);

char logf1[1000000];
char logf2[1000000];
char logf3[1000000];

sprintf(logf1,"%s%s%s",PATH,fechaActual,"_entrada.log");
sprintf(logf2,"%s%s%s",PATH,fechaActual,"_salida.log");
sprintf(logf3,"%s%s%s",PATH,fechaHora,"_fordebug.log");
soap_set_recv_logfile(&soap, logf1);
soap_set_sent_logfile(&soap, logf2);
soap_set_test_logfile(&soap, logf3);

soap_serve(&soap);
}


int ns__getInfo(struct soap *soap, struct Datainput *transaccion, struct ns__getInfoResponse *result_soap)
{
MYSQL *conn;
MYSQL_ROW row;
MYSQL_RES *retorno;
char consulta[1000];
char run[10];
char nombre[45];
char apellido1[45];
char apellido2[45];
char fec_nacim[10];

sprintf(run,"%.11s",transaccion->run);
sprintf(nombre,"%.46s",transaccion->nombre);
sprintf(apellido1,"%.46s",transaccion->apellido1);
sprintf(apellido2,"%.46s",transaccion->apellido2);
sprintf(fec_nacim,"%.11s",transaccion->fec_nacim);

//conexion a base
conn = mysql_init(NULL);
if (!mysql_real_connect(conn, server, user, password, database, 0, NULL, 0))
{
return soap_sender_fault(soap, "Error -1", "ERROR Conexion con Base de Datos");
}

//inicia transaccion
if (mysql_query(conn,"START TRANSACTION"))
{
return soap_sender_fault(soap, "Error 0", "Registro No Ingresado");
mysql_query(conn,"ROLLBACK");
}


sprintf(consulta,"insert into %s (run,nombre,apellido1,apellido2,fec_nacim) values ('%s','%s','%s','%s','%s')",tabla,run,nombre,apellido1,apellido2,fec_nacim);

if (mysql_query(conn,consulta))
{
return soap_sender_fault(soap, "Error 3", "Registro No Ingresado");
mysql_query(conn,"ROLLBACK");
}

int identificador = mysql_insert_id(conn);
result_soap->recepcion="OK";

return SOAP_OK;
//cierra conexion
mysql_close(conn);

}


Crear un archivo Makefile

CFLAGS=-ggdb
MYSQLFLAGS=`mysql_config --libs`
SOAPDIR=/home/usuario/Descargas/gsoap-2.7/gsoap
all: CreaAlumno
CreaAlumno:
$(SOAPDIR)/bin/linux386/soapcpp2 -c CreaAlumno.h
gcc -D DEBUG CreaAlumno.c $(SOAPDIR)/stdsoap2.c soapC.c soapServer.c -o CreaAlumno.cgi -O3 -I$(SOAPDIR) -L $(MYSQLFLAGS) $(CFLAGS)


Luego desde consola, se compila utilizando make y si todo sale bien, debiese salir algo parecido a:

usuario@usuario:/home/usuario/crea_alumnos$ make
** The gSOAP Stub and Skeleton Compiler for C and C++ 2.7.9l
** Copyright (C) 2000-2007, Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The gSOAP compiler is released under one of the following three licenses:
** GPL, the gSOAP public license, or the commercial license by Genivia Inc.

Saving soapStub.h
Saving soapH.h
Saving soapC.c
Saving soapClient.c
Saving soapClientLib.c
Saving soapServer.c
Saving soapServerLib.c
Using ns service name: CreaAlumno
Using ns service style: document
Using ns service encoding: literal
Using ns service location: http://localhost/cgi-bin/CreaAlumno.cgi
Using ns schema namespace: urn:CreaAlumno
Saving CreaAlumno.wsdl Web Service description
Saving CreaAlumno.getInfo.req.xml sample SOAP/XML request
Saving CreaAlumno.getInfo.res.xml sample SOAP/XML response
Saving CreaAlumno.nsmap namespace mapping table
Saving ns.xsd XML schema

Compilation successful

Si todo sale OK en el paso anterior, ya podríamos probar el WS desde consola, pare ello, se debe modificar el archivo CreaAlumno.getInfo.req.xml agregando data a los nodos.

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ns="urn:CreaAlumno">
<SOAP-ENV:Body>
<ns:getInfo>
<transaccion>
<run>11111111</run>
<nombre>felipe</nombre>
<apellido1>aaaaa</apellido1>
<apellido2>aaaaa</apellido2>
<fec-nacim>20110101</fec-nacim>
</transaccion>
</ns:getInfo>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Posteriormente y desde consola
usuario@usuario:/home/usuario/crea_alumnos$./CreaAlumno.cgi < CreaAlumno.getInfo.req.xml

Y revisamos el retorno que entrega OK.

Status: 200 OK
Server: gSOAP/2.7
Content-Type: text/xml; charset=utf-8
Content-Length: 418
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:CreaAlumno"><SOAP-ENV:Body><ns:getInfoResponse><recepcion>OK</recepcion></ns:getInfoResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>

b) Servicio de consula (select)

Crear un archivo BuscaAlumno.h

//gsoap ns service name: BuscaAlumno
//gsoap ns service namespace: urn:BuscaAlumno
//gsoap ns service location: http://localhost/cgi-bin/BuscaAlumno.cgi
//gsoap ns schema namespace: urn:BuscaAlumno

struct Datainput{
char *run;
};

struct ns__getInfoResponse{
char *nombre;
char *apellido1;
char *apellido2;
char *fec_nacim;
};

int ns__getInfo(struct Datainput *transaccion, struct ns__getInfoResponse *result_soap);


Crear un archivo BuscaAlumno.c

#include "soapH.h" /* get the gSOAP-generated definitions */
#include "BuscaAlumno.nsmap" /* get the gSOAP-generated namespace bindings */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <mysql/mysql.h>

#define PATH "/var/log/BuscaAlumno/"
#define server "localhost"
#define user "root"
#define password ""
#define database "basededatos"
#define tabla "alumnos"

int main()
{
struct soap soap;
soap_init(&soap);

time_t tiempo = time(0);
struct tm *tlocal = localtime(&tiempo);
char fechaActual[128];
strftime(fechaActual,128,"%Y%m%d",tlocal);

char fechaHora[128];
strftime(fechaHora,128,"%Y%m%d%H%M%S",tlocal);


char logf1[1000000];
char logf2[1000000];
char logf3[1000000];

sprintf(logf1,"%s%s%s",PATH,fechaActual,"_entrada.log");
sprintf(logf2,"%s%s%s",PATH,fechaActual,"_salida.log");
sprintf(logf3,"%s%s%s",PATH,fechaHora,"_fordebug.log");
soap_set_recv_logfile(&soap, logf1);
soap_set_sent_logfile(&soap, logf2);
soap_set_test_logfile(&soap, logf3);

soap_serve(&soap);
}


int ns__getInfo(struct soap *soap, struct Datainput *transaccion, struct ns__getInfoResponse *result_soap)
{
MYSQL *conn;
MYSQL_ROW row;
MYSQL_RES *retorno;
char consulta[1000];
char run[10];

sprintf(run,"%.11s",transaccion->run);

//conexion a base
conn = mysql_init(NULL);
if (!mysql_real_connect(conn, server, user, password, database, 0, NULL, 0))
{
return soap_sender_fault(soap, "Error -1", "ERROR Conexion con Base de Datos");
}

sprintf(consulta,"select nombre,apellido1,apellido2,fec_nacim from %s where run = %s",tabla,run);

if (mysql_query(conn,consulta))
{
return soap_sender_fault(soap, "Error 3", "No pude rescatar dato");
}

retorno = mysql_store_result(conn);

while((row=mysql_fetch_row(retorno)))
{
result_soap->nombre=row[0];
result_soap->apellido1=row[1];
result_soap->apellido2=row[2];
result_soap->fec_nacim=row[3];
}

return SOAP_OK;
mysql_close(conn);

}



Crear un archivo Makefile

CFLAGS=-ggdb
MYSQLFLAGS=`mysql_config --libs`
SOAPDIR=/home/usuario/Descargas/gsoap-2.7/gsoap
all: BuscaAlumno
BuscaAlumno:
$(SOAPDIR)/bin/linux386/soapcpp2 -c BuscaAlumno.h
gcc -D DEBUG BuscaAlumno.c $(SOAPDIR)/stdsoap2.c soapC.c soapServer.c -o BuscaAlumno.cgi -O3 -I$(SOAPDIR) -L $(MYSQLFLAGS) $(CFLAGS)


Luego desde consola, se compila utilizando make y si todo sale bien, debiese salir algo parecido a:

usuario@usuario:/home/usuario/busca_alumnos$ make

** The gSOAP Stub and Skeleton Compiler for C and C++ 2.7.9l
** Copyright (C) 2000-2007, Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The gSOAP compiler is released under one of the following three licenses:
** GPL, the gSOAP public license, or the commercial license by Genivia Inc.

Saving soapStub.h
Saving soapH.h
Saving soapC.c
Saving soapClient.c
Saving soapClientLib.c
Saving soapServer.c
Saving soapServerLib.c
Using ns service name: BuscaAlumno
Using ns service style: document
Using ns service encoding: literal
Using ns service location: http://localhost/cgi-bin/BuscaAlumno.cgi
Using ns schema namespace: urn:BuscaAlumno
Saving BuscaAlumno.wsdl Web Service description
Saving BuscaAlumno.getInfo.req.xml sample SOAP/XML request
Saving BuscaAlumno.getInfo.res.xml sample SOAP/XML response
Saving BuscaAlumno.nsmap namespace mapping table
Saving ns.xsd XML schema

Compilation successful

Si todo sale OK en el paso anterior, ya podríamos probar el WS desde consola, pare ello, se debe modificar el archivo BuscaAlumno.getInfo.req.xml

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:ns="urn:BuscaAlumno">
<SOAP-ENV:Body>
<ns:getInfo>
<transaccion>
<run>11111111</run>
</transaccion>
</ns:getInfo>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Ahora ponemos en consola
usuario@usuario:/home/usuario/busca_alumnos$./BuscaAlumno.cgi < BuscaAlumno.getInfo.req.xml

Y revisamos el retorno que entrega OK.

Status: 200 OK
Server: gSOAP/2.7
Content-Type: text/xml; charset=utf-8
Content-Length: 504
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:BuscaAlumno"><SOAP-ENV:Body><ns:getInfoResponse><nombre>feli</nombre><apellido1>aaaaa</apellido1><apellido2>aaaaa</apellido2><fec-nacim>2011-01-01</fec-nacim></ns:getInfoResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>

c) Con los servicios creados y probados, ya podemos mover (utilizando cuenta root) los cgi a su ubicación definitiva que para mi es path /usr/lib/cgi-bin/ (la ruta se define en el archivo de configuración de apache con soporte para cgi)

Finalmente, se realizan los clientes PHP de prueba.


a) script cliente de carga:


<?php
define("path_wsdl", "/home/usuario/crea_alumnos/CreaAlumno.wsdl");
define("metodo", "getInfo");
define("location", "http://localhost/cgi-bin/CreaAlumno.cgi");
define("trace",1);

function CreaAlumno($run,$nombre,$apellido1,$apellido2,$fec_nacim)
{

$metodoIdentificacion = metodo;
$servicio=path_wsdl;
$parametros=array();
$parametros['location']=location;
$parametros['trace']=trace;
$datos['run'] = $run;
$datos['nombre'] = $nombre;
$datos['apellido1'] = $apellido1;
$datos['apellido2'] = $apellido2;
$datos['fec-nacim'] = $fec_nacim;
$parametros['transaccion']= $datos;
try {
$client = new SoapClient($servicio, $parametros);
$result = $client->$metodoIdentificacion($parametros);
$salida = "REQUEST:\n" . $client->__getLastRequest() . "\n";
$salida .= "REQUEST HEADERS:\n" . $client->__getLastRequestHeaders() . "\n";
$salida .= "Response:\n" . $client->__getLastResponse() . "\n";
$salida .= "Response:\n" . $client->__getLastResponseHeaders() . "\n";
return $salida;
}
catch (SoapFault $fault) {
$error = "REQUEST:\n" . $client->__getLastRequest() . "\n";
$error .= "REQUEST HEADERS:\n" . $client->__getLastRequestHeaders() . "\n";
$error .= "Response:\n" . $client->__getLastResponse() . "\n";
$error .= "Response:\n" . $client->__getLastResponseHeaders() . "\n";
return $error;
}
}

print_r(CreaAlumno('3333333','felipe','aaaaa','bbbbb','20110101'));

?>

Al ejecutar el script debiese retornar algo parecido a:


REQUEST:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:CreaAlumno"><SOAP-ENV:Body><ns1:getInfo><transaccion><run>3333333</run><nombre>felipe</nombre><apellido1>aaaaa</apellido1><apellido2>bbbbb</apellido2><fec-nacim>20110101</fec-nacim></transaccion></ns1:getInfo></SOAP-ENV:Body></SOAP-ENV:Envelope>
REQUEST HEADERS:
POST /cgi-bin/CreaAlumno.cgi HTTP/1.1
Host: localhost
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.3.2-1ubuntu4.9
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 378
Response:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:CreaAlumno"><SOAP-ENV:Body><ns:getInfoResponse><recepcion>OK</recepcion></ns:getInfoResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>

Response:
HTTP/1.1 200 OK
Date: Fri, 21 Sep 2012 19:07:43 GMT
Server: Apache/2.2.14 (Ubuntu)
Connection: close
Content-Length: 418
Vary: Accept-Encoding
Content-Type: text/xml; charset=utf-8

b) script cliente de consulta

<?php
define("path_wsdl", "/home/usuario/busca_alumnos/BuscaAlumno.wsdl");
define("metodo", "getInfo");
define("location", "http://localhost/cgi-bin/BuscaAlumno.cgi");
define("trace",1);

function BuscaAlumno($run)
{

$metodoIdentificacion = metodo;
$servicio=path_wsdl;
$parametros=array();
$parametros['location']=location;
$parametros['trace']=trace;
$datos['run'] = $run;
$parametros['transaccion']= $datos;
try {
$client = new SoapClient($servicio, $parametros);
$result = $client->$metodoIdentificacion($parametros);
$salida = "REQUEST:\n" . $client->__getLastRequest() . "\n";
$salida .= "REQUEST HEADERS:\n" . $client->__getLastRequestHeaders() . "\n";
$salida .= "Response:\n" . $client->__getLastResponse() . "\n";
$salida .= "Response:\n" . $client->__getLastResponseHeaders() . "\n";
return $salida;
}
catch (SoapFault $fault) {
$error = "REQUEST:\n" . $client->__getLastRequest() . "\n";
$error .= "REQUEST HEADERS:\n" . $client->__getLastRequestHeaders() . "\n";
$error .= "Response:\n" . $client->__getLastResponse() . "\n";
$error .= "Response:\n" . $client->__getLastResponseHeaders() . "\n";
return $error;
}
}

print_r(BuscaAlumno('3333333'));

?>

Al ejecutar el script debiese retornar algo parecido a:

REQUEST:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:BuscaAlumno"><SOAP-ENV:Body><ns1:getInfo><transaccion><run>3333333</run></transaccion></ns1:getInfo></SOAP-ENV:Body></SOAP-ENV:Envelope>
REQUEST HEADERS:
POST /cgi-bin/BuscaAlumno.cgi HTTP/1.1
Host: localhost
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.3.2-1ubuntu4.9
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 269
Response:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:BuscaAlumno"><SOAP-ENV:Body><ns:getInfoResponse><nombre>felipe</nombre><apellido1>aaaaa</apellido1><apellido2>bbbbb</apellido2><fec-nacim>2011-01-01</fec-nacim></ns:getInfoResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
Response:
HTTP/1.1 200 OK
Date: Fri, 21 Sep 2012 19:36:04 GMT
Server: Apache/2.2.14 (Ubuntu)
Connection: close
Content-Length: 506
Vary: Accept-Encoding
Content-Type: text/xml; charset=utf-8


Nota:

Los PATH de logs definidos en los archivos C, se no son creados de forma automatica, deben ser creados de forma manual y dar los respectivos permisos.