Servidor RestFul con Harbour

Rafa Carmona
Harbour Magazine
Published in
8 min readNov 29, 2017

En la reunión que se celebró en Novelda, España, no tuve la oportunidad de mostrar el potencial de tener un servidor RestFul en un estado más simple. Esto seguramente se debe al poco tiempo disponible y que los conocimientos básicos son importantes para comprender las distintas partes que forman servidor RestFul y el tiempo apremiaba.

Como la conferencia tuve que partirla en dos, una para servidor web y otro como servidor RestFul, me faltó tiempo en explicar en más profundidad los detalles.

Decir que esto estará sobre Harbour 3.4, pues con los últimos cambios, es posible indicar que verbos del http o Métodos de petición vamos a manejar.
Pero no te preocupes, le meteremos el cuchillo a Harbour 3.2 en todo el core.prg para que puedas disfrutarlo ;-)

Aunque, lógicamente, no será un WS ‘puro’, tampoco eso nos tiene que preocupar mucho, ya que nuestro objetivo es cumplir con nuestras necesidades. En este ejemplo vamos a poder montar un servidor RestFul totalmente funcional, y aprenderemos a hacer CRUD sobre una tabla DBF.

Comentar , a diferencias de otros lenguajes, y en su concepción fue de actuar como un servidor web, no realiza el montaje automáticamente cuando queramos realizar cosas como http://cliente/1/factura/10, siendo eso nuestra responsabilidad el realizarlo y parsearlo.

Para ello vamos a montar un par de clases que nos va a extraer de la ‘supuesta’ complejidad de manejar las peticiones del cliente. Para las peticiones del cliente usaré la herramienta Postman, por cuestión de comodidad.

Pero para eso, voy a mostrar como hacerlo para que cualquiera pueda implementarlo en sus proyectos, aquí el boceto sobre el que trabajaremos, que al final quedará ligeramente diferente, pero no mucho.

Boceto de inspiración

Empecemos pues con la clase BaseController. Esta clase será la encargada de gestionar las peticiones que nos vienen desde el servidor hbhttpd.

Esta clase será la encargada de hacer las peticiones al objeto, a través de oService correspondiente. ¿ Y como sabe donde llamar ?
La explicación es que la clase que implementa el servicio debe de heredar de otra clase del tipo BaseService. Dicha clase implementa una serie de métodos , que deberán ser sobrescritos en la clase padre. Pero no te preocupes por ello ahora, lo veremos en los ejemplos y sabrás de dudas enseguida.

Siguiente con la clase BaseController, vamos a explicar cual es la metodología de implementar los controladores. La idea básica, es que podamos usar la clase de tipo BaseService , independiente si vamos a usar un servidor o no.

Clase BaseController

Si echamos un vistazo rápido, tenemos una clase bastante simple. ¿ Esperabas algo más complejo ? ;-) Vamos a empezar.

Controlamos que el servicio este instanciando

El método New( cID ), recibe como parámetro el ID , que puede estar vacía.
Además, se indica, a través de UAddHeader, que el mensaje de retorno va a ser del tipo JSON, y a continuación llama al método ::controller( cID ). Dicho método es el encargado de realizar la llamada correspondiente según el verbo http recibido. Y un apunte muy sencillo, si no se ha instancia el servicio nos dará un error 412, de esta manera localizaremos rápidamente si nos hemos olvidado de crear el servicio.

Determina el verbo http a procesar

Aquí lo único que hacemos es determinar que tipo de verbo http nos viene, GET, DELETE, PUT, POST. Ahora veremos cada cual.
El caso del GET, es especial, puede venir o no el ID. Si esta vacío, significa que desde la URI, ha sido lanzada desde http://127.0.0.1/clientes, por ejemplo. Si viniese el ID, sería http://127.0.0.1/clientes/120, siendo ID=120.

En el caso de PUT y POST, lo que se hace es discriminar el Content-type, en este caso solo vamos a permitir recibir JSON, en caso contrario, devolveremos un error http 415. Además, vamos a enviar el contenido del BODY_RAW, en forma de hash.

El resto de métodos

Y con estos métodos ya tenemos la clase lista. Estos métodos lo que hacen es llamar al método correspondiente del servicio instanciado previamente en la clase que hereda de esta clase. Observar que el método findAll(), va a enviar al servicio el offset y el limit, que las recogeremos desde la linea de la URI, para poder filtrar y paginar las consultas.

¿ Impresionante la sencillez ? ;-) Pues espera, que verás todavía en lo que se queda de simple la clase padre.

Ahora veremos la otra clase, BaseService, que nos servirá para completar la base sobre la que trabajar posteriormente.

