Forum Discussion

Dan_Markhasin_1's avatar
Dan_Markhasin_1
Icon for Nimbostratus rankNimbostratus
Dec 01, 2015

Capture all HTTP response headers

Hi,

 

Is there a way to capture all of the headers of the HTTP response? I am looking for something similar to HTTP::request which returns the raw HTTP headers of the request.

 

However, this command doesn't seem to exist in HTTP_RESPONSE events so I'm wondering if there's any other way of doing it (short of iterating over all of the headers and saving each one individually..)?

 

Thanks, Dan

 

6 Replies

  • I'm not sure what you mean about the difference between request and response. On 11.5.3, the following works as I would expect:

     

    ltm rule resp-header-iterate {
        when HTTP_RESPONSE {
            foreach hname [HTTP::header names] {
                log local0. "-- $hname = [HTTP::header values $hname]"
            }
        }
    }
    

     

    Do you find that either HTTP::header names or HTTP::header values does not work in HTTP_RESPONSE? If so, which version of code is running on your BIG-IP?

  • Hi,

     

    What you described would probably work, but it is exactly the solution that I am trying to avoid :-)

     

    In HTTP_REQUEST events, the HTTP::request command can be used to retrieve all of the request headers into a single variable. I was wondering if a similar command exists for HTTP_RESPONSE events (HTTP::request does not exist in these events).

     

    I know that I can iterate over the headers individually, just wondering if there was a better way to accomplish it.

     

  • Ah, I understand now. The answer -- for better-or-worse 🙂 -- is no, there is no equivalent for HTTP Response headers. You could extract them in the SERVER_DATA event, but that performance is likely to be worse than iteration. Out of curiosity, what is the objective in having the headers? Logging? Or something else?

    Oh, and just because I cannot resist a challenge, here is a possible iRule for raw Response header extraction:

     

    ltm rule get-response-headers {
        when RULE_INIT {
             there is no built-in for multi character splits in Tcl.  A common
             trick is to map the split character token sets to a character that
             cannot appear in the "real" data, then split on that single character.
             Headers cannot contain the null, so we use that.  Set this once because
             it is (potentially) repeatedly used in a single connection pass
            set static::http_header_multichar_split_map [list "\r\n" "\x00"]
        }
    
        when SERVER_CONNECTED {
            set rhlist [list]
            set hbuf ""
    
            TCP::collect
        }
    
        when SERVER_DATA {
            append hbuf [TCP::payload]
    
            set hdone 0
            if { $hbuf contains "\r\n\r\n" } {
                 The header section terminates with \r\n\r\n but the body may also contain
                 this sequence.  Ignore anything in the body.
                set hbuf [string range $hbuf 0 [expr { [string first "\r\n\r\n" $hbuf] - 1 }]]
                set hdone 1
            }
    
            set rhlist [concat $rhlist [split [string map $static::http_header_multichar_split_map $hbuf] "\x00"]]
    
            TCP::release
    
            if { $hdone } {
                set rhlist [lreplace $rhlist 0 1]
                log local0. "Have headers: $rhlist"
            }
            else {
                TCP::collect
            }
        }
    }
    

     

    I encourage you to test it, but as I said above, I suspect this is much less performant than iteration using the HTTP::header methods. And, well, it's probably super buggy, too :-p.

  • Irritatingly, the DevCentral editor won't allow me to change what I have above, so here is a modified version that actually, you know, works:

     

    ltm rule get-response-headers {
        when RULE_INIT {
             there is no built-in for multi character splits in Tcl.  A common
             trick is to map the split character token sets to a character that
             cannot appear in the "real" data, then split on that single character.
             Headers cannot contain the null, so we use that.  Set this once because
             it is (potentially) repeatedly used in a single connection pass
            set static::http_header_multichar_split_map [list "\r\n" "\x00"]
        }
    
    
        when SERVER_CONNECTED {
            set rhlist [list]
            set hbuf ""
    
            TCP::collect
        }
    
        when SERVER_DATA {
            append hbuf [TCP::payload]
    
            set hdone 0
            if { $hbuf contains "\r\n\r\n" } {
                 Only process up to the \r\n\r\n.  Ignore everything after
                set hbuf [string range $hbuf 0 [expr { [string first "\r\n\r\n" $hbuf] - 1 }]]
                set hdone 1
            }
    
            set rhlist [concat $rhlist [split [string map $static::http_header_multichar_split_map $hbuf] "\x00"]]
    
            if { !$hdone } {
                 remove from the rolling buffer whatever was just processed
                set hbuf [string range $hbuf [expr { [string last "\r\n" $hbuf] + 2 }] end]
            }
    
            TCP::release
    
            if { $hdone } {
                 Remove the start line (e.g., HTTP/1.1 200 OK)
                set rhlist [lreplace $rhlist 0 1]
                log local0. " --> Have headers: [llength $rhlist] <--"
                foreach hh $rhlist {
                    log local0. "$hh"
                }
            }
            else {
                TCP::collect
            }
        }
    }
    

     

  • Thanks a lot for your replies Vernon :-)

     

    As you guessed, the intention is for logging (we're logging requests/responses that were blocked by ASM). I guess the best approach would be to iterate over the headers as you suggested.

     

  • In that case, if you know the headers you are targeting, and you are running 11.3+, you can also use HTTP Request Logging (somewhat misnamed in this case because you can also use it for Response headers):

    It isn't completely obvious from the manual page, but you can place arbitrary headers in your Template using ${header-name}.