Dynamic WSDL updating with iRules
Today's internet is a fast paced, demanding, and ever changing environment. Applications that want to live in this space and keep up need to have the ability to roll with the punches, to change as quickly as the user's requirements do. This means being agile, loosely bound, and fast on their ... well ... bits. If this is starting to sound like another introduction to SOA, that's only because it is.
SOAs or Service Oriented Architectures are a way in which applications and the developers that build them can help keep up with the changing times. By using a series of smaller applications that all communicate with each other and perform specific tasks that combine to form the complete solution, changes can be made rapidly and much more easily than in an environment with a large, single application. In those situations a simple code change can take months because it could take down the entire app. With SOA in place it's much more likely that the same change could be made easily with much less risk involved.
Don't take my word for it, I'm by no means the SOA expert here at F5. We have a couple of those, one of which is Lori MacVittie. Lori can often be found on DevCentral enlightening us all about the Web Services world, application vulnerabilities gone awry, or other interesting things that you might need to know. In one of her older blog posts doing just that she talked about WSDLs (the meta-data largely used to communicate in an SOA environment) and how they are largely automatically generated by the back-end systems now. Unfortunately for some users, this ease of creation does not come without a cost. In this post you can read about the issues some people are facing with auto-generated WSDL info.
To quote:
Here's the relevant excerpt of an example WSDL (With some extra spaces to make it actually show up in HTML):
< wsdl:port name="MyServicePort" binding="es:MyServiceSOAPBinding" > < soap:address location=http://elmer.example.com/MyService/ > < /wsdl:port >
Problem: The endpoint address ("elmer.example.com") resolves to the server on which it was generated, and it's not publicly accessible. Houston, we have a problem. To what IP address does elmer.example.com resolve? By default, application servers generate a WSDL with the endpoint address as the hostname or IP address of the local server. What if the service is load balanced? What if it's not in the DMZ or the service is on a multi-homed server? What endpoint did the client end up with? Did I just break my SOA?
In the outlined example there is a very real issue. Since the back-end server that autogenerated the WSDL was not publicly accesible, we've now set an address in the WSDL location that no one is going to be able to access. Lori's solution, which I wholeheartedly support, is to use iRules. With an iRule you can, with relative ease, dynamically re-write the address to ensure that you're always sending people to the proper location. In the below example I'm setting a static address in the code, but you could easily expand the logic to do a class search and only change the address if needed, to change it to one of many addresses depending on the original location info, etc. There are many things you can do, as always, since you've got all the power of iRules at your fingertips.
Take a look and feel free to dig in and change things for your environment. This basic example should give you the general idea, though.
when HTTP_RESPONSE { if { [HTTP::header exists "Content-Length"] && [HTTP::header "Content-Length"] < 1048577 } { set content_length [HTTP::header "Content-Length"] } else { set content_length 1048576 } log local0.info "Content Length: $content_length" if { $content_length > 0 } { HTTP::collect $content_length } } when HTTP_RESPONSE_DATA { if { [HTTP::payload] contains "< soap:address location=\"http://elmer.example.com/myservice/\" >"} { set newPayload [string map {"< soap:address location=\"http://elmer.example.com/myservice/\ ">" \ "< soap:address location=\"http://10.10.10.50/myservice/\" >"} [HTTP::payload]] HTTP::payload replace 0 [string length $newPayload] $newPayload } HTTP::release }
Thanks go to Carla Rogowsky who was the user that posted the question looking for this kind of example to Lori’s old blog post. Thanks for stirring the pot and getting us thinking about what an actual example might look like. Hopefully she’ll find this helpful, along with the rest of the community.
Commands used: HTTP::header ; HTTP::payload ; HTTP::collect ; HTTP::release
- Thomas_SchaeferNimbostratusI think there is a typo. The length of the replace statement is supposed to be the length of the source. While I agree in many cases this is the same number as the new length, the example is misleading as it implies the length is the length of the new string. That is counter to the documentation.
- Thomas_SchaeferNimbostratusOne other thing...you also need to pad out your string to match the old length or you are going to be left with a remnant of the old string you replaced in the payload.