Forum Discussion

Mohanad_313515's avatar
Mohanad_313515
Icon for Nimbostratus rankNimbostratus
Apr 04, 2019

irule to insert action parameter inside Content-Type header for xml request

Hello

 

we have a lot of xml services using soap version 1.2, im facing issue regarding validate soap action header using xml profile.

 

To summarize, while SOAP 1.1 would have these HTTP headers:

 

Content-Type: text/xml

 

SOAPAction: "http://example.com/ticker"

 

A SOAP 1.2 message would have the following:

 

Content-Type: application/soap+_xml; action="http://example.com/ticker"

 

our client software is not sending action parameter inside Content-Type header and it's to hard to apply changes on the code level

 

http header is look like this: Content-Type: application/soap+_xml; charset=utf-8

 

so i want to insert action parameter inside Content-Type header to be: Content-Type: application/soap+_xml; charset=utf-8; action=";

 

sample of the request

 

POST /xxyyzz/Service.svc HTTP/1.1

Content-Type: application/soap xml; charset=utf-8

Host: 

Content-Length: 

Expect: 100-continue

Accept-Encoding: gzip, deflate

X-Forwarded-For: 



  http://tempuri.org/IACHService/SendEchoResponse
  

`

so i want to match string after "Action s:mustUnderstand="1">[http://tempuri.org/IACHService/"](http://tempuri.org/IACHService/); then inject it to http header to be look like this

if:
` http://tempuri.org/IACHService/Something
      

Content-Type: application/soap xml; charset=utf-8; action=http://tempuri.org/IACHService/Something

 

  • Hi,

    Can you begin with trying this irule please (we will optimize it later):

    when HTTP_REQUEST {
    
    set capture 0
    
    if { [HTTP::header exists Content-length] and [HTTP::method] equals "POST" } {
        set capture 1
        HTTP::collect [HTTP::header Content-Length]
    }
    }
    
    when HTTP_REQUEST_DATA {
    
    if {$capture} {
    
        set payload [HTTP::payload]
        regexp {^.*([^\"]+)<\/a:Action>.*$} $capture -> action
    
        log local0. "action: $action"
    
    }
    }
    
  • Hi Mohanad,

    the iRule below should do the trick for your.

    It uses the STREAM profile to search on POST-Request for the regex pattern:

    [^<|\r\n|\n]*
    

    If a match is found the

    STREAM_MATCHED
    event will be triggered to read (or even manipulate) the string value of the regex match.

    The action value get then extracted and stored into the

    $action_value
    via the
    [URI::basename]
    command.

    The iRule will finally replace the

    Content-Type
    header of the ongoing HTTP request based on the just extracted action value...

    when HTTP_REQUEST {
        if { ( [HTTP::method] eq "POST" )
         and ( [HTTP::path] ends_with "/Service.svc" ) } then {
            set stream_action 1
            STREAM::expression {@[^<|\r\n|\n]*@@}  
            STREAM::enable
        } else {
            set stream_action 0
            STREAM::disable
        }
    }
    when STREAM_MATCHED {
        set stream_action 2
        set action_value [URI::basename [STREAM::match]]
        log local0.debug "Found the action = $action_value"
        STREAM::replace
    }
    when HTTP_REQUEST_SEND {
        clientside {    
            if { $stream_action == 0 } then {
                 Let the request pass...
            } elseif { $stream_action == 1 } then {
                HTTP::header insert "Transfer-Encoding" "chunked"
            } else {
                HTTP::header remove "Content-Type"
                HTTP::header insert "Content-Type" "application/soap xml; charset=utf-8; action=http://tempuri.org/IACHService/$action_value"
                HTTP::header insert "Transfer-Encoding" "chunked"
                log local0.debug "New Header Value: [HTTP::header value "Content-Type"]"
            }
        }
    }
    

    Note: You need to assign the default STREAM profile to your virtual server.

    Cheers, Kai

  • Hi Mohanad,

    the iRule below uses a

    HTTP::collect
    approach, to intercept and buffer the POST request destined to your web service. If the entire POST-Body (or a maximum of X bytes amount) has been buffered, the iRule will use a combination of the
    [getfield]
    ,
    [substr]
    and
    [URI::basename]
    commands to extract the last portion of the URL defined in the
    Action
    XML element. If a action value could be extracted, the iRule will replace the Content-Type HTTP-Header with a new value including the extracted action value. Once the HTTP-Header has been changed, the request is released from the buffer and send to your web service...

    when RULE_INIT {
         Defining the maximum HTTP::collect size 
        set static::maximum_http_collect_size 1048576 ; Its 1 Mbyte per POST request. This should be lowered if possible
    }
    when HTTP_REQUEST {
        if { ( [HTTP::method] eq "POST" )
         and ( [HTTP::path] ends_with "/Service.svc" ) } then {
            if { not [string is integer [HTTP::header value "Content-Length"]] } then {
                 Skipping the malformated Content-Length header...         
            } elseif { [HTTP::header value "Content-Length"] >= $static::maximum_post_collect_size } then {
                 Collecting the HTTP payload until maximum size has been reached
                HTTP::collect $static::maximum_http_collect_size
            } elseif { [HTTP::header value "Content-Length"] > 0 } then {
                 Collecting the full HTTP payload.
                HTTP::collect [HTTP::header value "Content-Length"]
            }
        }
    }
    when HTTP_REQUEST_DATA {
        set action_value [URI::basename [substr [getfield [HTTP::payload] "" 2] 0 "<"]]
        log local0.debug "Found the action = $action_value"
        if { $action_value ne "" } then {
            HTTP::header remove "Content-Type"
            HTTP::header insert "Content-Type" "application/soap xml; charset=utf-8; action=http://tempuri.org/IACHService/$action_value"
            log local0.debug "New Header Value: [HTTP::header value "Content-Type"]"
        }
    }
    

    Note: I'm sorry if I've wasted some of your valuable time with my previous

    STREAM_MATCH
    approach. Well, the
    STREAM_MATCH
    approach is still superior in my opinion, since it does not need to buffer the entire payload into LTMs memory. But unfortunately it changes the ongoing request slightly, which may have certain side effects within your application which we don't know yet. The
    HTTP::collect
    approach is not bad at all. If you tweak the maximum collect size to a minimum (e.g. just a few kbyte), the impact to buffer portions of the request could be minimized to an acceptable degree.

    Cheers, Kai

  • Try this code:

     

    when HTTP_REQUEST {
        if { [set capture [expr {([HTTP::header exists Content-length] and [HTTP::method] equals "POST") and ([HTTP::header Content-Type] contains "soap")}]] } {
            HTTP::collect [HTTP::header Content-Length]
        }
    }
    
    when HTTP_REQUEST_DATA {
        if {$capture} {
            HTTP::header remove  "Content-Type" 
            HTTP::header insert "Content-Type" "application/soap xml; charset=utf-8; action=\"[string trim [findstr [HTTP::payload] "" 31 ""] ]\""
        }
    
    }