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