@@ -33,18 +33,21 @@ import kotlinx.coroutines.Job
3333import kotlinx.coroutines.cancelChildren
3434import kotlinx.coroutines.supervisorScope
3535import 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
3639import org.eclipse.jetty.http.HttpStatus
40+ import org.eclipse.jetty.util.FutureCallback
3741import org.eclipse.jetty.util.URIUtil
3842import org.eclipse.jetty.websocket.core.Configuration
43+ import org.eclipse.jetty.websocket.core.WebSocketConstants
3944import org.eclipse.jetty.websocket.core.server.WebSocketCreator
4045import org.eclipse.jetty.websocket.core.server.WebSocketMappings
41- import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory
4246import java.security.Principal
4347import java.time.Duration
4448import java.util.Optional
4549import java.util.concurrent.ConcurrentHashMap
4650import javax.annotation.Nullable
47- import kotlin.collections.set
4851
4952private 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 }
0 commit comments