(1). LogRequestsExample
// ... ...
final ChromeLauncher launcher = new ChromeLauncher();
// ********************************************************
// 2. 调用launch,创建:ChromeService
// ********************************************************
final ChromeService chromeService = launcher.launch(false);
// ... ...
(2). ChromeLauncher.launch
public ChromeService launch(boolean headless) throws ChromeProcessException {
// 是否以无头方式创建:ChromeService
// headless = false;会显示Chrome
// ******************************************************************
// 先获取chrome.exe所在位置,然后:调用内部私有方法:launch
// ******************************************************************
return launch(
// ****************************************************************
// 3. 获取chrome安装目录
// ****************************************************************
getChromeBinaryPath(),
// 创建ChromeArguments(运用早Build模式)
ChromeArguments.defaults(headless).build()
);
}
public ChromeService launch(Path chromeBinaryPath, ChromeArguments chromeArguments)
throws ChromeProcessException {
// **********************************************************
// 4. ChromeLauncher.launchChromeProcess
// **********************************************************
int port = launchChromeProcess(chromeBinaryPath, chromeArguments);
// **********************************************************
// 6. 创建:ChromeServiceImpl(创建WebSocket连接,并连接到指定端口)
// **********************************************************
return new ChromeServiceImpl(port);
}
(3). ChromeLauncher.getChromeBinaryPath
// 定义默认的:Chrome文件位置
private static final String[] CHROME_BINARIES = new String[] {
"/usr/bin/chromium",
"/usr/bin/chromium-browser",
"/usr/bin/google-chrome-stable",
"/usr/bin/google-chrome",
"/snap/bin/chromium",
"/Applications/Chromium.app/Contents/MacOS/Chromium",
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
"C:/Program Files (x86)/Google/Chrome/Application/chrome.exe"
};
public Path getChromeBinaryPath() {
// 1.从环境变量中获得:CHROME_PATH
String envChrome = environment.getEnv(ENV_CHROME_PATH);
if (envChrome != null) { // false
boolean isExecutable = processLauncher.isExecutable(envChrome);
if (isExecutable) { // 判断这个文件是否可执行
return Paths.get(envChrome).toAbsolutePath();
}
throw new RuntimeException("CHROME_PATH environment value is not an executable file.");
}
// 遍历二进制目录
for (String binary : CHROME_BINARIES) {
// 判断文件是否能运行
boolean isExecutable = processLauncher.isExecutable(binary);
if (isExecutable) {
// 能运行的情况下:返回绝对路径
return Paths.get(binary).toAbsolutePath();
}
}
throw new RuntimeException(
"Could not find chrome binary! Try setting CHROME_PATH env to chrome binary path.");
}//end getChromeBinaryPath
(4). ChromeLauncher.launchChromeProcess
// 定义关闭钩子线程处理 private Thread shutdownHookThread = new Thread(this::close);
private int launchChromeProcess(Path chromeBinary, ChromeArguments chromeArguments)
throws ChromeProcessException {
// chromeBinary = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
// chromeArguments = com.github.kklisura.cdt.launch.ChromeArguments
// 判断chrome是否已经运行了
if (isAlive()) { // false
throw new IllegalStateException("Chrome process has already been started started.");
}
// 注册关闭钩子回调线程
shutdownHookRegistry.register(shutdownHookThread);
// 把:ChromeArguments所有参数转换成Map
// {no-first-run=true, remote-debugging-port=0, disable-client-side-phishing-detection=true, disable-popup-blocking=true, disable-default-apps=true, disable-extensions=true, metrics-recording-only=true, no-default-browser-check=true, disable-background-timer-throttling=true, disable-translate=true, safebrowsing-disable-auto-update=true, disable-background-networking=true, disable-prompt-on-repost=true, disable-hang-monitor=true, disable-sync=true}
Map<String, Object> argumentsMap = getArguments(chromeArguments);
// Special case for user data directory.
// 没有为Chrome指定临时目录的情况下,则创建临时文件,指定临时目录
if (chromeArguments.getUserDataDir() == null) { // true
// TEMP_PREFIX = cdt-user-data-dir
// userDatDir= /var/folders/l2/v7kxnww15mjb9sps4yb25sqh0000gn/T/cdt-user-data-dir7226844001987293869
String userDatDir = randomTempDir(TEMP_PREFIX);
userDataDirPath = Paths.get(userDatDir);
// 把临时目录设以map中
argumentsMap.put(ChromeArguments.USER_DATA_DIR_ARGUMENT, userDatDir);
}
// 对map进行转换
[--no-first-run, --remote-debugging-port=0, --disable-client-side-phishing-detection, --disable-popup-blocking, --disable-default-apps, --disable-extensions, --metrics-recording-only, --no-default-browser-check, --disable-background-timer-throttling, --disable-translate, --safebrowsing-disable-auto-update, --disable-background-networking, --disable-prompt-on-repost, --user-data-dir=/var/folders/l2/v7kxnww15mjb9sps4yb25sqh0000gn/T/cdt-user-data-dir7226844001987293869, --disable-hang-monitor, --disable-sync]
List<String> arguments = argsMapToArgsList(argumentsMap);
LOGGER.info(
"Launching chrome process {} with arguments {}", chromeBinary.toString(), argumentsMap);
try {
// *******************************************************************
// 留到下一节再分析
// *******************************************************************
chromeProcess = processLauncher.launch(chromeBinary.toString(), arguments);
// *****************************************************************************
// 5. ChromeLauncher.waitForDevToolsServer
// *****************************************************************************
return waitForDevToolsServer(chromeProcess);
} catch (IOException e) {
// Unsubscribe from registry on exceptions.
shutdownHookRegistry.remove(shutdownHookThread);
throw new ChromeProcessException("Failed starting chrome process.", e);
} catch (Exception e) {
close();
throw e;
}
} // end launchChromeProcess
(5). ChromeLauncher.waitForDevToolsServer
private int waitForDevToolsServer(final Process process) throws ChromeProcessTimeoutException {
final AtomicInteger port = new AtomicInteger();
final AtomicBoolean success = new AtomicBoolean(false);
final AtomicReference<String> chromeOutput = new AtomicReference<>("");
// 创建线程读取:Process里的InputStream,获取创建Chrome随机创建的:ws端口
Thread readLineThread =
new Thread(
() -> {
StringBuilder chromeOutputBuilder = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
// 查看chrome是否有返回监听的端口信息
// "^DevTools listening on ws:\\/\\/.+?:(\\d+)\\/"
Matcher matcher = DEVTOOLS_LISTENING_LINE_PATTERN.matcher(line);
if (matcher.find()) {
// 设置端口以及成功,并跳出循环
port.set(Integer.parseInt(matcher.group(1)));
success.set(true);
break;
}
if (chromeOutputBuilder.length() != 0) {
chromeOutputBuilder.append(System.lineSeparator());
}
chromeOutputBuilder.append(line);
chromeOutput.set(chromeOutputBuilder.toString());
}
} catch (Exception e) {
LOGGER.error("Failed while waiting for dev tools server.", e);
} finally {
closeQuietly(reader);
}
});// end start
readLineThread.start();
try {
// 当前线程(main)等待:readLineThread 60秒
readLineThread.join(TimeUnit.SECONDS.toMillis(configuration.getStartupWaitTime()));
if (!success.get()) { // 不成功的情况下做什么事情,
close(readLineThread);
throw new ChromeProcessTimeoutException(
"Failed while waiting for chrome to start: "
+ "Timeout expired! Chrome output: "
+ chromeOutput.get());
}
} catch (InterruptedException e) {
close(readLineThread);
LOGGER.error("Interrupted while waiting for dev tools server.", e);
throw new RuntimeException("Interrupted while waiting for dev tools server.", e);
}
// 返回端口信息
return port.get();
} //end waitForDevToolsServer
(6). ChromeServiceImpl构造器(WebSocket连接)
public ChromeServiceImpl(int port) {
// port = 54614
// *****************************************************************
// 1. 委托给另一个构造器
// *****************************************************************
this(LOCALHOST, port);
}
public ChromeServiceImpl(String host, int port) {
// host = locahost
// port = 54614
// *****************************************************************
// 2. 创建websocket请求,并委给另一个构造器
// *****************************************************************
this(
host,
port,
// 创建WebSodket
(wsUrl -> WebSocketServiceImpl.create(URI.create(wsUrl)))
);
}
// *****************************************************************
// 3. 最后的构造器
// *****************************************************************
public ChromeServiceImpl(String host, int port, WebSocketServiceFactory webSocketServiceFactory) {
this.host = host;
this.port = port;
// (wsUrl -> WebSocketServiceImpl.create(URI.create(wsUrl)))
this.webSocketServiceFactory = webSocketServiceFactory;
}
(7). 总结
- 创建默认的参数(ChromeArguments).
- 获取chrome.exe所在的位置(“/Applications/Google Chrome.app/Contents/MacOS/Google Chrome”).
- 委托给:ProcessLauncherImpl创建Chrome进程,并读取Chrome创建进程时监听的WS端口信息.
- 创建:ChromeServiceImpl,该类实际为:WebSocket的连接器.
- 假如chrome的创建在远程,并把端口暴露信息给ZK,可从远程ZK在获得host和ip,然后创建:ChromeServiceImpl,即可.