简体   繁体   English

在服务REST中注入WebSocket

[英]Inject WebSocket in service REST

I need to send a message a client, after the creation of item. 创建项目后,我需要向客户发送消息。 The item is created an ApiRest. 该项目将创建一个ApiRest。 Then I created my WebSocket with @ApplicationScope and I Injected in serviceREST with @Inject. 然后,我使用@ApplicationScope创建了WebSocket,并使用@Inject注入了serviceREST。 The Problem is when the webSocket was initialized, in my serviceRest this webSocket's session still is null. 问题是当webSocket初始化时,在我的serviceRest中,此webSocket的会话仍然为null。 How I can use the web SOcket in my apirest? 如何在apirest中使用网络套接字?

@Path("citas")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class citaResource {

    @Inject
    com.softcase.citasmanager.websocket.ws websocket;

    @GET
    @Path("cita")
    @Produces("application/json")
    public Response cita() {
       websocket.onMessage("Your Item was created");//Session of webSocket is null
        return Response.ok("ItemCreated", MediaType.APPLICATION_JSON).build();
    }

}

@ApplicationScope
@ServerEndpoint("/item")
public class ws{   

    private Session session;

    @OnOpen
    public void open(Session session) {
        this.session = session;
    }

    @OnMessage
    public void onMessage(String message) {
            this.session.getBasicRemote().sendText(message);
    }

A little context 一点背景

Instances : there is a unique Session instance per client-server pair ie one instance of Session is created for a each client which connects to the WebSocket server endpoint. 实例 :每个客户端/服务器对都有一个唯一的Session实例,即,为连接到WebSocket服务器端点的每个客户端创建一个Session实例。 In short, the number of unique Session instances is equal to number of connected clients 简而言之,唯一会话实例的数量等于连接的客户端的数量

Source: https://abhirockzz.gitbooks.io/java-websocket-api-handbook/content/lifecycle_and_concurrency_semantics.html 来源: https//abhirockzz.gitbooks.io/java-websocket-api-handbook/content/lifecycle_and_concurrency_semantics.html

For more details: https://tyrus-project.github.io/documentation/1.13.1/index/lifecycle.html 有关更多详细信息: https : //tyrus-project.github.io/documentation/1.13.1/index/lifecycle.html

A suggestion is to use a static variables like 建议使用static变量,例如

// @ApplicationScope
@ServerEndpoint("/item")
public class ws{   
    // something like
    private static final Set<javax.websocket.Session> ALL_SESSIONS = new HashSet<>();

    // ...
}

An example can be found here . 一个例子可以在这里找到。 It's an option but I do not think it solves your injection issue. 这是一个选择,但我认为它不能解决您的注射问题。

Another option is to leverage the javax.websocket.Session#getOpenedSessions() method such as this chat example . 另一个选择是利用javax.websocket.Session#getOpenedSessions()方法,例如此聊天示例 But once again, it does not solve the injection issue. 但是再次,它不能解决注入问题。

Your example 你的例子

You are using both websocket and REST. 您正在同时使用websocket和REST。 As I understand, the flow is: 据我了解,流程为:

  1. User A, B, C are connected 用户A,B,C已连接
  2. User A submits a request to citas/cita and receives the REST response 用户A向citas/cita提交请求并收到REST响应
  3. At the same time, A, B, C receive a websocket notification 同时,A,B,C收到网络套接字通知

So, as you wrote, on one hand, you have 因此,正如您所写,一方面

@Path("citas")
// ...
public class CitaResource{
    // ...
}

and

// @ApplicationScope -> commented as irrelevant in your situation
@ServerEndpoint("/item")
public class ws{   
    // ...
}

In the example, there is one instance of CitaResource when user A made the request and three instances of ws as A, B, C are connected. 在该示例中,当用户A发出请求时,存在一个CitaResource实例,并且连接了ws三个实例(如A,B,C)。 However, you were right about the injection: you need to have something injected in CitaResource but you need a bean that is always available and as you noticed, websocket instances are not a good option and which session the container must inject? 但是,您对注入是正确的:您需要在CitaResource注入一些东西,但需要一个始终可用的bean,并且您注意到,websocket实例不是一个好选择,容器必须注入哪个会话?

A websocket sessions handler Websocket会话处理程序

The solution is to use an application scoped bean to handle all the existing sessions. 解决方案是使用应用程序范围的Bean处理所有现有会话。 I got it from Oracle tutorial . 我从Oracle教程中获得了它。 It goes like this: 它是这样的:

// com.softcase.citasmanager.websocket.SessionHandler
@ApplicatedScoped
@Named // optional
public class MySessionHandler{

