1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package nginx.unit.websocket.server; 18 19 import java.io.IOException; 20 import java.util.List; 21 import java.util.Map; 22 23 import javax.servlet.http.HttpSession; 24 import javax.servlet.http.HttpUpgradeHandler; 25 import javax.servlet.http.WebConnection; 26 import javax.websocket.CloseReason; 27 import javax.websocket.CloseReason.CloseCodes; 28 import javax.websocket.DeploymentException; 29 import javax.websocket.Endpoint; 30 import javax.websocket.EndpointConfig; 31 import javax.websocket.Extension; 32 33 import org.apache.juli.logging.Log; 34 import org.apache.juli.logging.LogFactory; 35 import org.apache.tomcat.util.res.StringManager; 36 37 import nginx.unit.websocket.Transformation; 38 import nginx.unit.websocket.WsIOException; 39 import nginx.unit.websocket.WsSession; 40 41 import nginx.unit.Request; 42 43 /** 44 * Servlet 3.1 HTTP upgrade handler for WebSocket connections. 45 */ 46 public class WsHttpUpgradeHandler implements HttpUpgradeHandler { 47 48 private final Log log = LogFactory.getLog(WsHttpUpgradeHandler.class); // must not be static 49 private static final StringManager sm = StringManager.getManager(WsHttpUpgradeHandler.class); 50 51 private final ClassLoader applicationClassLoader; 52 53 private Endpoint ep; 54 private EndpointConfig endpointConfig; 55 private WsServerContainer webSocketContainer; 56 private WsHandshakeRequest handshakeRequest; 57 private List<Extension> negotiatedExtensions; 58 private String subProtocol; 59 private Transformation transformation; 60 private Map<String,String> pathParameters; 61 private boolean secure; 62 private WebConnection connection; 63 private WsRemoteEndpointImplServer wsRemoteEndpointServer; 64 private WsSession wsSession; 65 66 WsHttpUpgradeHandler()67 public WsHttpUpgradeHandler() { 68 applicationClassLoader = Thread.currentThread().getContextClassLoader(); 69 } 70 preInit(Endpoint ep, EndpointConfig endpointConfig, WsServerContainer wsc, WsHandshakeRequest handshakeRequest, List<Extension> negotiatedExtensionsPhase2, String subProtocol, Transformation transformation, Map<String,String> pathParameters, boolean secure)71 public void preInit(Endpoint ep, EndpointConfig endpointConfig, 72 WsServerContainer wsc, WsHandshakeRequest handshakeRequest, 73 List<Extension> negotiatedExtensionsPhase2, String subProtocol, 74 Transformation transformation, Map<String,String> pathParameters, 75 boolean secure) { 76 this.ep = ep; 77 this.endpointConfig = endpointConfig; 78 this.webSocketContainer = wsc; 79 this.handshakeRequest = handshakeRequest; 80 this.negotiatedExtensions = negotiatedExtensionsPhase2; 81 this.subProtocol = subProtocol; 82 this.transformation = transformation; 83 this.pathParameters = pathParameters; 84 this.secure = secure; 85 } 86 87 88 @Override init(WebConnection connection)89 public void init(WebConnection connection) { 90 if (ep == null) { 91 throw new IllegalStateException( 92 sm.getString("wsHttpUpgradeHandler.noPreInit")); 93 } 94 95 String httpSessionId = null; 96 Object session = handshakeRequest.getHttpSession(); 97 if (session != null ) { 98 httpSessionId = ((HttpSession) session).getId(); 99 } 100 101 nginx.unit.Context.trace("UpgradeHandler.init(" + connection + ")"); 102 103 /* 104 // Need to call onOpen using the web application's class loader 105 // Create the frame using the application's class loader so it can pick 106 // up application specific config from the ServerContainerImpl 107 Thread t = Thread.currentThread(); 108 ClassLoader cl = t.getContextClassLoader(); 109 t.setContextClassLoader(applicationClassLoader); 110 */ 111 try { 112 Request r = (Request) handshakeRequest.getAttribute(Request.BARE); 113 114 wsRemoteEndpointServer = new WsRemoteEndpointImplServer(webSocketContainer); 115 wsSession = new WsSession(ep, wsRemoteEndpointServer, 116 webSocketContainer, handshakeRequest.getRequestURI(), 117 handshakeRequest.getParameterMap(), 118 handshakeRequest.getQueryString(), 119 handshakeRequest.getUserPrincipal(), httpSessionId, 120 negotiatedExtensions, subProtocol, pathParameters, secure, 121 endpointConfig, r); 122 123 ep.onOpen(wsSession, endpointConfig); 124 webSocketContainer.registerSession(ep, wsSession); 125 } catch (DeploymentException e) { 126 throw new IllegalArgumentException(e); 127 /* 128 } finally { 129 t.setContextClassLoader(cl); 130 */ 131 } 132 } 133 134 135 136 @Override destroy()137 public void destroy() { 138 if (connection != null) { 139 try { 140 connection.close(); 141 } catch (Exception e) { 142 log.error(sm.getString("wsHttpUpgradeHandler.destroyFailed"), e); 143 } 144 } 145 } 146 147 onError(Throwable throwable)148 private void onError(Throwable throwable) { 149 // Need to call onError using the web application's class loader 150 Thread t = Thread.currentThread(); 151 ClassLoader cl = t.getContextClassLoader(); 152 t.setContextClassLoader(applicationClassLoader); 153 try { 154 ep.onError(wsSession, throwable); 155 } finally { 156 t.setContextClassLoader(cl); 157 } 158 } 159 160 close(CloseReason cr)161 private void close(CloseReason cr) { 162 /* 163 * Any call to this method is a result of a problem reading from the 164 * client. At this point that state of the connection is unknown. 165 * Attempt to send a close frame to the client and then close the socket 166 * immediately. There is no point in waiting for a close frame from the 167 * client because there is no guarantee that we can recover from 168 * whatever messed up state the client put the connection into. 169 */ 170 wsSession.onClose(cr); 171 } 172 } 173