/*
AUTHOR : OLIVIER BOISSIN (oboissin@ftel.fr -  http://darkmag.net/darkBlog)

BASED ON : PHILIPPE GAUVIN'S WORK : http://dominoweb.blogspot.com/2006/07/ajax-lire-un-fichier-en-dehors-du.html
           TOMAS NIELSEN'S WORK : http://www.dominoexperts.com/de/forum.nsf/0/A4573078C76401BBC12573330054B72A

USAGE : /serlet/ProxyServlet?url=RelativeOrAbsoluteURL[&gzip=1|0(default)][&expires=1(default)|0] 

RELEASE NOTES :
 - 2007/10/01 : 
 		+ usage of byte streams instead of strings (binary safe)		
		+ added optionnal gzip compression (&gzip=1)
		+ added optionnal automatic expiration (&expires=1)
		+ added URL completion (if relative)
 - 2006/09/07 : initial release
*/

import java.io.*;
import java.util.zip.GZIPOutputStream;
import java.net.URL;
import java.net.HttpURLConnection;
import java.net.URLConnection;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.*;

public class ProxyServlet extends HttpServlet {
	private boolean debug = false;

	public void init(ServletConfig config) {
		System.out.println(this.getClass().getName() + " initialized");
	}

	public void destroy() {
		System.out.println(this.getClass().getName() + " destroyed");
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
		HttpURLConnection con = null ;
		URL url = null ;
		String address  = "" ;
		String httpAccept = null ;
		String httpAcceptLanguage = null ;
		String httpAcceptEncoding = null ;
		String httpContentType = null ;
		String httpCookie = null ;
		String httpSetCookie = null ;
		BufferedInputStream in = null ;
		OutputStream out = null ;
		String strDoGzip = null ;
		boolean doGzip = false ;
		String strDoExpires = null ;
		boolean doExpires = true ;
		
		try {
			// récupération de l'adresse en paramètre
			address = request.getParameter("url") ;
			if (address == null || address.length() == 0)
				throw new Exception(this.getClass().getName() + " : No url parameter in the query string");
				
			// complétion de l'URL si l'URL est relative
			address = (address.charAt(0) == '/' ? "http://" + request.getServerName() + ":" + request.getServerPort() + address : address);

			if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " URL parameter : " + address) ;
			
			// Gzip du contenu ?
			strDoGzip = request.getParameter("gzip") ;
			doGzip = (strDoGzip != null && strDoGzip.equals("1") ? true : false);

			if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " Gzip enabled : " + doGzip) ;

			// Expiration automatique du contenu ?
			strDoExpires = request.getParameter("expires") ;
			doExpires = (strDoExpires != null && strDoExpires.equals("0") ? false : true);

			if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " Automatic expiration : " + doExpires) ;
						
			// Récupération des entêtes HTTP envoyées par le client
			httpAccept = request.getHeader("Accept");
			httpAcceptLanguage = request.getHeader("Accept-Language");
			httpAcceptEncoding = request.getHeader("Accept-Encoding");
			httpCookie = request.getHeader("Cookie") ;

			// Création d'une connexion HTTP vers l'URL cible
			url = new URL(address);
			con = (HttpURLConnection) url.openConnection();

			con.setDoInput(true);
			con.setDoOutput(false);
			con.setUseCaches(false) ;
			con.setFollowRedirects(true);

			// Transfert des entêtes HTTP (client -> serveur => serveur -> cible)
			if (httpAccept != null) con.setRequestProperty("Accept", httpAccept) ;
			if (httpAcceptLanguage != null) con.setRequestProperty("Accept-Language", httpAcceptLanguage) ;
			if (httpCookie != null) con.setRequestProperty("Cookie", httpCookie);

			// Ajout d'entêtes spécifiques dans la foulée (cache, user agent)
			con.setRequestProperty("Cache-Control", "no-cache") ;
			con.setRequestProperty("Pragma", "no-cache") ;
			con.setRequestProperty("User-Agent", "Domino Proxy Servlet") ;

			// Ouverture de la connexion vers la cible
			if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " Opening connection...") ;

			// con.connect() semble bloquer la tâche HTTP de Domino dans certains cas (erreur 404 par ex)
			// l'astuce suivante résoud a priori ce problème
			try {
				con.getInputStream();
			}
			catch (Exception e) {
				int status = 404 ;
				
				try {
					 status = con.getResponseCode() ;
				}
				catch (Exception e2) {
				}
	
				if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " HTTP error code : " + status) ;
				response.sendError(con.getResponseCode()) ;
					
				return ;				
			}
			
			if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " HTTP response code : " + con.getResponseCode()) ;

			// Récupération des entêtes HTTP envoyées par le serveur cible pour les transférer vers le client
			httpContentType = con.getContentType();
			httpSetCookie = con.getHeaderField("Set-Cookie") ;

			if (debug) {
				System.out.println("[DEBUG] " + this.getClass().getName() + " Target response content type : " + httpContentType) ;
				System.out.println("[DEBUG] " + this.getClass().getName() + " Target response set-cookie : " + httpSetCookie) ;
			}

			// Envoi au client des entêtes HTTP reçues par le serveur cible
			if (httpContentType != null) response.setContentType(httpContentType);
			if (httpSetCookie != null) response.setHeader("Set-cookie", httpSetCookie);

			// Expiration automatique
			if (doExpires) {
				response.setHeader("Cache-Control", "no-cache, must-revalidate") ;
				response.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT") ;
			}
					
			// Ouverture des flux 
			in = new BufferedInputStream(con.getInputStream());
							
			if (httpAcceptEncoding != null && httpAcceptEncoding.indexOf("gzip") >= 0 && doGzip) {
				if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " Sending gzipped content") ;
				
				response.setHeader("Content-Encoding","gzip");

				// contenu gzippé
				out = new GZIPOutputStream(response.getOutputStream());
			}
			else {
				if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " Sending non-gzipped content") ;

				// contenu non gzipppé
				out = new BufferedOutputStream(response.getOutputStream());
			}
		
			// envoi du contenu
			byte[] buffer = new byte[4*1024];	// 4kb buffer
	
			int n = 0;
			while((n = in.read(buffer)) != -1) {
	        	out.write(buffer, 0, n);
      }
    
    	if (debug) System.out.println("[DEBUG] " + this.getClass().getName() + " Content sent. Disconnecting") ;

			out.flush();
			out.close();
		}
		catch(Exception e) {
			System.out.println("[EXCEPTION] " + e.getClass().getName() + " : "+ e.getMessage());
			e.printStackTrace();
			
			response.sendError(500);
		}
		finally {
			//dans tous les cas, on tente de fermer les connexions ouvertes
			try {
				in.close();
			}
			catch(Exception e) {
			}
		
			try {
				con.disconnect();
			}
			catch(Exception e) {
			}						
		}
	}
}