source: src/main/java/geniusweb/runserver/WebSocketProtToPartyConn.java@ 8

Last change on this file since 8 was 8, checked in by bart, 5 years ago

Update 28 jan 2020

File size: 7.5 KB
Line 
1package geniusweb.runserver;
2
3import java.io.BufferedReader;
4import java.io.IOException;
5import java.io.InputStreamReader;
6import java.net.HttpURLConnection;
7import java.net.SocketException;
8import java.net.URI;
9import java.net.URISyntaxException;
10import java.util.logging.Level;
11
12import javax.websocket.ClientEndpoint;
13import javax.websocket.CloseReason;
14import javax.websocket.ContainerProvider;
15import javax.websocket.DeploymentException;
16import javax.websocket.OnClose;
17import javax.websocket.OnError;
18import javax.websocket.OnMessage;
19import javax.websocket.OnOpen;
20import javax.websocket.Session;
21import javax.websocket.WebSocketContainer;
22
23import com.fasterxml.jackson.databind.ObjectMapper;
24
25import geniusweb.actions.Action;
26import geniusweb.actions.PartyId;
27import geniusweb.party.inform.Inform;
28import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
29import geniusweb.references.Reference;
30import tudelft.utilities.listener.DefaultListenable;
31import tudelft.utilities.logging.ReportToLogger;
32import tudelft.utilities.logging.Reporter;
33
34/**
35 * A websocket based {@link ProtocolToPartyConn}. it contains a websocket
36 * connection to a party (that will be started using the partyserver
37 * start-new-party protocol). Must be public so that it can be accessed as
38 * ClientEndpoint by apache tomcat. This is internally used.
39 *
40 * <p>
41 * The @ClientEndpoint annotation is important, the runserver is creating these
42 * sockets programatically, from a received ws: URL.
43 */
44@ClientEndpoint
45public class WebSocketProtToPartyConn extends DefaultListenable<Action>
46 implements ProtocolToPartyConn {
47 private final static ObjectMapper jackson = new ObjectMapper();
48 // 20MB seems reasonable limit?
49 private static final int MAX_MESSAGE_SIZE = 20 * 1024 * 1024;
50 private final Reporter log;
51
52 private final Reference reference;
53
54 private WebSocketContainer container;
55 private Session session = null;
56 private URI partyuri;
57 private Throwable error = null;
58
59 /**
60 * Constructor that uses default {@link Reporter}
61 *
62 * @param reference the address to to a http GET to get a websocket to a new
63 * running party.
64 */
65 public WebSocketProtToPartyConn(Reference reference) {
66 this(reference, new ReportToLogger("runserver"));
67 }
68
69 /**
70 *
71 * @param reference the address to to a http GET to get a websocket to a new
72 * running party.
73 * @param reporter the {@link Reporter} used for logging important
74 * events/issues
75 * @throws IOException if the party does not start properly.
76 */
77 public WebSocketProtToPartyConn(Reference reference, Reporter reporter) {
78 this.reference = reference;
79 this.log = reporter;
80
81 }
82
83 public WebSocketProtToPartyConn init() throws IOException {
84 log.log(Level.INFO, "Trying to start up party " + reference);
85 URI partyuri = startParty(reference);
86
87 container = ContainerProvider.getWebSocketContainer();
88 log.log(Level.INFO,
89 "Trying to make websocket connection to running party "
90 + partyuri);
91 try {
92 container.connectToServer(this, partyuri);
93 } catch (DeploymentException e) {
94 throw new IOException(
95 "Failed to connect with running party at " + partyuri, e);
96 }
97 log.log(Level.INFO, "Created websocket connection at " + partyuri);
98 return this;
99 }
100
101 /**
102 *
103 * @param reference the address to to a http GET to get a websocket to a new
104 * running party.
105 * @return URI of websocket behind which is a running party
106 * @throws IOException if party did not start correctly
107 */
108 private URI startParty(Reference reference) throws IOException {
109 HttpURLConnection conn = (HttpURLConnection) reference.getURI().toURL()
110 .openConnection();
111 // if this throws, the IOException seems to contain the "retry later"
112 // message already
113 conn.setRequestMethod("GET");
114 conn.setReadTimeout(20000);
115 conn.connect();
116 BufferedReader rd;
117 try {
118 rd = new BufferedReader(
119 new InputStreamReader(conn.getInputStream()));
120 } catch (IOException e) {
121 if (conn.getErrorStream() == null) {
122 throw new IOException("Failed to connect " + reference
123 + ", no server error message", e);
124 }
125 // read the detail message from the error stream
126 rd = new BufferedReader(
127 new InputStreamReader(conn.getErrorStream()));
128 throw new IOException(rd.readLine(), e);
129 }
130 String partysocketaddress = rd.readLine();
131 log.log(Level.INFO, "startparty returned " + partysocketaddress);
132 if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
133 throw new IOException(
134 "Something went wrong connecting to the party:"
135 + conn.getResponseCode());
136 }
137 try {
138 this.partyuri = new URI(partysocketaddress);
139 } catch (URISyntaxException e1) {
140 throw new IOException("Failed to start party at " + reference, e1);
141 }
142 return partyuri;
143 }
144
145 /************* implements {@link ConnectionWithParty} ***************/
146 @Override
147 public void send(Inform info) throws IOException {
148 if (session == null)
149 return;
150 session.getBasicRemote().sendText(jackson.writeValueAsString(info));
151 }
152
153 @Override
154 public Reference getReference() {
155 return this.reference;
156 }
157
158 @Override
159 public PartyId getParty() {
160 String name = partyuri.getPath();
161 return new PartyId(name.substring(name.lastIndexOf('/') + 1));
162 }
163
164 @Override
165 public void close() {
166 log.log(Level.INFO, "Party close called");
167 // a close event is not necessarily a bug?
168 // setError(new SocketException("socket is closed"));
169 try {
170 session.close();
171 this.session = null;
172 } catch (IOException e) {
173 log.log(Level.SEVERE, "failed to close websocket", e);
174 }
175 }
176
177 /************* implements ClientEndpoint ***************/
178
179 @OnOpen
180 public void onOpen(Session session) {
181 log.log(Level.INFO, "Party is being connected: " + session);
182 session.setMaxTextMessageBufferSize(MAX_MESSAGE_SIZE);
183 session.setMaxBinaryMessageBufferSize(MAX_MESSAGE_SIZE);
184 this.session = session;
185 }
186
187 @OnClose
188 public void onClose(Session userSession, CloseReason reason) {
189 log.log(Level.INFO, "closing websocket " + reference + ":" + reason);
190 if (reason.getReasonPhrase() != null) {
191 setError(new RemoteException("Session " + reference
192 + " closed abnoramlly (see party server for more details) "
193 + reason.getReasonPhrase()));
194 return;
195 }
196 setError(new SocketException("socket has been closed"));
197 }
198
199 @OnMessage
200 public void processMessage(String message) {
201 try {
202 Action act = jackson.readValue(message, Action.class);
203 notifyListeners(act);
204 } catch (IOException e) {
205 log.log(Level.WARNING,
206 "received bad message from client " + reference, e);
207 setError(e);
208 close();
209 }
210 }
211
212 @OnError
213 public void processError(Throwable t) {
214 log.log(Level.SEVERE, "Something went wrong internally!", t);
215 setError(t);
216 }
217
218 @Override
219 public URI getRemoteURI() {
220 return partyuri;
221 }
222
223 @Override
224 public Throwable getError() {
225 return error;
226 }
227
228 /**
229 * Sets the connection to final error state and report null to listeners.
230 * Only the first error is reported, the rest is ignored.
231 *
232 * @param err the error that occurred.
233 */
234 private void setError(Throwable err) {
235 boolean isSet = false;
236 synchronized (this) {
237 if (this.error == null) {
238 this.error = err;
239 isSet = true;
240 }
241 }
242 if (isSet) {
243 // special event that tells protocol that we're probably in error
244 // state.
245 notifyListeners(null);
246 }
247 }
248
249}
250
251/**
252 * Indicates an exceeption occured on a remote machine. The remote machine did
253 * not pass a stacktrace (because it does not fit into a websocket close call).
254 *
255 */
256class RemoteException extends Exception {
257 public RemoteException(String message) {
258 super(message, null, false, false);
259
260 }
261}
Note: See TracBrowser for help on using the repository browser.