# Async Servlet Support?

**URL:** <https://forum.inductiveautomation.com/t/async-servlet-support/91260>\
**Category:** Module Development\
**Created:** [June 3, 2024, 2:50am UTC](https://forum.inductiveautomation.com/t/async-servlet-support/91260 "2024-06-03T02:50:12Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 3, 2024, 2:50am UTC](https://forum.inductiveautomation.com/t/async-servlet-support/91260/1 "2024-06-03T02:50:12Z")

</div>

I'm trying to user Jetty's EventSourceServlet (from `org.eclipse.jetty:jetty-servlets:10.0.21`), which calls:

```python
AsyncContext async = request.startAsync();

```

and then subsequently throws:

```python
java.lang.IllegalStateException: !asyncSupported: NotAsync:com.inductiveautomation.ignition.gateway.bootstrap.MapServlet@3365c362

at org.eclipse.jetty.server.Request.startAsync(Request.java:2237)

at org.eclipse.jetty.servlets.EventSourceServlet.doGet(EventSourceServlet.java:92)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:503)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:590)

at com.inductiveautomation.ignition.gateway.bootstrap.MapServlet.service(MapServlet.java:86)

at org.eclipse.jetty.servlet.ServletHolder$NotAsync.service(ServletHolder.java:1410)

at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:764)

at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1665)

at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:527)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)

at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:598)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)

at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1580)

at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)

at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1384)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)

at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:484)

at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1553)

at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)

at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1306)

at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)

at com.inductiveautomation.catapult.handlers.RemoteHostNameLookupHandler.handle(RemoteHostNameLookupHandler.java:121)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)

at org.eclipse.jetty.rewrite.handler.RewriteHandler.handle(RewriteHandler.java:301)

at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)

at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:141)

at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)

at org.eclipse.jetty.server.Server.handle(Server.java:563)

at org.eclipse.jetty.server.HttpChannel$RequestDispatchable.dispatch(HttpChannel.java:1598)

at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:753)

at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:501)

at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:287)

at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)

at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)

at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)

at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:421)

at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:390)

at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:277)

at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:199)

at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)

at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)

at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)

at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)

at java.base/java.lang.Thread.run(Unknown Source)

```

Is this surprising or expected?

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [June 3, 2024, 11:48am UTC](https://forum.inductiveautomation.com/t/async-servlet-support/91260/2 "2024-06-03T11:48:39Z")

</div>

Seems to me the routers that feed requests to servlets would also have to be async, and they are not. 🤷‍♂️

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [June 3, 2024, 3:27pm UTC](https://forum.inductiveautomation.com/t/async-servlet-support/91260/3 "2024-06-03T15:27:39Z")

</div>

I would agree with Phil. You probably can't directly register your own servlet (that doesn't go through our `MapServlet`) implementation without doing something a bit unsupported and/or not very clean via a module. You could probably do an "unsafe" cast to `IgnitionGateway` on your context and find your way to the underlying Jetty instance somehow and register your servlet manually.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [June 3, 2024, 3:31pm UTC](https://forum.inductiveautomation.com/t/async-servlet-support/91260/4 "2024-06-03T15:31:07Z")

</div>

> [@paul-griffith](#):
>
> find your way to the underlying Jetty instance somehow

Hmmm. Sounds like a challenge..... ( **No!** No nerd-sniping today!)

I'll file this idea away for now... 😆

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 3, 2024, 3:56pm UTC](https://forum.inductiveautomation.com/t/async-servlet-support/91260/5 "2024-06-03T15:56:06Z")

</div>

> [@paul-griffith](#):
>
> You could probably do an "unsafe" cast to `IgnitionGateway` on your context and find your way to the underlying Jetty instance somehow and register your servlet manually.

👍🏻 I’ll do some poking. If that doesn’t work out, I’ll just come up with my own implement of the EventSourceServlet.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 3, 2024, 9:22pm UTC](https://forum.inductiveautomation.com/t/async-servlet-support/91260/6 "2024-06-03T21:22:48Z")

</div>

I'm not going to use this, but here's the proof it can be done.

```kotlin
fun <T : Any> T.getPrivateProperty(variableName: String): Any? {
    return javaClass.getDeclaredField(variableName).let { field ->
        field.isAccessible = true
        return@let field.get(this)
    }
}

val ignitionServer = context.webResourceManager.getPrivateProperty("server")
logger.info(ignitionServer?.javaClass?.name)
// com.inductiveautomation.catapult.IgnitionServer

val jettyServer = ignitionServer?.getPrivateProperty("server")
logger.info(jettyServer?.javaClass?.name)
// org.eclipse.jetty.server.Server

```

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [June 5, 2024, 3:36am UTC](https://forum.inductiveautomation.com/t/async-servlet-support/91260/7 "2024-06-05T03:36:22Z")

</div>

> [@bmusson](#):
>
> I'm not going to use this

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/c/4/c457c14a74134506f342550d5549f3efe0435bf1.jpeg)

```kotlin
val unmappedServlets = hashMapOf<String, ServletContextHandler>()

fun <T : WebResourceManager> T.getJettyServer(): Server {
    val ignitionServer = this.getPrivateProperty("server")
    return ignitionServer?.getPrivateProperty("server") as Server
}
fun <T : WebResourceManager> T.addUnmappedServlet(servlet: Class<out Servlet>, path: String) {
    val servletHandler = ServletContextHandler().apply {
        contextPath = "/$SHORT_MODULE_ID"
        addServlet(servlet, path)
    }
    unmappedServlets[path] = servletHandler
    (this.getJettyServer().handler as HandlerCollection).prependHandler(servletHandler)
    servletHandler.start()
}

fun <T : WebResourceManager> T.removeUnmappedServlet(path: String) {
    unmappedServlets[path]?.let{
        it.stop()
        (this.getJettyServer().handler as HandlerCollection).removeHandler(it)
    }
}

```

```kotlin
// Gateway Startup
context.webResourceManager.addUnmappedServlet(TagStreamServlet::class.java, "/sse")

// Gateway Shutdown
context.webResourceManager.removeUnmappedServlet("/sse")

```
