HTTP Super SIDEBAND Requestor (Client) Handles Redirects, Cookies, Chunked Transfer, APM Access, etc

Mark’s HTTP Super SIDEBAND Requestor!

This iRule implements a nifty HTTP client “http_req” using SIDEBAND. You may call it from another iRule to fetch or process data via some HTTP(S) service. It handles challenges like redirects and cookies (lets you access APM-protected services!), IPv6, chunked transfer coding, easy form submission, and Basic AuthN. http_req emulates the critical functions of a web browser (somewhat in the spirit of cURL).
For example, you might query a RESTful API service to decide, based on the geographic origin of an incoming LTM connection, which LTM pool to proxy it to. Usinghttp_reqa single (long) line is sufficient to send a query containing LTM IP Geolocation info then act on the response:
if {[call /Common/HSSR::http_req -uri $dbURI -rbody xpool
 -form [list country [whereis [IP::client_addr] country]]] == 200} { pool $xpool }

You may connect directly to remote HTTP servers or to virtual servers (http/https) on your BIG-IP. To connect to remote HTTPS servers you must utilize a simple helper virtual server with a Server SSL Profile. For universal outbound HTTP(S) support attach the iRule HSSR-helper to your helper virtual server. SeeHSSR-helperbelow for easy setup details.
Changes from previous version: iRules proc implementation; persistent-connection support; full/proper cookie support; IPv6 support; universal outbound HTTP(S) helper-virtual-server support (including SNI); easy HTML form submission; HTTP proxy support; HTTP 100-Continue support for Web Services; good RFC compliance; content character-set sniffing; much more besides!

Performance

http_req is efficient in three ways: (1) it is carefully coded to run quickly; (2) it supports reuse of http(s) connections (HTTP/1.1); (3) it frees you to spend your time implementing business logic rather than formatting HTTP requests and decoding the responses.
An example of connection reuse appears below; see APM+LTM SSO Cookie Example.

Installation

Save this iRule in the Common partition under the name HSSR. Other iRules will call procs (mainly http_req) from HSSR, e.g.,

set status [call /Common/HSSR::http_req -uri $someURL -state xyz]

and

call /Common/HSSR::http_done xyz
.
Also install iRule /Common/HSSR-helper (see below) for when you want to connect to a TLS-protected (https) service.
Follow SOL12224 to avert issues resolving hostnames in URI’s after you set System: Configuration: Device: DNS: BIND_Forwarder_Server_List

If you wish HSSR to query DNS nameservers in a Route Domain other than the default Route Domain 0 you must use the

-ns

option to pass HSSR the name of a virtual server which you have configured with a pool of forwarding nameservers for the non-0 Route Domain. See the explanation of the -ns option below.


Usage and Options

Every http_req option argument, such as

-uri

, takes exactly one value argument, such as

http://svc.example.com
. You may supply options in any order but later values replace earlier ones.
Basic usage example:
set sts [call /Common/HSSR::http_req -uri "http://svc.example.com/X" \
            -tag "getX" -rbody rbody]

log local0.info "server returned status=${sts}, content=${rbody}"

Secure access over TLS (SSL):
set sts [call /Common/HSSR::http_req -uri "https://secure.example.com/X" \
            -virt /Common/vs-HSSR-helper -tag "getX" -rbody rbody]

log local0.info "server returned status=${sts}, content=${rbody}"

Request with arguments:
set sts [call /Common/HSSR::http_req -uri "http://svc.example.com/user2pool" \
            -form [list srcIP [IP::client_addr] userID $userID] -rbody xpool]

#actual query will look like:  /user2pool?srcIP=198.51.100.49&userID=homer

if {$sts == 200} { pool $xpool }

Fancier: Log in to service with forms-based authentication, then send a query. After the login request the session cookie for the service is stored in variable
$hstate

for use with subsequent requests. Value of

-virt

is also in

$hstate
, so that later call to http_req is easier to type:
set logonform [list username "service_acct_RO" password "g7W2n0m8"]

