Solapas principales

Pregunta
Jose Giraldo · Sep 22, 2020

HTTPMessage Valor vacío en Stream

Tengo una operación con la clase EnsLib.HTTP.GenericOperation . Esta operación envía el siguiente mensaje json :

<HTTPMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="http://www.w3.org/2001/XMLSchema">

<Stream>

{ "resourceType":"Bundle", "id":"ec5ffdbe-3739-47e6-81e4-2b0498e99fb8", "meta":[ { "lastUpdated":"2020-09-20T15:04:17.764+02:00" }], "type":"searchset", "total":"1", "link":[ { "relation":"self", "url":"https:\/\/xxx:5665\/api\/fhir\/DSTU3\/CarePlan?encounter=861241" }], "entry":[ { "fullUrl":"https:\/\/xxx:5665\/api\/fhir\/DSTU3\/CarePlan\/23104", "resource":[ { "resourceType":"CarePlan", "id":"23104", "identifier":[ { "system":"urn:hcis:mrns", "value":"23104" }] }], "addresses":[ { "reference":"Condition\/76741" }], "subject":[ { "reference":"Patient\/10055765" }] }] }

</Stream><Type>

CG

</Type>

</HTTPMessage>

Lo envío al mismo namespace (localhost: 9980) a un servicio con la clase EnsLib.REST.GenericService. Cuando recibo, el flujo de mensajes está vacío. ¿Por qué?

<HTTPMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="http://www.w3.org/2001/XMLSchema">

<Stream></Stream>

<Type>BG</Type>

<HTTPHeaders><HTTPHeadersItem HTTPHeadersKey="CONTENT-LENGTH">0</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="CONTENT-TYPE">text/plain; charset="UTF-8"

</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="StatusLine">HTTP/1.1 500 Internal Server Error</HTTPHeadersItem></HTTPHeaders>

</HTTPMessage>

00
5 0 15 86

Respuestas

Puede que me equivoque, pero estoy viendo un 500 Internal Server Error, eso es un error que te devuelve el servicio. Por lo tanto ni siquiera se producen los mensajes. ¿Puedes elevar el nivel de log o meter trazas para saber qué está pasando? Por otro lado veo que el ContentType es "text/plain" y debería ser "application/json"

Buenas

He configurado los logs del servicio y no deja rastros.

Realice algunos cambios y configure el servicio con la clase de caracteres JSON

Cuando veo el mensaje en el servicio veo lo siguiente

<HTTPMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="http://www.w3.org/2001/XMLSchema">

<Type>GC</Type>

<HTTPHeaders>

<HTTPHeadersItem HTTPHeadersKey="CharEncoding">@JSON</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="EnsConfigName">HTTPWorkBench</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="HTTPVersion">1.1</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="HttpRequest">POST</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="IParams">0</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="RawParams" xsi:nil="true"></HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="TranslationTable">JSON</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="URL">/</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="content-length">0</HTTPHeadersItem>

<HTTPHeadersItem HTTPHeadersKey="host">localhost:9980</HTTPHeadersItem></HTTPHeaders>

</HTTPMessage>

Si veo en la operación la respuesta al JSON veo lo siguiente.

<Stream></Stream>

<Type>BG</Type>

<HTTPHeaders><HTTPHeadersItem HTTPHeadersKey="CONTENT-LENGTH">0</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="CONTENT-TYPE">text/plain; charset="UTF-8"</HTTPHeadersItem><HTTPHeadersItem HTTPHeadersKey="StatusLine">HTTP/1.1 500 Internal Server Error</HTTPHeadersItem></HTTPHeaders>

</HTTPMessage>

El codigo de la operacion que envio es el siguiente:

Class Test.BO.Adapter.HTTPOutboundAdapter Extends EnsLib.HTTP.OutboundAdapter
{ 
/// Send a POST to the configured Server, Port and URL, sending form data to the named form variables.
Method Post(Output pHttpResponse As %Net.HttpResponse, pFormVarNames As %String, pData...) As %Status
{     quit ..SendFormDataArray(.pHttpResponse, "POST", ..GetRequest(), .pFormVarNames, .pData)
}

ClassMethod GetRequest() As %Net.HttpRequest
{
    set request = ##class(%Net.HttpRequest).%New()
    set request.ContentType = "application/json"
    quit request
}
}

El adaptador que esta recibiendo es un EnsLib.REST.GenericService, deberia crear una clase que heredara de la anterior, ¿para poner los log que me indicas?¿Como los pondría para ver si recibo algún valor cuando el stream esta vacio?

He revisado en BBDD la tabla EnsLib_HTTP.GenericMessage  y en streamBG veo 

3485%Stream.GlobalBinary^CacheStream

No hay logica aun, ya que no tengo claro que este recibiendo el json, y por tanto es logico el error 500.

Un Saludo

