Como crear un Proxy HTTP con Apache 2

Como crear un Proxy HTTP con Apache 2


Recientemente he tenido que dar una solución para servir paginas externas a través de una de nuestras applicaciones (crossdomain) y continuar siendo completamente operativas además de hacer uso de un User-Agent distinto al del navegador.

Como solución me he decantado por mod_proxy junto con mod_proxy_http (y su dependencia de mod_xml2enc) de Apache 2 (solo para versión 2.4 y algunas versiones 2.2) ya que es un software bastante consolidado y siempre me ha dado buenos resultados. En este ejemplo voy a describir paso a paso cada una de las directivas que he utilizado y el porqué.

Para empezar la primera directiva es eliminar la cabecera Accept-Encoding remitida por el navegador para evitar problemas de codificación en el momento en el que el mod_proxy_html lee la pagina remota. Usando mod_header la desabilitamos con:

RequestHeader  unset  Accept-Encoding

Ahora pasamos a definir como mod_proxy_html debe de comportarse al hacer la reescritura de las rutas definidas en el HTML. La configuración la obtuve de un articulo de ckdake, a la que agregue algunas modificaciones para otros atributos.

ProxyHTMLLinks permite definir que etiquetas deben ser revisadas y cuales son los atributos a reescribir, este es mi ejemplo:

ProxyHTMLLinks a               href
ProxyHTMLLinks area            href
ProxyHTMLLinks link            href
ProxyHTMLLinks img             src longdesc usemap
ProxyHTMLLinks object          classid codebase data usemap
ProxyHTMLLinks q               cite
ProxyHTMLLinks blockquote      cite
ProxyHTMLLinks ins             cite
ProxyHTMLLinks del             cite    
ProxyHTMLLinks form            action
ProxyHTMLLinks input           src usemap
ProxyHTMLLinks head            profile
ProxyHTMLLinks base            href
ProxyHTMLLinks script          src for data-main
ProxyHTMLLinks iframe          src
ProxyHTMLEvents onclick ondblclick onmousedown onmouseup \
                    onmouseover onmousemove onmouseout onkeypress \
                    onkeydown onkeyup onfocus onblur onload \
                    onunload onsubmit onreset onselect onchange

Es cierto que para muchos de los port que podéis encontrar en las distintas distribuciones de Linux estas directivas ya están configuradas. En mi caso la configuración base no cubría algunas (como el caso de data-main para los que usais Requirejs).

Definimos cual es el filtro que Apache debe usar para procesar las respuestas del servidor, con SetOutputFilter y el filtro proxy-html.

SetOutputFilter proxy-html

Definimos que ficheros deben de ser reescritos con:

ProxyHTMLEnable On
ProxyHTMLExtended On

ProxyHTMLEnable filtrará todos los ficheros .html leídos de forma remota por Apache y ProxyHTMLExtended reescribirá también todos los ficheros .js y .css.

Con la ayuda de mod_rewrite reescribimos todas las peticiones provenientes de un patrón de URI especifica, para que sean servidas a través del proxy.

    RewriteEngine On
    RewriteRule ^iphone/(.*) http://my-remote-host/$1 [P]

Si la petición de una URI comienza por iphone, está será servida por el host remoto a traves del proxy gracias al parámetro [P]. Un ejemplo sería:

http://my-host/iphone/?my-parameter=true

esta URL se resolverá como:

http://my-remote-host/?my-parameter=true

Agregamos la URI vinculada la URL remota usando la directiva ProxyPass

ProxyPass /iphone http://my-remote-host/

Y para terminar agregamos la localización que contendrá la configuración del proxy y que describiré más abajo:


    RequestHeader set User-Agent "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"
    ProxyPassReverse http://my-remote-host/
    ProxyPassReverseCookiePath / /iphone    
    ProxyHTMLURLMap ^(http://my-remote-host/)(.*) /iphone/my-remote-host/$2 R
    ProxyHTMLURLMap ^/(?!iphone)(.*)$ /iphone/my-remote-host/$1 R

Como ya comente al inicio de este post vamos a emular un User-Agent, en este caso IPhone en vez del especificado por el navegador. mod_header con su directiva RequestHeader nos permite cambiar la cabecera (podéis encontrarlas miles en Google):

RequestHeader set User-Agent "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"

Ajustamos la URL inversa con ProxyPassReverse y la Cookie con ProxyPassReverseCookiePath para que se ajusten a la localización configurada en nuestro proxy.

ProxyPassReverse http://my-remote-host/
ProxyPassReverseCookiePath / /iphone    

Y finalmente definimos cuales son los patrones que seran sustituidos en los atributos definidos en las directivas ProxyHTMLLinks y ProxyHTMLEvents con ProxyHTMLURLMap. Como se puede ver abajo uso dos, el primero realiza el cambio de URLs absolutas y el segundo las relativas además de evitar bucles (excluyendo los casos ya sustituidos (?!iphone)).

ProxyHTMLURLMap ^(http://my-remote-host/)(.*) /iphone/$2 R
ProxyHTMLURLMap ^/(?!iphone)(.*)$ /iphone/$1 R

La Rdetermina que se usa una expresión regular.

Problemas encontrados

Nginx

Durante mis pruebas me encontre con problemas para servir paginas de Nginx (las peticiones se resolvian con 404) así que para estos casos use ProxyPreserveHost

ProxyPreserveHost Off

Mod_pagespeed

Si usas mod_pagespeed seguramente deberás eliminarlo o buscar una configuración compatible ya que el realiza reescritura del html y entra en conflicto (y puedo asegurarte que me trajo de cabeza).

Creo que es un árticulo largo así que para resumir aquí os dejo toda la configuración de ejemplo https://github.com/makensi/examples/tree/master/apache-proxy-http, espero que os ayude.