{"id":20884814,"url":"https://github.com/morgwai/servlet-scopes","last_synced_at":"2025-05-12T18:31:41.142Z","repository":{"id":45207698,"uuid":"356662986","full_name":"morgwai/servlet-scopes","owner":"morgwai","description":"Websocket and Servlet Guice Scopes automatically transferred between threads","archived":false,"fork":false,"pushed_at":"2025-04-21T22:13:58.000Z","size":1414,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-21T23:24:54.798Z","etag":null,"topics":["guice","guice-scopes","guice-servlet-scopes","guice-websocket","guice-websocket-scopes","servlet-guice-scopes","servlet-scopes","servlets","websocket","websocket-guice","websocket-guice-scopes","websocket-scopes"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/morgwai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-04-10T18:25:21.000Z","updated_at":"2025-04-21T22:14:02.000Z","dependencies_parsed_at":"2023-12-22T10:26:19.380Z","dependency_job_id":"d61a479f-829e-4ac4-91cd-fdcde29349d2","html_url":"https://github.com/morgwai/servlet-scopes","commit_stats":null,"previous_names":[],"tags_count":97,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgwai%2Fservlet-scopes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgwai%2Fservlet-scopes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgwai%2Fservlet-scopes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morgwai%2Fservlet-scopes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/morgwai","download_url":"https://codeload.github.com/morgwai/servlet-scopes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253798144,"owners_count":21966013,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["guice","guice-scopes","guice-servlet-scopes","guice-websocket","guice-websocket-scopes","servlet-guice-scopes","servlet-scopes","servlets","websocket","websocket-guice","websocket-guice-scopes","websocket-scopes"],"created_at":"2024-11-18T08:11:39.326Z","updated_at":"2025-05-12T18:31:41.125Z","avatar_url":"https://github.com/morgwai.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Servlet and Websocket Guice Scopes\n\n`containerCallScope` (either a `HttpServletRequest` or a websocket `Endpoint` event), `websocketConnectionScope` (`javax.websocket.Session`) and `httpSessionScope` for use in servlet+websocket apps and standalone websocket apps (both client and server).\u003cbr/\u003e\nCopyright 2021 Piotr Morgwai Kotarbinski, Licensed under the Apache License, Version 2.0\u003cbr/\u003e\n\u003cbr/\u003e\n**latest release: 17.2**\u003cbr/\u003e\n[javax flavor](https://search.maven.org/artifact/pl.morgwai.base/servlet-scopes/17.2-javax/jar)\n([javadoc](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/17.2-javax)) - supports Servlet `4.0.1` and Websocket `1.1` APIs\u003cbr/\u003e\n[jakarta flavor](https://search.maven.org/artifact/pl.morgwai.base/servlet-scopes/17.2-jakarta/jar)\n([javadoc](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/17.2-jakarta)) - supports Servlet `5.0.0` to at least `6.0.0` and Websocket `2.0.0` to at least `2.1.1` APIs\u003cbr/\u003e\n\u003cbr/\u003e\nSee [CHANGES](CHANGES.md) for the summary of changes between releases. If the major version of a subsequent release remains unchanged, it is supposed to be backwards compatible in terms of API and behaviour with previous ones with the same major version (meaning that it should be safe to just blindly update in dependent projects and things should not break under normal circumstances).\n\n\n\n## OVERVIEW\n\nProvides the below Guice `Scope`s:\n\n### [containerCallScope](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/WebsocketModule.html#containerCallScope)\nScopes bindings to either an `HttpServletRequest` or a websocket event (connection opened/closed, message received, error occurred).\u003cbr/\u003e\nSpans 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).\u003cbr/\u003e\nHaving a common `Scope` for servlet requests and websocket events allows to inject scoped objects both in `Servlet`s and `Endpoint`s without a need for 2 separate bindings in user `Module`s.\nThis `Scope` may be used in all 3 container types.\n\n### [websocketConnectionScope](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/WebsocketModule.html#websocketConnectionScope)\nScopes bindings to a websocket connection (`javax.websocket.Session`).\u003cbr/\u003e\nSpans 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`.\nThis `Scope` may be used in websocket containers both on a client and on a server side.\n\n### [httpSessionScope](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/ServletWebsocketModule.html#httpSessionScope)\nScopes bindings to a given `HttpSession`. Available only in `Servlet` containers to `Servlet`s and\noptionally server `Endpoint`s.\n\nAll the above scopes are built using [guice-context-scopes lib](https://github.com/morgwai/guice-context-scopes), so they are automatically transferred when dispatching using `AsyncContext`, `RequestDispatcher` or `ContextTrackingExecutor`.\n\n\n\n## MAIN USER CLASSES\n\n\u003cbr/\u003e\n\n- **BASE WEBSOCKET STUFF:**\n\n### [GuiceEndpointConfigurator](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/GuiceEndpointConfigurator.html)\nObtains `Endpoint` instances from Guice and ensures their methods run within websocket `Context`s by wrapping them with context-aware proxies. May be used directly to obtain client `Endpoint` instances.\n\n### [GuiceClientEndpoint](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/GuiceClientEndpoint.html)\nAnnotation for client `Endpoint`s that should be injected using a `GuiceEndpointConfigurator`.\n\n### [WebsocketModule](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/WebsocketModule.html)\nDefines `containerCallScope` and `websocketConnectionScope`, configures `GuiceEndpointConfigurator`. Necessary in all 3 container types.\n\n### [GuiceServerEndpointConfigurator](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/GuiceServerEndpointConfigurator.html)\n`ServerEndpointConfig.Configurator` (for use in `@ServerEndpoint` annotations as `configurator` argument) that obtains server `Endpoint` instances from a `GuiceEndpointConfigurator`.\n\n### [StandaloneWebsocketServerModule](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/StandaloneWebsocketServerModule.html)\n`Module` for standalone websocket server apps. Initializes `GuiceServerEndpointConfigurator`.\n\n\u003cbr/\u003e\n\n- **MIXED SERVLET-WEBSOCKET APPS:**\n\n### [ServletWebsocketModule](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/ServletWebsocketModule.html)\n`Module` for mixed Servlet-websocket apps. Embeds a `WebsocketModule` and defines `httpSessionScope`.\n\n### [GuiceServletContextListener](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/scopes/GuiceServletContextListener.html)\nBase class for app `ServletContextListener`s, creates and configures the app-wide `Injector` and `ServletWebsocketModule`, initializes `GuiceServerEndpointConfigurator`. Provides helper methods for programmatically adding `Servlet`s, `Filter`s and websocket `Endpoint`s.\n\n\u003cbr/\u003e\n\n- **MISC STUFF:**\n\n### [ContextBinder](https://javadoc.io/doc/pl.morgwai.base/guice-context-scopes/latest/pl/morgwai/base/guice/scopes/ContextBinder.html)\nBinds closures (`Runnable`s, `Consumer`s, `Callable`s etc) to `Context`s that were active at the time of a given binding. This can be used to transfer `Context`s semi-automatically when manually switching `Thread`s, for example when passing callbacks to async functions.\n\n### [ContextTrackingExecutor](https://javadoc.io/doc/pl.morgwai.base/guice-context-scopes/latest/pl/morgwai/base/guice/scopes/ContextTrackingExecutor.html)\nAn interface and a decorator for `Executor`s that automatically transfer active `Context`s when executing tasks.\n\n\u003cbr/\u003e\n\n- **INTEGRATION WITH [WebsocketPingerService](https://javadoc.io/doc/pl.morgwai.base/servlet-utils/latest/pl/morgwai/base/servlet/utils/WebsocketPingerService.html):**\n\n### [PingingEndpointConfigurator](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/utils/PingingServerEndpointConfigurator.html)\n`GuiceEndpointConfigurator` that additionally automatically registers and deregisters created `Endpoint`s to its associated `WebsocketPingerService`.\n\n### [PingingClientEndpoint](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/utils/PingingClientEndpoint.html)\nAnnotation for client `Endpoint`s that should be injected using a `PingingEndpointConfigurator`.\n\n### [PingingWebsocketModule](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/utils/PingingWebsocketModule.html)\nSubclass of `WebsocketModule` that allows to automatically register `Endpoint` instances to a `WebsocketPingerService` using `PingingEndpointConfigurator`.\n\n### [PingingServerEndpointConfigurator](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/utils/PingingServerEndpointConfigurator.html)\n`GuiceServerEndpointConfigurator` that uses `PingingEndpointConfigurator`.\n\n### [PingingServletContextListener](https://javadoc.io/doc/pl.morgwai.base/servlet-scopes/latest/pl/morgwai/base/servlet/guice/utils/PingingServletContextListener.html)\n`GuiceServletContextListener` that uses `PingingWebsocketModule` and `PingingEndpointConfigurator`. Creates and configures  the app-wide `WebsocketPingerService`.\n\n\n\n## USAGE\n\n### Adding Guice `Modules` and programmatic `Servlet`s and `Endpoint`s in a `ServletContextListener`\n```java\n@WebListener\npublic class MyServletContextListener extends GuiceServletContextListener {\n                          // ...or `extends PingingServletContextListener {`\n\n    @Override\n    protected LinkedList\u003cModule\u003e configureInjections() {\n        final var modules = new LinkedList\u003cModule\u003e();\n        modules.add((binder) -\u003e {\n            binder.bind(SomeService.class).to(MyService.class).in(containerCallScope);\n                // @Inject Provider\u003cSomeService\u003e myServiceProvider;\n                // will now work both in servlets and endpoints\n            // more bindings here...\n        });\n        return modules;\n    }\n\n    @Override\n    protected void addServletsFiltersEndpoints() throws ServletException, DeploymentException {\n        addEnsureSessionFilter(\"/websocket/*\");\n\n        // MyServlet and MyProgrammaticEndpoint instances will have their dependencies injected\n        addServlet(\"myServlet\", MyServlet.class, \"/myServlet\");\n        addEndpoint(MyProgrammaticEndpoint.class, \"/websocket/myProgrammaticSocket\");\n        // more servlets / filters / endpoints here...\n    }\n}\n```\n**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](https://javadoc.io/static/jakarta.servlet/jakarta.servlet-api/5.0.0/jakarta/servlet/ServletContext.html#setInitParameter-java.lang.String-java.lang.String-) 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)`).\n\n### Using annotated server `Endpoints`\n```java\n@ServerEndpoint(\n    value = \"/websocket/myAnnotatedSocket\",\n    configurator = GuiceServerEndpointConfigurator.class  // ...or PingingServerEndpointConfigurator\n)\npublic class MyAnnotatedEndpoint {\n\n    @Inject Provider\u003cSomeService\u003e myServiceProvider;  // will be injected automatically\n\n    // endpoint implementation here...\n}\n```\nNote: in case of annotated `Endpoints`, it is also necessary either for app's `ServletContextListener` to extend `GuiceServletContextListener` / `PingingServletContextListener` or to perform the setup manually as explained before.\n\n### Websocket client app sample\n```java\npublic class MyWebsocketClientApp {\n\n    static final String SERVER_URL = \"url\";\n    static final String REQUEST = \"request\";\n\n    @Inject @Named(SERVER_URL) String serverUrl;\n    @Inject @Named(REQUEST) String request;\n    @Inject @GuiceClientEndpoint MyClientEndpoint endpoint;\n    @Inject WebSocketContainer container;\n\n    @ClientEndpoint\n    public static class MyClientEndpoint {\n\n        @Inject ResponseProcessor responseProcessor;\n        Session connection;\n        final CountDownLatch connectionClosed = new CountDownLatch(1);\n        \n        @OnOpen public void onOpen(Session connection) {\n            this.connection = connection;\n        }\n\n        @OnMessage public void onMessage(String serverReply) {\n            try {\n                responseProcessor.process(serverReply);\n            } finally {\n                try {\n                    connection.close();\n                } catch (IOException ignored) {}\n            }\n        }\n        \n        @OnClose public void onClose() {\n            connectionClosed.countDown();\n        }\n\n        void awaitClosure(long timeout, TimeUnit unit) throws InterruptedException {\n            connectionClosed.await(timeout, unit);\n        }\n    }\n\n    void startAndAwait(long timeout, TimeUnit unit) throws Exception {\n        try( \n            final var connection = container.connectToServer(endpoint, URI.create(serverUrl));\n        ) {\n            connection.getBasicRemote().sendText(request);\n            endpoint.awaitClosure(timeout, unit);\n        }\n    }\n\n    public static void main(String[] args) throws Exception {\n        final var modules = new ArrayList\u003cModule\u003e();\n        final var websocketModule = new WebsocketModule(false, MyClientEndpoint.class);\n        modules.add(websocketModule);\n        modules.add((binder) -\u003e {\n            binder.bind(WebSocketContainer.class)\n                .toInstance(createClientWebsocketContainer());\n            binder.bind(String.class)\n                .annotatedWith(named(SERVER_URL))\n                .toInstance(args[0]);\n            binder.bind(String.class)\n                .annotatedWith(named(REQUEST))\n                .toInstance(args[1]);\n            binder.bind(ResponseProcessor.class);  // has default or @inject constructor\n            binder.bind(SomeService.class)\n                .to(MyService.class)\n                .in(websocketModule.containerCallScope);\n            // more bindings here...\n        });\n        // more modules here...\n        final var injector = Guice.createInjector(modules);\n        final var myApp = injector.getInstance(MyWebsocketClientApp.class);\n        myApp.startAndAwait(10, SECONDS);\n    }\n\n    static WebSocketContainer createClientWebsocketContainer() {\n        // container specific code\n    }\n}\n```\n\n### Websocket standalone server container sample\n```java\npublic class MyWebsocketServer {\n\n    public static void main(String[] args) throws Exception {\n        final var port = Integer.parseInt(args[0]);\n        final var deploymentPath = args[1];\n        final var modules = new ArrayList\u003cModule\u003e();\n        final var websocketModule = new WebsocketModule(false);\n        final var serverModule = new StandaloneWebsocketServerModule(deploymentPath);\n        modules.add(websocketModule);\n        modules.add(serverModule);\n        modules.add((binder) -\u003e {\n            binder.bind(SomeService.class)\n                .to(MyService.class)\n                .in(websocketModule.containerCallScope);\n            // more bindings here...\n        });\n        // more modules here...\n        final var injector = Guice.createInjector(modules);\n        final var server = createServer(port, deploymentPath, Config.class);\n        Runtime.getRuntime().addShutdownHook(new Thread(() -\u003e {\n            server.stop();\n            GuiceServerEndpointConfigurator.deregisterInjector(injector);\n        }));\n        server.awaitTermination();\n    }\n\n    public static class Config implements ServerApplicationConfig {\n\n        @Override\n        public Set\u003cClass\u003c?\u003e\u003e getAnnotatedEndpointClasses(Set\u003cClass\u003c?\u003e\u003e scanned) {\n            return Set.of(MyAnnotatedEndpoint.class);\n        }\n\n        @Override\n        public Set\u003cServerEndpointConfig\u003e getEndpointConfigs(Set\u003cClass\u003c? extends Endpoint\u003e\u003e s) {\n            return Set.of();\n        }\n    }\n\n    Server createServer(int port, String deploymentPath, Class\u003c?\u003e... configs) {\n        // container specific code\n    }\n}\n```\n\n### Dependency management\nDependencies of this jar on [guice](https://search.maven.org/artifact/com.google.inject/guice) is declared as optional, so that apps can use any version with compatible API.\n\nStandalone websocket apps must include `servlet-api` in their dependencies ([javax](https://central.sonatype.com/artifact/javax.servlet/javax.servlet-api) or [jakarta](https://central.sonatype.com/artifact/jakarta.servlet/jakarta.servlet-api) respectively).\n\nThere are 2 builds available:\n- build with `shadedbytebuddy` classifier includes relocated dependency on [byte-buddy](https://search.maven.org/artifact/net.bytebuddy/byte-buddy). Most apps should use this build. To do so, add `\u003cclassifier\u003eshadedbytebuddy\u003c/classifier\u003e` to your dependency declaration.\n- \"default\" build does not include any shaded dependencies and dependency on `byte-buddy` is marked as `optional`. This is useful for apps that also depend on `byte-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 that `servlet-scopes` depends on (in regard to features used by `servlet-scopes`). If this is not the case, then `shadedbytebuddy` build should be used.\n\n\n\n## EXTENSIONS\n\n[Tyrus connection proxy](https://github.com/morgwai/servlet-scopes-connection-proxy-tyrus) that provides unified, websocket API compliant access to clustered websocket connections and properties.\n\n\n\n## EXAMPLES\n\n[a trivial sample app built from the test code](sample).\n\n\n\n## FAQ\n\n**Why isn't this built on top of [official servlet scopes lib](https://github.com/google/guice/wiki/Servlets)?**\n* the official Guice-servlet has some [serious issues](https://github.com/google/guice/blob/6.0.0/extensions/servlet/src/com/google/inject/servlet/ServletScopes.java#L158)\n* 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 fake `HttpSevletRequest` wrappers), which seems awkward.\n* `guice-context-scopes` allows to remove objects from scopes.\n\n**Why do I have to install myself a filter that creates HTTP session for websocket requests? Can't `addEnsureSessionFilter(\"/*\")` be called automatically?**\n\nAlways 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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorgwai%2Fservlet-scopes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorgwai%2Fservlet-scopes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorgwai%2Fservlet-scopes/lists"}