WebFlux pointing push, push full use of the flexibility websocket

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/qq_18537055/article/details/98681154

Foreword

        WebFlux itself provides support for WebSocket protocol processing corresponding to the request needs WebSocket WebSocketHandler implement the interface handler, each has an associated WebSocket WebSocketSession, handshake information contains setup request  HandshakeInfo, as well as other relevant information. By the session  receive() to receive data from the client process, the session by  send() sending data to the client method.

Examples

The following is a simple example 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())));
    }
}

 

        With After the handler, need to let WebFlux request needs to know what the handler for processing, so to create the appropriate HandlerMapping.

        When HTTP requests, we often use the simplest WebFlux handler defined manner, i.e. by annotations  @RequestMapping to a specific processing method is defined as the path of the request handler. But this annotation is used to process HTTP requests, request for WebSocket, the receipt of the request also need to upgrade the protocol process, after the handler is executed, so we can not map requests directly through the annotation defined, but you can use SimpleUrlHandlerMapping add mappings.

@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();
    }
}

This can be sent to  /echo the request to EchoHandler WebSocket process.

We have to create the corresponding WebSocketHandlerAdapter as WebSocket type of handler, so that DispatcherHandler can call our WebSocketHandler.

After completion of these three steps, if a request reaches WebSocket WebFlux, first processed by DispatcherHandler, it will find the WebSocket corresponding to the request handler in accordance with the HandlerMapping existing, then find the interface handler implements WebSocketHandler, will then be by WebSocketHandlerAdapter the completion of the call handler's.

doubt

        It is obvious from the above example, after a request is not received, which must return a message on the inside, can not give him back the message. Followed every time I add or delete a new message handling class Handler, every time you have to modify the contents of the configuration file UrlMap SimpleUrlHandlerMapping, the feeling is not very friendly. Thus modified and adapted for the following two points:

 1. Sign up with a custom annotation Handler

Can we as a registered Handler HTTP request it, but also to register a similar RequestMapping Handler comment it?

Although no official associated implementation, but we can achieve a similar own notes, called wish  WebSocketMapping:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebSocketMapping {
    String value() default "";
}

@Retention(RetentionPolicy.RUNTIME) The notes indicate that work during operation, @Target(ElementType.TYPE) indicating that the annotation on the class action.

We look at this annotation final use. The following is an example of a TimeHandler, it will be sent once every second time to the client. We annotation  @WebSocketMapping("/time") completed the registration TimeHandler tell WebFlux when there WebSocket requests to  /echo the time path, on to EchoHandler treatment:

@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())));
    }
}

And RequestMapping is not as easy?

So far, this comment has no real function, it can not automatically registered handler. Recalling Register route above us, we have created a SimpleUrlHandlerMapping, and manually add the mapping rules EchoHandler, and then returns it as a HandlerMapping Bean.

Now we have to create a special class to handle WebSocketMapping HandlerMapping annotations automatically registered handler of:

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();
    }
}

Our WebSocketMappingHandlerMapping class, in fact SimpleUrlHandlerMapping, but adds some initialization operations.

initApplicationContext() Spring method is a method ApplicationObjectSupport class for initialization behavior from the definition of class, in our WebSocketMappingHandlerMapping, the initialization work is mainly collected using  @WebSocketMapping annotations and to implement  WebSocketHandler Component interface, then register them to the inside of SimpleUrlHandlerMapping. After the work is done by routing functionality of the parent class SimpleUrlHandlerMapping realized.

Now, we just need to return WebSocketMappingHandlerMapping of Bean, will be able to automatically handle  @WebSocketMapping annotated:

@Configuration
public class WebSocketConfiguration {

	@Bean
	public HandlerMapping webSocketMapping() {
		return new WebSocketMappingHandlerMapping();
	}

	@Bean
	public WebSocketHandlerAdapter handlerAdapter() {
		return new WebSocketHandlerAdapter();
	}
}

