Modify SOAP To header based on target node
Hi, I have 2 kinds of virtual servers set up. The first has no pools assigned. It takes requests over SSL, terminates the SSL and routes the requests (in plaintext) to wholly different pools based on content.
However, the SOAP To header from these requests needs to be modified. The protocol needs to be changed from https to http, the service URL is suffixed with a designator used for services without transport security and the port needs to be changed from 8443 to some other port.
It's the "some other port" that is causing me some issues. Following is my iRule on the SSL-terminating, content-based routing virtual server:
when HTTP_REQUEST {
if { [HTTP::method] eq "POST" } {
if { [HTTP::header exists "Content-Length"] } {
set content_length [HTTP::header "Content-Length"]
} else {
set content_length 1048576
}
HTTP::collect $content_length
} else {
HTTP::respond 405 content "Unsupported" Allow "POST"
}
}
when HTTP_REQUEST_DATA {
if { [HTTP::method] eq "POST" } {
set payload [HTTP::payload]
binary scan [sha1 [SSL::cert 0]] H* certHash
set newSoapHeader ""
if { [info exists certHash] } {
append newSoapHeader $certHash
} else {
append newSoapHeader "cert_hash_retrieval_failure"
}
append newSoapHeader ""
Try to stick the new header in the header collection
set numMatches [regsub {.*\<[A-Za-z0-9:]*Header[^\>]*\>} $payload [concat {&} $newSoapHeader] modifiedPayload]
if no matches from that, we know there was no header and, therefore, no subsitution. We need to introduce the whole Header block
if { $numMatches == 0 } {
set contentToInsert ""
append contentToInsert $newSoapHeader
append contentToInsert ""
set numMatches [regsub {.*\<[A-Za-z0-9:]*Envelope[^\>]*\>} $payload [concat {&} $contentToInsert] modifiedPayload]
}
Empty it of content first
HTTP::payload replace 0 [string length [HTTP::payload]] ""
Then replace the empty payload
HTTP::payload replace 0 0 $modifiedPayload
set currentVirtualServer [virtual name]
if { [string match *LEGACY* $currentVirtualServer] > 0 } {
Replace the HTTP Location Header
HTTP::header replace Location [string map { "https://" "http://" ".svc" "_U.svc" } [ HTTP::header Location]]
Replace the SOAP To Header
set badToHeaderPattern ".svc"
set lenBadHeaderPattern [string length $badToHeaderPattern]
set betterToHeaderPattern "_U.svc"
set offset [string first $badToHeaderPattern [HTTP::payload]]
if { $offset >= 0 } {
HTTP::payload replace $offset $lenBadHeaderPattern $betterToHeaderPattern
set badToHeaderPattern "https://"
set lenBadHeaderPattern [string length $badToHeaderPattern]
set betterToHeaderPattern "http://"
set offset [string first $badToHeaderPattern [HTTP::payload]]
if { $offset >= 0 } {
HTTP::payload replace $offset $lenBadHeaderPattern $betterToHeaderPattern
}
NOW REPLACE THE 8443 PORT WITH... um...
}
}
the trailing arguments align with the parts of the regex in parentheses. First is always the whole match, subsequent per parentheses pair
regexp {(\<[A-Za-z0-9:]*Organisation[^\>]*\>)([A-Za-z]*)} $modifiedPayload wholeMatch xmlElementMatch organisationNameMatch
if { [string length $organisationNameMatch] > 0 } {
string map takes a list of replacement pairs (e.g [list needle1 replace1 needle2 replace2... needleN replaceN])
set targetPool [string map [list -D--- --- MULTI $organisationNameMatch RELEASE RELEASE-U DEBUG DEBUG-U LEGACY ""] $currentVirtualServer]
pool $targetPool
} else {
HTTP::respond 400 content "Unknown"
}
}
Ensure that the request is released back to F5 so it may take control of the underlying connection and complete the routing
HTTP::release
}
I'll detail the HTTP_REQUEST if requested, but it's pretty much stock-standard stuff. All the magic is in the HTTP_REQUEST_DATA handling. I know a lot of my SOAP content replacement is really risky and needs better pattern matching/controls so I don't replace anything I shouldn't, so don't focus on that. But after I switch out the https with http in the SOAP To header (really every instance of https in the whole payload), I want to change the port 8443 to another port. The other port, however, is not known until I route it to the pool later (the "pool $targetPool" in the last couple of lines).
That routing works just fine and the correct pool gets the requests and processes them. But I can't seem to manipulate the payload any further after I set the new pool. That means I can't, say, get "[LB::server port]" and replace the mentions of 8443 with it. It looks like I don't have it available to manipulate on the LB_SELECTED event either.
So, how do I modify the port in the SOAP To Header (also need to modify the HTTP Location header) with a port I will only know after I've re-routed the request with the "pool" keyword?