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

Last change on this file since 18 was 17, checked in by bart, 4 years ago

Version 1.5.

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.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 container.setDefaultMaxTextMessageBufferSize(200000);
89
90 log.log(Level.INFO,
91 "Trying to make websocket connection to running party "
92 + partyuri);
93 try {
94 container.connectToServer(this, partyuri);
95 } catch (DeploymentException e) {
96 throw new IOException(
97 "Failed to connect with running party at " + partyuri, e);
98 }
99 log.log(Level.INFO, "Created websocket connection at " + partyuri);
100 return this;
101 }
102
103 /**
104 *
105 * @param reference the address to to a http GET to get a websocket to a new
106 * running party.
107 * @return URI of websocket behind which is a running party
108 * @throws IOException if party did not start correctly
109 */
110 private URI startParty(Reference reference) throws IOException {
111 HttpURLConnection conn = (HttpURLConnection) reference.getURI().toURL()
112 .openConnection();
113 // if this throws, the IOException seems to contain the "retry later"
114 // message already
115 conn.setRequestMethod("GET");
116 conn.setReadTimeout(20000);
117 conn.connect();
118 BufferedReader rd;
119 try {
120 rd = new BufferedReader(
121 new InputStreamReader(conn.getInputStream()));
122 } catch (IOException e) {
123 if (conn.getErrorStream() == null) {
124 throw new IOException("Failed to connect " + reference
125 + ", no server error message", e);
126 }
127 // read the detail message from the error stream
128 rd = new BufferedReader(
129 new InputStreamReader(conn.getErrorStream()));
130 throw new IOException(rd.readLine(), e);
131 }
132 String partysocketaddress = rd.readLine();
133 log.log(Level.INFO, "startparty returned " + partysocketaddress);
134 if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
135 throw new IOException(
136 "Something went wrong connecting to the party:"
137 + conn.getResponseCode());
138 }
139 try {
140 this.partyuri = new URI(partysocketaddress);
141 } catch (URISyntaxException e1) {
142 throw new IOException("Failed to start party at " + reference, e1);
143 }
144 return partyuri;
145 }
146
147 /************* implements {@link ConnectionWithParty} ***************/
148 @Override
149 public void send(Inform info) throws IOException {
150 if (session == null)
151 return;
152 session.getBasicRemote().sendText(jackson.writeValueAsString(info));
153 }
154
155 @Override
156 public Reference getReference() {
157 return this.reference;
158 }
159
160 @Override
161 public PartyId getParty() {
162 String name = partyuri.getPath();
163 return new PartyId(name.substring(name.lastIndexOf('/') + 1));
164 }
165
166 @Override
167 public void close() {
168 log.log(Level.INFO, "Party close called");
169 // a close event is not necessarily a bug?
170 // setError(new SocketException("socket is closed"));
171 try {
172 session.close();
173 this.session = null;
174 } catch (IOException e) {
175 log.log(Level.SEVERE, "failed to close websocket", e);
176 }
177 }
178
179 /************* implements ClientEndpoint ***************/
180
181 @OnOpen
182 public void onOpen(Session session) {
183 log.log(Level.INFO, "Party is being connected: " + session);
184 session.setMaxTextMessageBufferSize(MAX_MESSAGE_SIZE);
185 session.setMaxBinaryMessageBufferSize(MAX_MESSAGE_SIZE);
186 this.session = session;
187 }
188
189 @OnClose
190 public void onClose(Session userSession, CloseReason reason) {
191 log.log(Level.INFO, "closing websocket " + reference + ":" + reason);
192 if (reason.getReasonPhrase() != null) {
193 setError(new RemoteException("Session " + reference
194 + " closed abnoramlly (see party server for more details) "
195 + reason.getReasonPhrase()));
196 return;
197 }
198 setError(new SocketException("socket has been closed"));
199 }
200
201 @OnMessage
202 public void processMessage(String message) {
203 try {
204 Action act = jackson.readValue(message, Action.class);
205 notifyListeners(act);
206 } catch (IOException e) {
207 log.log(Level.WARNING,
208 "received bad message from client " + reference, e);
209 setError(e);
210 close();
211 }
212 }
213
214 @OnError
215 public void processError(Throwable t) {
216 log.log(Level.SEVERE, "Something went wrong internally!", t);
217 setError(t);
218 }
219
220 @Override
221 public URI getRemoteURI() {
222 return partyuri;
223 }
224
225 @Override
226 public Throwable getError() {
227 return error;
228 }
229
230 /**
231 * Sets the connection to final error state and report null to listeners.
232 * Only the first error is reported, the rest is ignored.
233 *
234 * @param err the error that occurred.
235 */
236 private void setError(Throwable err) {
237 boolean isSet = false;
238 synchronized (this) {
239 if (this.error == null) {
240 this.error = err;
241 isSet = true;
242 }
243 }
244 if (isSet) {
245 // special event that tells protocol that we're probably in error
246 // state.
247 notifyListeners(null);
248 }
249 }
250
251}
252
253/**
254 * Indicates an exceeption occured on a remote machine. The remote machine did
255 * not pass a stacktrace (because it does not fit into a websocket close call).
256 *
257 */
258class RemoteException extends Exception {
259 public RemoteException(String message) {
260 super(message, null, false, false);
261
262 }
263}
Note: See TracBrowser for help on using the repository browser.