| /* |
| * Copyright 2008 Google Inc. |
| * |
| * 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 com.google.gwt.dev.shell; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.core.ext.linker.ArtifactSet; |
| import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility; |
| import com.google.gwt.core.ext.linker.impl.StandardLinkerContext; |
| import com.google.gwt.dev.DevMode.HostedModeOptions; |
| import com.google.gwt.dev.cfg.ModuleDef; |
| import com.google.gwt.dev.shell.BrowserChannelServer.SessionHandlerServer; |
| import com.google.gwt.dev.util.NullOutputFileSet; |
| import com.google.gwt.dev.util.OutputFileSet; |
| import com.google.gwt.dev.util.OutputFileSetOnDirectory; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.BindException; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.MalformedURLException; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.net.SocketException; |
| import java.net.URL; |
| |
| /** |
| * Listens for connections from OOPHM clients. |
| */ |
| public class BrowserListener implements CodeServerListener { |
| |
| /** |
| * Get a query parameter to be added to the URL that specifies the address of this listener. |
| * |
| * @param address address of host to use for connections |
| * @param port TCP port number to use for connection |
| * @return a query parameter |
| */ |
| public static String getDevModeURLParams(String address, int port) { |
| return "gwt.codesvr=" + address + ":" + port; |
| } |
| |
| private ServerSocket listenSocket; |
| |
| private Thread listenThread; |
| |
| private boolean ignoreRemoteDeath = false; |
| |
| private HostedModeOptions options; |
| |
| private TreeLogger logger; |
| |
| /** |
| * Listens for new connections from browsers. |
| */ |
| public BrowserListener(TreeLogger treeLogger, HostedModeOptions options, |
| final SessionHandlerServer handler) { |
| try { |
| this.options = options; |
| this.logger = treeLogger; |
| listenSocket = new ServerSocket(); |
| listenSocket.setReuseAddress(true); |
| InetAddress address = InetAddress.getByName(options.getBindAddress()); |
| listenSocket.bind(new InetSocketAddress(address, options.getCodeServerPort())); |
| |
| if (logger.isLoggable(TreeLogger.TRACE)) { |
| logger.log(TreeLogger.TRACE, "Started code server on port " + listenSocket.getLocalPort(), |
| null); |
| } |
| listenThread = new Thread() { |
| @Override |
| public void run() { |
| while (true) { |
| try { |
| Socket sock = listenSocket.accept(); |
| TreeLogger branch = |
| logger.branch(TreeLogger.TRACE, "Connection received from " |
| + sock.getInetAddress().getCanonicalHostName() + ":" + sock.getPort()); |
| try { |
| sock.setTcpNoDelay(true); |
| sock.setKeepAlive(true); |
| } catch (SocketException e) { |
| // Ignore non-critical errors. |
| } |
| |
| BrowserChannelServer server = |
| new BrowserChannelServer(branch, sock, handler, ignoreRemoteDeath); |
| /* |
| * This object is special-cased by the SessionHandler, used for |
| * methods needed by the client like hasMethod/hasProperty/etc. |
| * handler is used for this object just to make sure it doesn't |
| * conflict with some real object exposed to the client. |
| */ |
| int id = server.getJavaObjectsExposedInBrowser().add(server); |
| assert id == BrowserChannel.SPECIAL_SERVERMETHODS_OBJECT; |
| } catch (IOException e) { |
| logger.log(TreeLogger.ERROR, "Communications error", e); |
| } |
| } |
| } |
| }; |
| listenThread.setName("Code server listener"); |
| listenThread.setDaemon(true); |
| } catch (BindException e) { |
| logger.log(TreeLogger.ERROR, "Unable to bind socket on port " + options.getPort() |
| + " -- is another session active?", e); |
| } catch (IOException e) { |
| logger.log(TreeLogger.ERROR, "Communications error", e); |
| } |
| } |
| |
| @Override |
| public int getSocketPort() { |
| return listenSocket.getLocalPort(); |
| } |
| |
| @Override |
| public URL makeStartupUrl(String url) throws UnableToCompleteException { |
| URL parsedUrl = null; |
| try { |
| parsedUrl = new URL(url); |
| String path = parsedUrl.getPath(); |
| String query = parsedUrl.getQuery(); |
| String hash = parsedUrl.getRef(); |
| String hostedParam = |
| BrowserListener.getDevModeURLParams(options.getConnectAddress(), getSocketPort()); |
| if (query == null) { |
| query = hostedParam; |
| } else { |
| query += '&' + hostedParam; |
| } |
| path += '?' + query; |
| if (hash != null) { |
| path += '#' + hash; |
| } |
| parsedUrl = new URL(parsedUrl.getProtocol(), parsedUrl.getHost(), parsedUrl.getPort(), path); |
| url = parsedUrl.toExternalForm(); |
| } catch (MalformedURLException e) { |
| logger.log(TreeLogger.ERROR, "Invalid URL " + url, e); |
| throw new UnableToCompleteException(); |
| } |
| return parsedUrl; |
| } |
| |
| @Override |
| public synchronized void writeCompilerOutput(StandardLinkerContext linkerStack, |
| ArtifactSet artifacts, ModuleDef module, boolean isRelink) throws UnableToCompleteException { |
| TreeLogger linkLogger = |
| logger.branch(TreeLogger.DEBUG, "Linking module '" + module.getName() + "'"); |
| |
| OutputFileSetOnDirectory outFileSet = |
| new OutputFileSetOnDirectory(options.getModuleBaseDir(), module.getName() + "/"); |
| OutputFileSetOnDirectory deployFileSet = |
| new OutputFileSetOnDirectory(options.getDeployDir(), module.getName() + "/"); |
| OutputFileSet extraFileSet = new NullOutputFileSet(); |
| if (options.getExtraDir() != null) { |
| extraFileSet = new OutputFileSetOnDirectory(options.getExtraDir(), module.getName() + "/"); |
| } |
| |
| linkerStack.produceOutput(linkLogger, artifacts, Visibility.Public, outFileSet); |
| linkerStack.produceOutput(linkLogger, artifacts, Visibility.Deploy, deployFileSet); |
| linkerStack.produceOutput(linkLogger, artifacts, Visibility.Private, extraFileSet); |
| |
| outFileSet.close(); |
| deployFileSet.close(); |
| try { |
| extraFileSet.close(); |
| } catch (IOException e) { |
| linkLogger.log(TreeLogger.ERROR, "Error emiting extra files", e); |
| throw new UnableToCompleteException(); |
| } |
| |
| // Update the timestamp for files that Super Dev Mode might previously have touched. |
| // The .nocache.js file produced by devmode has identical timestamp than the bootstrap |
| // html page, hence the browser uses superdevmode cached file instead of refreshing it |
| // with the new devmode version, setting ts to current time fixes the issue. |
| new File(options.getModuleBaseDir() + "/" + module.getName() + "/" + module.getName() + ".nocache.js") |
| .setLastModified(System.currentTimeMillis()); |
| } |
| |
| /** |
| * Set any created BrowserChannelServers to ignore remote deaths. |
| * |
| * <p> |
| * This is most commonly wanted by JUnitShell. |
| * |
| * @param ignoreRemoteDeath |
| */ |
| public void setIgnoreRemoteDeath(boolean ignoreRemoteDeath) { |
| this.ignoreRemoteDeath = ignoreRemoteDeath; |
| } |
| |
| @Override |
| public void start() { |
| if (listenThread != null) { |
| listenThread.start(); |
| } |
| } |
| } |