Forum Discussion

smp_86112's avatar
smp_86112
Icon for Cirrostratus rankCirrostratus
Feb 08, 2008

STREAM::replace

I'm trying to write a simple iRule using a STREAM profile on an LTM running 9.1.2. According to the iRules wiki, the STREAM::replace was introduced in 9.0.0. However this simple iRule generates the error:

 

 

line 2: [wrong args] [STREAM::replace @string1@string2@]

 

 

when HTTP_RESPONSE {

 

STREAM::replace @string1@string2@

 

}

 

 

I've tried lots of different permutations, like changing the character delimiter, adding quotes, etc... What's going on here?

 

 

8 Replies

  • So you're telling me this doc is inaccurate?

     

     

    http://devcentral.f5.com/wiki/default.aspx/iRules/STREAM__replace.html
  • Sorry, I thought you'd gotten the answer in the other thread and was just updating this one to indicate the answer was in that one.

     

     

    The STREAM::replace command just allows you to change the string which is inserted in place of the matched string in the stream profile. It won't accept multiple tokens. The syntax you're using is accepted in the STREAM::expression command. However, it looks like that command wasn't added until 9.2.

     

     

    What are you trying to accomplish with the stream profile? Can you configure the find/replace strings in the stream profile attached to the VIP? Else, if you need more dynamic control over the stream expression, can you upgrade to 9.3.x?

     

     

    Aaron
  • What I'm trying to do is replace all occurrences of the string "about:blank" with a different string in any HTTP_RESPONSE_DATA if { HTTP::header "Content-Type"] contains "application/x-javascript"}. My LTMs are running 9.1.2, and it appeared the only STREAM command available to me was STREAM::replace. I was hoping I could apply a blank stream profile to the VIP, then use the STREAM::replace command to accomplish the replacement. I misunderstood the explanation of the command.

     

     

    Based on your explanation of the stream profile, plus the requirement of only applying the stream profile under certain conditions, it appears must either upgrade or to try this using HTTP::payload. I think we've tried HTTP::payload in the past with pretty horrible consequences, so I'm a hesitant about using it since I'm not yet confident in my iRules expertise. But since an upgrade is not possible in the time constraint I'm under, then I guess that's my only option. I'm now pouring over the forums to find a good example.
  • If you're trying to collect response content and replace multiple instances of one string with another string of a different length, it gets pretty complicated. You need to update the indices for subsequent matches based on the length of the current replacement string.

     

     

    Fixing the app or upgrading the BIG-IP and using the newer stream commands would be ideal options. If you try the payload option and need to replace multiple instances of the search string, let me know and I'll see if I can find a past rule we used.

     

     

    Aaron
  • If you're trying to collect response content and replace multiple instances of one string with another string of a different length

     

     

    That is exactly what I am trying to do. If you drum up an example, I'd love to take a look.
  • No warranty on this, but it worked in tests I tried. I tested by using a few different 'find regexes' and replacement strings. I tested to see that variable length matches were replaced correctly. Note that I only tested with a single global replacement string. The rule would need to be tweaked if you want to dynamically set the replacement text per response.

    I tried to add comments to help you follow. There is an exorbitant amount of debug logging which will hopefully help. You could remove this altogether once you're done testing.

    
    when RULE_INIT {
        Log debug messages to /var/log/ltm? 1=yes, 0=no.
       set ::collect_debug 1
        A regular expression which describes what strings to replace (wrap the regex in curly braces).
       set ::find_regex {about:blank}
       set ::find_regex {[a-zA-Z]+}
       set ::find_regex {[0-9]+}
        The string to insert in replacement of the matched string(s)
       set ::replacement_string "abc"
       set ::replacement_string "x"
    }
    when HTTP_REQUEST {
        Don't allow response data to be chunked
       if { [HTTP::version] eq "1.1" } {
           Check if this is a keep alive connection
          if { [HTTP::header is_keepalive] } {
              Replace the Connection header with Keep-Alive
             HTTP::header replace "Connection" "Keep-Alive"
          }
           Set the serverside version to 1.0
          HTTP::version "1.0"
       }
    }
    when HTTP_RESPONSE {
        Only check responses that are a javascript content type
       if {[HTTP::header "Content-Type"] eq "application/x-javascript"} {
          if {$::collect_debug}{log local0. "Received javascript response"}
           Get the content length to collect
          if { [HTTP::header exists "Content-Length"] } {
             set content_length [HTTP::header "Content-Length"]
          } else {
             set content_length 1000000000
          }
          if { $content_length > 0 } {
             if {$::collect_debug}{log local0. "Collecting $content_length"}
              Collect the content
             HTTP::collect $content_length
          }
       }
    }
    when HTTP_RESPONSE_DATA {
       if {$::collect_debug}{log local0. "original payload: [HTTP::payload]"}
        Find all the match strings in one pass (-indices saves the start and end index of each match to a list)
       set match_indices [regexp -all -inline -indices $::find_regex [HTTP::payload]]
       if {$::collect_debug}{log local0. "match_indices = $match_indices"}
        Initialize a variable to track the offset of the match
       set match_offset_len 0
        Loop through the indices and replace the matching strings with the replacement string
       foreach match_index $match_indices {
           Take the first match's start index and adjust it if the payload has already been changed by a previous match
          set match_start [expr [lindex $match_index 0] - $match_offset_len]
          if {$::collect_debug}{log local0. "match_start = $match_start"}
           Take the first match's end index and adjust it if the payload has already been changed by a previous match
          set match_end [expr [lindex $match_index 1] - $match_offset_len + 1]
          if {$::collect_debug}{log local0. "match_end = $match_end"}
           Calculate the length of the match
          set match_len [expr $match_end - $match_start]
          if {$::collect_debug}{log local0. "match_len = $match_len"}
           Calculate the length of the offset based on the past offset plus the difference between the current match and the replacement string
          set match_offset_len [expr $match_offset_len + $match_len - [string length $::replacement_string]]
          if {$::collect_debug}{log local0. "match_offset_len = $match_offset_len"}
           For debug purposes, save and log the string which was matched
          set match_string [string range [HTTP::payload] $match_start [expr $match_end - 1]]
          if {$::collect_debug}{log local0. "match string = $match_string"}
           Replace the match with the replacement string
          HTTP::payload replace $match_start $match_len $::replacement_string
           Log the updated payload
          if {$::collect_debug}{log local0. "updated payload: [HTTP::payload]"}
       }
    }

    Aaron
  • I'm sorry I never got around to thanking you for this post. Very thorough and nicely written. Worked great!