2. WebSocket request processing Analysis

We look at the WebFlux based Reactor Netty specifically how to handle the WebSocket requests.

As mentioned above, WebSocket request after entering WebFlux, will first find the corresponding WebSocketHandler from HandlerMapping, and then carry out the actual call by the WebSocketHandlerAdapter. This no longer do more elaborate, friends who are interested can go and see WebSocketHandler, WebSocketHandlerAdapter.

3. The operation of the receive and transmit separate data

We know that HTTP protocol is a half-duplex communication, although the client and the server can send data to each other, but only within the same time by the other party to send data in one direction, and is the first client sends a request on the order before the the server returns the response data. Therefore, the HTTP server processing logic is very simple, that is, each receives a client request, it returns a response.

WebSocket is the full duplex communication, the client and server can at any time send data to the other party, it is not the "transmission request, returns a response" to the communication system. EchoHandler our example above with the remains and then the way that data is received targeted a return data, we take a look at the following two-way communication how to take advantage of WebSocket.

WebSocket process, mainly through the session to complete the operation of two data streams, one data stream client to the server, the server is a data stream to the client:

WebSocketSession method description
Flux<WebSocketMessage> receive() Receiving a data stream from a client, the connection is closed when the end of the data stream.
Mono<Void> send(Publisher<WebSocketMessage>) Transmitting data stream to the client, when the end of the data stream, the write operation to the client will come to an end, then the return  Mono<Void> will send a completion signal.

In WebSocketHandler, the final processing result should be two data streams into one signal stream, and a return  Mono<Void> is used to indicate whether the process ends.

We are two logical flow definition process:

  • For output streams: a digital server transmits to the second client;
  • For input streams: Whenever receiving the client message, it is printed to the standard output
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))));

 Both processing logic independent of each other, there is no relationship between them has, after executing the operation is a return  Mono<Void>, but how the results of these two operations into one signal stream back to the WebFlux it? We can use WebFlux of  Mono.zip() methods:

@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. The transmission data from the outside Handler

Mentioned here transmitting data from the outside, refers to the need of outside WebSocketHandler code range, in other places WebSocket to send data through the calling code.

Ideas: the definition session of  send() operation, create Flux programmatically, that the use of  Flux.create() methods to create, publish Flux data is  FluxSink exposed, and save, then where you need to send the data, calls  FluxSink<T> the  next(T data) method, the Flux subscribers data released.

create method is to create Flux advanced form of programming, which allows each generating a plurality of data, and can be produced by multiple threads.

The inside of the create method FluxSink exposed, FluxSink provided next, error, complete method. , Responsive stack API may be connected to another API to create method.

Consider such a scenario: the server and the client A to establish WebSocket connection, allows the client B to the client terminal A sends the data via HTTP.

Without regard to safety, robustness and other issues, we give a simple example.

The first is to achieve WebSocketHandler, a client sends a request to establish WebSocket, id is a need to specify the current connection parameters in the query, the server id to the bond to the corresponding value stored in the senderMap WebSocketSender in:

@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;
    }
}

Where senderMap is our own definition of Bean defined in the configuration file:

@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 Our own class is created, and the object is to save the session corresponding FluxSink WebSocket connection, to transmit data in the code range outside WebSocketHandler:

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));
    }
}

Then we achieved the Controller HTTP, the user when initiating the HTTP request, specifies the communication parameters by the query WebSocket connection id, and the data to be transmitted, and then taken out from the corresponding WebSocketSender senderMap, call its  send() method for transmitting data to the client:

@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. Test

I'll no longer write a page, directly with https://www.websocket.org/echo.html test, the results are as follows:

Thus, even if a fixed point to push the completed, the whole push, push and not write the part, as long as the come ConcurrentHashMap removed from it wants transmitted.

 

Guess you like

Origin blog.csdn.net/qq_18537055/article/details/98681154