    private final Set<Session> ALL_SESSIONS;
    // or use a map if you need to identify the
    // the session by a key. This example uses Set
    // private final Map<String, Session> ALL_SESSIONS;

    public MySessionHandler(){
        ALL_SESSIONS = new HashSet<>();
    }

    // manage sessions
    public void addSession(Session session){
        this.ALL_SESSIONS.add(session);
    }

    public void removeSession(Session session){
        this.ALL_SESSIONS.remove(session);
    }

    // send messages to all instances:
    public void sendMessage(String message){
        this.ALL_SESSIONS.stream()
            // optional
            .filter(s -> s.isOpen())
            // or whatever method you want to send a message
            .forEach( s -> s.getBasicRemote().sendText(message);
    }
    // or if you want to target a specific session
    // hence my questions in comments
    public void sendMessage(String message, String target){
        this.ALL_SESSIONS..stream()
            // identity the target session
            .filter(s -> s.equals(target))
            // optional
            .filter(s -> s.isOpen())
            .forEach( s -> s.getBasicRemote().sendText(message);
    }

}

Note: 注意:

  • I optionally check that the stored session is still opened. 我可以选择检查存储的会话是否仍然打开。 isOpen() is not mandatory but it might avoid some errors isOpen()不是强制性的,但可以避免一些错误
  • Think the session handler as the "captain": it knows everything about the websocket sessions whereas the sessions themselves do not know about each other. 将会话处理程序视为“队长”:它了解有关Websocket会话的所有信息,而会话本身并不相互了解。

However, you need to adapt your endpoint to make the session handler efficient: 但是,您需要调整端点以使会话处理程序高效:

// com.softcase.citasmanager.websocket.WsCita
@ServerEndpoint
public class WsCita{

    // there is no need to declare the session as attribute
    // private Session session;

    // ApplicatedScoped so always defined
    @Inject
    private MySessionHandler handler;

    @OnOpen
    public void open(Session session){
        handler.addSession(session);    // "Aye cap'tain, reporting from duty!"
        // your stuff
    }

    @OnClose
    public void close(Session session, CloseReason closeReason){
        handler.removeSession(session); // "Cya cap'tain, that's all for today!"
        // your stuff
    }

    // your OnMessage and other stuff here
}

Now we have set our websocket architecture, what now? 现在我们已经设置了websocket架构,现在呢?

  1. You have one instance of WsCita per client. 每个客户端有一个WsCita实例。 At any time, there might be zero, one or more instances. 在任何时候,可能有零个,一个或多个实例。
  2. MySessionHandler knows this information and is @ApplicatedScoped so it is safe to inject it MySessionHandler知道此信息,并且已@ApplicatedScoped因此可以安全地注入它

The REST endpoint then changes to: REST端点然后更改为:

@Path("citas")
// ...
public class citaResource {

    @Inject
    com.softcase.citasmanager.websocket.SessionHandler handler;

    @GET
    // ...
    public Response cita() {
        // REST processing
        // ...

        // Websocket processing:
        // - Handler is always here for you
        // - Handler knows which websocket sessions to send the message to.
        //   The RestController is not aware of the recipients
        handler.sendMessage("Your Item was created");
    }

}

Please note that I put the websocket processing after the REST processing as you may not always send the message (eg creation or whatever exception). 请注意,我将websocket处理放在REST处理之后,因为您可能并不总是发送消息(例如,创建或任何异常)。

Misc 杂项

Unrelated to your questions but I have some comments about your code: 与您的问题无关,但我对您的代码有一些评论:

  • Classes name are CamelCase starting with capitalized letter per Oracle recommendation 类名称为CamelCase, 每个Oracle建议以大写字母开头
  • Avoid generic name for your classes such as Ws . 避免为类使用通用名称,例如Ws I renamed it WsCita for the example 我将示例重命名为WsCita

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM