Forum Discussion

Adrian_Turcu_10's avatar
Adrian_Turcu_10
Icon for Nimbostratus rankNimbostratus
Oct 30, 2007

Help With HTTP_RESPONSE

Hello Gurus

 

 

Could someone help me with a quick rule to replace a header in the response back to the client, please?

 

 

My problem is like follows: we have a poxi app (not proxy) and the damn thing is sending a redirection back to the client to localhost instead of maintaining the hostname that requested the initial page and the protocol

 

I was talking with the developers and they they don't have an estimate for a fix (I'm gonna kill them soon) and reading through the posts I believe my interim solution will be arounf HTTP_RESPONSE actions in the iRules.

 

 

How can I use iRules in the response to the client to replace the beging of the redirection (http://127.0.0.1) with the protocol and host that was made for that request (https://acme.com) and maintain the reamining of the redirectied URI (/some/redirected/path/to/the/client.jsp)

 

 

Thanks in advance,

 

Adrian

 

18 Replies

  • How about like this.....

     

     

    when HTTP_REQUEST {

     

    set shost [getfield [HTTP::host] ":" 1]

     

    set smethod [HTTP::HTTP::method]

     

    }

     

     

    when HTTP_RESPONSE {

     

     

    if { [HTTP::status] starts_with "3" } {

     

    set rlocation [HTTP::header "Location"]

     

    set rurl [string range $rlocation 0 15]

     

    HTTP::header replace Location $smethod://$shost$ruri

     

    }

     

    }

     

     

    I believe that this should set $rurl to $rlocation minus the first 16 characters.
  • wow, i'm not that good on tcl (not good at all i should say)

     

    I might be wrong here, but from the tcl manual:

     

     

    string range string first last

     

    Returns a range of consecutive characters from string, starting with the character whose index is first and ending with the character whose index is last. An index of 0 refers to the first character of the string. An index of end (or any abbreviation of it) refers to the last character of the string. If first is less than zero then it is treated as if it were zero, and if last is greater than or equal to the length of the string then it is treated as if it were end. If first is greater than last then an empty string is returned.

     

     

    So, that would return me in the rulr the actual bit I want to get rid off: http://127.0.0.1

     

     

    If i read the manual right, the way to do it would be:

     

     

    set rurl [string range $rlocation 16 end]

     

     

    ... I believe.

     

     

    I complentely agree that needs to be tested. I'll try to crop something up tomorow (gmt time here) before I go live.

     

     

    Thank again for your time and thoughts,

     

    Adrian

     

  • hoolio's avatar
    hoolio
    Icon for Cirrostratus rankCirrostratus
    A couple of notes/suggestions:

    HTTP::method (Click here) won't return the protocol used for the request (HTTP or HTTPS). It will return the method used in the request (GET, HEAD, POST, etc).

    If you want to determine whether to use HTTP or HTTPS in the Location header value, you can either assume it based on whether the rule is added to an HTTP or HTTPS virtual server, or for wildcard port virtual servers you can check the requested port (using TCP::local_port in the client side context).

    The HTTP::host command will return what the client entered after the protocol (http:// or https://) and before the start of the URI (/path/to/file.txt) in the address bar of the browser. For example, if a client requested https://test.example.com:80/index.jsp?pageno=1, HTTP::host would return test.example.com:80. Most clients don't make requests which include the port number though. Unless the requested URL contained the port, splitting the HTTP::host value on a colon and taking the second field will not return anything.

    If you want to replace a string within a string in TCL, you can use the string map command (http://www.hume.com/html84/mann/string.html). The format is: string map "find1 replace1 find2 replace2" original_string. If you're using literal strings for the find/replace, you can use {}'s instead of the ""'s.

    You can test basic commands in the RULE_INIT event. This event is triggered when the rule is created or saved. You don't have to assign the rule to a virtual server even. You can check the /var/log/ltm log file for the log output. It can be an easy way to check string commands and others that are allowed in the RULE_INIT event. Here's an example:

    
    when RULE_INIT {
        save a sample string to the $::location_test variable
       set ::location_test "http://127.0.0.1/path/to/file.txt"
        save a sample HTTP host header value
       set ::host_test "www.example.com"
       
        log the value
       log local0. "Sample Location header value: $::location_test"
        replace the string "127.0.0.1" with the test host header value
       log local0. "Updated string 1: [string map "127.0.0.1 $::host_test" $::location_test]"
        replace the strings "http://" with "https://" and "127.0.0.1" with the test host header value
       log local0. "Updated string 2: [string map "http:// https:// 127.0.0.1 $::host_test" $::location_test]"
    }

    This logs the following:

    
    Rule : Sample Location header value: http://127.0.0.1/path/to/file.txt
    Rule : Updated string 1: http://www.example.com/path/to/file.txt
    Rule : Updated string 2: https://www.example.com/path/to/file.txt

    Based on that, you can build an example to perform the actual replacement of the Location header in 3xx redirect responses coming from the pool members. This one checks the port the client made a request to. If it was 443, then any instance of http:// in the Location header will be replaced with https://. If the port wasn't 443, then only the 127.0.0.1 string is replaced.

    
    when HTTP_REQUEST {
        Save host (without the port if it's specified)
       set shost [getfield [HTTP::host] ":" 1]
       log local0. "Parsed Host header value: $shost"
    }
    when HTTP_RESPONSE {
        check if response is a redirect (HTTP status of 3xx)
       if { [HTTP::status] starts_with "3" } {
           Save original Location value
          set location_original [HTTP::header value Location]
          log local0. "Original Location header value: $location_original"
           Check if the port the request was made on was 443
          if {[clientside {TCP::local_port}] == 443}{
              Request was made to an SSL port, so replace 127.0.0.1 with the Host header value from the request
                and replace http:// with https:// in the Location header value
             set location_updated [string map "http:// https:// 127.0.0.1 $shost" $location_original]
             log local0. "Updated Location header value for HTTP request: $location_updated"
              Perform the actual header replacement
             HTTP::header replace Location $location_updated
          } else {
              Request was made to an HTTP port, so just replace 127.0.0.1 with the Host header value from the request
             set location_updated [string map "127.0.0.1 $shost" $location_original]
             log local0. "Updated Location header value for HTTPS request: $location_updated"
              Perform the actual header replacement
             HTTP::header replace Location $location_updated
          }
       }
    }

    This logs the following for an HTTP request followed by an HTTPS request:

    
     : Parsed Host header value: 192.168.101.42
     : Original Location header value: http://127.0.0.1/test/file.txt
     : Updated Location header value for HTTP request: https://192.168.101.42/test/file.txt
     : Parsed Host header value: 192.168.101.42
     : Original Location header value: http://127.0.0.1/test/file.txt
     : Updated Location header value for HTTPS request: http://192.168.101.42/test/file.txt

    The above example uses extra variables so you can log the intermediate steps. It could be streamlined to remove the extra variables.

    Hope this helps...

    Aaron
  • Thanks Aaron.

     

    I have loccaly tested your solution and works like a charm.

     

    The production changes will be scheduled in the next couple of days and I don't suspect any issues.

     

     

    Devcentral rocks as always!

     

     

    Thanks to all,

     

    Adrian

     

  • hoolio's avatar
    hoolio
    Icon for Cirrostratus rankCirrostratus
    That's good to hear. Here is a version without the logging and intermediate variables:

    
    when HTTP_REQUEST {
        Save host (without the port if it's specified)
       set shost [getfield [HTTP::host] ":" 1]
    }
    when HTTP_RESPONSE {
        Check if response is a redirect (HTTP status of 3xx)
       if { [HTTP::status] starts_with "3" } {
           Check if the port the request was made on was 443
          if {[clientside {TCP::local_port}] == 443}{
              Request was made to an SSL port, so replace 127.0.0.1 with the Host header value from the request
                and replace http:// with https:// in the Location header value
             HTTP::header replace Location [string map "http:// https:// 127.0.0.1 $shost" [HTTP::header value Location]]
          } else {
              Request was made to an HTTP port, so just replace 127.0.0.1 with the Host header value from the request
             HTTP::header replace Location [string map "127.0.0.1 $shost" [HTTP::header value Location]]
          }
       }
    }

    Aaron
  • Hi fellows,

     

     

    Nothing to do with this topic

     

     

    I have posted an issue on this forum and in need to resolve it the earliest possible., and so far, a couple views and no hints.

     

     

    Could someone point me to a source of info i.e other forums etc.

     

     

    cheers

     

    t0
  • kjc's avatar
    kjc
    Icon for Nimbostratus rankNimbostratus
    Hey, this is VERY helpful.

     

     

    But not quite what the doctor order for my case of improper returnURLitus.

     

     

    In my case, I have an SSO product that is mangling the original URL on redirect to login from an SSL frontend. Both servers are fronted by the BigIP, both running SSL on the BigIP.

     

    So client requests https://serv1:443/page

     

    The SSO webagent looks at the authentication headers and redirects to login page on https://serv2:443/login?encoded_original_urlhttp://serv1/page

     

    The encoded URL now says "http" instead of "https" because the agent does not understand he is "https" on the front end.

     

    So after successful login the client is redirected back to his original URL sans the little secure "S" in the scheme of the URL, i.e. http://serv1:443/page

     

     

    But I do have unsecure port 80 pages on serv1, so this example would rewrite everything unsecure to secure and that's not quite what I need.

     

    Can I check the Location's port for 443 and then replace "http://" with "https://" instead of checking clientside port for 443?

     

     

    Or is there any easier way to make my virtual server on the BigIP listen to non-ssl and ssl on 443 and redirect the non-ssl requests to ssl?
  • kjc's avatar
    kjc
    Icon for Nimbostratus rankNimbostratus
    This works... on the server that sends the last redirect to the original URL. Not intiutive, though, is it?

     

     

    when HTTP_RESPONSE {

     

    check if response is a redirect (HTTP status of 3xx)

     

    if { [HTTP::status] starts_with "3" } {

     

     

    Save original Location value

     

    set location_original [HTTP::header value Location]

     

     

    Check if the port the request was made on was 443

     

    if { [HTTP::header value Location] contains ":443" } {

     

     

    Request was made to an SSL port, replace http:// with https:// in the Location header value

     

    set location_updated [string map "http:// https:// " $location_original]

     

     

    Perform the actual header replacement

     

    HTTP::header replace Location $location_updated

     

    log local4. "Updated Location header value for HTTP request: $location_updated"

     

    }

     

    }

     

    }