aboutsummaryrefslogtreecommitdiffstats
path: root/language-web
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-11 21:05:43 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-10-31 19:26:10 +0100
commitcd7067ef419780f7dcd4195da3d3dc869bb4544e (patch)
treebf235e97e523e478052050dc879f1f8afbdb3e8a /language-web
parentfeat(web): add websocket server (diff)
downloadrefinery-cd7067ef419780f7dcd4195da3d3dc869bb4544e.tar.gz
refinery-cd7067ef419780f7dcd4195da3d3dc869bb4544e.tar.zst
refinery-cd7067ef419780f7dcd4195da3d3dc869bb4544e.zip
feat(web): better websocket logging
Diffstat (limited to 'language-web')
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java4
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java69
-rw-r--r--language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java9
3 files changed, 54 insertions, 28 deletions
diff --git a/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java b/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
index 0942b680..f6311070 100644
--- a/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
+++ b/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java
@@ -35,6 +35,8 @@ public class ServerLauncher {
35 public static final int HTTP_DEFAULT_PORT = 80; 35 public static final int HTTP_DEFAULT_PORT = 80;
36 36
37 public static final int HTTPS_DEFAULT_PORT = 443; 37 public static final int HTTPS_DEFAULT_PORT = 443;
38
39 public static final String ALLOWED_ORIGINS_SEPARATOR = ";";
38 40
39 private static final Logger LOG = LoggerFactory.getLogger(ServerLauncher.class); 41 private static final Logger LOG = LoggerFactory.getLogger(ServerLauncher.class);
40 42
@@ -166,7 +168,7 @@ public class ServerLauncher {
166 private static Optional<String[]> getAllowedOrigins() { 168 private static Optional<String[]> getAllowedOrigins() {
167 var allowedOrigins = System.getenv("ALLOWED_ORIGINS"); 169 var allowedOrigins = System.getenv("ALLOWED_ORIGINS");
168 if (allowedOrigins != null) { 170 if (allowedOrigins != null) {
169 return Optional.of(allowedOrigins.split(XtextWebSocketServlet.ALLOWED_ORIGINS_SEPARATOR)); 171 return Optional.of(allowedOrigins.split(ALLOWED_ORIGINS_SEPARATOR));
170 } 172 }
171 return getAllowedOriginsFromPublicHostAndPort(); 173 return getAllowedOriginsFromPublicHostAndPort();
172 } 174 }
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java
index c76ef12f..4ad98b6e 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocket.java
@@ -7,10 +7,14 @@ import org.eclipse.emf.common.util.URI;
7import org.eclipse.jetty.websocket.api.Session; 7import org.eclipse.jetty.websocket.api.Session;
8import org.eclipse.jetty.websocket.api.StatusCode; 8import org.eclipse.jetty.websocket.api.StatusCode;
9import org.eclipse.jetty.websocket.api.WriteCallback; 9import org.eclipse.jetty.websocket.api.WriteCallback;
10import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
11import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
12import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
10import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; 13import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
11import org.eclipse.jetty.websocket.api.annotations.WebSocket; 14import org.eclipse.jetty.websocket.api.annotations.WebSocket;
12import org.eclipse.xtext.resource.IResourceServiceProvider; 15import org.eclipse.xtext.resource.IResourceServiceProvider;
13import org.eclipse.xtext.web.server.IServiceContext; 16import org.eclipse.xtext.web.server.IServiceContext;
17import org.eclipse.xtext.web.server.IServiceResult;
14import org.eclipse.xtext.web.server.ISession; 18import org.eclipse.xtext.web.server.ISession;
15import org.eclipse.xtext.web.server.IUnwrappableServiceResult; 19import org.eclipse.xtext.web.server.IUnwrappableServiceResult;
16import org.eclipse.xtext.web.server.InvalidRequestException; 20import org.eclipse.xtext.web.server.InvalidRequestException;
@@ -26,7 +30,7 @@ import com.google.gson.JsonParseException;
26import com.google.inject.Injector; 30import com.google.inject.Injector;
27 31
28@WebSocket 32@WebSocket
29public class XtextWebSocket implements WriteCallback { 33public class XtextWebSocket {
30 private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class); 34 private static final Logger LOG = LoggerFactory.getLogger(XtextWebSocket.class);
31 35
32 private final Gson gson = new Gson(); 36 private final Gson gson = new Gson();
@@ -40,61 +44,82 @@ public class XtextWebSocket implements WriteCallback {
40 this.resourceServiceProviderRegistry = resourceServiceProviderRegistry; 44 this.resourceServiceProviderRegistry = resourceServiceProviderRegistry;
41 } 45 }
42 46
47 @OnWebSocketConnect
48 public void onConnect(Session webSocketSession) {
49 LOG.debug("New websocket connection from {}", webSocketSession.getRemoteAddress());
50 }
51
52 @OnWebSocketClose
53 public void onClose(Session webSocketSession, int statusCode, String reason) {
54 if (statusCode == StatusCode.NORMAL) {
55 LOG.debug("{} closed connection normally: {}", webSocketSession.getRemoteAddress(), reason);
56 } else {
57 LOG.warn("{} closed connection with status code {}: {}", webSocketSession.getRemoteAddress(), statusCode,
58 reason);
59 }
60 }
61
62 @OnWebSocketError
63 public void onError(Session webSocketSession, Throwable error) {
64 LOG.error("Internal websocket error in connection from" + webSocketSession.getRemoteAddress(), error);
65 }
66
43 @OnWebSocketMessage 67 @OnWebSocketMessage
44 public void onMessage(Session webSocketSession, Reader reader) { 68 public void onMessage(Session webSocketSession, Reader reader) {
45 XtextWebSocketRequest request; 69 XtextWebSocketRequest request;
46 try { 70 try {
47 request = gson.fromJson(reader, XtextWebSocketRequest.class); 71 request = gson.fromJson(reader, XtextWebSocketRequest.class);
48 } catch (JsonIOException e) { 72 } catch (JsonIOException e) {
49 LOG.error("Cannot read from websocket " + webSocketSession.getRemoteAddress(), e); 73 LOG.error("Cannot read from websocket from" + webSocketSession.getRemoteAddress(), e);
50 if (webSocketSession.isOpen()) { 74 if (webSocketSession.isOpen()) {
51 webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot read payload"); 75 webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot read payload");
52 } 76 }
53 return; 77 return;
54 } catch (JsonParseException e) { 78 } catch (JsonParseException e) {
55 LOG.warn("Malformed websocket request from " + webSocketSession.getRemoteAddress(), e); 79 LOG.warn("Malformed websocket request from" + webSocketSession.getRemoteAddress(), e);
56 webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload"); 80 webSocketSession.close(XtextStatusCode.INVALID_JSON, "Invalid JSON payload");
57 return; 81 return;
58 } 82 }
59 var serviceContext = new SimpleServiceContext(session, request.getRequestData()); 83 var serviceContext = new SimpleServiceContext(session, request.getRequestData());
60 var response = handleMessage(serviceContext); 84 var response = handleMessage(webSocketSession, serviceContext);
61 response.setId(request.getId()); 85 response.setId(request.getId());
62 var responseString = gson.toJson(response); 86 var responseString = gson.toJson(response);
63 try { 87 try {
64 webSocketSession.getRemote().sendPartialString(responseString, true, this); 88 webSocketSession.getRemote().sendPartialString(responseString, true, new WriteCallback() {
89 @Override
90 public void writeFailed(Throwable x) {
91 LOG.warn("Cannot complete async write to websocket " + webSocketSession.getRemoteAddress(), x);
92 }
93 });
65 } catch (IOException e) { 94 } catch (IOException e) {
66 LOG.warn("Cannot initiaite async write to websocket to " + webSocketSession.getRemoteAddress(), e); 95 LOG.warn("Cannot initiaite async write to websocket " + webSocketSession.getRemoteAddress(), e);
67 if (webSocketSession.isOpen()) { 96 if (webSocketSession.isOpen()) {
68 webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot write payload"); 97 webSocketSession.close(StatusCode.SERVER_ERROR, "Cannot write payload");
69 } 98 }
70 } 99 }
71 } 100 }
72 101
73 @Override 102 protected XtextWebSocketResponse handleMessage(Session webSocketSession, IServiceContext serviceContext) {
74 public void writeFailed(Throwable x) { 103 IServiceResult serviceResult;
75 LOG.warn("Cannot complete async write to websocket", x);
76 }
77
78 protected XtextWebSocketResponse handleMessage(IServiceContext serviceContext) {
79 try { 104 try {
80 var injector = getInjector(serviceContext); 105 var injector = getInjector(serviceContext);
81 var serviceDispatcher = injector.getInstance(XtextServiceDispatcher.class); 106 var serviceDispatcher = injector.getInstance(XtextServiceDispatcher.class);
82 var service = serviceDispatcher.getService(serviceContext); 107 var service = serviceDispatcher.getService(serviceContext);
83 var serviceResult = service.getService().apply(); 108 serviceResult = service.getService().apply();
84 var response = new XtextWebSocketOkResponse();
85 if (serviceResult instanceof IUnwrappableServiceResult unwrappableServiceResult
86 && unwrappableServiceResult.getContent() != null) {
87 response.setResponseData(unwrappableServiceResult.getContent());
88 } else {
89 response.setResponseData(serviceResult);
90 }
91 return response;
92 } catch (InvalidRequestException e) { 109 } catch (InvalidRequestException e) {
93 LOG.warn("Invalid request", e); 110 LOG.warn("Invalid request from websocket " + webSocketSession.getRemoteAddress(), e);
94 var error = new XtextWebSocketErrorResponse(); 111 var error = new XtextWebSocketErrorResponse();
95 error.setErrorMessage(e.getMessage()); 112 error.setErrorMessage(e.getMessage());
96 return error; 113 return error;
97 } 114 }
115 var response = new XtextWebSocketOkResponse();
116 if (serviceResult instanceof IUnwrappableServiceResult unwrappableServiceResult
117 && unwrappableServiceResult.getContent() != null) {
118 response.setResponseData(unwrappableServiceResult.getContent());
119 } else {
120 response.setResponseData(serviceResult);
121 }
122 return response;
98 } 123 }
99 124
100 /** 125 /**
diff --git a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java
index 0de6c358..2db11325 100644
--- a/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java
+++ b/language-web/src/main/java/tools/refinery/language/web/xtext/XtextWebSocketServlet.java
@@ -38,11 +38,11 @@ public abstract class XtextWebSocketServlet extends JettyWebSocketServlet implem
38 @Override 38 @Override
39 public void init(ServletConfig config) throws ServletException { 39 public void init(ServletConfig config) throws ServletException {
40 var allowedOriginsStr = config.getInitParameter(ALLOWED_ORIGINS_INIT_PARAM); 40 var allowedOriginsStr = config.getInitParameter(ALLOWED_ORIGINS_INIT_PARAM);
41 if (allowedOriginsStr != null) { 41 if (allowedOriginsStr == null) {
42 log.warn("All WebSocket origins are allowed! This setting should not be used in production!");
43 } else {
42 allowedOrigins = Set.of(allowedOriginsStr.split(ALLOWED_ORIGINS_SEPARATOR)); 44 allowedOrigins = Set.of(allowedOriginsStr.split(ALLOWED_ORIGINS_SEPARATOR));
43 log.info("Allowed origins: {}", allowedOrigins); 45 log.info("Allowed origins: {}", allowedOrigins);
44 } else {
45 log.warn("All WebSocket origins are allowed! This setting should not be used in production!");
46 } 46 }
47 super.init(config); 47 super.init(config);
48 } 48 }
@@ -58,7 +58,7 @@ public abstract class XtextWebSocketServlet extends JettyWebSocketServlet implem
58 public Object createWebSocket(JettyServerUpgradeRequest req, JettyServerUpgradeResponse resp) { 58 public Object createWebSocket(JettyServerUpgradeRequest req, JettyServerUpgradeResponse resp) {
59 if (allowedOrigins != null) { 59 if (allowedOrigins != null) {
60 var origin = req.getOrigin(); 60 var origin = req.getOrigin();
61 if (origin != null && !allowedOrigins.contains(origin.toLowerCase())) { 61 if (origin == null || !allowedOrigins.contains(origin.toLowerCase())) {
62 log.error("Connection from {} from forbidden origin {}", req.getRemoteSocketAddress(), origin); 62 log.error("Connection from {} from forbidden origin {}", req.getRemoteSocketAddress(), origin);
63 try { 63 try {
64 resp.sendForbidden("Origin not allowed"); 64 resp.sendForbidden("Origin not allowed");
@@ -68,7 +68,6 @@ public abstract class XtextWebSocketServlet extends JettyWebSocketServlet implem
68 return null; 68 return null;
69 } 69 }
70 } 70 }
71 log.debug("New connection from {}", req.getRemoteSocketAddress());
72 var session = new SimpleSession(); 71 var session = new SimpleSession();
73 return new XtextWebSocket(session, IResourceServiceProvider.Registry.INSTANCE); 72 return new XtextWebSocket(session, IResourceServiceProvider.Registry.INSTANCE);
74 } 73 }