set s1 [call /Common/HSSR::http_req -state hstate -virt /Common/vs-HSSR-helper \
           -uri "https://svc.example.com/logon" -method POST -form $logonform -redir 0]

if {$s1 == 302} {
 #logon succeeded (otherwise server returns 200 with a blank logon form)
 set s2 [call /Common/HSSR::http_req -state hstate \
            -uri "https://svc.example.com/getinfo" -rbody info]

 if {$s2 == 200} {
  log local0.info "server returned info=${info}"
  #do something with $info
 }
} else {
 #logon failed.  Do something?
}

NOTE: options
-(state|rbody|rheader|rcookies|rformat) pqr

name variables

$pqr
into which data will be placed, destroying any previous contents!
Return value from http_req is HTTP status code (555 indicates some local problem; check the LTM log).
When you invoke http_req you supply:
option value
-uri absolute URI of target (required)
-virt (optional)

name of LTM virtual server to connect to. If given, used in lieu of host:port from

-uri

(or host:port from

-proxy

URL). Default: none (see explanation below).

-virt

value will be reused from

-state

info (see below) until you give

-virt

option again. To stop using

-virt

from state info specify

-virt {}

.

-state (optional)

name of variable in which connection state (plus cookies, credentials, custom headers,

-virt

value,

-proxy

value, and

-ns

value) will be stored between calls to http_req. In use this looks like

-state xyz

(no

$

before

xyz

). Default: none (stateless operation).


Important: If you will make multiple requests, either to a single server or to various servers which use cookies, please supply option
-state
.
Using
-state

lets http_req perform better by keeping a SIDEBAND connection open between requests when appropriate (http_req will manage connections properly).

-state
also stores cookies, credentials, and other state info, improving performance in most cases and letting you avoid repeating lengthy arguments on successive calls to http_req.
If you use
-state xyz

you may invoke

call /Common/HSSR::http_close xyz

after your final call to http_req to free SIDEBAND resources immediately (and discard all other state info such as cookies, so be careful). http_close is optional because open connections will close automatically after a while (see

-idle
option) and you will often want to keep state around pending iRule events like HTTP_REQUEST from which you wish to call http_req efficiently (that is, reusing an open connection when possible).
There are four connection possibilities:
(1) Supply just
-uri URI

to connect to the host specified in the target URI using HTTP over TCP/IP. (The source IP for each connection will be some BIG-IP Self-IP.) HTTP redirects to different URI hosts (see

-redir
) will engender new TCP connections.
(2) Supply
-virt VIRT

to query a service provided by a virtual-server on the current BIG-IP device. It doesn’t matter what IP address or port virtual-server VIRT uses, nor whether it has a Client SSL Profile. You must still supply

-uri
but the hostname in the target URI need not resolve to any IP address. All queries will be sent to VIRT even after an HTTP redirect to a different URI host.
(3) Supply
-virt HELPER

to connect to some service via virtual-server HELPER on the same BIG-IP. A helper virtual server proxies connections to some other host(s) using LTM features (pools, nodes, etc.) and may have a Server SSL Profile so it can connect to TLS-protected (https) services. You must still supply

-uri
.
If HELPER is a universal helper-virtual-server with iRule HSSR-helper plus a Server SSL Profile then it will proxy connections to any http or https URI. In this case the hostname in the target URI must resolve to an IP address. (You may SNAT the source IP for each connection in HELPER’s virtual-server settings.) A single universal helper-virtual-server will support multiple unrelated instances ofhttp_req.
http_req will send all queries over one connection to HELPER even after an HTTP redirect to a different URI host but a universal helper-virtual-server will make new server-side TCP connections to different hosts (remember, a helper-virtual-server is a sort of proxy).
(4) If you supply
-proxy PROXY-URI

then a connection is made to the specified HTTP proxy host instead of to the host named in

-uri URI

or any HTTP-redirect URI hosts.

-virt
works as in (2) or (3) but each target URI is simply passed to the HTTP proxy. (Each target URI hostname must be resolvable by the proxy.)
NB: Even when you set
-virt

