Forum Discussion

ukstin's avatar
ukstin
Icon for Nimbostratus rankNimbostratus
Jun 09, 2008

conditional redirect (404)

Hi people,

I´m trying to wrote a i-rule to redirect a 404 error based on the url. The i-rule I made is this one:

when HTTP_REQUEST { 
 set host [HTTP::host] 
 set url  [HTTP::uri] 
 } 
 when HTTP_RESPONSE { 
 set status_code [HTTP::status] 
 if { $status_code equals 404 } { 
 if { $host contains "abcd" } { 
 HTTP::redirect "http://abcd.otherdomain.com/$url" 
 } 
 elseif { $host contains "efg" } { 
 HTTP::redirect "http://efg.otherdomain.com/$url" 
 } 
 else {  
 HTTP::redirect "http://anotherone.com/xyz.html" 
 } 
   }   
 }

it seens ok to me and it works almost every time, but sometimes I received the following error:

Jun  9 19:25:07 tmm tmm[1133]: 01220001:3: TCL error: Rule irule_redirect_404  - can't read "host": no such variable     while executing "if { $host contains ..."

any ideas why the host variable doesn´t work sometimes?

4 Replies

  • Looks like the variable can't be used in another part of the irule.

    have you tried the following

      
      when HTTP_REQUEST {   
       set ::host [HTTP::host]   
       set ::url  [HTTP::uri]   
       }   
        
       when HTTP_RESPONSE {   
        if { [HTTP::status] == 404 } {   
       if { $::host contains "abcd" } {   
       HTTP::redirect "http://abcd.otherdomain.com/$::url"   
       } elseif { $::host contains "efg" } {   
       HTTP::redirect "http://efg.otherdomain.com/$::url"   
       } else {    
      HTTP::redirect "http://anotherone.com/xyz.html"   
       }   
         }     
       }  
      

    If you noticed I am changing your variable from local to global. I have not tested this, but give that a shot.

    CB

  • Patrick_Chang_7's avatar
    Patrick_Chang_7
    Historic F5 Account
    Some HTTP requests do not contain a host header. Therefore, [HTTP::host] will be empty. You could change the when HTTP_REQUEST logic to read the host from the first line of the raw HTTP::request if [HTTP::host] is an empty string. I would log the value of [HTTP::host] when you set the host variable, just to make sure though. You can remove the logging when the rule is fully debugged.
  • The host header is only required for HTTP 1.1. If a client sends an HTTP 1.0 (or even 0.9) request, the Host header value wouldn't necessarily be present, but regardless the $host variable should be set to a zero length string. I'm not sure how the runtime error could be triggered for $host not being defined, as it's set on every HTTP request (even if it's to a null value).

    Using the rule below, I did some tests on 9.2.4 using netcat. In every test, checking if the $host value contained a string succeeded without a TCL error. Between this post and a related one on the ProxyPass rule (Click here), I'd suggest opening a case with F5 Support. It seems like this might be a bug.

    As a workaround, you could add a check to see if $host exists before trying to check if it contains a string:

      
      when HTTP_REQUEST {   
         set host [HTTP::host]  
         set url  [HTTP::uri]   
      }  
      when HTTP_RESPONSE {   
         if { [HTTP::status] == 404 } {  
            if {[info exists host]}{  
               if {$host contains "abcd" } {  
                  HTTP::redirect "http://abcd.otherdomain.com/$url"  
               } elseif {[info exists host] && $host contains "efg" } {  
                  HTTP::redirect "http://efg.otherdomain.com/$url"  
               } else {  
                  HTTP::redirect "http://anotherone.com/xyz.html"  
               }  
            } else {  
                $host doesn't exist!  
               log local0. "[IP::client_addr]:[TCP::client_port]: \$host was not set!?"  
            }  
         }  
      }  
      

    Aaron

    Example rule which saves the HTTP::host output and checks the value in HTTP_RESPONSE:

      
      when HTTP_REQUEST {  
        
         set host [HTTP::host]  
           
         log local0. "HTTP version: [HTTP::version], HTTP host: $host, HTTP uri: [HTTP::uri]"  
      }  
      when HTTP_RESPONSE {  
        
         if { $host contains "www" } {  
            log local0. "matched $host"  
         } else {    
            log local0. "didn't match $host"  
         }    
      }  
      

    Request with a Host header:

    $ nc 192.168.101.40 80

    GET /withhost HTTP/1.1

    Connection: close

    Host: www.example.com

    Log output:

    Rule log_http_host_rule : HTTP version: 1.1, HTTP host: www.example.com, HTTP uri: /withhost

    Rule log_http_host_rule : matched www.example.com

    Request without a Host header:

    $ nc 192.168.101.40 80

    GET /withouthost HTTP/1.0

    Log output:

    Rule log_http_host_rule : HTTP version: 1.0, HTTP host: , HTTP uri: /withouthost

    Rule log_http_host_rule : didn't match

    0.9 formatted request:

    $ nc 192.168.101.40 80

    GET /

    Log output:

    Rule log_http_host_rule : HTTP version: 0.9, HTTP host: , HTTP uri: /

    Rule log_http_host_rule : didn't match

    Request with a Host header, but nothing after the colon:

    nc 192.168.101.40 80

    GET / HTTP/1.0

    Host:

    Test: value

    Log output:

    Rule log_http_host_rule : HTTP version: 1.1, HTTP host: Connection: close, HTTP uri: /

    Rule log_http_host_rule : didn't match Connection: close

    This last one could be considered a bug as well. Apparently the HTTP parser doesn't stop parsing the Host header value at the line terminating CRLF if there isn't a value for the header.

    'HTTP::header Host' seems to be more accurate:

     
     when HTTP_REQUEST { 
      
        set http_host [HTTP::host] 
        set http_header_host [HTTP::header Host] 
         
        log local0. "HTTP version: [HTTP::version], HTTP host: $http_host, HTTP header Host: $http_header_host, HTTP uri: [HTTP::uri]" 
     } 
     when HTTP_RESPONSE { 
      
        if { $http_host contains "www" } { 
           log local0. "http_host matched $http_host" 
        } else {   
           log local0. "http_host didn't match $http_host" 
        }   
        if { $http_header_host contains "www" } { 
           log local0. "http_header_host matched $http_header_host" 
        } else {   
           log local0. "http_header_host didn't match $http_header_host" 
        }   
     } 
     

    Request:

    $ nc 192.168.101.40 80

    GET / HTTP/1.0

    Host:

    Test: value

    Log output shows HTTP::host errantly returns the next header name and value, while HTTP::header Host correctly returns nothing:

    Rule log_http_host_rule : HTTP version: 1.0, HTTP host: Test: value, HTTP header Host: , HTTP uri: /

    Rule log_http_host_rule : http_host didn't match Test: value

    Rule log_http_host_rule : http_header_host didn't match

  • Yea, this looks really similar to the ProxyPass issue from a couple days ago. They were setting a variable in HTTP_REQUEST but occasionally they'd get a runtime error accessing it in HTTP_RESPONSE.

     

     

    I wonder if there's a bug or some weird situation where HTTP_RESPONSE is being triggered without a corresponding HTTP_REQUEST.

     

     

    Out of curiosity, do you have OneConnect enabled?