In case you have a websocket server, you need to pass the received websocket message to a backend service. But unfortunately the backend service is a restful based service, and only the rest API can be invoked. Hence you have to terminate the websocket firstly in websocket server side and pass the request to the backend server. At this point, this spring boot starter is what you may need.
You could sonsider the following things:
- This boot starter project is based on Spring boot2 (2.1.1.RELEASE and later) with webflux
- The websocket server should terminate the websocket message and invoke a backend service to get the result to client, regardless of the backend service is a restful based or message based service
- The backend service could invoke the actuator API(Rest API) of upstream websocket server, to notify a websocket client.
- The websocket server shall support sending or receiving PING/PONG frame in order to keep the connection alive
- You may need to know how many connections established, the details of the websocket session and IP address for a specific websocket client
If all of these features are what you need, you can consider using this non-intrusive and spring boot2 based dependency
With this start, all you need to do is:
- You need to define a websocket request and a class to process this request working as a consumer
Thus you can conveniently get the client request, and the starter can ensure the request is passed into your consumer class. Very convenient, right?
For gradle, you can add the dependency like this:
compile "zjtech:websocket-termination-spring-boot-starter:0.1.1"
websocket:
termination:
scan:
api-package: sample.api
api-package
this parameter specifies what package the webscoket request classes are placed in
websocket:
termination:
enabled: true
endpoint: "/ws"
order: -1
ping:
enabled: true
interval: 10
retries: 3
supress-log: true
scan:
api-package: sample.api
Configuration Item | Default Value | Description |
---|---|---|
websocket.termination.enabled | true | Enable WebSocket termination |
websocket.termination.endpoint | /ws | The actuator endpoint. By default, the value is ws://IP:Port/ws |
websocket.termination.order | -1 | The sort order for websocket handler mapping |
websocket.termination.ping.enabled | true | Enable PING/PONG |
websocket.termination.ping.interval | 10 | In seconds,to specify how oftern the server should send a PING frame to client |
websocket.termination.ping.retries | 3 | The retry count while server cannot get response from client ,and finally close the session |
websocket.termination.ping.supress-log | true | Whether print the PING/PONG log |
websocket.termination.ping.scan.api-package | The package that webscoket request classes are placed in. NA, but the developer should specify this package. |
A demo project is provided you may be interested in, you can clone this project to learn how to implement a websocket server, and how to terminate the websocket. https://github.com/zjtech/websocket-termination-demo
This class should extend zjtech.websocket.termination.api.Request
@Getter
@Setter
@WebSocketCommand("CREATE_POLICY")
public class CreatePolicyRequest implements Request {
private String name;
private String description;
}
@Slf4j
@Component
@MessageConsumer
public class RestMessageForwarder {
@Consume("CREATE_POLICY")
public void createPolicy(ConsumerContext<CreatePolicyRequest> ctx) {
//get the payload
CreatePolicyRequest request= ctx.getPayload();
// you can forward the payload to backend service
log.info("forward a CreatePolicyRequest to backend rest service.");
//then you construct a payload returned form backend service like this:
CreatePolicyResponse.Payload payload = new CreatePolicyResponse.Payload();
payload.setCreater("admin");
payload.setCreateTime(
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
payload.setDescription("a policy created in backend service");
payload.setId(11223344L);
payload.setName("policy1");
payload.setValidPolicy(true);
// and then construct and send a response to client
CreatePolicyResponse response = new CreatePolicyResponse();
response.setErrorCode(201);
response.setErrorMessage("A policy is created successfully.");
response.setCommand("CREATE_POLICY_RESPONSE");
response.setPayload(payload);
//send now
ctx.getSessionHandler().sendJsonString(response);
}
}
Here you can customize a response class, or you can directly implement the zjtech.websocket.termination.api.Response
interface.
The CreatePolicyResponse class used above is a customized class to implement the Response interface, meanwhile you can
add corresponding fileds as your business needs.
@Getter
@Setter
public class CreatePolicyResponse implements BaseResponse {
private int errorCode = 200;
private String errorMessage;
private String command;
private Payload payload = new Payload();
@Getter
@Setter
public static class Payload {
private long id;
private String name;
private String description;
private String createTime;
private String creater;
private boolean validPolicy;
}
}
After completed the above steps, you need to create a spring boot application class, once the server is started, the websocket function
should be enabled in together. And the CreatePolicyRequest would be processed by RestMessageForwarder class.
Note: The client should send the RequestWrapper class instead of CreatePolicyRequest
the format should looks like this:
{
"command": "CREATE_POLICY",
"payload": {
"name": "policy1" // payload stands for a Request, here it is CreatePolicyRequest
}
}
Which means the request client sent is to create a policy and the payload will be converted into CreatePolicyRequest.
- The following is a sample of WebSocket Client:
public class JavaClient {
@Test
public void runClient() {
ObjectMapper objectMapper = new ObjectMapper();
CreatePolicyRequest request = new CreatePolicyRequest();
request.setName("policy");
RequestWrapper<CreatePolicyRequest> requestWrapper = new RequestWrapper<>();
requestWrapper.setPayload(request);
requestWrapper.setCommand("CREATE_POLICY");
// requestWrapper.setHeader();
WebSocketClient client = new ReactorNettyWebSocketClient();
client
.execute(
URI.create("ws://localhost:5809/ws"),
session -> {
try {
return session
.send(
Mono.just(
session.textMessage(objectMapper.writeValueAsString(requestWrapper))))
.thenMany(session.receive().map(WebSocketMessage::getPayloadAsText).log())
.then();
} catch (JsonProcessingException e) {
e.printStackTrace();
return Mono.empty();
}
})
.block(Duration.ofSeconds(10L));
}
}
The server log will indicates the following line after the CREATE_POLICY request is sent:
11:16:24.492 [reactor-http-epoll-4] INFO reactor.Flux.Map.1 - onNext({"errorCode":201,"errorMessage":"A policy is created successfully.","command":"CREATE_POLICY_RESPONSE","payload":{"id":11223344,"name":"policy1","description":"a policy created in backend service","createTime":"2019-02-28 11:16:24","creater":"admin","validPolicy":true}})
- The web browser's websocket client
Here a chrome plugin "Simple Web Socket Client" is used to illustrate the client can send a websocket message to server side, and then the message will be passed into consumer class, and finally print the result in GUI.
Other features you may be interested in
- Actuator Endpoint
After the dependencyorg.springframework.boot:spring-boot-starter-actuator
is added, there're two actuator endpoints are enabled.
End Point | Description |
---|---|
/actuator/websocketInfo | GET /actuator/websocketInfo Show the internal mapping relationship and connected client info activeSessionCount: Current active session count activeSessions: list the sessionId and corresponding client IP address for each session mapping: Show the mapping relationship from Request to MessageConsumer |
/actuator/websocketOperation/{sessionId} | POST /actuator/websocketOperation/{sessionId}?message=a%20message : Call this API to send message to websocket client DELETE /actuator/websocketOperation/{sessionId} : Close and delete the client session by session id |