Para empezar veo que estás creando una clase Adaptador que hereda de Test.BO.Adapter.HTTPOutboundAdapter. Eso no es una Operación. Es un adaptador.

Y la verdad no veo dónde estás metiendo los datos del Body... quizá hay más código

Yo mejor haría una operación que herede de EnsLib.REST.Operation.

Ejemplo:

Class demo.bo.RESTOperation Extends EnsLib.REST.Operation
{

Parameter INVOCATION = "Queue";

Method SendHTTP(pHttpVerb As %String, pURL As %String, Output pHttpResponse As %Net.HttpResponse, jsonObject As %JSON.Adaptor) As %Status
{
    set httpRequest = ##class(%Net.HttpRequest).%New()
    set httpRequest.ContentType = "application/json"
    if $IsObject(jsonObject) {
        set tSC=jsonObject.%JSONExportToStream(httpRequest.EntityBody)
        if $$$ISERR(tSC) quit tSC
    }  
    set tSC = ..Adapter.SendFormDataURL(pURL,.pHttpResponse,pHttpVerb,httpRequest)
    quit tSC
}

Method PostJSON(pURL As %String, Output pHttpResponse As %Net.HttpResponse, jsonObject As %JSON.Adaptor) As %Status
{
    Return ..SendHTTP("POST",pURL,.pHttpResponse,jsonObject)
} 

Method OnPostRequest(pRequest As demo.PostRequest, Output pResponse As demo.PostResponse) As %Status
{
    set tURL=..Adapter.URL_"/demo"
    set tSC=..PostJSON(tURL,.tHttpResponse,pRequest.orden)
    if $$$ISERR(tSC)&&$IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data)&&tHttpResponse.Data.Size {
        set tSC=$$$ERROR($$$EnsErrGeneral,$$$StatusDisplayString(tSC)_":"_tHttpResponse.Data.Read())
    }
    if $$$ISERR(tSC) quit tSC
    // procesar respuesta
    set pResponse = ##class(demo.msg.PostResponse).%New()
    set pResponse.respuesta = ##class(demo.msg.Respuesta).%New()
    if tHttpResponse.StatusCode=200 {
        $$$TRACE(tHttpResponse.Data.Read())
        set tSC = pResponse.respuesta.%JSONImport(tHttpResponse.Data)
    } else { 
        set tSC=$$$ERROR($$$EnsErrGeneral,"HTTP Status:"_tHttpResponse.StatusCode)
    }
    quit tSC
}



XData MessageMap
{
<MapItems>
    <MapItem MessageType="demo.PostRequest"> 
        <Method>OnPostRequest</Method>
    </MapItem>
</MapItems>
}

}

Buenas David,

Estaba enfocando el error en el servicio (EnsLib.HTTP.GenericOperation) mas que en en el adaptador (Test.BO.Adapter.HTTPOutboundAdapter). Ya que lo tengo enviando por post a un servicio rest con otra ip, y me esta funcionando. Voy a revisar el codigo que me pasas e implementarlo. Al crear la clase (tal cual) me da el siguiente error:

¿sabes por que puede ser?

La clase demo.PostRequest no existe en tu Namespace

Cierto tienes razon, lo mire demasiado rapido.

He creado las siguientes clases:

  • demo.PostRequest
  • demo.msg.PostResponse
  • demo.msg.Respuesta
  • demo.PostResponse

¿Podria haber usado en su lugar  %Stream.GlobalCharacter en su lugar? ¿Las clases que valores deben tener? No tengo muy claro que propiedades debe tener la clase que contenga el mensaje JSON.

Aun asi me dice que debo crear la clase %JSON.Adaptador, entiendo que es una clase de sistema, yo no la tengo. Mi versión es cliente 2018.2 version 309.

Ando muy permido en este asunto.

Glup, eso no me lo esperaba!!... tendría que mirar desde cuándo está el JSON Adaptor

Pero para tu problema eso va a dar igual, me explico. El parámetro "jsonObject" es el que utiliza esa clase %JSON.Adaptor pero esa clase lo único que hace es ayudarte a manejar objetos JSON. Para resolver tu problema inicial en lugar de usar esa clase utiliza como tu dices un Stream. Por ejemplo el %Stream.GlobalCharacter.

Esta sentencia:

set tSC=..PostJSON(tURL,.tHttpResponse,pRequest.orden)

Lo que hace es pasar la URL, el objeto donde se va a colocar la respuesta a la vuelta (por referencia) y el contenido del mensaje pRequest. En este caso puedes inventarte un objeto tuyo que lleve un Stream con un JSON en texto plano para probar.

En este método:

Method PostJSON(pURL As %String, Output pHttpResponse As %Net.HttpResponse, pStream As %Stream.GlobalCharacter) As %Status 
{
  Return ..SendHTTP("POST",pURL,.pHttpResponse,pStream) 
}

