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

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

Release 1.1.0

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