init
This commit is contained in:
38
java/org/apache/tomcat/websocket/server/Constants.java
Normal file
38
java/org/apache/tomcat/websocket/server/Constants.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
/**
|
||||
* Internal implementation constants.
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM =
|
||||
"org.apache.tomcat.websocket.binaryBufferSize";
|
||||
public static final String TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM =
|
||||
"org.apache.tomcat.websocket.textBufferSize";
|
||||
public static final String ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM =
|
||||
"org.apache.tomcat.websocket.noAddAfterHandshake";
|
||||
|
||||
public static final String SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE =
|
||||
"javax.websocket.server.ServerContainer";
|
||||
|
||||
|
||||
private Constants() {
|
||||
// Hide default constructor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.HandshakeResponse;
|
||||
import javax.websocket.server.HandshakeRequest;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
public class DefaultServerEndpointConfigurator
|
||||
extends ServerEndpointConfig.Configurator {
|
||||
|
||||
@Override
|
||||
public <T> T getEndpointInstance(Class<T> clazz)
|
||||
throws InstantiationException {
|
||||
try {
|
||||
return clazz.getConstructor().newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
throw e;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
InstantiationException ie = new InstantiationException();
|
||||
ie.initCause(e);
|
||||
throw ie;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getNegotiatedSubprotocol(List<String> supported,
|
||||
List<String> requested) {
|
||||
|
||||
for (String request : requested) {
|
||||
if (supported.contains(request)) {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Extension> getNegotiatedExtensions(List<Extension> installed,
|
||||
List<Extension> requested) {
|
||||
Set<String> installedNames = new HashSet<>();
|
||||
for (Extension e : installed) {
|
||||
installedNames.add(e.getName());
|
||||
}
|
||||
List<Extension> result = new ArrayList<>();
|
||||
for (Extension request : requested) {
|
||||
if (installedNames.contains(request.getName())) {
|
||||
result.add(request);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean checkOrigin(String originHeaderValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyHandshake(ServerEndpointConfig sec,
|
||||
HandshakeRequest request, HandshakeResponse response) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
|
||||
serverContainer.addNotAllowed=No further Endpoints may be registered once an attempt has been made to use one of the previously registered endpoints
|
||||
serverContainer.configuratorFail=Failed to create configurator of type [{0}] for POJO of type [{1}]
|
||||
serverContainer.duplicatePaths=Multiple Endpoints may not be deployed to the same path [{0}] : existing endpoint was [{1}] and new endpoint is [{2}]
|
||||
serverContainer.encoderFail=Unable to create encoder of type [{0}]
|
||||
serverContainer.failedDeployment=Deployment of WebSocket Endpoints to the web application with path [{0}] in host [{1}] is not permitted due to the failure of a previous deployment
|
||||
serverContainer.missingAnnotation=Cannot deploy POJO class [{0}] as it is not annotated with @ServerEndpoint
|
||||
serverContainer.servletContextMissing=No ServletContext was specified
|
||||
|
||||
upgradeUtil.incompatibleRsv=Extensions were specified that have incompatible RSV bit usage
|
||||
|
||||
uriTemplate.duplicateParameter=The parameter [{0}] appears more than once in the path which is not permitted
|
||||
uriTemplate.emptySegment=The path [{0}] contains one or more empty segments which are is not permitted
|
||||
uriTemplate.invalidPath=The path [{0}] is not valid.
|
||||
uriTemplate.invalidSegment=The segment [{0}] is not valid in the provided path [{1}]
|
||||
|
||||
wsFrameServer.bytesRead=Read [{0}] bytes into input buffer ready for processing
|
||||
wsFrameServer.illegalReadState=Unexpected read state [{0}]
|
||||
wsFrameServer.onDataAvailable=Method entry
|
||||
|
||||
wsHttpUpgradeHandler.closeOnError=Closing WebSocket connection due to an error
|
||||
wsHttpUpgradeHandler.destroyFailed=Failed to close WebConnection while destroying the WebSocket HttpUpgradeHandler
|
||||
wsHttpUpgradeHandler.noPreInit=The preInit() method must be called to configure the WebSocket HttpUpgradeHandler before the container calls init(). Usually, this means the Servlet that created the WsHttpUpgradeHandler instance should also call preInit()
|
||||
wsHttpUpgradeHandler.serverStop=The server is stopping
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=Failed to close the ServletOutputStream connection cleanly
|
||||
@@ -0,0 +1,21 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
|
||||
serverContainer.missingAnnotation=Die POJO Klasse [{0}] kann nicht deployed werden da sie nicht mit @ServerEndpoint annotiert ist.
|
||||
serverContainer.servletContextMissing=Es wurde kein ServletContext angegeben
|
||||
|
||||
upgradeUtil.incompatibleRsv=Es wurden Erweiterungen spezifiziert, die eine inkompatible RSV Bit Konstellation erzeugen
|
||||
|
||||
uriTemplate.invalidPath=Der Pfad [{0}] ist nicht gültig.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
|
||||
serverContainer.configuratorFail=Fallo al crear configurador de tipo [{0}] para el POJO de tipo [{1}]
|
||||
|
||||
uriTemplate.invalidPath=El camino [{0}] no es válido.\n
|
||||
|
||||
wsFrameServer.bytesRead=[{0}] leídos del bufer de entrada llistos para ser procesados
|
||||
@@ -0,0 +1,40 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
|
||||
serverContainer.addNotAllowed=Aucune terminaison ne peut être enregistré une fois qu'une tentative d'utilisation d'une des terminaisons précédemment enregistrée a été faite
|
||||
serverContainer.configuratorFail=Echec de création du configurateur de type [{0}] pour le POJO de type [{1}]
|
||||
serverContainer.duplicatePaths=Plusieurs terminaisons ne peuvent pas être déployés vers le même chemin [{0}]: la terminaison existante était [{1}] et la nouvelle est [{2}]
|
||||
serverContainer.encoderFail=Impossible de créer un encodeur de type [{0}]
|
||||
serverContainer.failedDeployment=Le déploiement de terminaisons WebSocket dans l''application web au chemin [{0}] dans l''hôte [{1}] n''est pas autorisé à cause de l''échec lors d''un précédent déploiement
|
||||
serverContainer.missingAnnotation=Impossible de déployer la classe POJO [{0}] car elle n''a pas été annotée avec @ServerEndpoint
|
||||
serverContainer.servletContextMissing=Aucun ServletContext n'a été spécifié
|
||||
|
||||
upgradeUtil.incompatibleRsv=Des extensions qui ont été spécifiées ont une utilisation incompatible du bit RSV
|
||||
|
||||
uriTemplate.duplicateParameter=Le paramètre [{0}] apparaît plus d''une fois dans le chemin ce qui n''est pas permis
|
||||
uriTemplate.emptySegment=Le chemin [{0}] contient un ou plusieurs segments vide ce qui n''est pas autorisé
|
||||
uriTemplate.invalidPath=Le chemin [{0}] est invalide
|
||||
uriTemplate.invalidSegment=Le segment [{0}] est invalide pour le chemin fourni [{1}]
|
||||
|
||||
wsFrameServer.bytesRead=Lu [{0}] octets dans le buffer de réception prêts à être traités
|
||||
wsFrameServer.illegalReadState=Etat de lecture inattendu [{0}]
|
||||
wsFrameServer.onDataAvailable=Entrée de méthode
|
||||
|
||||
wsHttpUpgradeHandler.closeOnError=Fermeture de la connection WebSocket à cause d'une erreur
|
||||
wsHttpUpgradeHandler.destroyFailed=Echec de la fermeture de la WebConnection lors de la destruction du HttpUpgradeHandler de WebSocket
|
||||
wsHttpUpgradeHandler.noPreInit=La méthode preInit() doit être appelée pour configurer le HttpUpgradeHandler de Websockets avant que le container n'appelle init(), cela veut habituellement dire que le Servlet qui a crée l'instance du WsHttpUpgradeHandler doit aussi appeler preInit()
|
||||
wsHttpUpgradeHandler.serverStop=Le serveur est en train de s'arrêter
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=Impossible de fermer le ServletOutputStream proprement
|
||||
@@ -0,0 +1,40 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
|
||||
serverContainer.addNotAllowed=以前に登録したことのあるエンドポイントには登録できません。
|
||||
serverContainer.configuratorFail=POJO クラス [{1}] のインスタンスを構成するクラス [{0}] のインスタンスを作成できません。
|
||||
serverContainer.duplicatePaths=複数のエンドポイントを同じパス[{0}]に配備することはできません:既存のエンドポイントは[{1}]で、新しいエンドポイントは[{2}]です。
|
||||
serverContainer.encoderFail=タイプ[{0}]のエンコーダを作成できません
|
||||
serverContainer.failedDeployment=以前のデプロイメントが失敗したため、ホスト[{1}]内のパス[{0}]を持つWebアプリケーションへのWebSocketエンドポイントのデプロイメントは許可されていません。
|
||||
serverContainer.missingAnnotation=POJOクラス[{0}]は@ServerEndpointでアノテーション付けされていないため、デプロイ出来ません。
|
||||
serverContainer.servletContextMissing=ServletContextが指定されていません
|
||||
|
||||
upgradeUtil.incompatibleRsv=互換性のないRSVビットの使用法を持つ拡張が指定されました
|
||||
|
||||
uriTemplate.duplicateParameter=パス中にパラメーター [{0}] を複数回登場させることはできません。
|
||||
uriTemplate.emptySegment=パス [{0}] に一つ以上の空セグメントを含めることはできません。
|
||||
uriTemplate.invalidPath=[{0}] は不正なパスです。
|
||||
uriTemplate.invalidSegment=パス [{1}] に存在しないセグメント [{0}] が指定されました。
|
||||
|
||||
wsFrameServer.bytesRead=入力バッファーに読み込んだ [{0}] バイトのデータは処理可能です。
|
||||
wsFrameServer.illegalReadState=予期しない読み取り状態[{0}]
|
||||
wsFrameServer.onDataAvailable=メソッドエントリ
|
||||
|
||||
wsHttpUpgradeHandler.closeOnError=エラーが発生したため WebSocket コネクションを切断します。
|
||||
wsHttpUpgradeHandler.destroyFailed=WebSocket HttpUpgradeHandlerを破棄している間にWebConnectionを閉じることができませんでした。
|
||||
wsHttpUpgradeHandler.noPreInit=コンテナがinit()を呼び出す前に、preInit()メソッドを呼び出すようにWebSocket HttpUpgradeHandlerを設定する必要があります。 通常、これはWsHttpUpgradeHandlerインスタンスを作成したサーブレットがpreInit()を呼び出す必要があることを意味します。
|
||||
wsHttpUpgradeHandler.serverStop=サーバ停止中
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=ServletOutputStreamコネクションを正常に閉じることができませんでした
|
||||
@@ -0,0 +1,40 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
|
||||
serverContainer.addNotAllowed=이전에 등록된 엔드포인트들 중 하나라도 사용하려고 시도하게 되면, 엔드포인트들을 더 이상 등록할 수 없습니다.
|
||||
serverContainer.configuratorFail=타입이 [{1}]인 POJO를 위한 타입 [{0}]의 Configurator를 생성하지 못했습니다.
|
||||
serverContainer.duplicatePaths=여러 개의 엔드포인트들이 동일한 경로 [{0}]에 배치될 수 없습니다: 기존 엔드포인트는 [{1}]였으며 신규 엔드포인트는 [{2}]입니다.
|
||||
serverContainer.encoderFail=타입이 [{0}]인 인코더를 생성할 수 없습니다.
|
||||
serverContainer.failedDeployment=이전 배치의 실패로 인하여, 호스트 [{1}] 내에 경로 [{0}]의 웹 애플리케이션에 대한 웹소켓 엔드포인트들의 배치가 허용되지 않습니다.
|
||||
serverContainer.missingAnnotation=클래스가 @ServerEndpoint로 annotate되어 있지 않기에, POJO 클래스 [{0}]을(를) 배치할 수 없습니다.
|
||||
serverContainer.servletContextMissing=지정된 ServletContext가 없습니다.
|
||||
|
||||
upgradeUtil.incompatibleRsv=호환되지 않는 RSV 비트를 사용하여, Extension들이 지정되었습니다.
|
||||
|
||||
uriTemplate.duplicateParameter=허용되지 않는 경로에서, 파라미터 [{0}]이(가) 두번 이상 나타나고 있습니다.
|
||||
uriTemplate.emptySegment=경로 [{0}]이(가), 하나 이상의 허용되지 않는 empty segment들을 포함하고 있습니다.
|
||||
uriTemplate.invalidPath=경로 [{0}](은)는 유효하지 않습니다.
|
||||
uriTemplate.invalidSegment=세그먼트 [{0}]은(는) 제공된 경로 [{1}] 내에 유효하지 않습니다.
|
||||
|
||||
wsFrameServer.bytesRead=[{0}] 바이트를 입력 버퍼에 읽어 처리를 준비합니다.
|
||||
wsFrameServer.illegalReadState=예기치 않은 읽기 상태 [{0}]
|
||||
wsFrameServer.onDataAvailable=메소드 엔트리
|
||||
|
||||
wsHttpUpgradeHandler.closeOnError=오류 발생으로 인하여, 웹소켓 연결을 닫습니다.
|
||||
wsHttpUpgradeHandler.destroyFailed=웹소켓 HttpUpgradeHandler를 소멸시키는 중, WebConnection을 닫지 못했습니다.
|
||||
wsHttpUpgradeHandler.noPreInit=컨테이너가 init()을 호출하기 전에 웹소켓 HttpUpgradeHandler를 설정하기 위하여, preInit() 메소드가 반드시 호출되어야만 합니다. 통상 이는 WsHttpUpgradeHandler 인스턴스를 생성한 서블릿도 preInit()을 호출해야 함을 의미합니다.
|
||||
wsHttpUpgradeHandler.serverStop=서버가 중지되고 있는 중입니다.
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=해당 ServletOutputStream의 연결을 깨끗하게 닫지 못했습니다.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You 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.
|
||||
|
||||
serverContainer.configuratorFail=无法为[{1}]类型的POJO创建类型[{0}]的配置程序
|
||||
serverContainer.duplicatePaths=多个端点可能不能发不到同一个路径[{0}]:已经存在的端点[{1}]和新的端点[{2}]
|
||||
serverContainer.encoderFail=无法创建[{0}]类型的编码器
|
||||
serverContainer.failedDeployment=由于以前的部署失败,不允许将WebSocket终结点部署到主机[{1}]中路径为[{0}]的Web应用程序
|
||||
serverContainer.servletContextMissing=没有指定ServletContext
|
||||
|
||||
upgradeUtil.incompatibleRsv=指定扩展名具有不兼容的RSV位使用
|
||||
|
||||
uriTemplate.invalidPath=路径 [{0}] 无效。
|
||||
|
||||
wsFrameServer.bytesRead=将[{0}]个字节读入输入缓冲区,准备进行处理
|
||||
wsFrameServer.onDataAvailable=进入方法
|
||||
|
||||
wsHttpUpgradeHandler.noPreInit=在容器调用init()之前,必须调用preinit()方法来配置WebSocket HttpUpgradeHandler。通常,这意味着创建WsHttpUpgradeHandler 实例的servlet也应该调用preinit()
|
||||
|
||||
wsRemoteEndpointServer.closeFailed=无法完全关闭ServletOutputStream 连接
|
||||
339
java/org/apache/tomcat/websocket/server/UpgradeUtil.java
Normal file
339
java/org/apache/tomcat/websocket/server/UpgradeUtil.java
Normal file
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.HandshakeResponse;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.apache.tomcat.util.codec.binary.Base64;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.util.security.ConcurrentMessageDigest;
|
||||
import org.apache.tomcat.websocket.Constants;
|
||||
import org.apache.tomcat.websocket.Transformation;
|
||||
import org.apache.tomcat.websocket.TransformationFactory;
|
||||
import org.apache.tomcat.websocket.Util;
|
||||
import org.apache.tomcat.websocket.WsHandshakeResponse;
|
||||
import org.apache.tomcat.websocket.pojo.PojoEndpointServer;
|
||||
|
||||
public class UpgradeUtil {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(UpgradeUtil.class.getPackage().getName());
|
||||
private static final byte[] WS_ACCEPT =
|
||||
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(
|
||||
StandardCharsets.ISO_8859_1);
|
||||
|
||||
private UpgradeUtil() {
|
||||
// Utility class. Hide default constructor.
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if this is an HTTP request that includes a valid upgrade
|
||||
* request to web socket.
|
||||
* <p>
|
||||
* Note: RFC 2616 does not limit HTTP upgrade to GET requests but the Java
|
||||
* WebSocket spec 1.0, section 8.2 implies such a limitation and RFC
|
||||
* 6455 section 4.1 requires that a WebSocket Upgrade uses GET.
|
||||
* @param request The request to check if it is an HTTP upgrade request for
|
||||
* a WebSocket connection
|
||||
* @param response The response associated with the request
|
||||
* @return <code>true</code> if the request includes an HTTP Upgrade request
|
||||
* for the WebSocket protocol, otherwise <code>false</code>
|
||||
*/
|
||||
public static boolean isWebSocketUpgradeRequest(ServletRequest request,
|
||||
ServletResponse response) {
|
||||
|
||||
return ((request instanceof HttpServletRequest) &&
|
||||
(response instanceof HttpServletResponse) &&
|
||||
headerContainsToken((HttpServletRequest) request,
|
||||
Constants.UPGRADE_HEADER_NAME,
|
||||
Constants.UPGRADE_HEADER_VALUE) &&
|
||||
"GET".equals(((HttpServletRequest) request).getMethod()));
|
||||
}
|
||||
|
||||
|
||||
public static void doUpgrade(WsServerContainer sc, HttpServletRequest req,
|
||||
HttpServletResponse resp, ServerEndpointConfig sec,
|
||||
Map<String,String> pathParams)
|
||||
throws ServletException, IOException {
|
||||
|
||||
// Validate the rest of the headers and reject the request if that
|
||||
// validation fails
|
||||
String key;
|
||||
String subProtocol = null;
|
||||
if (!headerContainsToken(req, Constants.CONNECTION_HEADER_NAME,
|
||||
Constants.CONNECTION_HEADER_VALUE)) {
|
||||
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
if (!headerContainsToken(req, Constants.WS_VERSION_HEADER_NAME,
|
||||
Constants.WS_VERSION_HEADER_VALUE)) {
|
||||
resp.setStatus(426);
|
||||
resp.setHeader(Constants.WS_VERSION_HEADER_NAME,
|
||||
Constants.WS_VERSION_HEADER_VALUE);
|
||||
return;
|
||||
}
|
||||
key = req.getHeader(Constants.WS_KEY_HEADER_NAME);
|
||||
if (key == null) {
|
||||
resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Origin check
|
||||
String origin = req.getHeader(Constants.ORIGIN_HEADER_NAME);
|
||||
if (!sec.getConfigurator().checkOrigin(origin)) {
|
||||
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
// Sub-protocols
|
||||
List<String> subProtocols = getTokensFromHeader(req,
|
||||
Constants.WS_PROTOCOL_HEADER_NAME);
|
||||
subProtocol = sec.getConfigurator().getNegotiatedSubprotocol(
|
||||
sec.getSubprotocols(), subProtocols);
|
||||
|
||||
// Extensions
|
||||
// Should normally only be one header but handle the case of multiple
|
||||
// headers
|
||||
List<Extension> extensionsRequested = new ArrayList<>();
|
||||
Enumeration<String> extHeaders = req.getHeaders(Constants.WS_EXTENSIONS_HEADER_NAME);
|
||||
while (extHeaders.hasMoreElements()) {
|
||||
Util.parseExtensionHeader(extensionsRequested, extHeaders.nextElement());
|
||||
}
|
||||
// Negotiation phase 1. By default this simply filters out the
|
||||
// extensions that the server does not support but applications could
|
||||
// use a custom configurator to do more than this.
|
||||
List<Extension> installedExtensions = null;
|
||||
if (sec.getExtensions().size() == 0) {
|
||||
installedExtensions = Constants.INSTALLED_EXTENSIONS;
|
||||
} else {
|
||||
installedExtensions = new ArrayList<>();
|
||||
installedExtensions.addAll(sec.getExtensions());
|
||||
installedExtensions.addAll(Constants.INSTALLED_EXTENSIONS);
|
||||
}
|
||||
List<Extension> negotiatedExtensionsPhase1 = sec.getConfigurator().getNegotiatedExtensions(
|
||||
installedExtensions, extensionsRequested);
|
||||
|
||||
// Negotiation phase 2. Create the Transformations that will be applied
|
||||
// to this connection. Note than an extension may be dropped at this
|
||||
// point if the client has requested a configuration that the server is
|
||||
// unable to support.
|
||||
List<Transformation> transformations = createTransformations(negotiatedExtensionsPhase1);
|
||||
|
||||
List<Extension> negotiatedExtensionsPhase2;
|
||||
if (transformations.isEmpty()) {
|
||||
negotiatedExtensionsPhase2 = Collections.emptyList();
|
||||
} else {
|
||||
negotiatedExtensionsPhase2 = new ArrayList<>(transformations.size());
|
||||
for (Transformation t : transformations) {
|
||||
negotiatedExtensionsPhase2.add(t.getExtensionResponse());
|
||||
}
|
||||
}
|
||||
|
||||
// Build the transformation pipeline
|
||||
Transformation transformation = null;
|
||||
StringBuilder responseHeaderExtensions = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Transformation t : transformations) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
responseHeaderExtensions.append(',');
|
||||
}
|
||||
append(responseHeaderExtensions, t.getExtensionResponse());
|
||||
if (transformation == null) {
|
||||
transformation = t;
|
||||
} else {
|
||||
transformation.setNext(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have the full pipeline, validate the use of the RSV bits.
|
||||
if (transformation != null && !transformation.validateRsvBits(0)) {
|
||||
throw new ServletException(sm.getString("upgradeUtil.incompatibleRsv"));
|
||||
}
|
||||
|
||||
// If we got this far, all is good. Accept the connection.
|
||||
resp.setHeader(Constants.UPGRADE_HEADER_NAME,
|
||||
Constants.UPGRADE_HEADER_VALUE);
|
||||
resp.setHeader(Constants.CONNECTION_HEADER_NAME,
|
||||
Constants.CONNECTION_HEADER_VALUE);
|
||||
resp.setHeader(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,
|
||||
getWebSocketAccept(key));
|
||||
if (subProtocol != null && subProtocol.length() > 0) {
|
||||
// RFC6455 4.2.2 explicitly states "" is not valid here
|
||||
resp.setHeader(Constants.WS_PROTOCOL_HEADER_NAME, subProtocol);
|
||||
}
|
||||
if (!transformations.isEmpty()) {
|
||||
resp.setHeader(Constants.WS_EXTENSIONS_HEADER_NAME, responseHeaderExtensions.toString());
|
||||
}
|
||||
|
||||
WsHandshakeRequest wsRequest = new WsHandshakeRequest(req, pathParams);
|
||||
WsHandshakeResponse wsResponse = new WsHandshakeResponse();
|
||||
WsPerSessionServerEndpointConfig perSessionServerEndpointConfig =
|
||||
new WsPerSessionServerEndpointConfig(sec);
|
||||
sec.getConfigurator().modifyHandshake(perSessionServerEndpointConfig,
|
||||
wsRequest, wsResponse);
|
||||
wsRequest.finished();
|
||||
|
||||
// Add any additional headers
|
||||
for (Entry<String,List<String>> entry :
|
||||
wsResponse.getHeaders().entrySet()) {
|
||||
for (String headerValue: entry.getValue()) {
|
||||
resp.addHeader(entry.getKey(), headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
Endpoint ep;
|
||||
try {
|
||||
Class<?> clazz = sec.getEndpointClass();
|
||||
if (Endpoint.class.isAssignableFrom(clazz)) {
|
||||
ep = (Endpoint) sec.getConfigurator().getEndpointInstance(
|
||||
clazz);
|
||||
} else {
|
||||
ep = new PojoEndpointServer();
|
||||
// Need to make path params available to POJO
|
||||
perSessionServerEndpointConfig.getUserProperties().put(
|
||||
org.apache.tomcat.websocket.pojo.Constants.POJO_PATH_PARAM_KEY, pathParams);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
WsHttpUpgradeHandler wsHandler =
|
||||
req.upgrade(WsHttpUpgradeHandler.class);
|
||||
wsHandler.preInit(ep, perSessionServerEndpointConfig, sc, wsRequest,
|
||||
negotiatedExtensionsPhase2, subProtocol, transformation, pathParams,
|
||||
req.isSecure());
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static List<Transformation> createTransformations(
|
||||
List<Extension> negotiatedExtensions) {
|
||||
|
||||
TransformationFactory factory = TransformationFactory.getInstance();
|
||||
|
||||
LinkedHashMap<String,List<List<Extension.Parameter>>> extensionPreferences =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
// Result will likely be smaller than this
|
||||
List<Transformation> result = new ArrayList<>(negotiatedExtensions.size());
|
||||
|
||||
for (Extension extension : negotiatedExtensions) {
|
||||
List<List<Extension.Parameter>> preferences =
|
||||
extensionPreferences.get(extension.getName());
|
||||
|
||||
if (preferences == null) {
|
||||
preferences = new ArrayList<>();
|
||||
extensionPreferences.put(extension.getName(), preferences);
|
||||
}
|
||||
|
||||
preferences.add(extension.getParameters());
|
||||
}
|
||||
|
||||
for (Map.Entry<String,List<List<Extension.Parameter>>> entry :
|
||||
extensionPreferences.entrySet()) {
|
||||
Transformation transformation = factory.create(entry.getKey(), entry.getValue(), true);
|
||||
if (transformation != null) {
|
||||
result.add(transformation);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static void append(StringBuilder sb, Extension extension) {
|
||||
if (extension == null || extension.getName() == null || extension.getName().length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sb.append(extension.getName());
|
||||
|
||||
for (Extension.Parameter p : extension.getParameters()) {
|
||||
sb.append(';');
|
||||
sb.append(p.getName());
|
||||
if (p.getValue() != null) {
|
||||
sb.append('=');
|
||||
sb.append(p.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This only works for tokens. Quoted strings need more sophisticated
|
||||
* parsing.
|
||||
*/
|
||||
private static boolean headerContainsToken(HttpServletRequest req,
|
||||
String headerName, String target) {
|
||||
Enumeration<String> headers = req.getHeaders(headerName);
|
||||
while (headers.hasMoreElements()) {
|
||||
String header = headers.nextElement();
|
||||
String[] tokens = header.split(",");
|
||||
for (String token : tokens) {
|
||||
if (target.equalsIgnoreCase(token.trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This only works for tokens. Quoted strings need more sophisticated
|
||||
* parsing.
|
||||
*/
|
||||
private static List<String> getTokensFromHeader(HttpServletRequest req,
|
||||
String headerName) {
|
||||
List<String> result = new ArrayList<>();
|
||||
Enumeration<String> headers = req.getHeaders(headerName);
|
||||
while (headers.hasMoreElements()) {
|
||||
String header = headers.nextElement();
|
||||
String[] tokens = header.split(",");
|
||||
for (String token : tokens) {
|
||||
result.add(token.trim());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static String getWebSocketAccept(String key) {
|
||||
byte[] digest = ConcurrentMessageDigest.digestSHA1(
|
||||
key.getBytes(StandardCharsets.ISO_8859_1), WS_ACCEPT);
|
||||
return Base64.encodeBase64String(digest);
|
||||
}
|
||||
}
|
||||
177
java/org/apache/tomcat/websocket/server/UriTemplate.java
Normal file
177
java/org/apache/tomcat/websocket/server/UriTemplate.java
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.websocket.DeploymentException;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Extracts path parameters from URIs used to create web socket connections
|
||||
* using the URI template defined for the associated Endpoint.
|
||||
*/
|
||||
public class UriTemplate {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(UriTemplate.class);
|
||||
|
||||
private final String normalized;
|
||||
private final List<Segment> segments = new ArrayList<>();
|
||||
private final boolean hasParameters;
|
||||
|
||||
|
||||
public UriTemplate(String path) throws DeploymentException {
|
||||
|
||||
if (path == null || path.length() ==0 || !path.startsWith("/")) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("uriTemplate.invalidPath", path));
|
||||
}
|
||||
|
||||
StringBuilder normalized = new StringBuilder(path.length());
|
||||
Set<String> paramNames = new HashSet<>();
|
||||
|
||||
// Include empty segments.
|
||||
String[] segments = path.split("/", -1);
|
||||
int paramCount = 0;
|
||||
int segmentCount = 0;
|
||||
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
String segment = segments[i];
|
||||
if (segment.length() == 0) {
|
||||
if (i == 0 || (i == segments.length - 1 && paramCount == 0)) {
|
||||
// Ignore the first empty segment as the path must always
|
||||
// start with '/'
|
||||
// Ending with a '/' is also OK for instances used for
|
||||
// matches but not for parameterised templates.
|
||||
continue;
|
||||
} else {
|
||||
// As per EG discussion, all other empty segments are
|
||||
// invalid
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"uriTemplate.emptySegment", path));
|
||||
}
|
||||
}
|
||||
normalized.append('/');
|
||||
int index = -1;
|
||||
if (segment.startsWith("{") && segment.endsWith("}")) {
|
||||
index = segmentCount;
|
||||
segment = segment.substring(1, segment.length() - 1);
|
||||
normalized.append('{');
|
||||
normalized.append(paramCount++);
|
||||
normalized.append('}');
|
||||
if (!paramNames.add(segment)) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"uriTemplate.duplicateParameter", segment));
|
||||
}
|
||||
} else {
|
||||
if (segment.contains("{") || segment.contains("}")) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"uriTemplate.invalidSegment", segment, path));
|
||||
}
|
||||
normalized.append(segment);
|
||||
}
|
||||
this.segments.add(new Segment(index, segment));
|
||||
segmentCount++;
|
||||
}
|
||||
|
||||
this.normalized = normalized.toString();
|
||||
this.hasParameters = paramCount > 0;
|
||||
}
|
||||
|
||||
|
||||
public Map<String,String> match(UriTemplate candidate) {
|
||||
|
||||
Map<String,String> result = new HashMap<>();
|
||||
|
||||
// Should not happen but for safety
|
||||
if (candidate.getSegmentCount() != getSegmentCount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Iterator<Segment> candidateSegments =
|
||||
candidate.getSegments().iterator();
|
||||
Iterator<Segment> targetSegments = segments.iterator();
|
||||
|
||||
while (candidateSegments.hasNext()) {
|
||||
Segment candidateSegment = candidateSegments.next();
|
||||
Segment targetSegment = targetSegments.next();
|
||||
|
||||
if (targetSegment.getParameterIndex() == -1) {
|
||||
// Not a parameter - values must match
|
||||
if (!targetSegment.getValue().equals(
|
||||
candidateSegment.getValue())) {
|
||||
// Not a match. Stop here
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Parameter
|
||||
result.put(targetSegment.getValue(),
|
||||
candidateSegment.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasParameters() {
|
||||
return hasParameters;
|
||||
}
|
||||
|
||||
|
||||
public int getSegmentCount() {
|
||||
return segments.size();
|
||||
}
|
||||
|
||||
|
||||
public String getNormalizedPath() {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
private List<Segment> getSegments() {
|
||||
return segments;
|
||||
}
|
||||
|
||||
|
||||
private static class Segment {
|
||||
private final int parameterIndex;
|
||||
private final String value;
|
||||
|
||||
public Segment(int parameterIndex, String value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
public int getParameterIndex() {
|
||||
return parameterIndex;
|
||||
}
|
||||
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
/**
|
||||
* In normal usage, this {@link ServletContextListener} does not need to be
|
||||
* explicitly configured as the {@link WsSci} performs all the necessary
|
||||
* bootstrap and installs this listener in the {@link ServletContext}. If the
|
||||
* {@link WsSci} is disabled, this listener must be added manually to every
|
||||
* {@link ServletContext} that uses WebSocket to bootstrap the
|
||||
* {@link WsServerContainer} correctly.
|
||||
*/
|
||||
public class WsContextListener implements ServletContextListener {
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
ServletContext sc = sce.getServletContext();
|
||||
// Don't trigger WebSocket initialization if a WebSocket Server
|
||||
// Container is already present
|
||||
if (sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE) == null) {
|
||||
WsSci.init(sce.getServletContext(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
ServletContext sc = sce.getServletContext();
|
||||
Object obj = sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
|
||||
if (obj instanceof WsServerContainer) {
|
||||
((WsServerContainer) obj).destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
java/org/apache/tomcat/websocket/server/WsFilter.java
Normal file
86
java/org/apache/tomcat/websocket/server/WsFilter.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Handles the initial HTTP connection for WebSocket connections.
|
||||
*/
|
||||
public class WsFilter implements Filter {
|
||||
|
||||
private WsServerContainer sc;
|
||||
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
sc = (WsServerContainer) filterConfig.getServletContext().getAttribute(
|
||||
Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
// This filter only needs to handle WebSocket upgrade requests
|
||||
if (!sc.areEndpointsRegistered() ||
|
||||
!UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// HTTP request with an upgrade header for WebSocket present
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
HttpServletResponse resp = (HttpServletResponse) response;
|
||||
|
||||
// Check to see if this WebSocket implementation has a matching mapping
|
||||
String path;
|
||||
String pathInfo = req.getPathInfo();
|
||||
if (pathInfo == null) {
|
||||
path = req.getServletPath();
|
||||
} else {
|
||||
path = req.getServletPath() + pathInfo;
|
||||
}
|
||||
WsMappingResult mappingResult = sc.findMapping(path);
|
||||
|
||||
if (mappingResult == null) {
|
||||
// No endpoint registered for the requested path. Let the
|
||||
// application handle it (it might redirect or forward for example)
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(),
|
||||
mappingResult.getPathParams());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
192
java/org/apache/tomcat/websocket/server/WsFrameServer.java
Normal file
192
java/org/apache/tomcat/websocket/server/WsFrameServer.java
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
|
||||
import org.apache.tomcat.util.net.SocketEvent;
|
||||
import org.apache.tomcat.util.net.SocketWrapperBase;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.Transformation;
|
||||
import org.apache.tomcat.websocket.WsFrameBase;
|
||||
import org.apache.tomcat.websocket.WsIOException;
|
||||
import org.apache.tomcat.websocket.WsSession;
|
||||
|
||||
public class WsFrameServer extends WsFrameBase {
|
||||
|
||||
private final Log log = LogFactory.getLog(WsFrameServer.class); // must not be static
|
||||
private static final StringManager sm = StringManager.getManager(WsFrameServer.class);
|
||||
|
||||
private final SocketWrapperBase<?> socketWrapper;
|
||||
private final ClassLoader applicationClassLoader;
|
||||
|
||||
|
||||
public WsFrameServer(SocketWrapperBase<?> socketWrapper, WsSession wsSession,
|
||||
Transformation transformation, ClassLoader applicationClassLoader) {
|
||||
super(wsSession, transformation);
|
||||
this.socketWrapper = socketWrapper;
|
||||
this.applicationClassLoader = applicationClassLoader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when there is data in the ServletInputStream to process.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs while processing the available
|
||||
* data
|
||||
*/
|
||||
private void onDataAvailable() throws IOException {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("wsFrameServer.onDataAvailable");
|
||||
}
|
||||
if (isOpen() && inputBuffer.hasRemaining() && !isSuspended()) {
|
||||
// There might be a data that was left in the buffer when
|
||||
// the read has been suspended.
|
||||
// Consume this data before reading from the socket.
|
||||
processInputBuffer();
|
||||
}
|
||||
|
||||
while (isOpen() && !isSuspended()) {
|
||||
// Fill up the input buffer with as much data as we can
|
||||
inputBuffer.mark();
|
||||
inputBuffer.position(inputBuffer.limit()).limit(inputBuffer.capacity());
|
||||
int read = socketWrapper.read(false, inputBuffer);
|
||||
inputBuffer.limit(inputBuffer.position()).reset();
|
||||
if (read < 0) {
|
||||
throw new EOFException();
|
||||
} else if (read == 0) {
|
||||
return;
|
||||
}
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("wsFrameServer.bytesRead", Integer.toString(read)));
|
||||
}
|
||||
processInputBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isMasked() {
|
||||
// Data is from the client so it should be masked
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Transformation getTransformation() {
|
||||
// Overridden to make it visible to other classes in this package
|
||||
return super.getTransformation();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isOpen() {
|
||||
// Overridden to make it visible to other classes in this package
|
||||
return super.isOpen();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void sendMessageText(boolean last) throws WsIOException {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Thread.currentThread().setContextClassLoader(applicationClassLoader);
|
||||
super.sendMessageText(last);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void sendMessageBinary(ByteBuffer msg, boolean last) throws WsIOException {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Thread.currentThread().setContextClassLoader(applicationClassLoader);
|
||||
super.sendMessageBinary(msg, last);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void resumeProcessing() {
|
||||
socketWrapper.processSocket(SocketEvent.OPEN_READ, true);
|
||||
}
|
||||
|
||||
SocketState notifyDataAvailable() throws IOException {
|
||||
while (isOpen()) {
|
||||
switch (getReadState()) {
|
||||
case WAITING:
|
||||
if (!changeReadState(ReadState.WAITING, ReadState.PROCESSING)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
return doOnDataAvailable();
|
||||
} catch (IOException e) {
|
||||
changeReadState(ReadState.CLOSING);
|
||||
throw e;
|
||||
}
|
||||
case SUSPENDING_WAIT:
|
||||
if (!changeReadState(ReadState.SUSPENDING_WAIT, ReadState.SUSPENDED)) {
|
||||
continue;
|
||||
}
|
||||
return SocketState.SUSPENDED;
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsFrameServer.illegalReadState", getReadState()));
|
||||
}
|
||||
}
|
||||
|
||||
return SocketState.CLOSED;
|
||||
}
|
||||
|
||||
private SocketState doOnDataAvailable() throws IOException {
|
||||
onDataAvailable();
|
||||
while (isOpen()) {
|
||||
switch (getReadState()) {
|
||||
case PROCESSING:
|
||||
if (!changeReadState(ReadState.PROCESSING, ReadState.WAITING)) {
|
||||
continue;
|
||||
}
|
||||
return SocketState.UPGRADED;
|
||||
case SUSPENDING_PROCESS:
|
||||
if (!changeReadState(ReadState.SUSPENDING_PROCESS, ReadState.SUSPENDED)) {
|
||||
continue;
|
||||
}
|
||||
return SocketState.SUSPENDED;
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsFrameServer.illegalReadState", getReadState()));
|
||||
}
|
||||
}
|
||||
|
||||
return SocketState.CLOSED;
|
||||
}
|
||||
}
|
||||
189
java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
Normal file
189
java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.websocket.server.HandshakeRequest;
|
||||
|
||||
import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* Represents the request that this session was opened under.
|
||||
*/
|
||||
public class WsHandshakeRequest implements HandshakeRequest {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(WsHandshakeRequest.class);
|
||||
|
||||
private final URI requestUri;
|
||||
private final Map<String,List<String>> parameterMap;
|
||||
private final String queryString;
|
||||
private final Principal userPrincipal;
|
||||
private final Map<String,List<String>> headers;
|
||||
private final Object httpSession;
|
||||
|
||||
private volatile HttpServletRequest request;
|
||||
|
||||
|
||||
public WsHandshakeRequest(HttpServletRequest request, Map<String,String> pathParams) {
|
||||
|
||||
this.request = request;
|
||||
|
||||
queryString = request.getQueryString();
|
||||
userPrincipal = request.getUserPrincipal();
|
||||
httpSession = request.getSession(false);
|
||||
requestUri = buildRequestUri(request);
|
||||
|
||||
// ParameterMap
|
||||
Map<String,String[]> originalParameters = request.getParameterMap();
|
||||
Map<String,List<String>> newParameters =
|
||||
new HashMap<>(originalParameters.size());
|
||||
for (Entry<String,String[]> entry : originalParameters.entrySet()) {
|
||||
newParameters.put(entry.getKey(),
|
||||
Collections.unmodifiableList(
|
||||
Arrays.asList(entry.getValue())));
|
||||
}
|
||||
for (Entry<String,String> entry : pathParams.entrySet()) {
|
||||
newParameters.put(entry.getKey(), Collections.singletonList(entry.getValue()));
|
||||
}
|
||||
parameterMap = Collections.unmodifiableMap(newParameters);
|
||||
|
||||
// Headers
|
||||
Map<String,List<String>> newHeaders = new CaseInsensitiveKeyMap<>();
|
||||
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
|
||||
newHeaders.put(headerName, Collections.unmodifiableList(
|
||||
Collections.list(request.getHeaders(headerName))));
|
||||
}
|
||||
|
||||
headers = Collections.unmodifiableMap(newHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestURI() {
|
||||
return requestUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,List<String>> getParameterMap() {
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString() {
|
||||
return queryString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return userPrincipal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,List<String>> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(String role) {
|
||||
if (request == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
return request.isUserInRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHttpSession() {
|
||||
return httpSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the HandshakeRequest is no longer required. Since an instance
|
||||
* of this class retains a reference to the current HttpServletRequest that
|
||||
* reference needs to be cleared as the HttpServletRequest may be reused.
|
||||
*
|
||||
* There is no reason for instances of this class to be accessed once the
|
||||
* handshake has been completed.
|
||||
*/
|
||||
void finished() {
|
||||
request = null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* See RequestUtil.getRequestURL()
|
||||
*/
|
||||
private static URI buildRequestUri(HttpServletRequest req) {
|
||||
|
||||
StringBuffer uri = new StringBuffer();
|
||||
String scheme = req.getScheme();
|
||||
int port = req.getServerPort();
|
||||
if (port < 0) {
|
||||
// Work around java.net.URL bug
|
||||
port = 80;
|
||||
}
|
||||
|
||||
if ("http".equals(scheme)) {
|
||||
uri.append("ws");
|
||||
} else if ("https".equals(scheme)) {
|
||||
uri.append("wss");
|
||||
} else {
|
||||
// Should never happen
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("wsHandshakeRequest.unknownScheme", scheme));
|
||||
}
|
||||
|
||||
uri.append("://");
|
||||
uri.append(req.getServerName());
|
||||
|
||||
if ((scheme.equals("http") && (port != 80))
|
||||
|| (scheme.equals("https") && (port != 443))) {
|
||||
uri.append(':');
|
||||
uri.append(port);
|
||||
}
|
||||
|
||||
uri.append(req.getRequestURI());
|
||||
|
||||
if (req.getQueryString() != null) {
|
||||
uri.append("?");
|
||||
uri.append(req.getQueryString());
|
||||
}
|
||||
|
||||
try {
|
||||
return new URI(uri.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
// Should never happen
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("wsHandshakeRequest.invalidUri", uri.toString()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.WebConnection;
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
|
||||
import org.apache.tomcat.util.net.SSLSupport;
|
||||
import org.apache.tomcat.util.net.SocketEvent;
|
||||
import org.apache.tomcat.util.net.SocketWrapperBase;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.Transformation;
|
||||
import org.apache.tomcat.websocket.WsIOException;
|
||||
import org.apache.tomcat.websocket.WsSession;
|
||||
|
||||
/**
|
||||
* Servlet 3.1 HTTP upgrade handler for WebSocket connections.
|
||||
*/
|
||||
public class WsHttpUpgradeHandler implements InternalHttpUpgradeHandler {
|
||||
|
||||
private final Log log = LogFactory.getLog(WsHttpUpgradeHandler.class); // must not be static
|
||||
private static final StringManager sm = StringManager.getManager(WsHttpUpgradeHandler.class);
|
||||
|
||||
private final ClassLoader applicationClassLoader;
|
||||
|
||||
private SocketWrapperBase<?> socketWrapper;
|
||||
|
||||
private Endpoint ep;
|
||||
private ServerEndpointConfig serverEndpointConfig;
|
||||
private WsServerContainer webSocketContainer;
|
||||
private WsHandshakeRequest handshakeRequest;
|
||||
private List<Extension> negotiatedExtensions;
|
||||
private String subProtocol;
|
||||
private Transformation transformation;
|
||||
private Map<String,String> pathParameters;
|
||||
private boolean secure;
|
||||
private WebConnection connection;
|
||||
|
||||
private WsRemoteEndpointImplServer wsRemoteEndpointServer;
|
||||
private WsFrameServer wsFrame;
|
||||
private WsSession wsSession;
|
||||
|
||||
|
||||
public WsHttpUpgradeHandler() {
|
||||
applicationClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSocketWrapper(SocketWrapperBase<?> socketWrapper) {
|
||||
this.socketWrapper = socketWrapper;
|
||||
}
|
||||
|
||||
|
||||
public void preInit(Endpoint ep, ServerEndpointConfig serverEndpointConfig,
|
||||
WsServerContainer wsc, WsHandshakeRequest handshakeRequest,
|
||||
List<Extension> negotiatedExtensionsPhase2, String subProtocol,
|
||||
Transformation transformation, Map<String,String> pathParameters,
|
||||
boolean secure) {
|
||||
this.ep = ep;
|
||||
this.serverEndpointConfig = serverEndpointConfig;
|
||||
this.webSocketContainer = wsc;
|
||||
this.handshakeRequest = handshakeRequest;
|
||||
this.negotiatedExtensions = negotiatedExtensionsPhase2;
|
||||
this.subProtocol = subProtocol;
|
||||
this.transformation = transformation;
|
||||
this.pathParameters = pathParameters;
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init(WebConnection connection) {
|
||||
if (ep == null) {
|
||||
throw new IllegalStateException(
|
||||
sm.getString("wsHttpUpgradeHandler.noPreInit"));
|
||||
}
|
||||
|
||||
String httpSessionId = null;
|
||||
Object session = handshakeRequest.getHttpSession();
|
||||
if (session != null ) {
|
||||
httpSessionId = ((HttpSession) session).getId();
|
||||
}
|
||||
|
||||
// Need to call onOpen using the web application's class loader
|
||||
// Create the frame using the application's class loader so it can pick
|
||||
// up application specific config from the ServerContainerImpl
|
||||
Thread t = Thread.currentThread();
|
||||
ClassLoader cl = t.getContextClassLoader();
|
||||
t.setContextClassLoader(applicationClassLoader);
|
||||
try {
|
||||
wsRemoteEndpointServer = new WsRemoteEndpointImplServer(socketWrapper, webSocketContainer);
|
||||
wsSession = new WsSession(ep, wsRemoteEndpointServer,
|
||||
webSocketContainer, handshakeRequest.getRequestURI(),
|
||||
handshakeRequest.getParameterMap(),
|
||||
handshakeRequest.getQueryString(),
|
||||
handshakeRequest.getUserPrincipal(), httpSessionId,
|
||||
negotiatedExtensions, subProtocol, pathParameters, secure,
|
||||
serverEndpointConfig);
|
||||
wsFrame = new WsFrameServer(socketWrapper, wsSession, transformation,
|
||||
applicationClassLoader);
|
||||
// WsFrame adds the necessary final transformations. Copy the
|
||||
// completed transformation chain to the remote end point.
|
||||
wsRemoteEndpointServer.setTransformation(wsFrame.getTransformation());
|
||||
ep.onOpen(wsSession, serverEndpointConfig);
|
||||
webSocketContainer.registerSession(serverEndpointConfig.getPath(), wsSession);
|
||||
} catch (DeploymentException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
} finally {
|
||||
t.setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SocketState upgradeDispatch(SocketEvent status) {
|
||||
switch (status) {
|
||||
case OPEN_READ:
|
||||
try {
|
||||
return wsFrame.notifyDataAvailable();
|
||||
} catch (WsIOException ws) {
|
||||
close(ws.getCloseReason());
|
||||
} catch (IOException ioe) {
|
||||
onError(ioe);
|
||||
CloseReason cr = new CloseReason(
|
||||
CloseCodes.CLOSED_ABNORMALLY, ioe.getMessage());
|
||||
close(cr);
|
||||
}
|
||||
return SocketState.CLOSED;
|
||||
case OPEN_WRITE:
|
||||
wsRemoteEndpointServer.onWritePossible(false);
|
||||
break;
|
||||
case STOP:
|
||||
CloseReason cr = new CloseReason(CloseCodes.GOING_AWAY,
|
||||
sm.getString("wsHttpUpgradeHandler.serverStop"));
|
||||
try {
|
||||
wsSession.close(cr);
|
||||
} catch (IOException ioe) {
|
||||
onError(ioe);
|
||||
cr = new CloseReason(
|
||||
CloseCodes.CLOSED_ABNORMALLY, ioe.getMessage());
|
||||
close(cr);
|
||||
return SocketState.CLOSED;
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
String msg = sm.getString("wsHttpUpgradeHandler.closeOnError");
|
||||
wsSession.doClose(new CloseReason(CloseCodes.GOING_AWAY, msg),
|
||||
new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg));
|
||||
//$FALL-THROUGH$
|
||||
case DISCONNECT:
|
||||
case TIMEOUT:
|
||||
case CONNECT_FAIL:
|
||||
return SocketState.CLOSED;
|
||||
|
||||
}
|
||||
if (wsFrame.isOpen()) {
|
||||
return SocketState.UPGRADED;
|
||||
} else {
|
||||
return SocketState.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void timeoutAsync(long now) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (Exception e) {
|
||||
log.error(sm.getString("wsHttpUpgradeHandler.destroyFailed"), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void onError(Throwable throwable) {
|
||||
// Need to call onError using the web application's class loader
|
||||
Thread t = Thread.currentThread();
|
||||
ClassLoader cl = t.getContextClassLoader();
|
||||
t.setContextClassLoader(applicationClassLoader);
|
||||
try {
|
||||
ep.onError(wsSession, throwable);
|
||||
} finally {
|
||||
t.setContextClassLoader(cl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void close(CloseReason cr) {
|
||||
/*
|
||||
* Any call to this method is a result of a problem reading from the
|
||||
* client. At this point that state of the connection is unknown.
|
||||
* Attempt to send a close frame to the client and then close the socket
|
||||
* immediately. There is no point in waiting for a close frame from the
|
||||
* client because there is no guarantee that we can recover from
|
||||
* whatever messed up state the client put the connection into.
|
||||
*/
|
||||
wsSession.onClose(cr);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSslSupport(SSLSupport sslSupport) {
|
||||
// NO-OP. WebSocket has no requirement to access the TLS information
|
||||
// associated with the underlying connection.
|
||||
}
|
||||
}
|
||||
44
java/org/apache/tomcat/websocket/server/WsMappingResult.java
Normal file
44
java/org/apache/tomcat/websocket/server/WsMappingResult.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
class WsMappingResult {
|
||||
|
||||
private final ServerEndpointConfig config;
|
||||
private final Map<String,String> pathParams;
|
||||
|
||||
|
||||
WsMappingResult(ServerEndpointConfig config,
|
||||
Map<String,String> pathParams) {
|
||||
this.config = config;
|
||||
this.pathParams = pathParams;
|
||||
}
|
||||
|
||||
|
||||
ServerEndpointConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
Map<String,String> getPathParams() {
|
||||
return pathParams;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
/**
|
||||
* Wraps the provided {@link ServerEndpointConfig} and provides a per session
|
||||
* view - the difference being that the map returned by {@link
|
||||
* #getUserProperties()} is unique to this instance rather than shared with the
|
||||
* wrapped {@link ServerEndpointConfig}.
|
||||
*/
|
||||
class WsPerSessionServerEndpointConfig implements ServerEndpointConfig {
|
||||
|
||||
private final ServerEndpointConfig perEndpointConfig;
|
||||
private final Map<String,Object> perSessionUserProperties =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
WsPerSessionServerEndpointConfig(ServerEndpointConfig perEndpointConfig) {
|
||||
this.perEndpointConfig = perEndpointConfig;
|
||||
perSessionUserProperties.putAll(perEndpointConfig.getUserProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Encoder>> getEncoders() {
|
||||
return perEndpointConfig.getEncoders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Decoder>> getDecoders() {
|
||||
return perEndpointConfig.getDecoders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,Object> getUserProperties() {
|
||||
return perSessionUserProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEndpointClass() {
|
||||
return perEndpointConfig.getEndpointClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return perEndpointConfig.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSubprotocols() {
|
||||
return perEndpointConfig.getSubprotocols();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Extension> getExtensions() {
|
||||
return perEndpointConfig.getExtensions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configurator getConfigurator() {
|
||||
return perEndpointConfig.getConfigurator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.websocket.SendHandler;
|
||||
import javax.websocket.SendResult;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.net.AbstractEndpoint;
|
||||
import org.apache.tomcat.util.net.SocketWrapperBase;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.Transformation;
|
||||
import org.apache.tomcat.websocket.WsRemoteEndpointImplBase;
|
||||
|
||||
/**
|
||||
* This is the server side {@link javax.websocket.RemoteEndpoint} implementation
|
||||
* - i.e. what the server uses to send data to the client.
|
||||
*/
|
||||
public class WsRemoteEndpointImplServer extends WsRemoteEndpointImplBase {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(WsRemoteEndpointImplServer.class);
|
||||
private final Log log = LogFactory.getLog(WsRemoteEndpointImplServer.class); // must not be static
|
||||
|
||||
private final SocketWrapperBase<?> socketWrapper;
|
||||
private final WsWriteTimeout wsWriteTimeout;
|
||||
private volatile SendHandler handler = null;
|
||||
private volatile ByteBuffer[] buffers = null;
|
||||
|
||||
private volatile long timeoutExpiry = -1;
|
||||
|
||||
public WsRemoteEndpointImplServer(SocketWrapperBase<?> socketWrapper,
|
||||
WsServerContainer serverContainer) {
|
||||
this.socketWrapper = socketWrapper;
|
||||
this.wsWriteTimeout = serverContainer.getTimeout();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final boolean isMasked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doWrite(SendHandler handler, long blockingWriteTimeoutExpiry,
|
||||
ByteBuffer... buffers) {
|
||||
if (blockingWriteTimeoutExpiry == -1) {
|
||||
this.handler = handler;
|
||||
this.buffers = buffers;
|
||||
// This is definitely the same thread that triggered the write so a
|
||||
// dispatch will be required.
|
||||
onWritePossible(true);
|
||||
} else {
|
||||
// Blocking
|
||||
try {
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis();
|
||||
if (timeout <= 0) {
|
||||
SendResult sr = new SendResult(new SocketTimeoutException());
|
||||
handler.onResult(sr);
|
||||
return;
|
||||
}
|
||||
socketWrapper.setWriteTimeout(timeout);
|
||||
socketWrapper.write(true, buffer);
|
||||
}
|
||||
long timeout = blockingWriteTimeoutExpiry - System.currentTimeMillis();
|
||||
if (timeout <= 0) {
|
||||
SendResult sr = new SendResult(new SocketTimeoutException());
|
||||
handler.onResult(sr);
|
||||
return;
|
||||
}
|
||||
socketWrapper.setWriteTimeout(timeout);
|
||||
socketWrapper.flush(true);
|
||||
handler.onResult(SENDRESULT_OK);
|
||||
} catch (IOException e) {
|
||||
SendResult sr = new SendResult(e);
|
||||
handler.onResult(sr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void onWritePossible(boolean useDispatch) {
|
||||
ByteBuffer[] buffers = this.buffers;
|
||||
if (buffers == null) {
|
||||
// Servlet 3.1 will call the write listener once even if nothing
|
||||
// was written
|
||||
return;
|
||||
}
|
||||
boolean complete = false;
|
||||
try {
|
||||
socketWrapper.flush(false);
|
||||
// If this is false there will be a call back when it is true
|
||||
while (socketWrapper.isReadyForWrite()) {
|
||||
complete = true;
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
if (buffer.hasRemaining()) {
|
||||
complete = false;
|
||||
socketWrapper.write(false, buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (complete) {
|
||||
socketWrapper.flush(false);
|
||||
complete = socketWrapper.isReadyForWrite();
|
||||
if (complete) {
|
||||
wsWriteTimeout.unregister(this);
|
||||
clearHandler(null, useDispatch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
wsWriteTimeout.unregister(this);
|
||||
clearHandler(e, useDispatch);
|
||||
close();
|
||||
}
|
||||
|
||||
if (!complete) {
|
||||
// Async write is in progress
|
||||
long timeout = getSendTimeout();
|
||||
if (timeout > 0) {
|
||||
// Register with timeout thread
|
||||
timeoutExpiry = timeout + System.currentTimeMillis();
|
||||
wsWriteTimeout.register(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
if (handler != null) {
|
||||
// close() can be triggered by a wide range of scenarios. It is far
|
||||
// simpler just to always use a dispatch than it is to try and track
|
||||
// whether or not this method was called by the same thread that
|
||||
// triggered the write
|
||||
clearHandler(new EOFException(), true);
|
||||
}
|
||||
try {
|
||||
socketWrapper.close();
|
||||
} catch (Exception e) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info(sm.getString("wsRemoteEndpointServer.closeFailed"), e);
|
||||
}
|
||||
}
|
||||
wsWriteTimeout.unregister(this);
|
||||
}
|
||||
|
||||
|
||||
protected long getTimeoutExpiry() {
|
||||
return timeoutExpiry;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Currently this is only called from the background thread so we could just
|
||||
* call clearHandler() with useDispatch == false but the method parameter
|
||||
* was added in case other callers started to use this method to make sure
|
||||
* that those callers think through what the correct value of useDispatch is
|
||||
* for them.
|
||||
*/
|
||||
protected void onTimeout(boolean useDispatch) {
|
||||
if (handler != null) {
|
||||
clearHandler(new SocketTimeoutException(), useDispatch);
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void setTransformation(Transformation transformation) {
|
||||
// Overridden purely so it is visible to other classes in this package
|
||||
super.setTransformation(transformation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param t The throwable associated with any error that
|
||||
* occurred
|
||||
* @param useDispatch Should {@link SendHandler#onResult(SendResult)} be
|
||||
* called from a new thread, keeping in mind the
|
||||
* requirements of
|
||||
* {@link javax.websocket.RemoteEndpoint.Async}
|
||||
*/
|
||||
private void clearHandler(Throwable t, boolean useDispatch) {
|
||||
// Setting the result marks this (partial) message as
|
||||
// complete which means the next one may be sent which
|
||||
// could update the value of the handler. Therefore, keep a
|
||||
// local copy before signalling the end of the (partial)
|
||||
// message.
|
||||
SendHandler sh = handler;
|
||||
handler = null;
|
||||
buffers = null;
|
||||
if (sh != null) {
|
||||
if (useDispatch) {
|
||||
OnResultRunnable r = new OnResultRunnable(sh, t);
|
||||
AbstractEndpoint<?> endpoint = socketWrapper.getEndpoint();
|
||||
Executor containerExecutor = endpoint.getExecutor();
|
||||
if (endpoint.isRunning() && containerExecutor != null) {
|
||||
containerExecutor.execute(r);
|
||||
} else {
|
||||
// Can't use the executor so call the runnable directly.
|
||||
// This may not be strictly specification compliant in all
|
||||
// cases but during shutdown only close messages are going
|
||||
// to be sent so there should not be the issue of nested
|
||||
// calls leading to stack overflow as described in bug
|
||||
// 55715. The issues with nested calls was the reason for
|
||||
// the separate thread requirement in the specification.
|
||||
r.run();
|
||||
}
|
||||
} else {
|
||||
if (t == null) {
|
||||
sh.onResult(new SendResult());
|
||||
} else {
|
||||
sh.onResult(new SendResult(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class OnResultRunnable implements Runnable {
|
||||
|
||||
private final SendHandler sh;
|
||||
private final Throwable t;
|
||||
|
||||
private OnResultRunnable(SendHandler sh, Throwable t) {
|
||||
this.sh = sh;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (t == null) {
|
||||
sh.onResult(new SendResult());
|
||||
} else {
|
||||
sh.onResult(new SendResult(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
151
java/org/apache/tomcat/websocket/server/WsSci.java
Normal file
151
java/org/apache/tomcat/websocket/server/WsSci.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.HandlesTypes;
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Endpoint;
|
||||
import javax.websocket.server.ServerApplicationConfig;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
import org.apache.tomcat.util.compat.JreCompat;
|
||||
|
||||
/**
|
||||
* Registers an interest in any class that is annotated with
|
||||
* {@link ServerEndpoint} so that Endpoint can be published via the WebSocket
|
||||
* server.
|
||||
*/
|
||||
@HandlesTypes({ServerEndpoint.class, ServerApplicationConfig.class,
|
||||
Endpoint.class})
|
||||
public class WsSci implements ServletContainerInitializer {
|
||||
|
||||
@Override
|
||||
public void onStartup(Set<Class<?>> clazzes, ServletContext ctx)
|
||||
throws ServletException {
|
||||
|
||||
WsServerContainer sc = init(ctx, true);
|
||||
|
||||
if (clazzes == null || clazzes.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Group the discovered classes by type
|
||||
Set<ServerApplicationConfig> serverApplicationConfigs = new HashSet<>();
|
||||
Set<Class<? extends Endpoint>> scannedEndpointClazzes = new HashSet<>();
|
||||
Set<Class<?>> scannedPojoEndpoints = new HashSet<>();
|
||||
|
||||
try {
|
||||
// wsPackage is "javax.websocket."
|
||||
String wsPackage = ContainerProvider.class.getName();
|
||||
wsPackage = wsPackage.substring(0, wsPackage.lastIndexOf('.') + 1);
|
||||
for (Class<?> clazz : clazzes) {
|
||||
JreCompat jreCompat = JreCompat.getInstance();
|
||||
int modifiers = clazz.getModifiers();
|
||||
if (!Modifier.isPublic(modifiers) ||
|
||||
Modifier.isAbstract(modifiers) ||
|
||||
Modifier.isInterface(modifiers) ||
|
||||
!jreCompat.isExported(clazz)) {
|
||||
// Non-public, abstract, interface or not in an exported
|
||||
// package (Java 9+) - skip it.
|
||||
continue;
|
||||
}
|
||||
// Protect against scanning the WebSocket API JARs
|
||||
if (clazz.getName().startsWith(wsPackage)) {
|
||||
continue;
|
||||
}
|
||||
if (ServerApplicationConfig.class.isAssignableFrom(clazz)) {
|
||||
serverApplicationConfigs.add(
|
||||
(ServerApplicationConfig) clazz.getConstructor().newInstance());
|
||||
}
|
||||
if (Endpoint.class.isAssignableFrom(clazz)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Endpoint> endpoint =
|
||||
(Class<? extends Endpoint>) clazz;
|
||||
scannedEndpointClazzes.add(endpoint);
|
||||
}
|
||||
if (clazz.isAnnotationPresent(ServerEndpoint.class)) {
|
||||
scannedPojoEndpoints.add(clazz);
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
// Filter the results
|
||||
Set<ServerEndpointConfig> filteredEndpointConfigs = new HashSet<>();
|
||||
Set<Class<?>> filteredPojoEndpoints = new HashSet<>();
|
||||
|
||||
if (serverApplicationConfigs.isEmpty()) {
|
||||
filteredPojoEndpoints.addAll(scannedPojoEndpoints);
|
||||
} else {
|
||||
for (ServerApplicationConfig config : serverApplicationConfigs) {
|
||||
Set<ServerEndpointConfig> configFilteredEndpoints =
|
||||
config.getEndpointConfigs(scannedEndpointClazzes);
|
||||
if (configFilteredEndpoints != null) {
|
||||
filteredEndpointConfigs.addAll(configFilteredEndpoints);
|
||||
}
|
||||
Set<Class<?>> configFilteredPojos =
|
||||
config.getAnnotatedEndpointClasses(
|
||||
scannedPojoEndpoints);
|
||||
if (configFilteredPojos != null) {
|
||||
filteredPojoEndpoints.addAll(configFilteredPojos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Deploy endpoints
|
||||
for (ServerEndpointConfig config : filteredEndpointConfigs) {
|
||||
sc.addEndpoint(config);
|
||||
}
|
||||
// Deploy POJOs
|
||||
for (Class<?> clazz : filteredPojoEndpoints) {
|
||||
sc.addEndpoint(clazz, true);
|
||||
}
|
||||
} catch (DeploymentException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static WsServerContainer init(ServletContext servletContext,
|
||||
boolean initBySciMechanism) {
|
||||
|
||||
WsServerContainer sc = new WsServerContainer(servletContext);
|
||||
|
||||
servletContext.setAttribute(
|
||||
Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE, sc);
|
||||
|
||||
servletContext.addListener(new WsSessionListener(sc));
|
||||
// Can't register the ContextListener again if the ContextListener is
|
||||
// calling this method
|
||||
if (initBySciMechanism) {
|
||||
servletContext.addListener(new WsContextListener());
|
||||
}
|
||||
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
534
java/org/apache/tomcat/websocket/server/WsServerContainer.java
Normal file
534
java/org/apache/tomcat/websocket/server/WsServerContainer.java
Normal file
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.FilterRegistration;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.websocket.CloseReason;
|
||||
import javax.websocket.CloseReason.CloseCodes;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
import javax.websocket.server.ServerEndpointConfig.Configurator;
|
||||
|
||||
import org.apache.tomcat.InstanceManager;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
import org.apache.tomcat.websocket.WsSession;
|
||||
import org.apache.tomcat.websocket.WsWebSocketContainer;
|
||||
import org.apache.tomcat.websocket.pojo.PojoMethodMapping;
|
||||
|
||||
/**
|
||||
* Provides a per class loader (i.e. per web application) instance of a
|
||||
* ServerContainer. Web application wide defaults may be configured by setting
|
||||
* the following servlet context initialisation parameters to the desired
|
||||
* values.
|
||||
* <ul>
|
||||
* <li>{@link Constants#BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM}</li>
|
||||
* <li>{@link Constants#TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class WsServerContainer extends WsWebSocketContainer
|
||||
implements ServerContainer {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(WsServerContainer.class);
|
||||
|
||||
private static final CloseReason AUTHENTICATED_HTTP_SESSION_CLOSED =
|
||||
new CloseReason(CloseCodes.VIOLATED_POLICY,
|
||||
"This connection was established under an authenticated " +
|
||||
"HTTP session that has ended.");
|
||||
|
||||
private final WsWriteTimeout wsWriteTimeout = new WsWriteTimeout();
|
||||
|
||||
private final ServletContext servletContext;
|
||||
private final Map<String,ExactPathMatch> configExactMatchMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Integer,ConcurrentSkipListMap<String,TemplatePathMatch>> configTemplateMatchMap =
|
||||
new ConcurrentHashMap<>();
|
||||
private volatile boolean enforceNoAddAfterHandshake =
|
||||
org.apache.tomcat.websocket.Constants.STRICT_SPEC_COMPLIANCE;
|
||||
private volatile boolean addAllowed = true;
|
||||
private final ConcurrentMap<String,Set<WsSession>> authenticatedSessions =
|
||||
new ConcurrentHashMap<>();
|
||||
private volatile boolean endpointsRegistered = false;
|
||||
private volatile boolean deploymentFailed = false;
|
||||
|
||||
WsServerContainer(ServletContext servletContext) {
|
||||
|
||||
this.servletContext = servletContext;
|
||||
setInstanceManager((InstanceManager) servletContext.getAttribute(InstanceManager.class.getName()));
|
||||
|
||||
// Configure servlet context wide defaults
|
||||
String value = servletContext.getInitParameter(
|
||||
Constants.BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM);
|
||||
if (value != null) {
|
||||
setDefaultMaxBinaryMessageBufferSize(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
value = servletContext.getInitParameter(
|
||||
Constants.TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM);
|
||||
if (value != null) {
|
||||
setDefaultMaxTextMessageBufferSize(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
value = servletContext.getInitParameter(
|
||||
Constants.ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM);
|
||||
if (value != null) {
|
||||
setEnforceNoAddAfterHandshake(Boolean.parseBoolean(value));
|
||||
}
|
||||
|
||||
FilterRegistration.Dynamic fr = servletContext.addFilter(
|
||||
"Tomcat WebSocket (JSR356) Filter", new WsFilter());
|
||||
fr.setAsyncSupported(true);
|
||||
|
||||
EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST,
|
||||
DispatcherType.FORWARD);
|
||||
|
||||
fr.addMappingForUrlPatterns(types, true, "/*");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Published the provided endpoint implementation at the specified path with
|
||||
* the specified configuration. {@link #WsServerContainer(ServletContext)}
|
||||
* must be called before calling this method.
|
||||
*
|
||||
* @param sec The configuration to use when creating endpoint instances
|
||||
* @throws DeploymentException if the endpoint cannot be published as
|
||||
* requested
|
||||
*/
|
||||
@Override
|
||||
public void addEndpoint(ServerEndpointConfig sec) throws DeploymentException {
|
||||
addEndpoint(sec, false);
|
||||
}
|
||||
|
||||
|
||||
void addEndpoint(ServerEndpointConfig sec, boolean fromAnnotatedPojo) throws DeploymentException {
|
||||
|
||||
if (enforceNoAddAfterHandshake && !addAllowed) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.addNotAllowed"));
|
||||
}
|
||||
|
||||
if (servletContext == null) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.servletContextMissing"));
|
||||
}
|
||||
|
||||
if (deploymentFailed) {
|
||||
throw new DeploymentException(sm.getString("serverContainer.failedDeployment",
|
||||
servletContext.getContextPath(), servletContext.getVirtualServerName()));
|
||||
}
|
||||
|
||||
try {
|
||||
String path = sec.getPath();
|
||||
|
||||
// Add method mapping to user properties
|
||||
PojoMethodMapping methodMapping = new PojoMethodMapping(sec.getEndpointClass(),
|
||||
sec.getDecoders(), path);
|
||||
if (methodMapping.getOnClose() != null || methodMapping.getOnOpen() != null
|
||||
|| methodMapping.getOnError() != null || methodMapping.hasMessageHandlers()) {
|
||||
sec.getUserProperties().put(org.apache.tomcat.websocket.pojo.Constants.POJO_METHOD_MAPPING_KEY,
|
||||
methodMapping);
|
||||
}
|
||||
|
||||
UriTemplate uriTemplate = new UriTemplate(path);
|
||||
if (uriTemplate.hasParameters()) {
|
||||
Integer key = Integer.valueOf(uriTemplate.getSegmentCount());
|
||||
ConcurrentSkipListMap<String,TemplatePathMatch> templateMatches =
|
||||
configTemplateMatchMap.get(key);
|
||||
if (templateMatches == null) {
|
||||
// Ensure that if concurrent threads execute this block they
|
||||
// all end up using the same ConcurrentSkipListMap instance
|
||||
templateMatches = new ConcurrentSkipListMap<>();
|
||||
configTemplateMatchMap.putIfAbsent(key, templateMatches);
|
||||
templateMatches = configTemplateMatchMap.get(key);
|
||||
}
|
||||
TemplatePathMatch newMatch = new TemplatePathMatch(sec, uriTemplate, fromAnnotatedPojo);
|
||||
TemplatePathMatch oldMatch = templateMatches.putIfAbsent(uriTemplate.getNormalizedPath(), newMatch);
|
||||
if (oldMatch != null) {
|
||||
// Note: This depends on Endpoint instances being added
|
||||
// before POJOs in WsSci#onStartup()
|
||||
if (oldMatch.isFromAnnotatedPojo() && !newMatch.isFromAnnotatedPojo() &&
|
||||
oldMatch.getConfig().getEndpointClass() == newMatch.getConfig().getEndpointClass()) {
|
||||
// The WebSocket spec says to ignore the new match in this case
|
||||
templateMatches.put(path, oldMatch);
|
||||
} else {
|
||||
// Duplicate uriTemplate;
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.duplicatePaths", path,
|
||||
sec.getEndpointClass(),
|
||||
sec.getEndpointClass()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Exact match
|
||||
ExactPathMatch newMatch = new ExactPathMatch(sec, fromAnnotatedPojo);
|
||||
ExactPathMatch oldMatch = configExactMatchMap.put(path, newMatch);
|
||||
if (oldMatch != null) {
|
||||
// Note: This depends on Endpoint instances being added
|
||||
// before POJOs in WsSci#onStartup()
|
||||
if (oldMatch.isFromAnnotatedPojo() && !newMatch.isFromAnnotatedPojo() &&
|
||||
oldMatch.getConfig().getEndpointClass() == newMatch.getConfig().getEndpointClass()) {
|
||||
// The WebSocket spec says to ignore the new match in this case
|
||||
configExactMatchMap.put(path, oldMatch);
|
||||
} else {
|
||||
// Duplicate path mappings
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.duplicatePaths", path,
|
||||
oldMatch.getConfig().getEndpointClass(),
|
||||
sec.getEndpointClass()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endpointsRegistered = true;
|
||||
} catch (DeploymentException de) {
|
||||
failDeployment();
|
||||
throw de;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides the equivalent of {@link #addEndpoint(ServerEndpointConfig)}
|
||||
* for publishing plain old java objects (POJOs) that have been annotated as
|
||||
* WebSocket endpoints.
|
||||
*
|
||||
* @param pojo The annotated POJO
|
||||
*/
|
||||
@Override
|
||||
public void addEndpoint(Class<?> pojo) throws DeploymentException {
|
||||
addEndpoint(pojo, false);
|
||||
}
|
||||
|
||||
|
||||
void addEndpoint(Class<?> pojo, boolean fromAnnotatedPojo) throws DeploymentException {
|
||||
|
||||
if (deploymentFailed) {
|
||||
throw new DeploymentException(sm.getString("serverContainer.failedDeployment",
|
||||
servletContext.getContextPath(), servletContext.getVirtualServerName()));
|
||||
}
|
||||
|
||||
ServerEndpointConfig sec;
|
||||
|
||||
try {
|
||||
ServerEndpoint annotation = pojo.getAnnotation(ServerEndpoint.class);
|
||||
if (annotation == null) {
|
||||
throw new DeploymentException(
|
||||
sm.getString("serverContainer.missingAnnotation",
|
||||
pojo.getName()));
|
||||
}
|
||||
String path = annotation.value();
|
||||
|
||||
// Validate encoders
|
||||
validateEncoders(annotation.encoders());
|
||||
|
||||
// ServerEndpointConfig
|
||||
Class<? extends Configurator> configuratorClazz =
|
||||
annotation.configurator();
|
||||
Configurator configurator = null;
|
||||
if (!configuratorClazz.equals(Configurator.class)) {
|
||||
try {
|
||||
configurator = annotation.configurator().getConstructor().newInstance();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"serverContainer.configuratorFail",
|
||||
annotation.configurator().getName(),
|
||||
pojo.getClass().getName()), e);
|
||||
}
|
||||
}
|
||||
sec = ServerEndpointConfig.Builder.create(pojo, path).
|
||||
decoders(Arrays.asList(annotation.decoders())).
|
||||
encoders(Arrays.asList(annotation.encoders())).
|
||||
subprotocols(Arrays.asList(annotation.subprotocols())).
|
||||
configurator(configurator).
|
||||
build();
|
||||
} catch (DeploymentException de) {
|
||||
failDeployment();
|
||||
throw de;
|
||||
}
|
||||
|
||||
addEndpoint(sec, fromAnnotatedPojo);
|
||||
}
|
||||
|
||||
|
||||
void failDeployment() {
|
||||
deploymentFailed = true;
|
||||
|
||||
// Clear all existing deployments
|
||||
endpointsRegistered = false;
|
||||
configExactMatchMap.clear();
|
||||
configTemplateMatchMap.clear();
|
||||
}
|
||||
|
||||
|
||||
boolean areEndpointsRegistered() {
|
||||
return endpointsRegistered;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Until the WebSocket specification provides such a mechanism, this Tomcat
|
||||
* proprietary method is provided to enable applications to programmatically
|
||||
* determine whether or not to upgrade an individual request to WebSocket.
|
||||
* <p>
|
||||
* Note: This method is not used by Tomcat but is used directly by
|
||||
* third-party code and must not be removed.
|
||||
*
|
||||
* @param request The request object to be upgraded
|
||||
* @param response The response object to be populated with the result of
|
||||
* the upgrade
|
||||
* @param sec The server endpoint to use to process the upgrade request
|
||||
* @param pathParams The path parameters associated with the upgrade request
|
||||
*
|
||||
* @throws ServletException If a configuration error prevents the upgrade
|
||||
* from taking place
|
||||
* @throws IOException If an I/O error occurs during the upgrade process
|
||||
*/
|
||||
public void doUpgrade(HttpServletRequest request,
|
||||
HttpServletResponse response, ServerEndpointConfig sec,
|
||||
Map<String,String> pathParams)
|
||||
throws ServletException, IOException {
|
||||
UpgradeUtil.doUpgrade(this, request, response, sec, pathParams);
|
||||
}
|
||||
|
||||
|
||||
public WsMappingResult findMapping(String path) {
|
||||
|
||||
// Prevent registering additional endpoints once the first attempt has
|
||||
// been made to use one
|
||||
if (addAllowed) {
|
||||
addAllowed = false;
|
||||
}
|
||||
|
||||
// Check an exact match. Simple case as there are no templates.
|
||||
ExactPathMatch match = configExactMatchMap.get(path);
|
||||
if (match != null) {
|
||||
return new WsMappingResult(match.getConfig(), Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
// No exact match. Need to look for template matches.
|
||||
UriTemplate pathUriTemplate = null;
|
||||
try {
|
||||
pathUriTemplate = new UriTemplate(path);
|
||||
} catch (DeploymentException e) {
|
||||
// Path is not valid so can't be matched to a WebSocketEndpoint
|
||||
return null;
|
||||
}
|
||||
|
||||
// Number of segments has to match
|
||||
Integer key = Integer.valueOf(pathUriTemplate.getSegmentCount());
|
||||
ConcurrentSkipListMap<String,TemplatePathMatch> templateMatches = configTemplateMatchMap.get(key);
|
||||
|
||||
if (templateMatches == null) {
|
||||
// No templates with an equal number of segments so there will be
|
||||
// no matches
|
||||
return null;
|
||||
}
|
||||
|
||||
// List is in alphabetical order of normalised templates.
|
||||
// Correct match is the first one that matches.
|
||||
ServerEndpointConfig sec = null;
|
||||
Map<String,String> pathParams = null;
|
||||
for (TemplatePathMatch templateMatch : templateMatches.values()) {
|
||||
pathParams = templateMatch.getUriTemplate().match(pathUriTemplate);
|
||||
if (pathParams != null) {
|
||||
sec = templateMatch.getConfig();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sec == null) {
|
||||
// No match
|
||||
return null;
|
||||
}
|
||||
|
||||
return new WsMappingResult(sec, pathParams);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean isEnforceNoAddAfterHandshake() {
|
||||
return enforceNoAddAfterHandshake;
|
||||
}
|
||||
|
||||
|
||||
public void setEnforceNoAddAfterHandshake(
|
||||
boolean enforceNoAddAfterHandshake) {
|
||||
this.enforceNoAddAfterHandshake = enforceNoAddAfterHandshake;
|
||||
}
|
||||
|
||||
|
||||
protected WsWriteTimeout getTimeout() {
|
||||
return wsWriteTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Overridden to make it visible to other classes in this package.
|
||||
*/
|
||||
@Override
|
||||
protected void registerSession(Object key, WsSession wsSession) {
|
||||
super.registerSession(key, wsSession);
|
||||
if (wsSession.isOpen() &&
|
||||
wsSession.getUserPrincipal() != null &&
|
||||
wsSession.getHttpSessionId() != null) {
|
||||
registerAuthenticatedSession(wsSession,
|
||||
wsSession.getHttpSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Overridden to make it visible to other classes in this package.
|
||||
*/
|
||||
@Override
|
||||
protected void unregisterSession(Object key, WsSession wsSession) {
|
||||
if (wsSession.getUserPrincipal() != null &&
|
||||
wsSession.getHttpSessionId() != null) {
|
||||
unregisterAuthenticatedSession(wsSession,
|
||||
wsSession.getHttpSessionId());
|
||||
}
|
||||
super.unregisterSession(key, wsSession);
|
||||
}
|
||||
|
||||
|
||||
private void registerAuthenticatedSession(WsSession wsSession,
|
||||
String httpSessionId) {
|
||||
Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId);
|
||||
if (wsSessions == null) {
|
||||
wsSessions = Collections.newSetFromMap(
|
||||
new ConcurrentHashMap<WsSession,Boolean>());
|
||||
authenticatedSessions.putIfAbsent(httpSessionId, wsSessions);
|
||||
wsSessions = authenticatedSessions.get(httpSessionId);
|
||||
}
|
||||
wsSessions.add(wsSession);
|
||||
}
|
||||
|
||||
|
||||
private void unregisterAuthenticatedSession(WsSession wsSession,
|
||||
String httpSessionId) {
|
||||
Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId);
|
||||
// wsSessions will be null if the HTTP session has ended
|
||||
if (wsSessions != null) {
|
||||
wsSessions.remove(wsSession);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void closeAuthenticatedSession(String httpSessionId) {
|
||||
Set<WsSession> wsSessions = authenticatedSessions.remove(httpSessionId);
|
||||
|
||||
if (wsSessions != null && !wsSessions.isEmpty()) {
|
||||
for (WsSession wsSession : wsSessions) {
|
||||
try {
|
||||
wsSession.close(AUTHENTICATED_HTTP_SESSION_CLOSED);
|
||||
} catch (IOException e) {
|
||||
// Any IOExceptions during close will have been caught and the
|
||||
// onError method called.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void validateEncoders(Class<? extends Encoder>[] encoders)
|
||||
throws DeploymentException {
|
||||
|
||||
for (Class<? extends Encoder> encoder : encoders) {
|
||||
// Need to instantiate decoder to ensure it is valid and that
|
||||
// deployment can be failed if it is not
|
||||
@SuppressWarnings("unused")
|
||||
Encoder instance;
|
||||
try {
|
||||
encoder.getConstructor().newInstance();
|
||||
} catch(ReflectiveOperationException e) {
|
||||
throw new DeploymentException(sm.getString(
|
||||
"serverContainer.encoderFail", encoder.getName()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TemplatePathMatch {
|
||||
private final ServerEndpointConfig config;
|
||||
private final UriTemplate uriTemplate;
|
||||
private final boolean fromAnnotatedPojo;
|
||||
|
||||
public TemplatePathMatch(ServerEndpointConfig config, UriTemplate uriTemplate,
|
||||
boolean fromAnnotatedPojo) {
|
||||
this.config = config;
|
||||
this.uriTemplate = uriTemplate;
|
||||
this.fromAnnotatedPojo = fromAnnotatedPojo;
|
||||
}
|
||||
|
||||
|
||||
public ServerEndpointConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
public UriTemplate getUriTemplate() {
|
||||
return uriTemplate;
|
||||
}
|
||||
|
||||
|
||||
public boolean isFromAnnotatedPojo() {
|
||||
return fromAnnotatedPojo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ExactPathMatch {
|
||||
private final ServerEndpointConfig config;
|
||||
private final boolean fromAnnotatedPojo;
|
||||
|
||||
public ExactPathMatch(ServerEndpointConfig config, boolean fromAnnotatedPojo) {
|
||||
this.config = config;
|
||||
this.fromAnnotatedPojo = fromAnnotatedPojo;
|
||||
}
|
||||
|
||||
|
||||
public ServerEndpointConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
public boolean isFromAnnotatedPojo() {
|
||||
return fromAnnotatedPojo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
|
||||
public class WsSessionListener implements HttpSessionListener{
|
||||
|
||||
private final WsServerContainer wsServerContainer;
|
||||
|
||||
|
||||
public WsSessionListener(WsServerContainer wsServerContainer) {
|
||||
this.wsServerContainer = wsServerContainer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent se) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void sessionDestroyed(HttpSessionEvent se) {
|
||||
wsServerContainer.closeAuthenticatedSession(se.getSession().getId());
|
||||
}
|
||||
}
|
||||
128
java/org/apache/tomcat/websocket/server/WsWriteTimeout.java
Normal file
128
java/org/apache/tomcat/websocket/server/WsWriteTimeout.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 org.apache.tomcat.websocket.server;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.tomcat.websocket.BackgroundProcess;
|
||||
import org.apache.tomcat.websocket.BackgroundProcessManager;
|
||||
|
||||
/**
|
||||
* Provides timeouts for asynchronous web socket writes. On the server side we
|
||||
* only have access to {@link javax.servlet.ServletOutputStream} and
|
||||
* {@link javax.servlet.ServletInputStream} so there is no way to set a timeout
|
||||
* for writes to the client.
|
||||
*/
|
||||
public class WsWriteTimeout implements BackgroundProcess {
|
||||
|
||||
private final Set<WsRemoteEndpointImplServer> endpoints =
|
||||
new ConcurrentSkipListSet<>(new EndpointComparator());
|
||||
private final AtomicInteger count = new AtomicInteger(0);
|
||||
private int backgroundProcessCount = 0;
|
||||
private volatile int processPeriod = 1;
|
||||
|
||||
@Override
|
||||
public void backgroundProcess() {
|
||||
// This method gets called once a second.
|
||||
backgroundProcessCount ++;
|
||||
|
||||
if (backgroundProcessCount >= processPeriod) {
|
||||
backgroundProcessCount = 0;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
for (WsRemoteEndpointImplServer endpoint : endpoints) {
|
||||
if (endpoint.getTimeoutExpiry() < now) {
|
||||
// Background thread, not the thread that triggered the
|
||||
// write so no need to use a dispatch
|
||||
endpoint.onTimeout(false);
|
||||
} else {
|
||||
// Endpoints are ordered by timeout expiry so if this point
|
||||
// is reached there is no need to check the remaining
|
||||
// endpoints
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setProcessPeriod(int period) {
|
||||
this.processPeriod = period;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* The default value is 1 which means asynchronous write timeouts are
|
||||
* processed every 1 second.
|
||||
*/
|
||||
@Override
|
||||
public int getProcessPeriod() {
|
||||
return processPeriod;
|
||||
}
|
||||
|
||||
|
||||
public void register(WsRemoteEndpointImplServer endpoint) {
|
||||
boolean result = endpoints.add(endpoint);
|
||||
if (result) {
|
||||
int newCount = count.incrementAndGet();
|
||||
if (newCount == 1) {
|
||||
BackgroundProcessManager.getInstance().register(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void unregister(WsRemoteEndpointImplServer endpoint) {
|
||||
boolean result = endpoints.remove(endpoint);
|
||||
if (result) {
|
||||
int newCount = count.decrementAndGet();
|
||||
if (newCount == 0) {
|
||||
BackgroundProcessManager.getInstance().unregister(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Note: this comparator imposes orderings that are inconsistent with equals
|
||||
*/
|
||||
private static class EndpointComparator implements
|
||||
Comparator<WsRemoteEndpointImplServer> {
|
||||
|
||||
@Override
|
||||
public int compare(WsRemoteEndpointImplServer o1,
|
||||
WsRemoteEndpointImplServer o2) {
|
||||
|
||||
long t1 = o1.getTimeoutExpiry();
|
||||
long t2 = o2.getTimeoutExpiry();
|
||||
|
||||
if (t1 < t2) {
|
||||
return -1;
|
||||
} else if (t1 == t2) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
java/org/apache/tomcat/websocket/server/package-info.java
Normal file
21
java/org/apache/tomcat/websocket/server/package-info.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.
|
||||
*/
|
||||
/**
|
||||
* Server-side specific implementation classes. These are in a separate package
|
||||
* to make packaging a pure client JAR simpler.
|
||||
*/
|
||||
package org.apache.tomcat.websocket.server;
|
||||
Reference in New Issue
Block a user