All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.aludratest.cloud.selenium.impl.HttpResourceProxyServer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2010-2015 AludraTest.org and the contributors.
 *
 * 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 org.aludratest.cloud.selenium.impl;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aludratest.cloud.resource.Resource;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract base class for proxy servers proxying HTTP requests to custom HttpProxies. A server created by this class serves all
 * requests to /proxyNN URLs and delegates them to the according registered proxy.
 * 
 * @author falbrech
 * 
 * @param 
 *            Type of the Proxies managed by this server.
 * @param 
 *            Type of the resources accessed via the proxies.
 */
public abstract class HttpResourceProxyServer extends HttpServlet {

	private static final long serialVersionUID = 6018055751276833467L;

	private static final Logger LOG = LoggerFactory.getLogger(HttpResourceProxyServer.class);

	private Map proxies = new LinkedHashMap();

	private AtomicInteger nextProxyId = new AtomicInteger(0);

	private Server jettyServer;

	private ServletContextHandler servletContextHandler;

	private volatile ServletConfig servletConfig;

	private String hostName;

	private int port;

	private int maxProxyQueueSize;

	private int maxProxyThreads;

	public HttpResourceProxyServer(String hostName, int port, int maxProxyQueueSize, int maxProxyThreads) {
		this.hostName = hostName;
		this.port = port;
		this.maxProxyQueueSize = maxProxyQueueSize;
		this.maxProxyThreads = maxProxyThreads;
		createJetty(port);
	}

	private void createJetty(int port) {
		jettyServer = new Server(port);

		createThreadPool();

		servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
		servletContextHandler.setContextPath("/");

		servletContextHandler.addServlet(new ServletHolder(this), "/*");

		jettyServer.setHandler(servletContextHandler);
	}

	private void createThreadPool() {
		LinkedBlockingQueue queue = new LinkedBlockingQueue(maxProxyQueueSize);

		ThreadFactory threadFactory = new ThreadFactory() {

			private AtomicInteger threadNum = new AtomicInteger();

			@Override
			public Thread newThread(Runnable r) {
				Thread thread = new Thread(r);
				thread.setName("Selenium Proxy Server Jetty-" + threadNum.incrementAndGet());
				return thread;
			}
		};

		ThreadPoolExecutor executor = new ThreadPoolExecutor(5, maxProxyThreads, 5, TimeUnit.MINUTES, queue, threadFactory);
		ExecutorThreadPool threadPool = new ExecutorThreadPool(executor);
		jettyServer.setThreadPool(threadPool);
	}

	public int getPort() {
		return port;
	}

	public void start() throws Exception {
		jettyServer.start();
	}

	public boolean isRunning() {
		return jettyServer.isRunning();
	}

	public void restartJetty(int newJettyPort) throws Exception {
		if (jettyServer.isRunning()) {
			jettyServer.stop();
			jettyServer.join();
		}
		this.port = newJettyPort;
		createJetty(newJettyPort);
		start();
		updateProxyAccessUrls();
	}

	public void shutdown() throws Exception {
		if (jettyServer.isRunning()) {
			jettyServer.stop();
			jettyServer.join();
		}

		for (T proxy : proxies.values()) {
			proxy.destroy();
		}
		proxies.clear();
	}

	/**
	 * Reconfigures the proxy server's parameters. Does not reflect changes in port value, as this would require a server restart.
	 * Use {@link #restartJetty(int)} for restarting Jetty, which throws e.g. an exception when the port is blocked.
	 * 
	 */
	public void reconfigure(int maxProxyThreads, int maxProxyQueueSize) {
		boolean updateThreadPool = this.maxProxyQueueSize != maxProxyQueueSize || this.maxProxyThreads != maxProxyThreads;

		this.maxProxyQueueSize = maxProxyQueueSize;
		this.maxProxyThreads = maxProxyThreads;

		if (updateThreadPool) {
			createThreadPool();
		}
	}

	protected abstract T findExistingProxy(R resource, Collection existingProxies);

	protected abstract T createProxy(int id, R resource, String path, String accessUrl);

	protected abstract void updateProxyAccessUrl(T proxy, String newAccessUrl);

	protected final synchronized List getAllProxies() {
		return new ArrayList(proxies.values());
	}

	public T addProxyForResource(R resource) throws MalformedURLException {
		T proxy = findExistingProxy(resource, getAllProxies());
		if (proxy != null) {
			return proxy;
		}

		int id = nextProxyId.incrementAndGet();
		String path = "/proxy" + id;
		String accessUrl = "http://" + hostName + ":" + port + path;

		proxy = createProxy(id, resource, path, accessUrl);

		synchronized (this) {
			proxies.put(id, proxy);
		}

		if (servletConfig != null) {
			try {
				proxy.init(servletConfig);
			}
			catch (ServletException e) {
				LOG.error("Could not init HTTP Proxy", e);
			}
		}

		return proxy;
	}

	public void removeProxy(T proxy) {
		synchronized (this) {
			if (!proxies.containsValue(proxy)) {
				return;
			}
			proxies.remove(proxy.getId());
		}

		proxy.destroy();
	}

	public void updateHostName(String newHostName) {
		hostName = newHostName;
		updateProxyAccessUrls();
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		this.servletConfig = config;

		for (T proxy : getAllProxies()) {
			proxy.init(config);
		}
	}

	protected final Server getJettyServer() {
		return jettyServer;
	}

	private static final Pattern PATTERN_PROXY_ID = Pattern.compile("/proxy([0-9]+)/.*");

	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// extract and identify proxy ID; forward to proxy
		String path = req.getPathInfo();
		if (path == null) {
			throw new FileNotFoundException();
		}

		Matcher m = PATTERN_PROXY_ID.matcher(path);
		if (m.matches()) {
			try {
				int id = Integer.parseInt(m.group(1));
				if (proxies.containsKey(id)) {
					proxies.get(id).service(req, resp);
					return;
				}
			}
			catch (ServletException e) {
				throw e;
			}
			catch (IOException e) {
				throw e;
			}
			catch (Throwable t) {
				LOG.warn("Delegating resource proxy threw unknown error", t);
				throw new FileNotFoundException();
			}
		}

		LOG.debug("No proxy found to handle request to " + path);
		throw new FileNotFoundException();
	}

	private void updateProxyAccessUrls() {
		for (T proxy : getAllProxies()) {
			String path = "/proxy" + proxy.getId();
			String accessUrl = "http://" + hostName + ":" + port + path;
			updateProxyAccessUrl(proxy, accessUrl);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy