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

org.openqa.selenium.server.BrowserSessionFactory Maven / Gradle / Ivy

There is a newer version: 4.0.0-alpha-2
Show newest version
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you 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.openqa.selenium.server;


import org.openqa.selenium.Capabilities;
import org.openqa.selenium.server.browserlaunchers.BrowserLauncher;
import org.openqa.selenium.remote.SessionId;
import org.openqa.selenium.remote.server.log.LoggingManager;
import org.openqa.selenium.remote.server.log.PerSessionLogHandler;
import org.openqa.selenium.server.browserlaunchers.BrowserLauncherFactory;
import org.openqa.selenium.server.browserlaunchers.InvalidBrowserExecutableException;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Manages browser sessions, their creation, and their closure.
 * 

* Maintains a cache of unused and available browser sessions in case the server is reusing * sessions. Also manages the creation and finalization of all browser sessions. * * @author [email protected] (Jennifer Bevan) */ public class BrowserSessionFactory { private static final long DEFAULT_CLEANUP_INTERVAL = 300000; // 5 minutes. private static final long DEFAULT_MAX_IDLE_SESSION_TIME = 600000; // 10 minutes private static Logger log = Logger.getLogger(BrowserSessionFactory.class.getName()); // cached, unused, already-launched browser sessions. protected final Set availableSessions = Collections.synchronizedSet(new HashSet()); // active browser sessions. protected final Set activeSessions = Collections.synchronizedSet(new HashSet()); private final BrowserLauncherFactory browserLauncherFactory; private final Timer cleanupTimer; private final long maxIdleSessionTime; private final boolean doCleanup; public BrowserSessionFactory(BrowserLauncherFactory blf) { this(blf, DEFAULT_CLEANUP_INTERVAL, DEFAULT_MAX_IDLE_SESSION_TIME, true); } /** * Constructor for testing purposes. * * @param blf an injected BrowserLauncherFactory. * @param cleanupInterval the time between idle available session cleaning sweeps. * @param maxIdleSessionTime the max time in ms for an available session to be idle. * @param doCleanup whether or not the idle session cleanup thread should run. */ protected BrowserSessionFactory(BrowserLauncherFactory blf, long cleanupInterval, long maxIdleSessionTime, boolean doCleanup) { browserLauncherFactory = blf; this.maxIdleSessionTime = maxIdleSessionTime; this.doCleanup = doCleanup; cleanupTimer = new Timer(/* daemon= */true); if (doCleanup) { cleanupTimer.schedule(new CleanupTask(), 0, cleanupInterval); } } /** * Gets a new browser session, using the SeleniumServer static fields to populate parameters. * * @param browserString browser name string * @param startURL starting url * @param extensionJs per-session user extension Javascript * @param configuration Remote Control configuration. Cannot be null. * @param browserConfigurations capabilities of requested browser * @return the BrowserSessionInfo for the new browser session. * @throws RemoteCommandException remote command exception if new session fails */ public BrowserSessionInfo getNewBrowserSession(String browserString, String startURL, String extensionJs, Capabilities browserConfigurations, RemoteControlConfiguration configuration) throws RemoteCommandException { return getNewBrowserSession(browserString, startURL, extensionJs, browserConfigurations, configuration.reuseBrowserSessions(), configuration.isEnsureCleanSession(), configuration); } /** * Gets a new browser session * * @param browserString browser name string * @param startURL starting url * @param extensionJs per-session user extension Javascript * @param configuration Remote Control configuration. Cannot be null. * @param useCached if a cached session should be used if one is available * @param ensureClean if a clean session (e.g. no previous cookies) is required. * @param browserConfigurations capabilities of requested browser * @return the BrowserSessionInfo for the new browser session. * @throws RemoteCommandException remote command exception if new session fails */ protected BrowserSessionInfo getNewBrowserSession(String browserString, String startURL, String extensionJs, Capabilities browserConfigurations, boolean useCached, boolean ensureClean, RemoteControlConfiguration configuration) throws RemoteCommandException { BrowserSessionInfo sessionInfo = null; browserString = validateBrowserString(browserString, configuration); if (configuration.getProxyInjectionModeArg()) { InjectionHelper.setBrowserSideLogEnabled(configuration.isBrowserSideLogEnabled()); InjectionHelper.init(); } if (useCached) { log.info("grabbing available session..."); sessionInfo = grabAvailableSession(browserString, startURL); } // couldn't find one in the cache, or not reusing sessions. if (null == sessionInfo) { log.info("creating new remote session"); sessionInfo = createNewRemoteSession(browserString, startURL, extensionJs, browserConfigurations, ensureClean, configuration); } assert null != sessionInfo; if (false/* ensureClean */) { // need to add this to the launcher API. // sessionInfo.launcher.hideCurrentSessionData(); } return sessionInfo; } /** * Ends all browser sessions. *

* Active and available but inactive sessions are ended. * @param configuration remote control configuration */ protected void endAllBrowserSessions(RemoteControlConfiguration configuration) { boolean done = false; Set allSessions = new HashSet<>(); while (!done) { // to avoid concurrent modification exceptions... synchronized (activeSessions) { for (BrowserSessionInfo sessionInfo : activeSessions) { allSessions.add(sessionInfo); } } synchronized (availableSessions) { for (BrowserSessionInfo sessionInfo : availableSessions) { allSessions.add(sessionInfo); } } for (BrowserSessionInfo sessionInfo : allSessions) { endBrowserSession(true, sessionInfo.sessionId, configuration); } done = (0 == activeSessions.size() && 0 == availableSessions.size()); allSessions.clear(); if (doCleanup) { cleanupTimer.cancel(); } } } /** * Ends a browser session, using SeleniumServer static fields to populate parameters. * * @param sessionId the id of the session to be ended * @param configuration Remote Control configuration. Cannot be null. */ public void endBrowserSession(String sessionId, RemoteControlConfiguration configuration) { endBrowserSession(false, sessionId, configuration, configuration.isEnsureCleanSession()); } /** * Ends a browser session, using SeleniumServer static fields to populate parameters. * * @param forceClose if the session should not be reused * @param sessionId the id of the session to be ended * @param configuration Remote Control configuration. Cannot be null. */ public void endBrowserSession(boolean forceClose, String sessionId, RemoteControlConfiguration configuration) { endBrowserSession(forceClose, sessionId, configuration, configuration.isEnsureCleanSession()); } /** * Ends a browser session. * * @param forceClose if the session should not be reused * @param sessionId the id of the session to be ended * @param configuration Remote Control configuration. Cannot be null. * @param ensureClean if clean sessions (e.g. no leftover cookies) are required. */ protected void endBrowserSession(boolean forceClose, String sessionId, RemoteControlConfiguration configuration, boolean ensureClean) { BrowserSessionInfo sessionInfo = lookupInfoBySessionId(sessionId, activeSessions); if (null != sessionInfo) { activeSessions.remove(sessionInfo); try { if (forceClose || !configuration.reuseBrowserSessions()) { shutdownBrowserAndClearSessionData(sessionInfo); } else { if (null != sessionInfo.session) { // optional field sessionInfo.session.reset(sessionInfo.baseUrl); } // mark what time this session was ended sessionInfo.lastClosedAt = System.currentTimeMillis(); availableSessions.add(sessionInfo); } } finally { LoggingManager.perSessionLogHandler().removeSessionLogs(new SessionId(sessionId)); if (ensureClean) { // need to add this to the launcher API. // sessionInfo.launcher.restoreOriginalSessionData(); } } } else { // look for it in the available sessions. sessionInfo = lookupInfoBySessionId(sessionId, availableSessions); if (null != sessionInfo && (forceClose || !configuration.reuseBrowserSessions())) { try { availableSessions.remove(sessionInfo); shutdownBrowserAndClearSessionData(sessionInfo); } finally { LoggingManager.perSessionLogHandler().removeSessionLogs(new SessionId(sessionId)); if (ensureClean) { // sessionInfo.launcher.restoreOriginalSessionData(); } } } } } /** * Shuts down this browser session's launcher and clears out its session data (if session is not * null). * * @param sessionInfo the browser session to end. */ protected void shutdownBrowserAndClearSessionData(BrowserSessionInfo sessionInfo) { try { sessionInfo.launcher.close(); // can throw RuntimeException } finally { if (null != sessionInfo.session) { FrameGroupCommandQueueSet.clearQueueSet(sessionInfo.sessionId); } } } /** * Rewrites the given browser string based on server settings. * * @param inputString the input browser string * @return a possibly-modified browser string. * @throws IllegalArgumentException if inputString is null. */ private String validateBrowserString(String inputString, RemoteControlConfiguration configuration) throws IllegalArgumentException { String browserString = inputString; if (configuration.getForcedBrowserMode() != null) { browserString = configuration.getForcedBrowserMode(); log.info("overriding browser mode w/ forced browser mode setting: " + browserString); } if (configuration.getProxyInjectionModeArg() && browserString.equals("*iexplore")) { log.warning("running in proxy injection mode, but you used a *iexplore browser string; this is " + "almost surely inappropriate, so I'm changing it to *piiexplore..."); browserString = "*piiexplore"; } else if (configuration.getProxyInjectionModeArg() && (browserString.equals("*firefox") || browserString.equals("*firefox2") || browserString.equals("*firefox3"))) { log.warning("running in proxy injection mode, but you used a " + browserString + " browser string; this is " + "almost surely inappropriate, so I'm changing it to *pifirefox..."); browserString = "*pifirefox"; } if (null == browserString) { throw new IllegalArgumentException("browser string may not be null"); } return browserString; } /** * Retrieves an available, unused session from the cache. * * @param browserString the necessary browser for a suitable session * @param baseUrl the necessary baseUrl for a suitable session * @return the session info of the cached session, null if none found. */ protected BrowserSessionInfo grabAvailableSession(String browserString, String baseUrl) { BrowserSessionInfo sessionInfo = null; synchronized (availableSessions) { sessionInfo = lookupInfoByBrowserAndUrl(browserString, baseUrl, availableSessions); if (null != sessionInfo) { availableSessions.remove(sessionInfo); } } if (null != sessionInfo) { activeSessions.add(sessionInfo); } return sessionInfo; } /** * Isolated dependency * * @param sessionId session id * @param port port * @param configuration Remote Control Configuration * @return a new FrameGroupCommandQueueSet instance */ protected FrameGroupCommandQueueSet makeQueueSet(String sessionId, int port, RemoteControlConfiguration configuration) { return FrameGroupCommandQueueSet.makeQueueSet(sessionId, configuration.getPortDriversShouldContact(), configuration); } /** * Isolated dependency * * @param sessionId session id * @return an existing FrameGroupCommandQueueSet instance */ protected FrameGroupCommandQueueSet getQueueSet(String sessionId) { return FrameGroupCommandQueueSet.getQueueSet(sessionId); } /** * Creates and tries to open a new session. * * @param browserString browser name string * @param startURL starting url * @param extensionJs per-session user extension javascript * @param configuration Remote Control configuration. Cannot be null. * @param browserConfiguration capabilities of requested browser * @param ensureClean if a clean session is required * @return the BrowserSessionInfo of the new session. * @throws RemoteCommandException if the browser failed to launch and request work in the required * amount of time. */ protected BrowserSessionInfo createNewRemoteSession(String browserString, String startURL, String extensionJs, Capabilities browserConfiguration, boolean ensureClean, RemoteControlConfiguration configuration) throws RemoteCommandException { final FrameGroupCommandQueueSet queueSet; final BrowserSessionInfo sessionInfo; final BrowserLauncher launcher; String sessionId; sessionId = UUID.randomUUID().toString().replace("-", ""); if ("*webdriver".equals(browserString) && browserConfiguration != null) { Object id = browserConfiguration.getCapability("webdriver.remote.sessionid"); if (id != null && id instanceof String) { sessionId = (String) id; } } queueSet = makeQueueSet(sessionId, configuration.getPortDriversShouldContact(), configuration); queueSet.setExtensionJs(extensionJs); try { launcher = browserLauncherFactory.getBrowserLauncher(browserString, sessionId, configuration, browserConfiguration); } catch (InvalidBrowserExecutableException e) { throw new RemoteCommandException(e.getMessage(), ""); } sessionInfo = new BrowserSessionInfo(sessionId, browserString, startURL, launcher, queueSet); SessionIdTracker.setLastSessionId(sessionId); log.info("Allocated session " + sessionId + " for " + startURL + ", launching..."); final PerSessionLogHandler perSessionLogHandler = LoggingManager.perSessionLogHandler(); perSessionLogHandler.attachToCurrentThread(new SessionId(sessionId)); try { launcher.launchRemoteSession(startURL); queueSet.waitForLoad(configuration.getTimeoutInSeconds() * 1000L); // TODO DGF log4j only // NDC.push("sessionId="+sessionId); FrameGroupCommandQueueSet queue = getQueueSet(sessionId); queue.doCommand("setContext", sessionId, ""); activeSessions.add(sessionInfo); return sessionInfo; } catch (Exception e) { /* * At this point the session might not have been added to neither available nor active * sessions. This session is unlikely to be of any practical use so we need to make sure we * close the browser and clear all session data. */ log.log(Level.SEVERE, "Failed to start new browser session, shutdown browser and clear all session data", e); shutdownBrowserAndClearSessionData(sessionInfo); throw new RemoteCommandException("Error while launching browser", "", e); } finally { perSessionLogHandler.detachFromCurrentThread(); } } /** * Adds a browser session that was not created by this factory to the set of active sessions. *

* Allows for creation of unmanaged sessions (i.e. no FrameGroupCommandQueueSet) for task such as * running the HTML tests (see HTMLLauncher.java). All fields other than session are required to * be non-null. * * @param sessionInfo the session info to register. * @return true if session was registered */ protected boolean registerExternalSession(BrowserSessionInfo sessionInfo) { boolean result = false; if (BrowserSessionInfo.isValid(sessionInfo)) { activeSessions.add(sessionInfo); result = true; } return result; } /** * Removes a previously registered external browser session from the list of active sessions. * * @param sessionInfo the session to remove. */ protected void deregisterExternalSession(BrowserSessionInfo sessionInfo) { activeSessions.remove(sessionInfo); } /** * Looks up a session in the named set by session id * * @param sessionId the session id to find * @param set the Set to inspect * @return the matching BrowserSessionInfo or null if not found. */ protected BrowserSessionInfo lookupInfoBySessionId(String sessionId, Set set) { BrowserSessionInfo result = null; synchronized (set) { for (BrowserSessionInfo info : set) { if (info.sessionId.equals(sessionId)) { result = info; break; } } } return result; } /** * Looks up a session in the named set by browser string and base URL * * @param browserString the browser string to match * @param baseUrl the base URL to match. * @param set the Set to inspect * @return the matching BrowserSessionInfo or null if not found. */ protected BrowserSessionInfo lookupInfoByBrowserAndUrl(String browserString, String baseUrl, Set set) { BrowserSessionInfo result = null; synchronized (set) { for (BrowserSessionInfo info : set) { if (info.browserString.equals(browserString) && info.baseUrl.equals(baseUrl)) { result = info; break; } } } return result; } protected void removeIdleAvailableSessions() { long now = System.currentTimeMillis(); synchronized (availableSessions) { Iterator iter = availableSessions.iterator(); while (iter.hasNext()) { BrowserSessionInfo info = iter.next(); if (now - info.lastClosedAt > maxIdleSessionTime) { iter.remove(); shutdownBrowserAndClearSessionData(info); } } } } /** * for testing only * @param sessionId session id * @return true if it has one */ protected boolean hasActiveSession(String sessionId) { BrowserSessionInfo info = lookupInfoBySessionId(sessionId, activeSessions); return (null != info); } /** * for testing only * @param sessionId session id * @return true if it has one */ protected boolean hasAvailableSession(String sessionId) { BrowserSessionInfo info = lookupInfoBySessionId(sessionId, availableSessions); return (null != info); } /** * for testing only * @param sessionInfo browser session info */ protected void addToAvailableSessions(BrowserSessionInfo sessionInfo) { availableSessions.add(sessionInfo); } /** * Collection class to hold the objects associated with a browser session. * * @author [email protected] (Jennifer Bevan) */ public static class BrowserSessionInfo { public BrowserSessionInfo(String sessionId, String browserString, String baseUrl, BrowserLauncher launcher, FrameGroupCommandQueueSet session) { this.sessionId = sessionId; this.browserString = browserString; this.baseUrl = baseUrl; this.launcher = launcher; this.session = session; // optional field; may be null. lastClosedAt = 0; } public final String sessionId; public final String browserString; public final String baseUrl; public final BrowserLauncher launcher; public final FrameGroupCommandQueueSet session; public long lastClosedAt; /** * Browser sessions require the session id, the browser, the base URL, and the launcher. They * don't actually require the session to be set up as a FrameGroupCommandQueueSet. * * @param sessionInfo the sessionInfo to validate. * @return true if all fields excepting session are non-null. */ protected static boolean isValid(BrowserSessionInfo sessionInfo) { boolean result = (null != sessionInfo.sessionId && null != sessionInfo.browserString && null != sessionInfo.baseUrl && null != sessionInfo.launcher); return result; } } /** * TimerTask that looks for unused sessions in the availableSessions collection. * * @author [email protected] (Jennifer Bevan) */ protected class CleanupTask extends TimerTask { @Override public void run() { removeIdleAvailableSessions(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy