/** * Sahi - Web Automation and Test Tool * * Copyright 2006 V Narayan Raman * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sf.sahi; import net.sf.sahi.command.MockResponder; import net.sf.sahi.config.Configuration; import net.sf.sahi.request.HttpRequest; import net.sf.sahi.response.HttpFileResponse; import net.sf.sahi.response.HttpResponse; import net.sf.sahi.response.SimpleHttpResponse; import net.sf.sahi.ssl.SSLHelper; import net.sf.sahi.util.ThreadLocalMap; import net.sf.sahi.util.TrafficLogger; import net.sf.sahi.util.Utils; import org.apache.log4j.Logger; import org.bouncycastle.operator.OperatorCreationException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Date; import java.util.HashMap; import java.util.Properties; /** * User: nraman Date: May 13, 2005 Time: 7:06:11 PM To */ public class ProxyProcessor implements Runnable { private Socket client; private boolean isSSLSocket = false; private static Logger logger = Logger.getLogger(ProxyProcessor.class); public RemoteRequestProcessor remoteRequestProcessor = new RemoteRequestProcessor(); private static HashMap<String, String> hostAddresses = new HashMap<String, String>(100); private static String localhost; static { try { localhost = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } } public ProxyProcessor(Socket client) { this.client = client; isSSLSocket = (client instanceof SSLSocket); } public void run() { if (client.isClosed()) return; ThreadLocalMap.clearAll(); HttpRequest requestFromBrowser = null; try { requestFromBrowser = getRequestFromBrowser(); String uri = requestFromBrowser.uri(); logger.debug(uri); if (uri != null) { int _s_ = uri.indexOf("/_s_/"); int q = uri.indexOf("?"); if (_s_ != -1 && (q == -1 || (q > _s_))) { processLocally(uri, requestFromBrowser); } else { if (isHostTheProxy(requestFromBrowser.host()) && requestFromBrowser.port() == Configuration.getPort()) { processLocally(uri, requestFromBrowser); } else if (uri.indexOf("favicon.ico") != -1) { sendResponseToBrowser(new HttpFileResponse(Configuration.getHtdocsRoot() + "spr/favicon.ico")); } else { processAsProxy(requestFromBrowser); } } } else { sendResponseToBrowser(new SimpleHttpResponse("")); } if (isKeepAlive() && !client.isClosed()) { new Thread(new ProxyProcessor(client)).start(); } } catch (SSLHandshakeException ssle) { logger.warn(ssle.getMessage()); } catch (Exception e) { logger.debug(e.getMessage()); try { // should close only in case of exception. Do not move this to finally. Will cause sockets to not be reused. client.close(); } catch (IOException e2) { logger.warn(e2.getMessage()); } } } private boolean isHostTheProxy(final String host) { try { if (host.equals(Configuration.getCommonDomain())) return true; String hostAddress = getHostAddress(host); return hostAddress.equals(localhost) || hostAddress.equals("127.0.0.1"); } catch (Exception e) { return false; } } private String getHostAddress(final String host) throws UnknownHostException { if (!hostAddresses.containsKey(host)) { hostAddresses.put(host, InetAddress.getByName(host).getHostAddress()); } return hostAddresses.get(host); } private void processAsProxy(HttpRequest requestFromBrowser) throws Exception { logTraffic(requestFromBrowser); if (requestFromBrowser.isConnect()) { logHeaderAndBody(requestFromBrowser); processConnect(requestFromBrowser); } else { if (handleDifferently(requestFromBrowser)) { return; } HttpResponse responseFromHost = null; try { responseFromHost = remoteRequestProcessor.processHttp(requestFromBrowser); } catch (Exception e) { e.printStackTrace(); responseFromHost = new SimpleHttpResponse(""); } if (responseFromHost == null) { responseFromHost = new SimpleHttpResponse(""); } logger.debug("Fetching >> :" + new String(requestFromBrowser.url())); sendResponseToBrowser(responseFromHost); } } private void logHeaderAndBody(HttpRequest requestFromBrowser) { TrafficLogger.storeRequestHeader(requestFromBrowser.rawHeaders(), "unmodified"); TrafficLogger.storeRequestBody(requestFromBrowser.data(), "unmodified"); } private void logTraffic(HttpRequest requestFromBrowser) { final String fileName = requestFromBrowser.fileName(); Date time = new Date(); TrafficLogger.createLoggerForThread(fileName, "unmodified", Configuration.isUnmodifiedTrafficLoggingOn(), time); TrafficLogger.createLoggerForThread(fileName, "modified", Configuration.isModifiedTrafficLoggingOn(), time); } private boolean handleDifferently(final HttpRequest request) throws Exception { final MockResponder mockResponder = request.session().mockResponder(); HttpResponse response = mockResponder.getResponse(request); if (response == null) { return false; } sendResponseToBrowser(response); return true; } private void processConnect(HttpRequest requestFromBrowser) { try { if (isBlockableDomain(requestFromBrowser.host())) { client.getOutputStream().write(("HTTP/1.0 404 NOT FOUND\r\n\r\n").getBytes()); client.close(); return; } client.getOutputStream().write(("HTTP/1.0 200 OK\r\n\r\n").getBytes()); SSLSocket sslSocket = null; try { sslSocket = SSLHelper.getInstance().convertToSecureServerSocket(client, requestFromBrowser.host()); } catch (OperatorCreationException e) { throw new RuntimeException(e); } catch (CertificateException e) { throw new RuntimeException(e); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (KeyStoreException e) { throw new RuntimeException(e); } catch (UnrecoverableKeyException e) { throw new RuntimeException(e); } catch (Exception e) { throw new RuntimeException(e); } ProxyProcessor delegatedProcessor = new ProxyProcessor(sslSocket); delegatedProcessor.run(); } catch (SSLHandshakeException e) { // FIXME logging } catch (IOException e) { try { client.close(); } catch (IOException e1) { e1.printStackTrace(); } e.printStackTrace(); } } private boolean isBlockableDomain(String host) { String[] list = Configuration.getBlockableSSLDomainsList(); for (int i = 0; i < list.length; i++) { String pattern = list[i]; if (host.matches(pattern.trim())) { return true; } } return false; } private void processLocally(String uri, final HttpRequest requestFromBrowser) throws IOException { HttpResponse httpResponse; try { httpResponse = new LocalRequestProcessor().getLocalResponse(uri, requestFromBrowser); } catch (Exception e) { Properties props = new Properties(); props.put("responseCode", "500"); props.put("time", "" + (new Date())); props.put("message", Utils.getStackTraceString(e, true)); httpResponse = new HttpFileResponse( Configuration.getHtdocsRoot() + "spr/5xx.htm", props, false, true); } sendResponseToBrowser(httpResponse); } private HttpRequest getRequestFromBrowser() throws IOException { InputStream in = client.getInputStream(); return new HttpRequest(in, isSSLSocket); } protected void sendResponseToBrowser(final HttpResponse responseFromHost) throws IOException { OutputStream outputStreamToBrowser = client.getOutputStream(); responseFromHost.sendHeaders(outputStreamToBrowser, isKeepAlive()); responseFromHost.sendBody(outputStreamToBrowser); if (!isKeepAlive()) { outputStreamToBrowser.close(); client.close(); } responseFromHost.cleanUp(); } private boolean isKeepAlive() { return Configuration.isKeepAliveEnabled() && !isSSLSocket; } protected Socket client() { return client; } }