Oh, otra sorpresa, simplicidad.
Tenemos que tener en cuenta que la variable uValue es la que vamos alimentar desde nuestra clase servicio.
La variable lError la usaremos para indicar si ocurre algún error , sobretodo es importante determinar en la instancia de nuestra clase, si alguna apertura de tabla, etc.. , indicarlo, para que el controlador posteriormente actue en consecuencia.

Métodos que tenemos que sobrecargar si queremos funcionalidad

Por defecto, si nos envían un verbo http, por ejemplo, DELETE, y el servicio nuestro no da soporte a DELETE, simplemente informará con un 501 que no está implementado.

Bien, estas dos clases tan simples son las que nos ahorraran un montón de horas. Y la pregunta es ¿ como usarlas ?

Lo primero será crear nuestra clase, StatusController.

Ya está. Lo único a tener en cuenta ;

  • Heredar de la clase BaseController
  • Method New recibe un parámetro, cID
  • Instanciar nuestro Servicio en la variable oService
  • Llamar a ::Super:New( cID ) para procesar la petición

En este punto, antes de continuar en la implementación de nuestro servicio, StatusServiceAPI, vamos a ver como podemos cambiar el funcionamiento que trae por defecto, por ejemplo, findAll().

Si recordamos, findAll(), recibe por defecto el offset y el limit. Nosotros seremos los encargados en el servicio de obtener esos valores y establecer nuestras reglas, ya sea en SQL, DBF, etc…

En el caso que , pongamos por defecto, recibir un tercer parámetro u otra serie de parámetros, lo conseguimos simplemente sobrecargado el method en nuestra clase que nos interesa, en este caso mi findAll() va a tratar más parámetros aparte del offset y limit;

Lógicamente, tendremos que poner memvar get, para acceder a la variable pública get del servidor RestFul. Esto es todo lo que hay que hacer en nuestra clase controller.

Nos vamos ya para terminar nuestra clase servicio que quedaba pendiente, StatusServiceAPI;

Sobrecarga de métodos que vayamos a implementar

Empecemos pues por la creación de un registro , a través de Postman;

Viendo la imagen, tenemos por un lado;

Detalle

Donde pone POST, seleccionaremos el verbo HTTP correspondiente, en este caso , para crear un registro POST.

También tenemos la URI, donde vamos a llamar ;
http://127.0.0.1:8002/v1/statusType

Por otro lado, seleccionamos Body, raw y JSON(application/json) y colocamos el JSON que vamos a enviar.

Si todo va bien tenemos el status y el tiempo que ha tardado en ejecutar la petición, en este caso, hemos devuelto un 201, para indicar que el registro ha sido creado.

¿ Y como lo hace ? He aquí el código:

Se llama al método Create( ), que recibe el Hash , y lo insertamos. En este ejemplo, he intentado aplicar los posibles errores, como el 409, que se esta intentando crear un elemento que ya existe. Recordad esto tiene que funcionar como mejor te convenga, intentando mantener un criterio lo más aproximado a lo que otros realizan.

Por último, comentar el método findAll(). La llamada desde el Postman;

Se devuelve un array de objetos status

Observar el paso de parametros, y lo que se ve, es el resultado del segundo registro, offset desplaza un registro en la dbf y limit obliga a coger solo uno. Como siempre aquí el código:

Creo que es suficiente para ver como se monta un servidor simple, podéis coger todo el código disponible en github;

https://github.com/rafathefull/restful

Lo nuevo de este sistema es que al tener separado el servicio del controlador, podemos usar directamente el servicio, independiente si esta corriendo en un servidor web o no.

Otra de las ventajas , es que podremos depurar nuestro servicio sin tener que correr un ws.

Bien, una vez visto lo esencial del sistema de controladores y servicios, vamos a ver como arrancamos esto, lo último que nos queda ;-)

Observar como se hace controlan 2 peticiones, una con parámetro y otra sin,no hay nada más!

Ah! Por cierto, se me olvidaba, para los Harbour 3.2, simplemente coger en las contrib/hbhbtppd/core.prg

En la función STATIC FUNCTION ProcessConnection()
Buscar donde este estas lineas , y añadir lo de negrita;
server := hb_HClone( aServer )
get := { => }
post := { => }
server[ “BODY_RAW” ] := NIL

En la funcion STATIC PROCEDURE ParseRequestBody( cRequest ), añadir lo negrita;
LOCAL nI, cPart, cEncoding
server[ “BODY_RAW” ] := cRequest

Bien, ahora solo nos toca poner los verbos, buscar GET POST y debéis encontrar esta linea;
ELSEIF ! server[ “REQUEST_METHOD” ] $ “GET POST” pues le añades los métodos a soportar;
ELSEIF ! server[ “REQUEST_METHOD” ] $ “GET POST PUT DELETE”
genera de nuevo la librería y ya está lista.

Espero que este articulo os brinde la oportunidad de hacer crecer vuestras aplicaciones y con ello a Harbour.

--

--