/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.server.hmux; import java.io.IOException; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.VersionFactory; import com.caucho.cloud.network.ClusterServer; import com.caucho.cloud.topology.CloudCluster; import com.caucho.cloud.topology.CloudPod; import com.caucho.cloud.topology.CloudServer; import com.caucho.server.cluster.Server; import com.caucho.server.host.Host; import com.caucho.server.host.HostController; import com.caucho.server.webapp.WebApp; import com.caucho.server.webapp.WebAppContainer; import com.caucho.server.webapp.WebAppController; import com.caucho.util.Alarm; import com.caucho.util.Base64; import com.caucho.util.CharBuffer; import com.caucho.util.Crc64; import com.caucho.vfs.ReadStream; import com.caucho.vfs.WriteStream; /** * Handles the filter mapping (config) requests from a remote dispatcher. */ public class HmuxDispatchRequest { private static final Logger log = Logger.getLogger(HmuxDispatchRequest.class.getName()); // other, specialized protocols public static final int HMUX_HOST = 'h'; public static final int HMUX_QUERY_ALL = 'q'; public static final int HMUX_QUERY_URL = 'r'; public static final int HMUX_QUERY_SERVER = 's'; public static final int HMUX_WEB_APP = 'a'; public static final int HMUX_MATCH = 'm'; public static final int HMUX_IGNORE = 'i'; public static final int HMUX_ETAG = 'e'; public static final int HMUX_NO_CHANGE = 'n'; public static final int HMUX_CLUSTER = 'c'; public static final int HMUX_SRUN = 's'; public static final int HMUX_SRUN_BACKUP = 'b'; public static final int HMUX_SRUN_SSL = 'e'; public static final int HMUX_UNAVAILABLE = 'u'; public static final int HMUX_WEB_APP_UNAVAILABLE = 'U'; private CharBuffer _cb = new CharBuffer(); private HmuxRequest _request; private Server _server; public HmuxDispatchRequest(HmuxRequest request) { _request = request; _server = request.getServer(); } /** * Handles a new request. Initializes the protocol handler and * the request streams. * * <p>Note: ClientDisconnectException must be rethrown to * the caller. */ public boolean handleRequest(ReadStream is, WriteStream os) throws IOException { boolean isLoggable = log.isLoggable(Level.FINE); int code; int len; String host = ""; String etag = null; while (true) { os.flush(); code = is.read(); switch (code) { case -1: if (isLoggable) log.fine(dbgId() + "end of file"); return false; case HmuxRequest.HMUX_QUIT: if (isLoggable) log.fine(dbgId() + (char) code + ": end of request"); return true; case HmuxRequest.HMUX_EXIT: if (isLoggable) log.fine(dbgId() + (char) code + ": end of socket"); return false; case HMUX_ETAG: len = (is.read() << 8) + is.read(); _cb.clear(); is.readAll(_cb, len); etag = _cb.toString(); if (isLoggable) log.fine(dbgId() + "etag: " + etag); break; case HMUX_HOST: len = (is.read() << 8) + is.read(); _cb.clear(); is.readAll(_cb, len); host = _cb.toString(); if (isLoggable) log.fine(dbgId() + "host: " + host); break; case HMUX_QUERY_ALL: len = (is.read() << 8) + is.read(); _cb.clear(); is.readAll(_cb, len); if (isLoggable) log.fine(dbgId() + "query: " + _cb); queryAll(os, host, _cb.toString(), etag); break; /* case HMUX_QUERY_SERVER: len = (is.read() << 8) + is.read(); _cb.clear(); is.readAll(_cb, len); if (isLoggable) log.fine(dbgId() + "query-server: " + _cb); queryCluster(os, host, _cb.toString()); break; */ default: len = (is.read() << 8) + is.read(); if (isLoggable) log.fine(dbgId() + (char) code + " " + len + " (dispatch)"); is.skip(len); break; } } // _filter.setClientClosed(true); // return false; } /** * Returns the url. */ private void queryAll(WriteStream os, String hostName, String url, String etag) throws IOException { int channel = 2; boolean isLoggable = log.isLoggable(Level.FINE); os.write(HmuxRequest.HMUX_CHANNEL); os.write(channel >> 8); os.write(channel); Host host = _server.getHost(hostName, 80); if (host == null) { HostController controller = _server.getHostController(hostName, 80); if (controller != null) { writeString(os, HMUX_UNAVAILABLE, ""); } else { writeString(os, HmuxRequest.HMUX_HEADER, "check-interval"); writeString(os, HmuxRequest.HMUX_STRING, String.valueOf(_server.getDependencyCheckInterval() / 1000)); } if (isLoggable) log.fine(dbgId() + "host '" + hostName + "' not configured"); return; } else if (! host.getState().isActive()) { writeString(os, HMUX_UNAVAILABLE, ""); if (isLoggable) log.fine(dbgId() + "host '" + host + "' not active"); return; } if (host.getConfigETag() == null) sendQuery(null, host, hostName, url); if (etag == null) { } else if (etag.equals(host.getConfigETag())) { if (isLoggable) log.fine(dbgId() + "host '" + host + "' no change"); writeString(os, HMUX_NO_CHANGE, ""); return; } else if (etag.equals("h-" + host.getName())) { if (isLoggable) { log.fine(dbgId() + "host alias '" + hostName + " -> '" + host + "' no change"); } writeString(os, HMUX_NO_CHANGE, ""); return; } else { if (isLoggable) log.fine(dbgId() + "host '" + host + "' changed"); } sendQuery(os, host, hostName, url); } /** * Writes the host data, returning the crc */ private void sendQuery(WriteStream os, Host host, String hostName, String url) throws IOException { boolean isLoggable = log.isLoggable(Level.FINE); long crc64 = 0; if (! Alarm.isTest()) crc64 = Crc64.generate(crc64, VersionFactory.getFullVersion()); queryServer(os); String canonicalHostName = host.getHostName(); if (canonicalHostName.equals("default")) canonicalHostName = ""; writeString(os, HMUX_HOST, canonicalHostName); if (hostName.equals(canonicalHostName)) { crc64 = queryCluster(os, host, crc64); WebAppContainer webAppContainer = host.getWebAppContainer(); WebAppController controller = webAppContainer.findByURI(url); if (controller != null) { try { controller.request(); } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } } for (WebAppController appEntry : webAppContainer.getWebAppList()) { if (appEntry.getParent() != null && appEntry.getParent().isDynamicDeploy()) { continue; } writeString(os, HMUX_WEB_APP, appEntry.getContextPath()); if (isLoggable) log.fine(dbgId() + "web-app '" + appEntry.getContextPath() + "'"); crc64 = Crc64.generate(crc64, appEntry.getContextPath()); WebApp app = appEntry.getWebApp(); if (appEntry.isDynamicDeploy()) { writeString(os, HMUX_MATCH, "/*"); crc64 = Crc64.generate(crc64, "/*"); if (isLoggable) log.fine(dbgId() + "dynamic '" + appEntry.getContextPath() + "'"); } else if (app == null || ! app.isActive()) { if (isLoggable) log.fine(dbgId() + "not active '" + appEntry.getContextPath() + "'"); writeString(os, HMUX_WEB_APP_UNAVAILABLE, ""); } else { if (isLoggable) log.fine(dbgId() + "active '" + appEntry.getContextPath() + "'"); ArrayList<String> patternList = app.getServletMappingPatterns(); for (int j = 0; patternList != null && j < patternList.size(); j++) { String pattern = patternList.get(j); writeString(os, HMUX_MATCH, pattern); crc64 = Crc64.generate(crc64, pattern); } patternList = app.getServletIgnoreMappingPatterns(); for (int j = 0; patternList != null && j < patternList.size(); j++) { String pattern = patternList.get(j); writeString(os, HMUX_IGNORE, pattern); crc64 = Crc64.generate(crc64, "i"); crc64 = Crc64.generate(crc64, pattern); } } } CharBuffer cb = new CharBuffer(); Base64.encode(cb, crc64); String newETag = cb.close(); host.setConfigETag(newETag); writeString(os, HMUX_ETAG, host.getConfigETag()); } else { // aliased hosts use the host name as the etag writeString(os, HMUX_ETAG, "h-" + host.getName()); } } /** * Queries the cluster. */ private long queryCluster(WriteStream os, Host host, long crc64) throws IOException { /* int channel = 2; os.write(HmuxRequest.HMUX_CHANNEL); os.write(channel >> 8); os.write(channel); */ CloudCluster cluster = host.getCluster(); if (cluster == null) return 0; writeString(os, HMUX_CLUSTER, cluster.getId()); crc64 = Crc64.generate(crc64, cluster.getId()); CloudPod []pods = cluster.getPodList(); int serverLength = (pods.length > 0 ? pods[0].getServerLength() : 0); CloudServer []servers = (pods.length > 0 ? pods[0].getServerList() : null); if (serverLength > 0) { CloudServer cloudServer = servers[0]; ClusterServer server = cloudServer.getData(ClusterServer.class); crc64 = writeTime(os, crc64, "live-time", server.getLoadBalanceIdleTime()); crc64 = writeTime(os, crc64, "dead-time", server.getLoadBalanceRecoverTime()); crc64 = writeTime(os, crc64, "read-timeout", server.getLoadBalanceSocketTimeout()); crc64 = writeTime(os, crc64, "connect-timeout", server.getLoadBalanceConnectTimeout()); } for (int i = 0; i < serverLength; i++) { CloudServer cloudServer = servers[i]; if (cloudServer == null) continue; ClusterServer server = cloudServer.getData(ClusterServer.class); if (server != null) { String srunHost = server.getAddress() + ":" + server.getPort(); boolean isSSL = false; // server.isSSL(); if (server.isBackup()) writeString(os, HMUX_SRUN_BACKUP, srunHost); else if (isSSL) writeString(os, HMUX_SRUN_SSL, srunHost); else writeString(os, HMUX_SRUN, srunHost); crc64 = Crc64.generate(crc64, srunHost); } } return crc64; } private long writeTime(WriteStream os, long crc, String header, long time) throws IOException { writeString(os, HmuxRequest.HMUX_HEADER, header); writeString(os, HmuxRequest.HMUX_STRING, "" + (time / 1000)); crc = Crc64.generate(crc, time); return crc; } /** * Queries the cluster. */ private void queryServer(WriteStream os) throws IOException { writeString(os, HmuxRequest.HMUX_HEADER, "check-interval"); writeString(os, HmuxRequest.HMUX_STRING, String.valueOf(_server.getDependencyCheckInterval() / 1000)); writeString(os, HmuxRequest.HMUX_HEADER, "cookie"); writeString(os, HmuxRequest.HMUX_STRING, _server.getSessionCookie()); writeString(os, HmuxRequest.HMUX_HEADER, "ssl-cookie"); writeString(os, HmuxRequest.HMUX_STRING, _server.getSSLSessionCookie()); writeString(os, HmuxRequest.HMUX_HEADER, "session-url-prefix"); writeString(os, HmuxRequest.HMUX_STRING, _server.getSessionURLPrefix()); writeString(os, HmuxRequest.HMUX_HEADER, "alt-session-url-prefix"); writeString(os, HmuxRequest.HMUX_STRING, _server.getAlternateSessionURLPrefix()); if (_server.getConnectionErrorPage() != null) { writeString(os, HmuxRequest.HMUX_HEADER, "connection-error-page"); writeString(os, HmuxRequest.HMUX_STRING, _server.getConnectionErrorPage()); } } void writeString(WriteStream os, int code, String value) throws IOException { if (os == null) return; if (value == null) value = ""; int len = value.length(); os.write(code); os.write(len >> 8); os.write(len); os.print(value); if (log.isLoggable(Level.FINE)) log.fine(dbgId() + (char)code + " " + value); } void writeString(WriteStream os, int code, CharBuffer value) throws IOException { if (os == null) return; int len = value.length(); os.write(code); os.write(len >> 8); os.write(len); os.print(value); if (log.isLoggable(Level.FINE)) log.fine(dbgId() + (char)code + " " + value); } private String dbgId() { return _request.dbgId(); } }