containerCallScope
(either a HttpServletRequest
or a websocket Endpoint
event), websocketConnectionScope
(javax.websocket.Session
) and httpSessionScope
for use in Servlet
/websocket server containers and websocket client containers.
Copyright 2021 Piotr Morgwai Kotarbinski, Licensed under the Apache License, Version 2.0
latest release: 16.3
javax flavor
(javadoc) - supports Servlet 4.0.1
and Websocket 1.1
APIs
jakarta flavor
(javadoc) - supports Servlet 5.0.0
to at least 6.0.0
and Websocket 2.0.0
to at least 2.1.1
APIs
Provides the below Guice scopes:
Scopes bindings to either an HttpServletRequest
or a websocket event (connection opened/closed, message received, error occurred).
Spans over a single container-initiated call to either one of servlet's doXXX(...)
methods or to a websocket endpoint life-cycle method (annotated with one of the websocket annotations or overriding those of javax.websocket.Endpoint
or of registered javax.websocket.MessageHandler
s).
Having a common Scope
for servlet requests and websocket events allows to inject scoped objects both in servlets and endpoints without a need for 2 separate bindings in user Module
s.
This Scope
may be used both on a client and on a server side.
Scopes bindings to a websocket connection (javax.websocket.Session
).
Spans over a lifetime of a given endpoint instance: all calls to life-cycle methods of a given endpoint instance (annotated with @OnOpen
, @OnMessage
, @OnError
, @OnClose
, or overriding those of javax.websocket.Endpoint
together with methods of registered MessageHandler
s) are executed within the same associated websocketConnectionScope
.
This Scope
may be used both on a client and on a server side.
Scopes bindings to a given HttpSession
. Available only on a server side both to servlets and websocket endpoints.
All the above scopes are built using guice-context-scopes lib, so they are automatically transferred to a new thread when dispatching using AsyncContext.dispatch()
or ServletContextTrackingExecutor
(see below).
Contains the above Scope
s, related ContextTracker
s and some helper methods.
Websocket ServerEndpoint
Configurator
that ensures that Endpoint
instances have their dependencies injected and that their methods run within websocket contexts, so that the above Scope
s work properly.
Base class for app ServletContextListener
s. Creates and configures an app-wide Guice Injector
instance, the above mentioned ServletModule
and performs bookkeeping related to GuiceServerEndpointConfigurator
and ServletContextTrackingExecutor
s. Also provides helper methods for creating and configuring programmatic Servlet
s, Filter
s and Endpoint
s.
Subclass of GuiceServerEndpointConfigurator
that additionally automatically registers and deregisters created Endpoint
instances to its associated WebsocketPingerService.
Subclass of GuiceServletContextListener
that uses PingingEndpointConfigurator
for programmatic Endpoint
s and configures app's WebsocketPingerService
.
A ThreadPoolExecutor
that upon dispatching a task, automatically transfers all the active Context
s to the thread running the task.
Binds tasks and callbacks (Runnable
s, Consumer
s, BiConsumer
s, Function
s and BiFunction
s) to contexts that were active at the time of binding. This can be used to transfer Context
s almost fully automatically when it's not possible to use GrpcContextTrackingExecutor
when switching threads (for example when providing callbacks as arguments to async functions). See a usage sample below.
Context-aware proxy for client Endpoints
. Executes lifecycle methods of its wrapped Endpoint
and of its registered MessageHandlers
within websocket Contexts
.
Subclass of ClientEndpointProxy
that additionally automatically registers and deregisters its wrapped Endpoint
to its associated WebsocketPingerService.
@WebListener
public class ServletContextListener extends GuiceServletContextListener {
// ...or `extends PingingServletContextListener {`
@Override
protected LinkedList<Module> configureInjections() throws Exception {
final var modules = new LinkedList<Module>();
modules.add((binder) -> {
binder.bind(MyService.class).in(containerCallScope);
// @Inject Provider<MyService> myServiceProvider;
// will now work both in servlets and endpoints
// more bindings here...
});
return modules;
}
@Override
protected void configureServletsFiltersEndpoints() throws ServletException, DeploymentException
{
addEnsureSessionFilter("/websocket/*");
// MyServlet and MyProgrammaticEndpoint instances will have their dependencies injected
addServlet("myServlet", MyServlet.class, "/myServlet");
addEndpoint(MyProgrammaticEndpoint.class, "/websocket/myProgrammaticSocket");
// more servlets / filters / unannotated endpoints here...
}
}
NOTE: If the servlet container being used uses mechanism other than the standard Java Serialization to persist/replicate HttpSession
s, then a deployment init-param named pl.morgwai.base.servlet.guice.scopes.HttpSessionContext.customSerialization
must be set to true
either in web.xml
or programmatically before any request is served (for example in ServletContextListener.contextInitialized(event)
).
Note: in cases where it is not possible to extend GuiceServletContextListener
, all the setup required to use ServletModule
(with all its Scopes
etc) and GuiceServerEndpointConfigurator
/ PingingEndpointConfigurator
, can be done manually: see an example in ManualServletContextListener.
@ServerEndpoint(
value = "/websocket/myAnnotatedSocket",
configurator = GuiceServerEndpointConfigurator.class // ...or PingingEndpointConfigurator
)
public class MyAnnotatedEndpoint {
@Inject Provider<MyService> myServiceProvider; // will be injected automatically
// endpoint implementation here...
}
Note: in case of annotated Endpoints
, it is still necessary either for app's ServletContextListener
to extend GuiceServletContextListener
/ PingingServletContextListener
or to perform the setup manually as explained before.
public class MyWebsocketClientApp {
public static void main(String[] args) throws Exception {
final var modules = new ArrayList<Module>();
final var servletModule = new ServletModule();
modules.add(servletModule);
modules.add((binder) -> {
binder.bind(MyClientEndpointDependency.class).in(servletModule.containerCallScope);
// more bindings here...
});
// more modules here...
final var injector = Guice.createInjector(modules);
final WebSocketContainer clientWebsocketContainer = createClientWebsocketContainer();
final var myClientEndpoint = injector.getInstance(MyClientEndpoint.class);
clientWebsocketContainer.connectToServer(
new ClientEndpointProxy(myClientEndpoint, servletModule.containerCallContextTracker),
null,
URI.create("wss://someapp.example.com/websocket/someservice")
);
myClientEndpoint.awaitClosure(10, SECONDS);
}
static WebSocketContainer createClientWebsocketContainer() {/* ... */}
}
class MyComponent {
@Inject ContextBinder ctxBinder;
void methodThatCallsSomeAsyncMethod(/* ... */) {
// other code here...
someAsyncMethod(arg1, /* ... */ argN, ctxBinder.bindToContext((callbackParam) -> {
// callback code here...
}));
}
}
NOTE: when dispatching work to servlet container threads using any of AsyncContext.dispatch()
methods, the context is transferred automatically.
Dependencies of this jar on guice is declared as optional, so that apps can use any version with compatible API.
There are 2 builds available:
- build with
shadedbytebuddy
classifier includes relocated dependency on byte-buddy. Most apps should use this build. To do so, add<classifier>shadedbytebuddy</classifier>
to your dependency declaration. - "default" build does not include any shaded dependencies and dependency on
byte-buddy
is marked asoptional
. This is useful for apps that also depend onbyte-buddy
and need to save space (byte-buddy
is over 3MB in size). Note that the version provided by the app needs to be compatible with the version thatservlet-scopes
depends on (in regard to features used byservlet-scopes
). If this is not the case, thenshadedbytebuddy
build should be used.
Tyrus connection proxy that provides unified, websocket API compliant access to clustered websocket connections and properties.
a trivial sample app built from the test code.
Why isn't this built on top of official servlet scopes lib?
- the official Guice-servlet has some serious issues
- in order to extend the official Guice-servlet lib to support websockets, the code would need to pretend that everything is an
HttpServletRequest
(websocket events and websocket connections would need to be wrapped in some fakeHttpSevletRequest
wrappers), which seems awkward. guice-context-scopes
allows to remove objects from scopes.
Why do I have to install myself a filter that creates HTTP session for websocket requests? Can't addEnsureSessionFilter("/*")
be called automatically?
Always enforcing a session creation is not acceptable in many cases, so this would limit applicability of this lib. Reasons may be technical (cookies disabled, non-browser clients that don't even follow redirections), legal (user explicitly refusing any data storage) and probably others. It's a sad trade-off between applicability and API safety.