Skip to content

Commit 11c4f16

Browse files
Port to Dropwizard 5/Jetty 12 [BREAKING CHANGE]
Maven and java packages changed to ee10 versions of things. Websocket API changes invovling callbacks/etc, also the upgrade process changes to deal with jetty Request vs/ servlet requests. Similar mock/logging changes for those request APIs. Adopt dropwizard/dropwizard#9970 to avoid errors in request logs. Downstreams might be ok, but API changes ae major enough that they might not be so bump version and label as breaking change.
1 parent 3b683de commit 11c4f16

File tree

34 files changed

+976
-536
lines changed

34 files changed

+976
-536
lines changed

.github/dependabot.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,3 @@ updates:
1212
- dependency-name: org.jetbrains:annotations
1313
versions:
1414
- "> 13.0"
15-
- dependency-name: org.eclipse.jetty:*
16-
versions:
17-
- ">= 12.0.0"
18-
- dependency-name: ch.qos.logback:*
19-
versions:
20-
- ">= 1.5.0"
21-
- dependency-name: jakarta.servlet:jakarta.servlet-api
22-
versions:
23-
- ">= 6.0.0"

build-resources/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
<groupId>io.github.josephlbarnett</groupId>
99
<artifactId>build-resources</artifactId>
10-
<version>3.2-SNAPSHOT</version>
10+
<version>3.3-SNAPSHOT</version>
1111

1212
<name>LeakyCauldron Build Resources</name>
1313
<description>Resources for use during the build process</description>

config/pom.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
<parent>
99
<groupId>io.github.josephlbarnett</groupId>
1010
<artifactId>leakycauldron</artifactId>
11-
<version>3.2-SNAPSHOT</version>
11+
<version>3.3-SNAPSHOT</version>
1212
<relativePath>../pom.xml</relativePath>
1313
</parent>
1414

15-
<groupId>io.github.josephlbarnett</groupId>
1615
<artifactId>config</artifactId>
1716
<name>LeakyCauldron Config</name>
1817

db/pom.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
<parent>
99
<groupId>io.github.josephlbarnett</groupId>
1010
<artifactId>leakycauldron</artifactId>
11-
<version>3.2-SNAPSHOT</version>
11+
<version>3.3-SNAPSHOT</version>
1212
<relativePath>../pom.xml</relativePath>
1313
</parent>
1414

15-
<groupId>io.github.josephlbarnett</groupId>
1615
<artifactId>db</artifactId>
1716
<name>LeakyCauldron DB</name>
1817

graphql/pom.xml

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
<parent>
99
<groupId>io.github.josephlbarnett</groupId>
1010
<artifactId>leakycauldron</artifactId>
11-
<version>3.2-SNAPSHOT</version>
11+
<version>3.3-SNAPSHOT</version>
1212
<relativePath>../pom.xml</relativePath>
1313
</parent>
1414

15-
<groupId>io.github.josephlbarnett</groupId>
1615
<artifactId>graphql</artifactId>
1716
<name>LeakyCauldon GraphQL</name>
1817

@@ -159,32 +158,36 @@
159158
<artifactId>metrics-annotation</artifactId>
160159
</dependency>
161160
<dependency>
162-
<groupId>org.eclipse.jetty</groupId>
163-
<artifactId>jetty-servlet</artifactId>
161+
<groupId>org.eclipse.jetty.ee10</groupId>
162+
<artifactId>jetty-ee10-servlet</artifactId>
164163
</dependency>
165164
<dependency>
166165
<groupId>org.eclipse.jetty</groupId>
167166
<artifactId>jetty-util</artifactId>
168167
</dependency>
169168
<dependency>
170169
<groupId>org.eclipse.jetty</groupId>
171-
<artifactId>jetty-http</artifactId>
170+
<artifactId>jetty-security</artifactId>
172171
</dependency>
173172
<dependency>
174-
<groupId>org.eclipse.jetty.websocket</groupId>
175-
<artifactId>websocket-core-common</artifactId>
173+
<groupId>org.eclipse.jetty</groupId>
174+
<artifactId>jetty-server</artifactId>
175+
</dependency>
176+
<dependency>
177+
<groupId>org.eclipse.jetty</groupId>
178+
<artifactId>jetty-http</artifactId>
176179
</dependency>
177180
<dependency>
178181
<groupId>org.eclipse.jetty.websocket</groupId>
179-
<artifactId>websocket-core-server</artifactId>
182+
<artifactId>jetty-websocket-core-common</artifactId>
180183
</dependency>
181184
<dependency>
182185
<groupId>org.eclipse.jetty.websocket</groupId>
183-
<artifactId>websocket-jetty-api</artifactId>
186+
<artifactId>jetty-websocket-core-server</artifactId>
184187
</dependency>
185188
<dependency>
186189
<groupId>org.eclipse.jetty.websocket</groupId>
187-
<artifactId>websocket-jetty-server</artifactId>
190+
<artifactId>jetty-websocket-jetty-api</artifactId>
188191
</dependency>
189192
<dependency>
190193
<groupId>io.swagger.core.v3</groupId>
@@ -198,6 +201,10 @@
198201
<groupId>org.glassfish.jersey.core</groupId>
199202
<artifactId>jersey-common</artifactId>
200203
</dependency>
204+
<dependency>
205+
<groupId>org.glassfish.jersey.containers</groupId>
206+
<artifactId>jersey-container-servlet-core</artifactId>
207+
</dependency>
201208
<dependency>
202209
<groupId>org.glassfish.jersey.media</groupId>
203210
<artifactId>jersey-media-sse</artifactId>
@@ -214,7 +221,11 @@
214221
</dependency>
215222
<dependency>
216223
<groupId>org.eclipse.jetty.websocket</groupId>
217-
<artifactId>websocket-jetty-client</artifactId>
224+
<artifactId>jetty-websocket-jetty-client</artifactId>
225+
</dependency>
226+
<dependency>
227+
<groupId>org.eclipse.jetty.ee10.websocket</groupId>
228+
<artifactId>jetty-ee10-websocket-jetty-server</artifactId>
218229
</dependency>
219230
</dependencies>
220231
</project>

graphql/src/main/kotlin/com/trib3/graphql/modules/DefaultGraphQLModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import graphql.execution.instrumentation.ChainedInstrumentation
2121
import graphql.execution.instrumentation.Instrumentation
2222
import io.dropwizard.servlets.assets.AssetServlet
2323
import jakarta.inject.Named
24+
import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer
2425
import org.eclipse.jetty.websocket.core.server.WebSocketCreator
25-
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer
2626

2727
/**
2828
* Default Guice module for GraphQL applications. Sets up

graphql/src/main/kotlin/com/trib3/graphql/resources/GraphQLResource.kt

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,21 @@ import kotlinx.coroutines.Job
3333
import kotlinx.coroutines.cancelChildren
3434
import kotlinx.coroutines.supervisorScope
3535
import mu.KotlinLogging
36+
import org.eclipse.jetty.ee10.servlet.ServletContextHandler
37+
import org.eclipse.jetty.ee10.servlet.ServletContextRequest
38+
import org.eclipse.jetty.ee10.websocket.server.internal.JettyServerFrameHandlerFactory
3639
import org.eclipse.jetty.http.HttpStatus
40+
import org.eclipse.jetty.util.FutureCallback
3741
import org.eclipse.jetty.util.URIUtil
3842
import org.eclipse.jetty.websocket.core.Configuration
43+
import org.eclipse.jetty.websocket.core.WebSocketConstants
3944
import org.eclipse.jetty.websocket.core.server.WebSocketCreator
4045
import org.eclipse.jetty.websocket.core.server.WebSocketMappings
41-
import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory
4246
import java.security.Principal
4347
import java.time.Duration
4448
import java.util.Optional
4549
import java.util.concurrent.ConcurrentHashMap
4650
import javax.annotation.Nullable
47-
import kotlin.collections.set
4851

4952
private val log = KotlinLogging.logger {}
5053

@@ -72,6 +75,58 @@ internal fun unauthorizedResponse(): Response =
7275
"Basic realm=\"realm\"",
7376
).build()
7477

78+
/**
79+
* Attempts to do a websocket upgrade, returning 101 if it succeeds,
80+
* and 405 if the negotiation fails.
81+
*
82+
* See JettyWebSocketServlet for the core logic here, as it may
83+
* need to change when jetty version upgrades happen.
84+
*/
85+
fun doWebSocketUpgrade(
86+
request: HttpServletRequest,
87+
response: HttpServletResponse,
88+
creator: WebSocketCreator,
89+
webSocketConfig: Configuration.ConfigurationCustomizer,
90+
): Response {
91+
val webSocketMapping =
92+
request.servletContext?.let {
93+
WebSocketMappings.getMappings(
94+
ServletContextHandler.getServletContextHandler(it),
95+
)
96+
}
97+
98+
val pathSpec = WebSocketMappings.parsePathSpec(URIUtil.addPaths(request.servletPath, request.pathInfo))
99+
if (webSocketMapping != null && webSocketMapping.getWebSocketCreator(pathSpec) == null) {
100+
synchronized(webSocketMapping) {
101+
if (webSocketMapping.getWebSocketCreator(pathSpec) == null) {
102+
webSocketMapping.addMapping(
103+
pathSpec,
104+
creator,
105+
JettyServerFrameHandlerFactory.getFactory(request.servletContext),
106+
webSocketConfig,
107+
)
108+
}
109+
}
110+
}
111+
if (webSocketMapping != null) {
112+
val wsrequest = ServletContextRequest.getServletContextRequest(request)
113+
val wsresponse = wsrequest.servletContextResponse
114+
val callback = FutureCallback()
115+
try {
116+
wsrequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, request)
117+
wsrequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response)
118+
if (webSocketMapping.upgrade(wsrequest, wsresponse, callback, null)) {
119+
callback.block()
120+
return Response.status(HttpStatus.SWITCHING_PROTOCOLS_101).build()
121+
}
122+
} finally {
123+
wsrequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE)
124+
wsrequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE)
125+
}
126+
}
127+
return Response.status(HttpStatus.METHOD_NOT_ALLOWED_405).build()
128+
}
129+
75130
/**
76131
* Jersey Resource entry point to GraphQL execution. Configures the graphql schemas at
77132
* injection time and then executes a [GraphQLRequest] specified query when requested.
@@ -196,33 +251,14 @@ open class GraphQLResource
196251
principal: Optional<Principal>,
197252
@Context request: HttpServletRequest,
198253
@Context response: HttpServletResponse,
199-
@Context containerRequestContext: ContainerRequestContext,
200254
): Response {
201255
val origin = request.getHeader("Origin")
202256
if (origin != null) {
203257
if (!origin.matches(corsRegex)) {
204258
return unauthorizedResponse()
205259
}
206260
}
207-
val webSocketMapping =
208-
request.servletContext?.let {
209-
WebSocketMappings.getMappings(it)
210-
}
211-
val pathSpec = WebSocketMappings.parsePathSpec(URIUtil.addPaths(request.servletPath, request.pathInfo))
212-
if (webSocketMapping != null && webSocketMapping.getWebSocketCreator(pathSpec) == null) {
213-
webSocketMapping.addMapping(
214-
pathSpec,
215-
creator,
216-
JettyServerFrameHandlerFactory.getFactory(request.servletContext),
217-
webSocketConfig,
218-
)
219-
}
220261

221-
// Create a new WebSocketCreator for each request bound to an optional authorized principal
222-
return if (webSocketMapping != null && webSocketMapping.upgrade(request, response, null)) {
223-
Response.status(HttpStatus.SWITCHING_PROTOCOLS_101).build()
224-
} else {
225-
Response.status(HttpStatus.METHOD_NOT_ALLOWED_405).build()
226-
}
262+
return doWebSocketUpgrade(request, response, creator, webSocketConfig)
227263
}
228264
}

graphql/src/main/kotlin/com/trib3/graphql/websocket/GraphQLWebSocketAdapter.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlinx.coroutines.cancel
1010
import kotlinx.coroutines.channels.Channel
1111
import kotlinx.coroutines.runBlocking
1212
import mu.KotlinLogging
13-
import org.eclipse.jetty.websocket.api.WebSocketAdapter
13+
import org.eclipse.jetty.websocket.api.Session
1414

1515
private val log = KotlinLogging.logger {}
1616

@@ -24,9 +24,10 @@ open class GraphQLWebSocketAdapter(
2424
val channel: Channel<OperationMessage<*>>,
2525
val objectMapper: ObjectMapper,
2626
dispatcher: CoroutineDispatcher = Dispatchers.IO,
27-
) : WebSocketAdapter(),
27+
) : Session.Listener.AutoDemanding,
2828
CoroutineScope by CoroutineScope(dispatcher) {
2929
val objectWriter = objectMapper.writerWithDefaultPrettyPrinter()!!
30+
var session: Session? = null
3031

3132
companion object {
3233
private val CLIENT_SOURCED_MESSAGES =
@@ -40,6 +41,10 @@ open class GraphQLWebSocketAdapter(
4041
)
4142
}
4243

44+
override fun onWebSocketOpen(session: Session?) {
45+
this.session = session
46+
}
47+
4348
/**
4449
* Parse incoming messages as [OperationMessage]s, then send them to the channel consumer
4550
*/
@@ -90,7 +95,7 @@ open class GraphQLWebSocketAdapter(
9095
* Must be called from the Subscriber's observation context
9196
*/
9297
internal fun sendMessage(message: OperationMessage<*>) {
93-
remote?.sendString(objectWriter.writeValueAsString(subProtocol.getServerToClientMessage(message)))
98+
session?.sendText(objectWriter.writeValueAsString(subProtocol.getServerToClientMessage(message)), null)
9499
}
95100

96101
/**

graphql/src/main/kotlin/com/trib3/graphql/websocket/GraphQLWebSocketCreator.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import kotlinx.coroutines.CoroutineDispatcher
1212
import kotlinx.coroutines.Dispatchers
1313
import kotlinx.coroutines.channels.Channel
1414
import kotlinx.coroutines.launch
15+
import org.eclipse.jetty.security.AuthenticationState
16+
import org.eclipse.jetty.server.Request
17+
import org.eclipse.jetty.util.Callback
1518
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest
1619
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse
1720
import org.eclipse.jetty.websocket.core.server.WebSocketCreator
@@ -55,6 +58,7 @@ class GraphQLWebSocketCreator(
5558
override fun createWebSocket(
5659
req: ServerUpgradeRequest,
5760
resp: ServerUpgradeResponse,
61+
callback: Callback,
5862
): Any {
5963
val containerRequestContext = convertToRequestContext(req)
6064
val subProtocol =
@@ -88,26 +92,27 @@ class GraphQLWebSocketCreator(
8892
* for doing auth in the [GraphQLWebSocketConsumer].
8993
*/
9094
internal fun convertToRequestContext(req: ServerUpgradeRequest): ContainerRequestContext {
95+
val authState = Request.getAuthenticationState(req) as? AuthenticationState.Succeeded
9196
val containerRequestContext =
9297
ContainerRequest(
93-
req.requestURI,
94-
req.requestURI,
98+
req.httpURI.toURI(),
99+
req.httpURI.toURI(),
95100
req.method,
96101
object : SecurityContext {
97-
override fun getUserPrincipal(): Principal? = req.userPrincipal
102+
override fun getUserPrincipal(): Principal? = authState?.userPrincipal
98103

99-
override fun isUserInRole(role: String?): Boolean = req.isUserInRole(role)
104+
override fun isUserInRole(role: String?): Boolean = authState?.isUserInRole(role) == true
100105

101106
override fun isSecure(): Boolean = req.isSecure
102107

103-
override fun getAuthenticationScheme(): String = req.httpServletRequest.authType
108+
override fun getAuthenticationScheme(): String? = authState?.authenticationType
104109
},
105110
MapPropertiesDelegate(emptyMap()),
106111
null,
107112
)
108113
// transfer HTTP headers
109-
req.headersMap?.forEach {
110-
containerRequestContext.headers(it.key, it.value)
114+
req.headers?.forEach {
115+
containerRequestContext.headers(it.name, it.value)
111116
}
112117
return containerRequestContext
113118
}

graphql/src/main/kotlin/com/trib3/graphql/websocket/GraphQLWebSocketProtocol.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes.Type
1010
import com.fasterxml.jackson.annotation.JsonTypeInfo
1111
import com.fasterxml.jackson.annotation.JsonValue
1212
import com.trib3.graphql.execution.MessageGraphQLError
13+
import org.eclipse.jetty.websocket.api.Callback
1314
import org.eclipse.jetty.websocket.api.Session
1415
import org.eclipse.jetty.websocket.api.StatusCode
1516
import kotlin.reflect.KClass
@@ -77,6 +78,7 @@ enum class GraphQLWebSocketSubProtocol(
7778
GraphQLWebSocketCloseReason.INVALID_MESSAGE.description
7879
.replace("<id>", msgId.orEmpty())
7980
.replace("<body>", msgBody),
81+
Callback.NOOP,
8082
)
8183
},
8284
onDuplicateQueryCallback = { message, adapter ->
@@ -86,6 +88,7 @@ enum class GraphQLWebSocketSubProtocol(
8688
"<unique-operation-id>",
8789
message.id.orEmpty(),
8890
),
91+
Callback.NOOP,
8992
)
9093
},
9194
onDuplicateInitCallback = { _, adapter ->
@@ -109,10 +112,10 @@ enum class GraphQLWebSocketSubProtocol(
109112
message: OperationMessage<T>,
110113
mapping: Map<String, String>,
111114
): OperationMessage<T> =
112-
if (!mapping.containsKey(message.type?.type)) {
115+
if (message.type == null || !mapping.containsKey(message.type.type)) {
113116
message
114117
} else {
115-
message.copy(type = message.type?.copy(type = mapping.getValue(message.type.type)))
118+
message.copy(type = message.type.copy(type = mapping.getValue(message.type.type)))
116119
}
117120

118121
/**
@@ -184,7 +187,7 @@ enum class GraphQLWebSocketCloseReason(
184187
* code/description pairs
185188
*/
186189
fun Session.close(reason: GraphQLWebSocketCloseReason) {
187-
this.close(reason.code, reason.description)
190+
this.close(reason.code, reason.description, Callback.NOOP)
188191
}
189192

190193
/**

0 commit comments

Comments
 (0)