Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class loading broken for WebSocketClient used inside webapp #6287

Closed
sbordet opened this issue May 17, 2021 · 4 comments · Fixed by #6293 or #6325
Closed

Class loading broken for WebSocketClient used inside webapp #6287

sbordet opened this issue May 17, 2021 · 4 comments · Fixed by #6293 or #6325
Assignees

Comments

@sbordet
Copy link
Contributor

sbordet commented May 17, 2021

Jetty version
9.4.x

Description
The fix for #5859 resolved the leaking of ClassLoaders, but now causes class loading issues in web applications, see for example: cometd/cometd#1037.

A web application using the WebSocket client may trigger the creation of a thread, which before #5859 was inheriting the webapp ClassLoader as context ClassLoader, but now the thread gets the server ClassLoader as the context ClassLoader.

If the task at hand needs to load a class from the webapp (for example due to JSON parsing using oej.util.Loader.loadClass(), it was succeeding before #5859, but fails now.

Obviously we want to keep the fix for #5859, but we need to explicitly set the context ClassLoader before calling webapp code (e.g. listeners, callbacks, etc.) in those components that are provided by the server, in particular WebSocketClient.

@lachlan-roberts lachlan-roberts self-assigned this May 18, 2021
lachlan-roberts added a commit that referenced this issue May 18, 2021
lachlan-roberts added a commit that referenced this issue May 19, 2021
lachlan-roberts added a commit that referenced this issue May 20, 2021
…ntClassLoading

Issue #6287 - fix classloading for WebSocketClient in webapp
@sbordet sbordet linked a pull request May 25, 2021 that will close this issue
lachlan-roberts added a commit that referenced this issue May 26, 2021
lachlan-roberts added a commit that referenced this issue May 26, 2021
lachlan-roberts added a commit that referenced this issue May 26, 2021
@Deepak-D
Copy link

Deepak-D commented May 26, 2021

Hello @sbordet / @lachlan-roberts, good day to you! I guess I'm facing this problem. More details here. Could this be confirmed?
Thanks in advance!!

@lachlan-roberts
Copy link
Contributor

@Deepak-D from reading the description it doesn't seem to be the same issue. This issue is only relating to the use of WebSocketClient inside the webapp. Are you using the WebSocketClient and accessing the ContextClassLoader from within the websocket endpoint?

If not would it be possible to attach a simple reproducer where the getContextClassLoader() gives WebAppClassLoader in 9.4.36 and then startJarLoader if upgraded to 9.4.37. You should open a separate issue for this if this is the case.

@Deepak-D
Copy link

Hello @lachlan-roberts , thanks for your feedback..

Here is my case!

I have WebSocket ClientEndpoint in a jar (Say client.jar) which is deployed in JETTY_BASE/lib/ext (since this client is accessed by multiple web application deployed in JETTY_BASE/webapps and each webapp is differentiated by unique client id).

ClientEndPoint will look like below

@ClientEndpoint(decoders = {MessageDecoder.class}, encoders = {MessageEncoder.class})
public class WSClientEndPoint {
	WebSocketContainer container = ContainerProvider.getWebSocketContainer();
	
	public boolean connectToWSURL() {
		Session localSession = null;
		try {
			localSession = container.connectToServer(this, new URI(this.wsURL)); //wsURL has ServerEndPoint URL
		}
		catch (Exception e) {
		}
	}
	
	@OnOpen
	public void onOpen(Session session) {
	}
	
	@OnMessage
	public void onMessage(final Session session, Message msg) {
	}
	
	@OnError
	public void onError(Session session, Throwable error) {
	}
	
	@OnClose
	public void onClose(Session session, CloseReason closeReason) {
	}
}

WebSocket ServerEndpoint in another web application which is also deployed in the same JETTY_BASE/webapps. I use javax.websocket.WebSocketContainer to establish connection to the server endpoint with its endpoint url.

ServerEndPoint will look like below

@ServerEndpoint(value = "/server/{client-id}", decoders = {MessageDecoder.class}, encoders = {MessageEncoder.class})
public class ServerResource {
	@OnOpen
	public void onOpen(Session session, @PathParam("client-id") final String clientId) {
	}
	
	@OnClose
	public void onClose(Session session, CloseReason closeReason) {
	}
	
	@OnMessage
	public void message(final Session session, Message message) {
	}
	
	@OnError
	public void onError(Throwable t, Session session) {
	}
	
	@OnMessage
	public void onPong(PongMessage pongMessage, Session session) {
	}
}

This client and server is designed to work based on the publish/subscribe model using topics (Similar to mqtt).

I have a interface in client.jar and this interface will be implemented inside webapp which uses this client.jar to handle data received from server endpoint for the subscription of each topic.

interface in client.jar will look like below

public interface NotificationHandler {
	public boolean handle(String topic, String message);
}

Implementation inside webapp will look like below

package com.webappx.websocket.subscribehandlers
public class ConfigurationHandler implements NotificationHandler{
	@Override
	public boolean handle(String topic, String message) {
		InputStream inputStream;
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		try {
			inputStream = classLoader.getResourceAsStream("x.properties");
		}catch (Exception ie) {
			ie.printStackTrace();
		}
	}
}

I have list of topics and its dedicated class full name implementing the NotificationHandler interface inside webapp. On webapp startup, I'm establishing websocket connection to the server endpoint using client.jar which has client endpoint using the webapp's unigue client id to differentiate client's session on server side. Then, I'm doing topic subscription to the server and I will create class instance dedicated for that specific topic and persist inside client.jar in a HashMap. Whenever I receive subscription message from the server for the topic, I will get the instance from HashMap and call handle method of the NotificationHandler interface passing topic and data received.

In this flow, when I try to get the class loader instance using ClassLoader classLoader = Thread.currentThread().getContextClassLoader() as shown above, I get startJarLoader from Jetty 9.4.37 which causes problem loading resource files from WEB-INF\classes but with Jetty 9.4.36 and lower versions, I get WebAppClassLoader.

Hope this helps! If not, I will try to prepare a reproducer.

@lachlan-roberts
Copy link
Contributor

lachlan-roberts commented May 27, 2021

@Deepak-D this does sound like it is the same issue.

If you want to verify this, then you can build the branch for PR #6325 (jetty-9.4.x-6287-WebSocketClientClassLoading) and test with your code to see if this fixes it for you.

lachlan-roberts added a commit that referenced this issue Jun 3, 2021
…tClassLoading

 Issue #6287 - fix classloading for WebSocketClient in webapp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants