序文
WebSocket WebSocketHandler各々は、関連のWebSocket WebSocketSessionを有する、インターフェース・ハンドラを実装する必要がある要求に対応するのWebSocketプロトコル処理のためのサポートを提供し、それ自体をWebFlux、ハンドシェーク情報は、設定要求含まれ HandshakeInfo
、ならびにその他の関連情報。セッションによって receive()
によって、クライアントプロセスからセッションデータを受信するための send()
クライアント・メソッドにデータを送信します。
例
以下は簡単な例WebSocketHandlerです。
@Component
public class EchoHandler implements WebSocketHandler {
public Mono<Void> handle(WebSocketSession session) {
return session.send(
session.receive().map(
msg -> session.textMessage("ECHO -> " + msg.getPayloadAsText())));
}
}
ハンドラの後で、WebFlux要求を処理するためのハンドラは、その適切なHandlerMappingを作成するために何を知っている必要があるようにする必要があります。
HTTPリクエストは、我々は、多くの場合、注釈によって、すなわち、最も単純なWebFluxハンドラ定義された方法を使用する場合、 @RequestMapping
具体的な処理方法に要求ハンドラのパスとして定義されます。しかし、この注釈は、HTTPリクエストを処理するために使用されるのWebSocketを要求され、要求の領収書はまた、ハンドラが実行された後、プロトコル処理をアップグレードする必要があるので、我々は定義されたアノテーションを介して直接リクエストをマッピングすることはできませんが、SimpleUrlHandlerMappingを使用することができますマッピングを追加します。
@Configuration
public class WebSocketConfiguration {
@Bean
public HandlerMapping webSocketMapping(EchoHandler echoHandler) {
final Map<String, WebSocketHandler> map = new HashMap<>(1);
map.put("/echo", echoHandler);
final SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
mapping.setUrlMap(map);
return mapping;
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
これはに送信することができ /echo
EchoHandlerのWebSocketプロセスに要求。
私たちは、DispatcherHandlerが私たちのWebSocketHandlerを呼び出すことができるように、ハンドラのWebSocketのタイプとして、対応するWebSocketHandlerAdapterを作成する必要があります。
要求のWebSocket WebFlux、第DispatcherHandlerによって処理達した場合には、これら三つのステップが完了した後、それはHandlerMapping、既存に応じて要求ハンドラに対応するのWebSocketを見つける、次いでWebSocketHandlerAdapterによりなり、インターフェースハンドラはWebSocketHandlerを実装見つけますコールハンドラの完了。
疑い
これは、要求が受信されていない後、内側にメッセージを返す必要があり、上記の例からも明らかなメッセージを彼に戻って与えることはできません。私は追加したり、新しいメッセージ処理クラスのハンドラを削除するたびに、その後、あなたは設定ファイルUrlMap SimpleUrlHandlerMappingの内容を変更する必要があるたびに、気持ちは非常に友好的ではありません。このように修正し、次の二点に適合:
1.カスタム注釈ハンドラを登録
登録されたハンドラHTTPなど、私たちはそれを要求することができますが、また、それをコメントに類似RequestMappingハンドラを登録するには?
公式関連する実装が、私たちは、と呼ばれる同様の願いを自分のノートを達成することができます WebSocketMapping
:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebSocketMapping {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
ノートには、運転中にその作業を示す@Target(ElementType.TYPE)
集団訴訟上の注釈ことを示しています。
私たちは、この注釈最終用途を見てください。以下は、TimeHandlerの一例であり、それはクライアントに1秒に1回の時間送信されます。私たちは、注釈 @WebSocketMapping("/time")
への要求があったときのWebSocket WebFluxを伝えるTimeHandler登録完了 /echo
EchoHandler処理に上、時間のパスを:
@Component
@WebSocketMapping("/echo")
public class EchoHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(final WebSocketSession session) {
return session.send(
session.receive()
.map(msg -> session.textMessage(
"服务端返回:小明, -> " + msg.getPayloadAsText())));
}
}
そしてRequestMappingはほど簡単ではないのですか?
これまでのところ、このコメントは、本当の機能を持っていない、それが自動的にハンドラを登録することはできません。私たちの上に登録ルートを想起し、我々はSimpleUrlHandlerMappingを作成している、と手動EchoHandlerマッピングルールを追加し、HandlerMappingビーンとして返します。
今、私たちは、自動的にハンドラを登録WebSocketMapping HandlerMapping注釈を処理するための特別なクラスを作成する必要があります。
public class WebSocketMappingHandlerMapping extends SimpleUrlHandlerMapping{
private Map<String, WebSocketHandler> handlerMap = new LinkedHashMap<>();
/**
* Register WebSocket handlers annotated by @WebSocketMapping
* @throws BeansException
*/
@Override
public void initApplicationContext() throws BeansException {
Map<String, Object> beanMap = obtainApplicationContext()
.getBeansWithAnnotation(WebSocketMapping.class);
beanMap.values().forEach(bean -> {
if (!(bean instanceof WebSocketHandler)) {
throw new RuntimeException(
String.format("Controller [%s] doesn't implement WebSocketHandler interface.",
bean.getClass().getName()));
}
WebSocketMapping annotation = AnnotationUtils.getAnnotation(
bean.getClass(), WebSocketMapping.class);
//webSocketMapping 映射到管理中
handlerMap.put(Objects.requireNonNull(annotation).value(),(WebSocketHandler) bean);
});
super.setOrder(Ordered.HIGHEST_PRECEDENCE);
super.setUrlMap(handlerMap);
super.initApplicationContext();
}
}
私たちのWebSocketMappingHandlerMappingクラスは、実際にはSimpleUrlHandlerMappingが、いくつかの初期化操作を追加します。
initApplicationContext()
スプリング方法は、私たちのWebSocketMappingHandlerMappingで、初期化作業が主に使用して収集され、クラスの定義から初期化動作のための方法ApplicationObjectSupportクラスで @WebSocketMapping
注釈をして実装するため WebSocketHandler
、その後、コンポーネント・インタフェースをSimpleUrlHandlerMappingの内側にそれらを登録します。作業は、親クラスの機能をルーティングすることによって行われた後に実現SimpleUrlHandlerMapping。
今、私たちは自動的に処理することができるようになります、ビーンのWebSocketMappingHandlerMappingを返す必要が @WebSocketMapping
注釈付き:
@Configuration
public class WebSocketConfiguration {
@Bean
public HandlerMapping webSocketMapping() {
return new WebSocketMappingHandlerMapping();
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
2.のWebSocket要求処理解析
私たちは、WebSocketの要求を処理する方法を、具体的WebFlux基づき原子炉ネッティーを見てください。
上述したように、WebSocketの要求はWebFluxに入った後、最初のHandlerMappingから対応WebSocketHandlerを見つけ、その後WebSocketHandlerAdapterによる実際の呼び出しを行うであろう。これはもはや、行くとWebSocketHandler、WebSocketHandlerAdapterを見ることができます興味を持っている友人をより精巧行いません。
3の動作は、受信して別のデータを送信します
私たちは、クライアントが、サーバーが一方向にデータを送信するために他の当事者によって、同じ時間内で相互にデータを送ることができますが、HTTPプロトコルは半二重通信であることを知って、そして最初のクライアントは、前の順番にリクエストを送信していますサーバが応答データを返します。そのため、HTTPサーバの処理ロジックは、つまり、各クライアントの要求を受け、非常に簡単です、それは応答を返します。
WebSocketクライアントとサーバーは、いつでも相手にデータを送信することができ、全二重通信では、通信システムに「応答を返し、送信要求」ではありません。EchoHandlerデータが受信される方法は、戻り値のデータを対象とその後、遺体として上記の例に、我々はどのようにのWebSocketを活用するために、次の双方向通信を見てみましょう。
主にセッションでのWebSocketプロセスは、2つのデータストリームの操作を完了するために、サーバーへの1つのデータストリームのクライアントは、サーバがクライアントにデータ・ストリームは次のとおりです。
WebSocketSession 方法 |
説明 |
---|---|
Flux<WebSocketMessage> receive() |
クライアントからのデータストリームを受信すると、接続が閉じているときに、データストリームの終わり。 |
Mono<Void> send(Publisher<WebSocketMessage>) |
データストリームの最後に、クライアントへの書き込み動作は、その後の復帰が、最後に来るとき、クライアントにデータストリームを送信 Mono<Void> 完了信号を送信します。 |
WebSocketHandlerでは、最終的な処理結果は、一つの信号ストリームに2つのデータストリームであるべきであり、復帰は Mono<Void>
処理が終了したか否かを示すために使用されます。
我々は2つの論理フロー定義プロセスです:
- 出力ストリームのためのデジタルサーバは、第2のクライアントに送信します。
- 入力ストリームのためのクライアント・メッセージを受信するたびに、それは、標準出力に出力され
Mono<Void> input = session.receive()
.map(WebSocketMessage::getPayloadAsText)
.map(msg -> id + ": " + msg)
.doOnNext(System.out::println).then();
Mono<Void> output = session.send(Flux.create(sink ->
senderMap.put(id, new WebSocketSender(session, sink))));
互いに独立した処理ロジックの両方が、それらの間に関係は、操作を実行した後の戻りがない有している Mono<Void>
が、どのように1つの信号に、これら二つの動作の結果はWebFluxそれに戻っストリーミング?私たちは、のWebFluxを使用することができます Mono.zip()
方法:
@Component
@WebSocketMapping("/echo")
public class EchoHandler implements WebSocketHandler {
@Autowired
private ConcurrentHashMap<String, WebSocketSender> senderMap;
@Override
public Mono<Void> handle(WebSocketSession session) {
Mono<Void> input = session.receive()
.map(WebSocketMessage::getPayloadAsText).map(msg -> id + ": " + msg)
.doOnNext(System.out::println).then();
Mono<Void> output = session.send(Flux.create(sink ->
senderMap.put(id, new WebSocketSender(session, sink))));
/**
* Mono.zip() 会将多个 Mono 合并为一个新的 Mono,
* 任何一个 Mono 产生 error 或 complete 都会导致合并后的 Mono
* 也随之产生 error 或 complete,此时其它的 Mono 则会被执行取消操作。
*/
return Mono.zip(input, output).then();
}
}
4.外部ハンドラからの送信データ
外部からのデータを送信し、ここで述べたように、呼び出し元のコードを介してデータを送信するためのWebSocket他の場所で、外WebSocketHandlerコード範囲の必要性を指します。
アイデア:の定義セッション send()
操作、プログラム的にフラックスを作成し、使用することを Flux.create()
メソッドが作成する、フラックスデータがされて公開 FluxSink
さらされ、あなたがデータを送信するために必要な場所、その後、保存、呼び出す メソッドを、フラックスの契約者発表したデータ。FluxSink<T>
next(T data)
作成方法は、それぞれが複数のデータを生成することができ、複数のスレッドによって製造することができるプログラミングのフラックス高度なフォームを作成することです。
作成方法の内部が露出FluxSink、FluxSinkは次、エラー、完全な方法を提供しました。、応答スタックAPIは、メソッドを作成するために、別のAPIに接続することができます。
このようなシナリオを考えてみましょう:サーバーとのWebSocket接続を確立するためのクライアントA、HTTPを介してデータを送信するクライアント端末AへのクライアントBができます。
安全性、堅牢性やその他の問題を考慮せずに、我々は簡単な例を与えます。
最初はWebSocketHandlerを達成することである、クライアントがWebSocketの確立要求を送信し、IDは、クエリで、現在の接続パラメータを指定する必要がある、senderMap WebSocketSender内に保存されている対応する値との結合にサーバーIDは次のとおりです。
@Component
@WebSocketMapping("/echo")
public class EchoHandler implements WebSocketHandler {
@Autowired
private ConcurrentHashMap<String, WebSocketSender> senderMap;
@Override
public Mono<Void> handle(WebSocketSession session) {
// TODO Auto-generated method stub
HandshakeInfo handshakeInfo = session.getHandshakeInfo();
Map<String, String> queryMap = getQueryMap(handshakeInfo.getUri().getQuery());
String id = queryMap.getOrDefault("id", "defaultId");
Mono<Void> input = session.receive().map(WebSocketMessage::getPayloadAsText).map(msg -> id + ": " + msg)
.doOnNext(System.out::println).then();
Mono<Void> output = session.send(Flux.create(sink -> senderMap.put(id, new WebSocketSender(session, sink))));
/**
* Mono.zip() 会将多个 Mono 合并为一个新的 Mono,任何一个 Mono 产生 error 或 complete 都会导致合并后的 Mono
* 也随之产生 error 或 complete,此时其它的 Mono 则会被执行取消操作。
*/
return Mono.zip(input, output).then();
}
//用于获取url参数
private Map<String, String> getQueryMap(String queryStr) {
Map<String, String> queryMap = new HashMap<>();
if (!StringUtils.isEmpty(queryStr)) {
String[] queryParam = queryStr.split("&");
Arrays.stream(queryParam).forEach(s -> {
String[] kv = s.split("=", 2);
String value = kv.length == 2 ? kv[1] : "";
queryMap.put(kv[0], value);
});
}
return queryMap;
}
}
どこsenderMap
豆の私たち自身の定義は、構成ファイルで定義されています。
@Configuration
public class WebSocketConfiguration {
@Bean
public HandlerMapping webSocketMapping() {
return new WebSocketMappingHandlerMapping();
}
@Bean
public ConcurrentHashMap<String, WebSocketSender> senderMap() {
return new ConcurrentHashMap<String, WebSocketSender>();
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
WebSocketSender
当社独自のクラスが作成され、オブジェクトは、コード範囲外のWebSocketHandlerでデータを送信するために、FluxSinkのWebSocket接続に対応するセッションを保存することです。
public class WebSocketSender {
private WebSocketSession session;
private FluxSink<WebSocketMessage> sink;
public WebSocketSender(WebSocketSession session, FluxSink<WebSocketMessage> sink) {
this.session = session;
this.sink = sink;
}
public void sendData(String data) {
sink.next(session.textMessage(data));
}
}
その後、我々は、その呼び出し、HTTP要求を開始すると、コントローラHTTP、ユーザは、クエリ用WebSocket接続IDにより通信パラメータを指定達成し、送信すべきデータ、及び、対応するWebSocketSender senderMapから取り出した send()
データをクライアントに送信するための方法:
@RestController
@RequestMapping("/msg")
public class MsgController {
@Autowired
private ConcurrentHashMap<String, WebSocketSender> senderMap;
@RequestMapping("/send")
public String sendMessage(@RequestParam String id, @RequestParam String data) {
WebSocketSender sender = senderMap.get(id);
if (sender != null) {
sender.sendData(data);
return String.format("Message '%s' sent to connection: %s.", data, id);
} else {
return String.format("Connection of id '%s' doesn't exist", id);
}
}
}
5.テスト
私はもはや直接に、ページを書きませんよhttps://www.websocket.org/echo.htmlを次のようにテスト、結果は以下のとおりです。
このように、固定点は完了し、全体のプッシュ、プッシュプッシュしないようにしても、それが送信望んでいるから来るのConcurrentHashMapを削除する限り、一部を書きます。