, your

-uri

URI must include some hostname or IP address (which by default will be copied into the HTTP

Host

header). If you do not set

-virt

, then: ensure your BIG-IP DNS setup is valid so

[RESOLV::lookup]

works; or supply the

-ns

option; or put server IP (or IP:port) in

-uri

URI. If you put server IP into URI you may use the

-host

option to force a hostname (rather than numeric IP) into the HTTP

Host

header (plus TLS SNI when using HSSR-helper). To make https requests you must use

-virt /Common/vs-HSSR-helper
or similar. Attach the iRule HSSR-helper to your helper-virtual-server.
You should supply as well:
-tag a short name for target (e.g., hostname) just for local logging.
-key

a short ID for this particular request in the local log, such as

[ACCESS::session data get sess

ion.user.display_sessionid]

. Default is client address:port (if available).


To receive data from the target server into variables, indicate:
option value
-rbody

name (without

$

) of variable to receive (final) response entity body.

-rbody

value will be a TCL string in either binary or Unicode-text format (perhaps transcoded, in the latter case, from text in another charset like ISO-8859-1; see

-rformat

value to find out which). (This is mandated by the way iRules TCL strings work. See

-rformat

and

-sniff

options plus Note 1 for more details.)

-rheaders

name of variable to receive (final) reply headers. A fake header

X-HSSR-Charset

will be appended to indicate what charset (if any) http_req thinks was used to encode the response entity body (see the

-sniff

option).

-rcookies name of variable to receive final summary of cookies (just cookie names and values, so beware of duplicate names).
-rformat

name of variable which will be set to “binary” or “text” to indicate format of

-rbody

value. May be used without

-rbody

option.

-maxbody

longest acceptable response body (octets). Default 1 Mbyte. Before increasing

-maxbody

length-limit review SOL6578.


For speed, http_req only sniffs content-type when given one or more of options
-(rbody|rheaders|rformat)
.
Each of
-(rbody|rheaders|rcookies|rformat)

is optional and

-(rheaders|rcookies)
variables, if requested, will be lists of alternating names and values.
You may optionally supply the following (use only ASCII characters in values for options other than
-body

and

-form
):
option value
-method [GET|POST|…], default is GET.
-form

a TCL list of (alternating) form-field names and values to submit to

-uri

, like

[list "name" "homer" "food" "d

onut”]

. The form-field data will be converted to format

application/x-www-form-urlenco

ded

then appended to URI if

-method

is GET or HEAD, uploaded in request body otherwise (e.g., POST). Note that with GET

-form

yields standard query-string format like

http://svc.example.com/somethi

ng?name=homer&food=donut

. Values must be text. A suitable

Content-Type

header will be sent. You may not use both

-form

and

-(body|type)

at the same time. Default: none.

-body

data to send (e.g., with

-method POST

). Default: none (but see

-form

option). Also set

-type

.

-type

value of

Content-Type

header to send with

-body

data, such as “

application/json; charset=utf-

8

” or

multipart/form-data

. It is very important to set

-type

when sending

-body

data! Default: none.

-headers

list of alternating HTTP header names and values to add to request, such as

  {Accept text/* Accept-Language
fr-CA}

. These headers override any corresponding defaults except for

Host

and

Content-(Length|Type)

. Defaults:

  {Accept */* Accept-Language en
User-Agent {BigIP/nn.xxxx (HSSRv

2)} Accept-Charset {utf-8;q=1, is o-8859-1;q=0.9, *;q=0.8} Accept-E ncoding identity}

where nn.xxxx is TMOS version+ build-identifier and (HSSRv2) is version string for HTTP Super SIDEBAND Requestor. Headers set using

-headers

will be reused from

-state

info until you set

-headers

again. To cancel all custom headers give option

-headers [list]

.

-cookies

list of alternating cookie names and values to inject into request. Default: none. (To accumulate cookies between calls to http_req use the

-state

option. Do not copy

-rcookies

value to

-cookies

.)

-userid a username for Basic Authentication (default none) and…
-passwd

…a password (missing

-passwd

value defaults to empty string). NB: to gain speed in the normal case, these credentials will be sent preemptively and promiscuously to all servers queried (even after redirection). Credentials will be reused from

-state

info until you specify

-(userid|passwd)

again. To clear credentials give option

-userid {}

.

-nocache

[false|true] where false=trust upstream caches to do their jobs; true=demand fresh responses from origin servers. Default: false. Note:

-nocache true

may cause delays. Also, http_req does not cache content locally; if you need a local cache, use a helper virtual server with a cache profile.

-ns

IP address of a single DNS nameserver or name (like /Common/vs-dns) of a virtual server having a pool of nameservers, which should be used to resolve hostnames in URI’s to IP addresses. Default: none (use BIG-IP management-plane BIND forwarding nameservers, per SOL12224). To make HSSR query nameservers in a Route Domain other than 0 you should use

-ns

with a virtual server using a pool of nameservers in the Route Domain of interest. Value of

-ns

will be reused from

-state

info until you give

-ns

option again. To stop using custom nameserver specify

-ns {}

.

-hostname (optional)

hostname to place in HTTP Host header rather than hostname or IPaddress from

-uri

URI.


To utilize an HTTP(S) proxy server, supply:
-proxy

URL (like

https://proxy.example.com:8443

/

) of HTTP proxy to use for both http and https requests. To access the proxy itself via TLS you will need a helper virtual server. If you access a proxy via plain http you may omit

-virt

but your traffic will not be secure en-route to proxy. If proxy wants authentication (Basic AuthN only) place user-ID and password into proxy URL like this:

https://userid:password@proxy.

example.com/

. Default: none. The value of

-proxy

will be reused from

-state

info until you give

-proxy

option again. To stop using any proxy specify

-proxy {}

.


In special cases you may wish to supply:
option value
-redir

[0|**1**|2] where 0=don’t follow redirects; 1=follow redirects and retry POST/PUT/?? after any redir out-and-back (supports POST to an APM-protected resource), but if

-virt

is set send all requests to it; 2=like 1 except do not retry POST/etc. Default is 1.

-expect

[0|100] where 0=send

-(body|form)

data with request headers; 100=send request headers then wait for server prompt before sending content (sometimes used with WS+SOAP). Default is 0.

-sniff

[0|1|**2**|3|4|5] where 0=determine content charset strictly per rfc2616 so text defaults to ISO-8859-1; 1=like 0 but text defaults to UTF-8; 2=when charset is not set by rfc2616 header, determine charset heuristically (W3C method; best); 3=treat response as ISO-8859-1 text regardless of headers or other info; 4=treat response as UTF-8 text regardless; 5=treat response as binary no matter what. Default 2 (see Note 1). You may use

-rheaders

option and inspect value of

X-HSSR-Charset

to learn http_req’s charset determination (likely easier than parsing reply headers and body yourself).

-munch

[0|**1**] where 0=enforce cookie origin rules; 1=accept and send cookies liberally when hostname in

-uri

is not FQDN (helpful when you access some resource with just IP or a short alias for host in

-uri

, perhaps by using

-virt

). Default is 1.

-timeout maximum wait for connection in milliseconds. Default 7500.
-wait

maximum wait for server response in seconds. Default 10. The receive-polling interval is proportional to

-wait

. Note: to fetch a very large response from a slow server you may have to increase

-wait

.

-idle maximum idle time on connection in seconds. Default 300.
-maxtime

number of seconds after which http_req will cease trying to complete request and just return the current status. Use this option to limit “tolerance stacking” whereby a series of redirects and other delays, each of which seems tolerable by itself, may sum to an intolerable delay. Default is roughly 4-1/2 minutes using default

-wait

and

-timeout

values. Note: setting

-maxtime

isn’t mandatory; http_req always terminates even if server is malicious (see Note 2).

-debug [0|1] where 1 means log debug info. Default is 0.