Artículo
· 2 hr atrás Lectura de 9 min

Solucionando problemas en APIs REST con %CSP.Request y %CSP.Response

Existen numerosas herramientas excelentes para probar vuestras APIs REST, especialmente cuando están en funcionamiento. Postman, distintas extensiones de navegador e incluso código personalizado en ObjectScript usando objetos %Net.HttpRequest pueden hacer el trabajo. Sin embargo, a menudo resulta complicado probar únicamente la API REST sin involucrar, sin querer, el esquema de autenticación, la configuración de la aplicación web o incluso la conectividad de red. Son muchos obstáculos solo para probar el código dentro de vuestra clase dispatch.

La buena noticia es que, si nos tomamos el tiempo para comprender cómo funciona internamente la clase %CSP.REST, encontraremos una alternativa que permite probar únicamente el contenido de la clase dispatch. Podemos configurar los objetos de request y response para invocar los métodos directamente.

Comprendiendo %request y %response

Si alguna vez habéis trabajado con APIs REST en InterSystems, probablemente os hayáis encontrado con los dos objetos que impulsan todo el proceso: %request y %response. Para entender su utilidad, debemos recordar algunos fundamentos de ObjectScript sobre las “variables con porcentaje”. Normalmente, una variable en ObjectScript solo puede ser accesible directamente dentro del proceso que la define. Sin embargo, una variable con el prefijo de porcentaje es pública dentro de ese proceso, lo que significa que todos los métodos del mismo proceso pueden acceder a ella. Por eso, al definir estas variables nosotros mismos, podemos invocar los métodos de nuestra API REST exactamente como se ejecutarían al recibir una solicitud real en la API.

Considerad la siguiente clase básica %CSP.REST. Cuenta con dos rutas y dos métodos correspondientes. La primera ruta acepta una solicitud que contiene una fecha de nacimiento en formato ODBC y devuelve la edad en días. La segunda ruta acepta un nombre como parámetro en la URL, comprueba que el método de la solicitud sea correcto y simplemente devuelve un mensaje de saludo.

Class User.REST Extends %CSP.REST
{

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
  <!-- Ruta que calcula la edad en días a partir de la fecha de nacimiento -->
  <Route Url="/calc/age" Method="POST" Call="CalcAge" />

  <!-- Ruta que devuelve un saludo con el nombre proporcionado en la URL -->
  <Route Url="/echoname/:david" Method="GET" Call="EchoName" />
</Routes>
}

ClassMethod CalcAge() As %Status
{
    try {
        // Obtenemos los datos de la solicitud en formato JSON
        set reqObj = {}.%FromJSON(%request.Content)
        set dob = reqObj.%Get("dob")

        // Calculamos la fecha actual y la fecha de nacimiento en formato $H
        set today = $P($H,",",1)
        set dobh = $ZDH(dob,3)

        // Calculamos la edad en días
        set agedays = today - dobh

        // Preparamos el objeto de respuesta
        set respObj = {}.%New()
        do respObj.%Set("age_in_days",agedays)

        // Definimos el tipo de contenido de la respuesta como JSON
        set %response.ContentType = "application/json"
        write respObj.%ToJSON()

        return $$$OK
    }
    catch ex {
        return ex.AsStatus()
    }
}

ClassMethod EchoName(name As %String) As %Status
{
    try {
        // Verificamos que el método de la solicitud sea GET
        if %request.Method '= "GET" {
            $$$ThrowStatus($$$ERROR($$$GeneralError,"Método incorrecto."))
        }

        // Devolvemos un mensaje de saludo
        write "Hola, "_name
    }
    catch ex {
        // Mensaje de error si ocurre algún problema
        write "Lo siento, "_name_". Me temo que no puedo hacer eso."
    }
}

} 

Si intentamos llamar a estos métodos desde una sesión de terminal, fallarán porque %request no está definido.

