Forum Discussion

boneyard's avatar
Oct 01, 2013

iRule to change HTTP GET into POST

i have an incoming GET request on my virtual server which i want to change into a POST request towards the node. in princple this would mean:

 

  • change the word GET into POST
  • change the uri to remove the query part
  • put the query string in the content
  • add a Content-Length header
  • possibly add a Content-Type header

but im getting stuck with turning the word GET into POST, this doesnt work: HTTP::method "POST" (error: [wrong args]), same with HTTP::request. i found this irule https://devcentral.f5.com/wiki/iRules.HTTP_mothod_conversion.ashx but it feels quite a lot of work and might not work looking at the comment. is this really so complicated or am i missing something?

 

  • Hi I have managed to do this without using TCP::collect. I have borrowed the idea from this post https://devcentral.f5.com/codeshare/http-method-conversion, but without the need to use 2 separate virtual servers.

    The following iRule will convert a GET request with query parameters into a POST to the same path, but placing the query parameters in the request body instead of the request line.

    Be careful....you could exhaust all your connections on the box if you tinker with it and get it wrong 🙂

    when HTTP_REQUEST {
        set pool [LB::server pool]
        set method [HTTP::method]
    
        if {[HTTP::uri] eq "/reentrant"} {
            HTTP::respond 200 
            return
        } elseif {[HTTP::method] eq "GET"} {
    
             Create POST
            set post "POST [HTTP::path] HTTP/1.1\r\nHost: [HTTP::host]\r\nContent-Type: text/plain\r\nContent-Length: [string length [URI::query [HTTP::uri]]]\r\n\r\n[URI::query [HTTP::uri]]"
    
             Change the uri so that when this event is fired again we know to return a 200
            HTTP::uri "/reentrant"
             Invoke this iRule again re-entrantly
            virtual [virtual]
        }
    }
    
    when HTTP_RESPONSE {
         If the clientside request was a GET,  retry as a POST
        if {$method eq "GET"} {
            pool $pool
            HTTP::retry $post
            return
        }
    }
    
  • The trick is that you have to do this in the TCP events (CLIENT_ACCEPTED and CLIENT_DATA) as the HTTP::method command is read-only. Here is a somewhat simple example of how this looks:

    when CLIENT_ACCEPTED {
        TCP::collect
    }
    when CLIENT_DATA {
         separate uri and query string
        set uri [findstr [TCP::payload] "GET " 4 " HTTP/1."]
        set uri_no_query [findstr $uri "" 0 "?"]
        set query [findstr $uri "?" 1]
    
         replace TCP payload
        regsub -all -nocase "GET" [TCP::payload] "POST" newdata
        set newdata1 [string map "$uri $uri_no_query" $newdata]
        regsub -all -nocase "Accept: " $newdata1 "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: [string length $query]\r\nAccept: " newdata2
    
        TCP::payload replace 0 [TCP::payload length] ${newdata2}$query
        TCP::release
    }
    

    I first separate the URI from the query string, then perform a series of replace functions to 1) change GET to POST, change the original URI to the URI without query string, and 3) inserts the Content-Type and Content-Length headers before the Accept.

    This is just an example, so it doesn't account for the following conditions:

    1. Whether or not the request is originally a GET or POST
    2. Whether or not Content-Type and Content-Length headers are already present
  • I can't speak for why HTTP::request and HTTP::method are read-only commands, but the overhead of doing this at layer 4 should be minimal if you filter the payload appropriately. You know that the TCP::payload of a GET request will start with;

    GET /some-uri HTTP/1.    
    

    so simply issue a TCP::release if it's not what you're looking for.

    By the way, if you're doing this with an HTTPS VIP, just change TCP:: to SSL:: and use the CLIENTSSL_HANDSHAKE and CLIENTSSL_DATA events.

  • This may or may not help, but I had a similar problem with a POST turning into a GET after a 302 redirect. Instead of converting the GET back into a POST with iRules, I simply issued HTTP::respond 307 Location "http://post.here.com"

     

    302s cause browsers to convert the initial POST request into a GET. 307s tells the client to resubmit data as a post after a redirect. This saved me a lot of trouble.