Question Matthew Martinez · Mar 13

Checking for a value within a stream in the condition of a routing rule

Hello all,

I have a EnsLib.HTTP.GenericMessage inbound from a webhook with a GC stream.

example of stream contents

My router is defined as the following:

 

General Message Routing Rule

The msgClass for said rule is: EnsLib.HTTP.GenericMessage

I have tried a few variants of using a Contains in the condition to check the following: Document.StreamGC.Attributes.

I want to check the Stream for "HITL".  If it contains that, we send downstream.

Is there a way to do this within the condition in the rule?

Is the best solution to instead write a function that rewinds the stream and returns a flag?

Thank you!

Product version: IRIS 2022.1

Comments

Stephen Canzano · Mar 13

In the rule you should be able to have an expression with 

Stream.Read(some number of character.. you might consider 3200000)  

to get the first X characters of the stream content... of course assuming the thing you are searching for can be found in that string.

0
Matthew Martinez  Mar 13 to Stephen Canzano

The streams we are processing are fairly short.  I will give this a shot.

0
Matthew Martinez  Mar 13 to Stephen Canzano

Any suggestion on syntax?  I cannot get it to resolve in the rule editor:

 

 

Does not play nice.

Thank you

0
Robert Cemper  Mar 13 to Matthew Martinez

assuming  Document.StreamGC is of type %Stream.Object you may
use method FindAt which combines Read() and Contains()
see: method FindAt() 

method FindAt(position As %Integer, target As %RawString,
   ByRef tmpstr As %RawString = "", caseinsensitive As %Boolean = 0) as %Integer

Find the first occurrence of target in the stream, starting the search at position.
The method returns the position of this match, counting from the beginning of 
the stream, and leaves the stream positioned at an indeterminate location. 
If it does not find the target string, it returns -1.

If position=-1 then it starts searching from the location found in the 
previous search and returns the offset from the last search. 
This is useful for searching through the entire file. 
If you are doing this, you should pass in tmpstr by reference in every call. 
This is used to store the last buffer read, so the next call will start 
where the last one left off. 
If caseinsensitive=1 then the search will be case insensitive, 
rather than the default case-sensitive search.
0
Matthew Martinez  Mar 15 to Robert Cemper

Hello Robert,

Any suggestions on the below syntax?

 

I am unable to compile/save the rule.  Is this what you had in mind?

Thank you

0
Matthew Martinez  Mar 15 to Robert Cemper

Thanks for kicking me in the right direction, found a built in function in the building tool to create a syntax like the below that is working for me:

StreamFindAt(Document.StreamGC,"HITL",) >= 1

Thank you all.

0
Robert Cemper  Mar 15 to Matthew Martinez

THANKS for your feedback !

0
Jenna Makin · Mar 15

Yes — you’re correct to think about that edge case.

If you read the stream in fixed-size chunks (e.g., Read(32000)), a simple tChunk[pNeedle search can miss matches where the target string spans the boundary between two reads. For example, "HITL" could appear as "HI" at the end of one chunk and "TL" at the beginning of the next.

A common solution is to keep a small overlap buffer from the end of the previous chunk and prepend it to the next chunk before searching. The overlap only needs to be ($Length(pNeedle)-1) characters.

The general pattern looks like this:

  1. Read the next chunk from the stream
  2. Prepend the trailing characters from the previous iteration
  3. Search the combined buffer
  4. Preserve the last (needle length - 1) characters as the next overlap
  5. Continue until a match is found or the stream ends
  6. Rewind the stream before returning so downstream components can still read it

Here’s an example implementation

Class MyApp.Rule.Functions Extends Ens.Rule.FunctionSet
{
ClassMethod StreamContains(
   pMsg As EnsLib.HTTP.GenericMessage,
   pNeedle As %String = "HITL"
   ) As %Boolean
{
   Set tSC = $$$OK
   Set tFound = 0
   Set tTail = ""
   Set tChunkLen = 32000
   Set tNeedleLen = $Length(pNeedle)
   Set tStream = pMsg.Stream
   If '$IsObject(tStream) Quit 0
   If pNeedle="" Quit 0
   Do tStream.Rewind()
   While ('tStream.AtEnd) && ('tFound) {
       Set tChunk = tStream.Read(tChunkLen,.tSC)
       Quit:$$$ISERR(tSC)
       Set tData = tTail _ tChunk
       If tData[pNeedle {
           Set tFound = 1
           Quit
       }
       If tNeedleLen>1 {
           Set tTail = $Extract(tData,*-(tNeedleLen-2),*)
       } Else {
           Set tTail = ""
       }
   }
   Do tStream.Rewind()
   Quit tFound
}
}

Then your routing rule condition can simply call the function:

MyApp.Rule.Functions.StreamContains(Document,"HITL")

This keeps the routing rule readable while safely inspecting the stream without consuming it.

0
Matthew Martinez  Mar 16 to Jenna Makin

This is awesome, thank you Jenna.  I will save this and modify our implementation.

0