Question
· May 18, 2016

Ensemble HTTP OnProcessInput

I am just trying to make a quick hand off HTTP production. I have the class built and compiled onto the server and the production is running as a service. When the production is called (by an inbound job) I can see that the HTTP.InboundAdapter is being used successfully and the stack continues all the up to ProcessInput where OnProcessInput is called in Ens.BusinessService. Instead of using the class that I wrote which has both extended Ens.BusinessService and implemented my own overriden OnProcessInput method, it calls the OnProcessInput within the Ens.BusinessService which just returns a $$$ERROR($$$NotImplemented).

The following is the output from the call:

"0 "_$lb($lb(5003,,,,,,,,,$lb(,"HSREPOSITORY",$lb("e^zOnProcessInput+1^myClass.1^1","e^zProcessInput+13^Ens.BusinessService.1^1","e^zOnConnected+154^EnsLib.HTTP.InboundAdapter.1^1","e^zOnTask+5^EnsLib.TCP.InboundAdapter.1^1","e^zOnTask+29^Ens.BusinessService.1^1","e^zStart+62^Ens.Job.1^2","e^zconnectedJob+2^EnsLib.TCP.InboundAdapter.1^1","d^ClassMethod+4^%apiOBJ^1","d^^^0"))))/* ERROR #5003: Not implemented */
 

I have tried extending EnsLib.REST.Service, EnsLib.HTTP.Service, and now just the Ens.BusinessService. All with the same output.

 

Any ideas for what might be wrong?

 

Edit:

Class myClass extends Ens.BusinessService {

/// Set Adapter
Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";

/// Set this to 0 to prevent normalizing of HTTP header variable names to lowercase
Parameter TOLOWERHEADERVARS = 1;

/// Set this to make page parse form variables from the form body in case of a form POST
Parameter PARSEBODYFORMVARS = 0;

/// Copied from EnsLib.HTTP.Service

Method OnInit() As %Status
{
    If $IsObject(..Adapter) {
        Set ..Adapter.%ToLowerHeaderVars=..#TOLOWERHEADERVARS
        Set ..Adapter.ParseBodyFormVars=..#PARSEBODYFORMVARS
    }
    Quit ##super()
}

/// Same method signature as EnsLib.REST.Service
Method OnProcessInput(pInput As %Library.AbstractStream, Output pOutput As %Stream.Object = {$$$NULLOREF}) As %Status
{
    // Nothing in here gets touched, thus my problem
}

}

Discussion (15)1
Log in or sign up to continue

Here's an example of a business service that receives input in the form of a delimited string, passes the input off to a business process (in your case it would be a business operation), gets the response and returns the response as the return document.

Class AC.Services.HTTPService Extends Ens.BusinessService
{

Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";

Method OnProcessInput(pInput As %GlobalCharacterStream, Output pOutput As %Stream.Object) As %Status
{
    // Instanciate the output stream
    set pOutput=##class(%GlobalCharacterStream).%New()
    
    // Read the delimited string passed in the content stream
    set iStr=pInput.Read(,.tSC)

    // Build the request message
    set request=##class(AC.Messages.RouteMessagePreProcessRequest).%New()
    set request.DOB=$p(iStr,"^",1)
    set request.Facility=$p(iStr,"^",2)
    set request.RXNumber=$p(iStr,"^",3)
    set request.SSN4=$p(iStr,"^",4)
    set request.CMOPFlag=$p(iStr,"^",5)
    
    // Send request message to RouterPrePRocess
    do ..SendRequestSync("RouteMessagePreProcess",.request,.response)
    
    // Return response to caller
    do pOutput.Write(response.RESP)

    Quit $$$OK
}

}
 

Interesting,  I took your code and just added some code to the OnProcessInput method so that it would just take a string int he body of the http request and then echo that string back as the response.  here is the code, which when tested with my http test tool seems to work fine which would indicate that there might be a problem elsewhere.

What settings are you using when adding your service to the production, and what version of Ensemble are you using?

Here's a copy of the working code I tested in 2016.1

Class Community.Services.MyService Extends Ens.BusinessService
{

/// Set Adapter
Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";

/// Set this to 0 to prevent normalizing of HTTP header variable names to lowercase
Parameter TOLOWERHEADERVARS = 1;

/// Set this to make page parse form variables from the form body in case of a form POST
Parameter PARSEBODYFORMVARS = 0;

/// Copied from EnsLib.HTTP.Service
Method OnInit() As %Status
{
    If $IsObject(..Adapter) {
        Set ..Adapter.%ToLowerHeaderVars=..#TOLOWERHEADERVARS
        Set ..Adapter.ParseBodyFormVars=..#PARSEBODYFORMVARS
    }
    Quit ##super()
}

/// Same method signature as EnsLib.REST.Service
Method OnProcessInput(pInput As %Library.AbstractStream, Output pOutput As %Stream.Object = {$$$NULLOREF}) As %Status
{
    set pOutput=##class(%GlobalCharacterStream).%New()
    set string=pInput.Read()
    do pOutput.Write(string)
    quit $$$OK
}

}
 

