14.2.1 Làm việc với mô hình yêu cầu - phản hồi (request-response)
Việc tạo một server RSocket trong Spring đơn giản như việc tạo một lớp controller, tương tự như khi bạn tạo ứng dụng web hoặc dịch vụ REST. Controller sau đây là một ví dụ về dịch vụ RSocket xử lý lời chào từ client và phản hồi lại bằng một lời chào khác.
Liệt kê 14.2 Một server RSocket đơn giản sử dụng mô hình request-response
package rsocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@Controller
@Slf4j
public class GreetingController{
@MessageMapping("greeting")
public Mono<String> handleGreeting(Mono<String> greetingMono) {
return greetingMono
.doOnNext(greeting ->
log.info("Received a greeting: " + greeting))
.map(greeting -> "Hello back to you!");
}
}Như bạn có thể thấy, điểm khác biệt chính giữa một web controller và một RSocket controller là thay vì xử lý các HTTP request theo đường dẫn (sử dụng @GetMapping hoặc @PostMapping), một RSocket controller xử lý các message đến trên một route cụ thể với annotation @MessageMapping. Trong ví dụ này, phương thức handleGreeting() được gọi khi client gửi một yêu cầu đến route có tên "greeting".
Phương thức handleGreeting() nhận payload của message từ client dưới dạng tham số Mono<String>. Trong trường hợp này, lời chào đủ đơn giản để biểu diễn bằng một chuỗi String, nhưng payload nhận vào có thể là một kiểu dữ liệu phức tạp hơn nếu cần. Khi nhận được Mono<String>, phương thức chỉ ghi log rằng nó đã nhận lời chào, sau đó sử dụng hàm map() trên Mono để tạo một Mono<String> mới mang phản hồi được trả về cho client.
Mặc dù các RSocket controller không xử lý HTTP request theo đường dẫn, nhưng tên route có thể được tạo sao cho trông giống như một đường dẫn, bao gồm cả các biến placeholder có thể được truyền vào phương thức xử lý. Ví dụ, hãy xem xét một biến thể của phương thức handleGreeting() sau:
@MessageMapping("greeting/{name}")
public Mono<String> handleGreeting(
@DestinationVariable("name") String name,
Mono<String> greetingMono) {
return greetingMono
.doOnNext(greeting ->
log.info("Received a greeting from " + name + " : " + greeting))
.map(greeting -> "Hello to you, too, " + name);
}Trong trường hợp này, route được chỉ định trong @MessageMapping chứa một biến placeholder có tên "name". Biến này được biểu diễn bằng dấu ngoặc nhọn, tương tự như các biến trong đường dẫn trong controller Spring MVC. Tương tự, phương thức sẽ chấp nhận một tham số kiểu String được đánh dấu với annotation @DestinationVariable để tham chiếu đến biến placeholder. Cũng như annotation @PathVariable trong Spring MVC, @DestinationVariable được sử dụng để trích xuất giá trị được chỉ định trong placeholder của route và truyền vào phương thức xử lý. Bên trong phiên bản mới của handleGreeting(), tên được chỉ định trong route sẽ được dùng để trả về lời chào mang tính cá nhân hóa hơn cho client.
Còn một điều bạn cần nhớ khi tạo server RSocket: chỉ định cổng để lắng nghe. Mặc định, các dịch vụ RSocket hoạt động dựa trên TCP và chạy như một server riêng lắng nghe trên một cổng cụ thể. Thuộc tính cấu hình spring.rsocket.server.port sẽ thiết lập cổng cho server RSocket, như sau:
spring:
rsocket:
server:
port: 7000Thuộc tính spring.rsocket.server.port có hai mục đích: kích hoạt server và chỉ định cổng mà server sẽ lắng nghe. Nếu không thiết lập thuộc tính này, Spring sẽ giả định rằng ứng dụng của bạn chỉ hoạt động như một client và sẽ không có cổng server nào được mở. Trong trường hợp này, chúng ta đang khởi động một server, do đó việc thiết lập spring.rsocket.server.port như ví dụ trên sẽ tạo một server lắng nghe trên cổng 7000.
Bây giờ, hãy chuyển sự chú ý sang phía client RSocket. Trong Spring, client RSocket được triển khai bằng RSocketRequester. Cấu hình tự động của Spring Boot cho RSocket sẽ tự động tạo một bean kiểu RSocketRequester.Builder trong context của ứng dụng Spring. Bạn có thể inject bean builder này vào bất kỳ bean nào khác để tạo một thể hiện của RSocketRequester.
Ví dụ, sau đây là phần đầu của một bean ApplicationRunner được inject với RSocketRequester.Builder:
package rsocket;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.rsocket.RSocketRequester;
@Configuration
@Slf4j
public class RSocketClientConfiguration {
@Bean
public ApplicationRunner sender(RSocketRequester.Builder requesterBuilder) {
return args -> {
RSocketRequester tcp = requesterBuilder.tcp("localhost", 7000);
// ... send messages with RSocketRequester ...
};
}
}Trong trường hợp này, builder được sử dụng để tạo một RSocketRequester lắng nghe tại localhost, cổng 7000. RSocketRequester được tạo ra sau đó có thể được dùng để gửi message đến server.
Trong mô hình request-response, request cần (ít nhất) chỉ định route và payload dữ liệu. Như bạn còn nhớ, controller phía server của chúng ta xử lý request cho route "greeting" và mong đợi một input kiểu String. Nó cũng trả về một output kiểu String. Đoạn mã client đầy đủ dưới đây cho thấy cách gửi lời chào đến server và xử lý phản hồi.
Liệt kê 14.3 Gửi một yêu cầu từ phía client
RSocketRequester tcp = requesterBuilder.tcp("localhost", 7000);
// ... send messages with RSocketRequester ...
tcp
.route("greeting")
.data("Hello RSocket!")
.retrieveMono(String.class)
.subscribe(response -> log.info("Got a response: " + response));Yêu cầu này sẽ gửi lời chào "Hello RSocket!" đến server theo route "greeting". Lưu ý rằng nó cũng mong đợi một Mono<String> được trả về, như được chỉ định trong lời gọi đến retrieveMono(). Phương thức subscribe() đăng ký với Mono được trả về và xử lý payload bằng cách ghi log giá trị.
Giả sử bây giờ bạn muốn gửi lời chào đến một route khác có chấp nhận giá trị biến trong route. Mã phía client hoạt động gần như giống nhau, ngoại trừ việc bạn cần bao gồm placeholder biến trong giá trị truyền vào route(), cùng với giá trị thực tế như sau:
String who = "Craig";
tcp
.route("greeting/{name}", who)
.data("Hello RSocket!")
.retrieveMono(String.class)
.subscribe(response -> log.info("Got a response: " + response));Ở đây, message sẽ được gửi đến route "greeting/Craig", sẽ được xử lý bởi phương thức controller mà @MessageMapping đã chỉ định route "greeting/{name}". Mặc dù bạn cũng có thể hardcode tên vào trong route hoặc dùng nối chuỗi String để tạo tên route, nhưng việc sử dụng placeholder trong client khiến việc gán giá trị trở nên dễ dàng hơn, tránh sự rắc rối của nối chuỗi String.
Mặc dù mô hình request-response có thể là mô hình dễ hiểu nhất trong các mô hình giao tiếp của RSocket, nhưng đó chỉ là bước khởi đầu. Hãy xem cách xử lý các request có thể trả về nhiều phản hồi bằng mô hình request-stream.
