Skip to content

Commit

Permalink
dbeaver/pro#3807 jakarta websocket (#3141)
Browse files Browse the repository at this point in the history
* dbeaver/pro#3807 jakarta websocket

* dbeaver/pro#3807 fix timeout

* dbeaver/pro#3807 fix test deps
  • Loading branch information
alexander-skoblikov authored Dec 23, 2024
1 parent d798fd6 commit 2f03486
Show file tree
Hide file tree
Showing 18 changed files with 312 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,33 @@
package io.cloudbeaver.model.session;

import jakarta.servlet.http.HttpServletRequest;
import org.jkiss.code.Nullable;

public class WebHttpRequestInfo {
public static final String USER_AGENT = "User-Agent";

@Nullable
private final String id;
@Nullable
private final Object locale;
@Nullable
private final String lastRemoteAddress;
@Nullable
private final String lastRemoteUserAgent;

public WebHttpRequestInfo(HttpServletRequest request) {
this.id = request.getSession().getId();
this.id = request.getSession() == null ? null : request.getSession().getId();
this.locale = request.getAttribute("locale");
this.lastRemoteAddress = request.getRemoteAddr();
this.lastRemoteUserAgent = request.getHeader("User-Agent");
this.lastRemoteUserAgent = request.getHeader(USER_AGENT);
}

public WebHttpRequestInfo(String id, Object locale, String lastRemoteAddress, String lastRemoteUserAgent) {
public WebHttpRequestInfo(
@Nullable String id,
@Nullable Object locale,
@Nullable String lastRemoteAddress,
@Nullable String lastRemoteUserAgent
) {
this.id = id;
this.locale = locale;
this.lastRemoteAddress = lastRemoteAddress;
Expand All @@ -43,14 +54,17 @@ public String getId() {
return id;
}

@Nullable
public Object getLocale() {
return locale;
}

@Nullable
public String getLastRemoteAddress() {
return lastRemoteAddress;
}

@Nullable
public String getLastRemoteUserAgent() {
return lastRemoteUserAgent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import io.cloudbeaver.server.jobs.SessionStateJob;
import io.cloudbeaver.server.jobs.WebDataSourceMonitorJob;
import io.cloudbeaver.server.jobs.WebSessionMonitorJob;
import io.cloudbeaver.service.session.CBSessionManager;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
Expand Down Expand Up @@ -84,6 +83,7 @@ protected synchronized void initialize() {
}

protected void scheduleServerJobs() {
super.scheduleServerJobs();
new WebSessionMonitorJob(this, application.getSessionManager())
.scheduleMonitor();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,22 @@
import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebApplication;
import io.cloudbeaver.server.graphql.GraphQLEndpoint;
import io.cloudbeaver.server.servlets.CBImageServlet;
import io.cloudbeaver.server.servlets.CBStaticServlet;
import io.cloudbeaver.server.servlets.WebStatusServlet;
import io.cloudbeaver.server.websockets.CBJettyWebSocketManager;
import io.cloudbeaver.server.websockets.CBEventsWebSocket;
import io.cloudbeaver.server.websockets.CBWebSocketServerConfigurator;
import io.cloudbeaver.service.DBWServiceBindingServlet;
import io.cloudbeaver.service.DBWServiceBindingWebSocket;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.ee10.servlet.ServletMapping;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.session.DefaultSessionCache;
import org.eclipse.jetty.session.DefaultSessionIdManager;
import org.eclipse.jetty.session.NullSessionDataStore;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
Expand All @@ -48,7 +46,6 @@
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;

public class CBJettyServer {
Expand Down Expand Up @@ -137,7 +134,7 @@ public void runServer() {
}

CBJettyWebSocketContext webSocketContext = new CBJettyWebSocketContext(server, servletContextHandler);
for (DBWServiceBindingWebSocket<CBApplication> wsb : WebServiceRegistry.getInstance()
for (DBWServiceBindingWebSocket wsb : WebServiceRegistry.getInstance()
.getWebServices(DBWServiceBindingWebSocket.class)
) {
if (wsb.isApplicable(this.application)) {
Expand All @@ -149,16 +146,16 @@ public void runServer() {
}
}

WebSocketUpgradeHandler webSocketHandler = WebSocketUpgradeHandler.from(server, servletContextHandler, (wsContainer) -> {
wsContainer.setIdleTimeout(Duration.ofMinutes(5));
// Add websockets
wsContainer.addMapping(
serverConfiguration.getServicesURI() + "ws",
new CBJettyWebSocketManager(this.application.getSessionManager())
);
}
);
servletContextHandler.insertHandler(webSocketHandler);
JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, (context, container) -> {
// Add echo endpoint to server container
ServerEndpointConfig eventWsEnpoint = ServerEndpointConfig.Builder
.create(
CBEventsWebSocket.class,
serverConfiguration.getServicesURI() + "ws"
).configurator(new CBWebSocketServerConfigurator(application.getSessionManager()))
.build();
container.addEndpoint(eventWsEnpoint);
});

JettyUtils.initSessionManager(
this.application.getMaxSessionIdleTime(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import io.cloudbeaver.registry.WebHandlerRegistry;
import io.cloudbeaver.registry.WebServletHandlerDescriptor;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -49,7 +48,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -227,7 +225,7 @@ private void patchStaticContentIfNeeded(HttpServletRequest request, HttpServletR
response.setHeader(HttpHeader.CACHE_CONTROL.toString(), "no-cache, no-store, must-revalidate");
response.setHeader(HttpHeader.CONTENT_TYPE.toString(), MimeTypes.TEXT_HTML);
response.setHeader(HttpHeader.EXPIRES.toString(), "0");
response.getOutputStream().write(ByteBuffer.wrap(indexBytes));
response.getOutputStream().write(indexBytes);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,18 @@
import io.cloudbeaver.registry.WebHandlerRegistry;
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBConstants;
import io.cloudbeaver.server.WebAppSessionManager;
import io.cloudbeaver.server.events.WSWebUtils;
import io.cloudbeaver.service.DBWSessionHandler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Session;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.auth.SMAuthInfo;
import org.jkiss.dbeaver.model.security.user.SMAuthPermissions;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSUserDeletedEvent;
import org.jkiss.dbeaver.model.websocket.event.session.WSSessionStateEvent;
import org.jkiss.utils.CommonUtils;
Expand Down Expand Up @@ -163,15 +159,12 @@ public WebSession getWebSession(
* @return WebSession object or null, if session expired or invalid
*/
@Nullable
public WebSession getOrRestoreSession(@NotNull Request request) {
var sessionIdCookie = Request.getCookies(request).stream().filter(
c -> c.getName().equals(CBConstants.CB_SESSION_COOKIE_NAME)
).findAny().orElse(null);
if (sessionIdCookie == null) {
public WebSession getOrRestoreWebSession(@NotNull WebHttpRequestInfo requestInfo) {
final var sessionId = requestInfo.getId();
if (sessionId == null) {
log.debug("Http session is null. No Web Session returned");
return null;
}
var sessionId = sessionIdCookie.getValue();
WebSession webSession;
synchronized (sessionMap) {
if (sessionMap.containsKey(sessionId)) {
Expand All @@ -189,12 +182,7 @@ public WebSession getOrRestoreSession(@NotNull Request request) {
return null;
}

webSession = createWebSessionImpl(new WebHttpRequestInfo(
request.getId(),
request.getAttribute("locale"),
Request.getRemoteAddr(request),
request.getHeaders().get("User-Agent")
));
webSession = createWebSessionImpl(requestInfo);
restorePreviousUserSession(webSession, oldAuthInfo);

sessionMap.put(sessionId, webSession);
Expand Down Expand Up @@ -301,15 +289,18 @@ public Collection<BaseWebSession> getAllActiveSessions() {
}

@Nullable
public WebHeadlessSession getHeadlessSession(Request request, Session session, boolean create) throws DBException {
String smAccessToken = request.getHeaders().get(WSConstants.WS_AUTH_HEADER);
public WebHeadlessSession getHeadlessSession(
@Nullable String smAccessToken,
@NotNull WebHttpRequestInfo requestInfo,
boolean create
) throws DBException {
if (CommonUtils.isEmpty(smAccessToken)) {
return null;
}
synchronized (sessionMap) {
var tempCredProvider = new SMTokenCredentialProvider(smAccessToken);
SMAuthPermissions authPermissions = application.createSecurityController(tempCredProvider).getTokenPermissions();
var sessionId = session != null ? session.getId()
var sessionId = requestInfo.getId() != null ? requestInfo.getId()
: authPermissions.getSessionId();

var existSession = sessionMap.get(sessionId);
Expand Down
2 changes: 1 addition & 1 deletion server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
org.jkiss.utils;visibility:=reexport,
org.jkiss.dbeaver.model.sql;visibility:=reexport,
org.jkiss.dbeaver.data.gis,
org.jkiss.bundle.jetty.server;visibility:=reexport,
org.jkiss.bundle.jakarta.jetty.server;visibility:=reexport,
com.google.gson;visibility:=reexport,
org.jkiss.bundle.graphql.java;visibility:=reexport,
org.jkiss.bundle.apache.dbcp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.cloudbeaver.server;

import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.server.websockets.WebSocketPingPongJob;
import org.eclipse.core.runtime.Plugin;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
Expand Down Expand Up @@ -132,7 +133,9 @@ private void initTempFolder(@NotNull DBRProgressMonitor monitor) {
@NotNull
public abstract WebApplication getApplication();

protected abstract void scheduleServerJobs();
protected void scheduleServerJobs() {
new WebSocketPingPongJob(WebAppUtils.getWebPlatform()).scheduleMonitor();
}

@Override
public synchronized void dispose() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebHeadlessSession;
import io.cloudbeaver.model.session.WebHttpRequestInfo;
import io.cloudbeaver.model.session.WebSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Session;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
Expand Down Expand Up @@ -56,9 +55,13 @@ WebSession getWebSession(

Collection<BaseWebSession> getAllActiveSessions();

WebSession getOrRestoreSession(Request httpRequest);
WebSession getOrRestoreWebSession(WebHttpRequestInfo httpRequest);

WebHeadlessSession getHeadlessSession(Request request, Session session, boolean create) throws DBException;
WebHeadlessSession getHeadlessSession(
@Nullable String smAccessToken,
@NotNull WebHttpRequestInfo requestInfo,
boolean create
) throws DBException;

boolean touchSession(HttpServletRequest request, HttpServletResponse response) throws DBWebException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,34 @@
package io.cloudbeaver.server.jetty;

import io.cloudbeaver.service.DBWWebSocketContext;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.websocket.api.Configurable;
import org.eclipse.jetty.websocket.server.WebSocketCreator;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler;
import org.jkiss.code.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

public class CBJettyWebSocketContext implements DBWWebSocketContext {
private final List<String> mappings = new ArrayList<>();

private final Server server;
private final ContextHandler handler;
private final ServletContextHandler servletContextHandler;

public CBJettyWebSocketContext(@NotNull Server server, @NotNull ContextHandler handler) {
public CBJettyWebSocketContext(@NotNull Server server, @NotNull ServletContextHandler servletContextHandler) {
this.server = server;
this.handler = handler;
this.servletContextHandler = servletContextHandler;
}


@Override
public void addWebSocket(@NotNull String mapping, @NotNull Function<Configurable, WebSocketCreator> configurator) {
handler.insertHandler(WebSocketUpgradeHandler.from(
server,
handler,
container -> container.addMapping(mapping, configurator.apply(container))
));
mappings.add(mapping);
public void addWebSocket(@NotNull ServerEndpointConfig endpointConfig) {
// Add jakarta.websocket support
JakartaWebSocketServletContainerInitializer.configure(servletContextHandler, (context, container) -> {
container.addEndpoint(endpointConfig);
this.mappings.add(endpointConfig.getPath());
});
}

@NotNull
Expand Down
Loading

0 comments on commit 2f03486

Please sign in to comment.