Interesting indeed. I am using 2016.2.

In Ensemble I have the myClass set up as a service listening on port 12016 and all the other settings are the default.

The code in the method is just:

Method OnProcessInput(pInput As %Stream.GlobalCharacter, Output pOutput As %Stream.Object = {$$$NULLOREF}) As %Status
{
    set ^pRR51616($I(^pRR51616)) = "onProcessInput"
    // Create the Provide and Register message
    // Automatically assigns a document unique ID
    set tMessage = ##class(HS.Message.IHE.XDSb.ProvideAndRegisterRequest).%New()
    set tMessage.SourceId = "1.1.1.1"
    set tDocument = ##class(HS.Message.IHE.XDSb.Document).%New()
    set ^pRR51616($I(^pRR51616)) = "Pre-CopyFrom"
    set ^pRR51616($I(^pRR51616)) = tDocument.BodyCharacter.CopyFrom(pInput)
    set ^pRR51616($I(^pRR51616)) = "Document Created"
    // Set required minimum document metadata
    set tDocument.MimeType = "text/xml"
    set tDocument.FormatCode = ##class(HS.IHE.XDSb.Types.CodedValue).Create(
        "na", "IHE PCC", "Summary of Care")
    set tDocument.HealthcareFacilityTypeCode = ##class(HS.IHE.XDSb.Types.CodedValue).Create(
        "Outpatient", "Connect-a-thon healthcareFacilityTypeCodes", "Outpatient")
    set tDocument.PracticeSettingCode = ##class(HS.IHE.XDSb.Types.CodedValue).Create(
        "General Medicine", "Connect-a-thon practiceSettingCodes", "General Medicine")
    do tMessage.Documents.Insert(tDocument)
    set ^pRR51616($I(^pRR51616)) = "PnR request created"
    // Send Provide and Register synchronously
    set pOutput = ##class(HS.Test.Service).SendSync(tMessage, .rr)
    Quit $$$OK
}

With the globals in there I would be able to tell if the method was even being touched.

The only strangeness I see here is that in your code you set pOutput to the result of HS.Test.Service.SendSync which returns a %Status.

In your method, the pOutput variable is used in the method declaration and is supposed to be a stream object because that is how the adapter responds back as the response to the http request.

I wonder if this could possibly be the problem.  I have to admit that the behavior is very strange, if indeed that is the problem.

I changed the output to mimic what you did earlier but to no avail. I agree that the behavior would not have been indictive of the problem. The confusing part about this problem is that when the ..OnProcessInput() is called in Ens.BusinessService and heads to it's own method rather than mine, the error returned says that my class and method is on the stack....

Either way, the process is not even jumping into my method as the first line is setting a global which to this second remains empty.

Solved this issue. It was an Atelier issue. I created this class and checked it into git and it was compiling but when we went to re-push the file it wasn't being tracked. We then re-saved and re-compiled the class and was then able to track it again. At that point Atelier pushed the compiled code up to the server.

My thoughts were that every time you compile, Atelier pushes it to the server.

So in a sense, even though Ensemble picked up that myClass was a service class I was able to add it as a production but then it failed to recognize it again...?

Your $zv looks very suspect Paul.   On one hand you have a 2016.2 beta of Ensemble, but you have HealthShare libraries also which aren't distributed with Ensemble.

Was this a HealthShare instance of 2016.1 that you overlayed with a 2016.2 install of Ensemble?

I'm not sure that can ever be expected to work right.

Maybe try your service on a fresh install of 2016.2 beta and see if it works then, although you won't have access to the HS libraries 

If you follow Dave's excellent advice and use the CSP  port, you have two options.

  1. Extend EnsLib.HTTP.Service as described in the class doc.
  2. Extend %CSP.Page and call CreateBusinessService.

The class doc for EnsLib.HTTP.Service says

In order for the CSP mechanism to work, Web Services derived from this class must be configured in one of the following ways: 

  1. with their configuration name the same as their class name,
  2. or the invoking URL must include ?CfgItem= giving the config item name,
  3. or using a CSP application with a DispatchClass configured and the config item name as the next URL piece after the application name.

(Configured Services exposed using the HTTP Inbound Adapter may also be invoked with this URL parameter but because each configured Inbound Adapter listens on its own TCP/IP port this parameter is just a safety check for them.)