USER>w $SYSTEM.Status.GetErrorText(##class(User.REST).CalcAge())

ERROR #5002: Error de ObjectScript: <INVALID OREF>CalcAge+2^User.REST.1 

Normalmente, la clase %CSP.REST crea este objeto a partir de la solicitud HTTP recibida como una instancia de %CSP.Request. De la misma manera, el objeto %response se genera como una instancia de %CSP.Response.

USER>set %request = ##class(%CSP.Request).%New()

USER>set %request.Content = ##class(%CSP.CharacterStream).%New()

USER>do %request.Content.Write("{""dob"":""1900-01-01""}")

USER>set %response = ##class(%CSP.Response).%New()

USER>do ##class(User.REST).CalcAge()

{"age_in_days":45990}

En estas pocas líneas, diseñáis la solicitud e inicializáis su propiedad Content como un %CSP.CharacterStream. Este paso es vital, ya que, de lo contrario, esa propiedad permanecerá indefinida y no se podrá escribir en ella. Otra alternativa válida sería %CSP.BinaryStream. Llenáis el flujo de contenido con un JSON e inicializáis %response como un %CSP.Response sin necesidad de hacer nada más. Simplemente debe estar definido para que el método funcione.

Esta vez, al llamar a vuestro método de clase, podéis ver la salida esperada. También podríais usar el comando zwrite %response para inspeccionar el tipo de contenido de la respuesta o el código de estado HTTP, lo que ayuda a solucionar cualquier problema que pudiera surgir.

Probar la salida de vuestro método de clase solo requiere cinco líneas. Evidentemente, es un ejemplo simplificado a propósito. Sin embargo, es mucho más eficiente que navegar por el System Management Portal para configurar una aplicación y crear las solicitudes HTTP necesarias para autenticar y llamar a este método.

Además, os permite verificar el método en total aislamiento. Cuando enviáis una solicitud a través de las herramientas habituales para probar APIs REST, estáis comprobando toda la pila: conectividad de red, configuración de la aplicación, mecanismo de autenticación, despacho de la solicitud y el método, todo a la vez. El enfoque descrito anteriormente, en cambio, permite probar exclusivamente el método definido en la API, lo que facilita encontrar el problema durante la depuración.

De manera similar, podéis probar el segundo método configurando el objeto %request correspondiente y luego llamándolo. En este caso, pasaréis el nombre de la misma manera que lo haríais con cualquier otro método de clase.

USER>set %request = ##class(%CSP.Request).%New()

USER>set %request.Method = “GET”

USER>set %response = ##class(%CSP.Response).%New()

USER>do ##class(User.REST).EchoName(“Dave”)

Hola, Dave

Procesamiento de solicitudes

Podéis llevar esto un paso más allá y probar también el procesamiento de las solicitudes. Es posible que hayáis notado que en el ejemplo de EchoName comprobé manualmente el método HTTP de la solicitud CSP. Lo hice porque las llamadas directas a métodos omiten las reglas de enrutamiento, lo que normalmente evita un mapeo incorrecto.

Si decidís continuar con vuestra sesión de terminal usando el %request que habéis definido, podéis determinar su método HTTP de la siguiente manera:

USER>set %request.Method = “GET”

Para probar el procesamiento, simplemente tenéis que llamar al método DispatchRequest de la clase. Si todo funciona correctamente, volveréis a ver la salida de vuestro método:

USER>do ##class(User.REST).DispatchRequest(“/calc/age/Dave”,%request.Method)

Hola, Dave

Con el objeto %request correctamente configurado, incluyendo el método HTTP, podéis validar vuestras rutas. Podéis realizar una prueba similar para el endpoint de cálculo de edad configurando nuevamente vuestro %request como hicisteis antes y utilizando el método POST:

USER>set %request = ##class(%CSP.Request).%New()

USER>set %request.Content = ##class(%CSP.CharacterStream).%New()

USER>do %request.Content.Write("{""dob"":""1900-01-01""}")

USER>set %request.Method = “POST”

USER>set %response = ##class(%CSP.Response).%New()

USER>do ##class(User.REST).CalcAge()

USER>do ##class(User.REST).DispatchRequest(“/calc/age”,%request.Method)

{"age_in_days":45990}

Tenéis que tener en cuenta que hemos definido el argumento URL para el método DispatchRequest como únicamente la parte de la URL que se encuentra en el nodo route definido en el XData de la clase de despacho. ¡Esto es todo lo que necesitáis para probar el procesamiento! Esto os permite realizar pruebas de forma independiente, sin necesidad de conocer la URL final de despliegue ni la configuración de la aplicación web. De este modo, podéis probar todo lo que normalmente se define dentro de una clase %CSP.REST sin preocuparos por los factores externos habituales que la rodean.

Por cierto, he estado iniciando las sesiones creando %request como una nueva instancia de %CSP.Request. Sin embargo, si tenéis pensado hacer varias pruebas seguidas, también podéis usar el método %request.Reset() para reiniciar el objeto de solicitud. Lo mismo aplica para el objeto %response.

Dado que vuestra solicitud podría ser más compleja que este ejemplo, es recomendable que os familiaricéis con las propiedades y métodos de la clase %CSP.Request. Por ejemplo, es bastante común que vuestra API verifique el tipo de contenido de la solicitud y asegure que sea el esperado. En ese caso, podéis configurar %request.ContentType como application/json o cualquier otro que sea adecuado para vuestro uso. También podéis configurar cookies, datos MIME y datos de la solicitud. Además, podéis ajustar la propiedad Secure para simular si la solicitud utilizó HTTPS o no.

Un par de advertencias

Hay dos consideraciones principales que tenéis que tener en cuenta al probar de esta manera. Primero, si vuestra API devuelve un archivo, la salida en el terminal puede volverse un poco difícil de manejar. En esos casos, debéis redirigir la salida a un archivo. Podéis hacerlo con los siguientes comandos:

USER>set %request = ##class(%CSP.Request).%New()

USER>set %request.Method = "GET"
USER>set io = "C:\Users\DavidHockenbroch\Desktop\output.txt"
USER>open io:"NW":10
USER>write $TEST
1
USER>use io do ##class(User.REST).DispatchRequest("/echoname/Dave",%request.Method)

USER>close io

Ahora tengo un archivo de texto en el escritorio que dice “Hello, Dave.” Usar N y W en el comando open creará el archivo si aún no existe y lo abrirá para escritura. Tened en cuenta que comprobamos $TEST. Si esa variable es 0 al verificarla, ha habido un problema al abrir el archivo como dispositivo para escritura. Esto podría indicar un problema de permisos del archivo, o que el archivo ya esté abierto y bloqueado por otro proceso. Como buena práctica, siempre debéis recordar cerrar el dispositivo.

La segunda advertencia es que, si planeáis configurar una clase de eventos de sesión especial en la configuración de la aplicación web, y esos eventos personalizados afectan la forma en que funcionan vuestros métodos REST, puede causar problemas, ya que estamos omitiendo esos métodos. En ese caso, tendréis que llamarlos manualmente como métodos de clase desde el terminal.

¡Adelante!

Ahora que hemos establecido una manera de probar nuestra API sin complicaciones adicionales, estamos listos para pasar a nuestro verdadero objetivo: las pruebas unitarias. ¡No os perdáis el próximo artículo!

Comentarios (0)1
Inicie sesión o regístrese para continuar