cambia la firma del método para usar el Stream. Y finalmente en este método envía el contenido como Body de la llamada HTTP.

Method SendHTTP(pHttpVerb As %String, pURL As %String, Output pHttpResponse As %Net.HttpResponse, pStream As %Stream.GlobalCharacter) As %Status 
{  
set httpRequest = ##class(%Net.HttpRequest).%New()     
set httpRequest.ContentType = "application/json"     
if $IsObject(jsonObject) {         
  set tSC=httpRequest.EntityBody.CopyFrom(pStream)
  if $$$ISERR(tSC)
    quit tSC     
}
set tSC = ..Adapter.SendFormDataURL(pURL,.pHttpResponse,pHttpVerb,httpRequest)     
quit tSC 
}

No lo he probado. Creo que el uso del método CopyFrom es correcto pero si te falla es fácil copiar el contenido de un Stream a otro mediante Write y Read.

Espero que te sirva. 

Gracias David, pero vuelvo a la Carga

He implementado la clase de la siguiente manera:

Class demo.bo.RESTOperation Extends EnsLib.REST.Operation
{ Parameter INVOCATION = "Queue"; Method SendHTTP(pHttpVerb As %String, pURL As %String, Output pHttpResponse As %Net.HttpResponse, pStream As %Stream.GlobalCharacter) As %Status
{
    set httpRequest = ##class(%Net.HttpRequest).%New()
    set httpRequest.ContentType = "application/json"
    if $IsObject(jsonObject) {
        set tSC=httpRequest.EntityBody.CopyFrom(pStream)
        if $$$ISERR(tSC) quit tSC
    }  
    set tSC = ..Adapter.SendFormDataURL(pURL,.pHttpResponse,pHttpVerb,httpRequest)
    quit tSC
} Method PostJSON(pURL As %String, Output pHttpResponse As %Net.HttpResponse, pStream As %Stream.GlobalCharacter) As %Status
{
  Return ..SendHTTP("POST",pURL,.pHttpResponse,pStream)
} Method OnPostRequest(pRequest As %Stream.GlobalCharacter, Output pResponse As %Stream.GlobalCharacter) As %Status
{
    set tURL=..Adapter.URL_"/demo"
    set tSC=..PostJSON(tURL,.tHttpResponse,pRequest.orden)
    if $$$ISERR(tSC)&&$IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data)&&tHttpResponse.Data.Size {
        set tSC=$$$ERROR($$$EnsErrGeneral,$$$StatusDisplayString(tSC)_":"_tHttpResponse.Data.Read())
    }
    if $$$ISERR(tSC) quit tSC
    // procesar respuesta
    set pResponse = ##class(%Stream.GlobalCharacter).%New()
    set pResponse.respuesta = ##class(%Stream.GlobalCharacter).%New()
    if tHttpResponse.StatusCode=200 {
        $$$TRACE(tHttpResponse.Data.Read())
        set tSC = pResponse.respuesta.%JSONImport(tHttpResponse.Data)
    else 
        set tSC=$$$ERROR($$$EnsErrGeneral,"HTTP Status:"_tHttpResponse.StatusCode)
    }
    quit tSC
} XData MessageMap
{
<MapItems>
    <MapItem MessageType="%Stream.GlobalCharacter">
        <Method>OnPostRequest</Method>
    </MapItem>
</MapItems>
} }
 

Y al enviar a mi puerto local tengo el siguiente error, y no llega hasta su destino

 

ERROR <Ens>ErrRequestNotHandled: Request message '5@EnsLib.HTTP.GenericMessage' not handled

El EnsLib.HTTP.GenericMessage lo genero al crear el json desde un Ens.BusinessProcessBPL que lo envia al adaptador que ahora tiene la clase demo.bo.RESTOperation.

<call name='LLamada' target='FHIR out x2' async='1' xpos='335' ypos='850' >
<request type='EnsLib.HTTP.GenericMessage>
<assign property="callrequest.Streamvalue="context.RequestFHIRaction="set" />
</request>
<response type='EnsLib.HTTP.GenericMessage/>
</call>

He probado ha poner diversas clases en el request y en el response sin exito.

Despues he modificado el metodo para ponerle el EnsLib.HTTP.GenericMessege quedando asi:Method OnPostRequest(pRequest As EnsLib.HTTP.GenericMessage, Output pResponse As EnsLib.HTTP.GenericMessage) As %Status

Pero el error persiste:

ERROR <Ens>ErrRequestNotHandled: Request message '5@EnsLib.HTTP.GenericMessage' not handled

Un Saludo

Esa me la sé, el MessageMap está mal. No manejas el mensaje EnsLib.HTTP.GenericMessagens

Gracias Nancy no lo vi.

Realizo el cambio en el MessageMap dejandolo asi:

<MapItems>
    <MapItem MessageType="EnsLib.HTTP.GenericMessage">
        <Method>OnPostRequest</Method>
    </MapItem>
</MapItems>
}

Ahora me da este error

ERROR <Ens>ErrException: <PROPERTY DOES NOT EXIST>zOnPostRequest+2^demo.bo.RESTOperation.1 *orden,EnsLib.HTTP.GenericMessage -- logged as '-' number - @' set tSC=..PostJSON(tURL,.tHttpResponse,pRequest.orden)'

Quito el orden del pRequest y ahora me da este error:

ERROR <Ens>ErrException: <UNDEFINED>zSendHTTP+3^demo.bo.RESTOperation.1 *jsonObject -- logged as '-' number - @' if $IsObject(jsonObject) {'

Claro,

Estas haciendo referencia a una variable que no existe aquí:

 if $IsObject(jsonObject) {...

Pues he dejado el metodo asi:

Method SendHTTP(pHttpVerb As %String, pURL As %String, Output pHttpResponse As %Net.HttpResponse, pStream As %Stream.GlobalCharacter) As %Status
{
    set httpRequest = ##class(%Net.HttpRequest).%New()
    set httpRequest.ContentType = "application/json"
    set tSC httpRequest.EntityBody.CopyFrom(pStream)
    set tSC = ..Adapter.SendFormDataURL(pURL,.pHttpResponse,pHttpVerb,httpRequest)
    quit tSC
}

He tenido que depurar un par de errores que conocia y ahora el error es:

ERROR <Ens>ErrException: <NOLINE>^demo.bo.RESTOperation.1 -- logged as '-' number - @' ;demo.bo.RESTOperation.1'

 

No se cómo ha quedado tu versión del código final ni cuándo se produce el error, ¿al compilar? ¿puedes pasar el código final?

Buenas

El error se produce al intentar realizar el envio, pongo una imagen

Esta es la versión final de codigo:

Class demo.bo.RESTOperation Extends EnsLib.REST.Operation
{ Parameter INVOCATION = "Queue"; Method SendHTTP(pHttpVerb As %String, pURL As %String, Output pHttpResponse As %Net.HttpResponse, pStream As %Stream.GlobalCharacter) As %Status
{
    set httpRequest = ##class(%Net.HttpRequest).%New()
    set httpRequest.ContentType = "application/json"
    set tSC = httpRequest.EntityBody.CopyFrom(pStream)
    set tSC = ..Adapter.SendFormDataURL(pURL,.pHttpResponse,pHttpVerb,httpRequest)
    quit tSC
} Method PostJSON(pURL As %String, Output pHttpResponse As %Net.HttpResponse, pStream As %Stream.GlobalCharacter) As %Status
{
  Return ..SendHTTP("POST",pURL,.pHttpResponse,pStream)
} Method OnPostRequest(pRequest As EnsLib.HTTP.GenericMessage, Output pResponse As EnsLib.HTTP.GenericMessage) As %Status
{
    set tURL=..Adapter.URL_"/demo"
    set tSC=..PostJSON(tURL,.tHttpResponse,pRequest)
    if $$$ISERR(tSC)&&$IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data)&&tHttpResponse.Data.Size {
        set tSC=$$$ERROR($$$EnsErrGeneral,$$$StatusDisplayString(tSC)_":"_tHttpResponse.Data.Read())
    }
    if $$$ISERR(tSC) quit tSC
    // procesar respuesta
    set pResponse = ##class(%Stream.GlobalCharacter).%New()
    set pResponse.respuesta = ##class(%Stream.GlobalCharacter).%New()
    if tHttpResponse.StatusCode=200 {
        $$$TRACE(tHttpResponse.Data.Read())
        set tSC = pResponse.respuesta.%JSONImport(tHttpResponse.Data)
    else 
        set tSC=$$$ERROR($$$EnsErrGeneral,"HTTP Status:"_tHttpResponse.StatusCode)
    }
    quit tSC
} XData MessageMap
{
<MapItems>
    <MapItem MessageType="EnsLib.HTTP.GenericMessage">
        <Method>OnPostRequest</Method>
    </MapItem>
</MapItems>
} }
 

Buenas David, ¿has podido revisarlo?. He intentado seguir la documentación https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?K... pero no consigo realizar el envio del json. Al volver a compilar la clase (es el codigo del mensaje anterior a este) al re enviar un mensaje tengo este ERROR <Ens>ErrException: <METHOD DOES NOT EXIST>zCopyFrom+2^%Stream.Object.1 *Rewind,EnsLib.HTTP.GenericMessage -- logged as '-' number - @''

Comentaste antes " Creo que el uso del método CopyFrom es correcto pero si te falla es fácil copiar el contenido de un Stream a otro mediante Write y Read. "Pero no he sabido hacerlo.

Saludos