// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sdk.internal.wip;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.AbstractList;
import java.util.List;
import org.chromium.sdk.ConnectionLogger;
import org.chromium.sdk.TabDebugEventListener;
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
import org.chromium.sdk.internal.transport.SocketWrapper;
import org.chromium.sdk.internal.transport.SocketWrapper.LoggableInputStream;
import org.chromium.sdk.internal.transport.SocketWrapper.LoggableOutputStream;
import org.chromium.sdk.internal.websocket.HandshakeUtil;
import org.chromium.sdk.internal.websocket.Hybi00WsConnection;
import org.chromium.sdk.internal.websocket.Hybi17WsConnection;
import org.chromium.sdk.internal.websocket.WsConnection;
import org.chromium.sdk.internal.wip.protocol.WipParserAccess;
import org.chromium.sdk.internal.wip.protocol.input.WipTabList;
import org.chromium.sdk.internal.wip.protocol.input.WipTabList.TabDescription;
import org.chromium.sdk.wip.WipBrowser.WipTabConnector;
import org.chromium.sdk.wip.WipBrowserFactory.LoggerFactory;
import org.chromium.sdk.wip.WipBrowserTab;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class WipBackendImpl extends WipBackendBase {
private static final int DEFAULT_CONNECTION_TIMEOUT_MS = 1000;
private static final boolean USE_OLD_WEBSOCKET = false;
private static final String ID = "WK@118685";
private static final String DESCRIPTION =
"Google Chrome/Chromium: 21.0.1180.*\n" +
"WebKit revision: 118685\n";
public WipBackendImpl() {
super(ID, DESCRIPTION);
}
@Override
public List<? extends WipTabConnector> getTabs(final WipBrowserImpl browserImpl)
throws IOException {
InetSocketAddress socketAddress = browserImpl.getSocketAddress();
String content = readHttpResponseContent(socketAddress, "/json",
browserImpl.getConnectionLoggerFactory());
final List<WipTabList.TabDescription> list = parseJsonReponse(content);
return new AbstractList<WipTabConnector>() {
@Override
public WipTabConnector get(int index) {
return new TabConnectorImpl(list.get(index), browserImpl);
}
@Override
public int size() {
return list.size();
}
};
}
private class TabConnectorImpl implements WipTabConnector {
private final TabDescription description;
private final WipBrowserImpl browserImpl;
private TabConnectorImpl(TabDescription description, WipBrowserImpl browserImpl) {
this.description = description;
this.browserImpl = browserImpl;
}
@Override
public boolean isAlreadyAttached() {
return description.webSocketDebuggerUrl() == null;
}
@Override
public String getUrl() {
return description.url();
}
@Override
public String getTitle() {
return description.title();
}
@Override
public WipBrowserTab attach(TabDebugEventListener listener) throws IOException {
LoggerFactory connectionLoggerFactory = browserImpl.getConnectionLoggerFactory();
ConnectionLogger connectionLogger;
if (connectionLoggerFactory == null) {
connectionLogger = null;
} else {
connectionLogger = connectionLoggerFactory.newTabConnectionLogger();
}
String webSocketDebuggerUrl = description.webSocketDebuggerUrl();
if (webSocketDebuggerUrl == null) {
throw new IOException("Tab is already attached");
}
URI uri = URI.create(webSocketDebuggerUrl);
WsConnection socket;
if (USE_OLD_WEBSOCKET) {
socket = Hybi00WsConnection.connect(browserImpl.getSocketAddress(),
DEFAULT_CONNECTION_TIMEOUT_MS, uri.getPath(), "empty origin", connectionLogger);
} else {
socket = Hybi17WsConnection.connect(browserImpl.getSocketAddress(),
DEFAULT_CONNECTION_TIMEOUT_MS, uri.getPath(),
Hybi17WsConnection.MaskStrategy.TRANSPARENT_MASK, connectionLogger);
}
return new WipTabImpl(socket, browserImpl, listener, description.url());
}
}
private String readHttpResponseContent(InetSocketAddress socketAddress, String resource,
LoggerFactory loggerFactory) throws IOException {
ConnectionLogger browserConnectionLogger = loggerFactory.newBrowserConnectionLogger();
final SocketWrapper socketWrapper = new SocketWrapper(
socketAddress, DEFAULT_CONNECTION_TIMEOUT_MS, browserConnectionLogger,
HandshakeUtil.ASCII_CHARSET);
try {
if (browserConnectionLogger != null) {
browserConnectionLogger.start();
browserConnectionLogger.setConnectionCloser(new ConnectionLogger.ConnectionCloser() {
@Override
public void closeConnection() {
socketWrapper.getShutdownRelay().sendSignal(null, new Exception("UI close request"));
}
});
}
LoggableOutputStream output = socketWrapper.getLoggableOutput();
writeHttpLine(output, "GET " + resource + " HTTP/1.1");
writeHttpLine(output, "User-Agent: ChromeDevTools for Java SDK");
writeHttpLine(output, "Host: " + socketAddress.getHostName() + ":" +
socketAddress.getPort());
writeHttpLine(output, "");
output.getOutputStream().flush();
LoggableInputStream input = socketWrapper.getLoggableInput();
HandshakeUtil.HttpResponse httpResponse =
HandshakeUtil.readHttpResponse(HandshakeUtil.createLineReader(input.getInputStream()));
if (httpResponse.getCode() != 200) {
throw new IOException("Unrecognized respose: " + httpResponse.getCode() + " " +
httpResponse.getReasonPhrase());
}
String lengthStr = httpResponse.getFields().get("content-length");
if (lengthStr == null) {
throw new IOException("Unrecognizable respose: no content-length");
}
int length;
try {
length = Integer.parseInt(lengthStr.trim());
} catch (NumberFormatException e) {
throw new IOException("Unrecognizable respose: incorrect content-length");
}
byte[] responseBytes = new byte[length];
{
int readSoFar = 0;
while (readSoFar < length) {
int res = input.getInputStream().read(responseBytes, readSoFar, length - readSoFar);
if (res == -1) {
throw new IOException("Unexpected EOS");
}
readSoFar += res;
}
}
return new String(responseBytes, HandshakeUtil.UTF_8_CHARSET);
} finally {
if (browserConnectionLogger != null) {
browserConnectionLogger.handleEos();
}
socketWrapper.getShutdownRelay().sendSignal(null, null);
}
}
private static void writeHttpLine(LoggableOutputStream output, String line) throws IOException {
OutputStream stream = output.getOutputStream();
stream.write(line.getBytes(HandshakeUtil.ASCII_CHARSET));
stream.write(0xD);
stream.write(0xA);
}
private static List<WipTabList.TabDescription> parseJsonReponse(String content)
throws IOException {
Object jsonValue;
try {
jsonValue = new JSONParser().parse(content);
} catch (ParseException e) {
throw new IOException("Failed to parse a JSON tab list response", e);
}
try {
WipTabList tabList = WipParserAccess.get().parseTabList(jsonValue);
return tabList.asTabList();
} catch (JsonProtocolParseException e) {
throw new IOException(
"Failed to parse tab list response (on protocol level)", e);
}
}
}