/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed 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.springframework.web.reactive.socket.adapter;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.reactivex.netty.protocol.http.ws.WebSocketConnection;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Observable;
import rx.RxReactiveStreams;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.web.reactive.socket.CloseStatus;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
/**
* Spring {@link WebSocketSession} implementation that adapts to the RxNetty
* {@link io.reactivex.netty.protocol.http.ws.WebSocketConnection}.
* For internal use within the framework.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class RxNettyWebSocketSession extends NettyWebSocketSessionSupport<WebSocketConnection> {
/**
* The {@code ChannelHandler} name to use when inserting a
* {@link WebSocketFrameAggregator} in the channel pipeline.
*/
public static final String FRAME_AGGREGATOR_NAME = "websocket-frame-aggregator";
public RxNettyWebSocketSession(WebSocketConnection conn, HandshakeInfo info, NettyDataBufferFactory factory) {
super(conn, info, factory);
}
/**
* Insert an {@link WebSocketFrameAggregator} after the
* {@code WebSocketFrameDecoder} for receiving full messages.
* @param channel the channel for the session
* @param frameDecoderName the name of the WebSocketFrame decoder
*/
public RxNettyWebSocketSession aggregateFrames(Channel channel, String frameDecoderName) {
ChannelPipeline pipeline = channel.pipeline();
if (pipeline.context(FRAME_AGGREGATOR_NAME) != null) {
return this;
}
ChannelHandlerContext frameDecoder = pipeline.context(frameDecoderName);
if (frameDecoder == null) {
throw new IllegalArgumentException("WebSocketFrameDecoder not found: " + frameDecoderName);
}
ChannelHandler frameAggregator = new WebSocketFrameAggregator(DEFAULT_FRAME_MAX_SIZE);
pipeline.addAfter(frameDecoder.name(), FRAME_AGGREGATOR_NAME, frameAggregator);
return this;
}
@Override
public Flux<WebSocketMessage> receive() {
Observable<WebSocketMessage> messages = getDelegate()
.getInput()
.filter(frame -> !(frame instanceof CloseWebSocketFrame))
.map(super::toMessage);
return Flux.from(RxReactiveStreams.toPublisher(messages));
}
@Override
public Mono<Void> send(Publisher<WebSocketMessage> messages) {
Observable<WebSocketFrame> frames = RxReactiveStreams.toObservable(messages).map(this::toFrame);
Observable<Void> completion = getDelegate().writeAndFlushOnEach(frames);
return Mono.from(RxReactiveStreams.toPublisher(completion));
}
@Override
public Mono<Void> close(CloseStatus status) {
Observable<Void> completion = getDelegate().close();
return Mono.from(RxReactiveStreams.toPublisher(completion));
}
}