Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x
This commit is contained in:
commit
50654b0dde
|
@ -135,7 +135,7 @@
|
|||
<!-- ensure we don't have legacy comments that confuse javadoc tooling -->
|
||||
<module name="Regexp">
|
||||
<property name="id" value="LegacyMethodSeparators"/>
|
||||
<property name="format" value="/\*\s*[=*-]*\s*\*/"/>
|
||||
<property name="format" value="/\*\s*[=*-]+\s*\*/" />
|
||||
<property name="illegalPattern" value="true"/>
|
||||
<property name="ignoreComments" value="false"/>
|
||||
<property name="message" value="Legacy Method Separators"/>
|
||||
|
|
|
@ -54,23 +54,28 @@ public class TestSecurityAnnotationConversions
|
|||
{
|
||||
}
|
||||
|
||||
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = {
|
||||
"tom", "dick", "harry"
|
||||
}))
|
||||
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed =
|
||||
{
|
||||
"tom", "dick", "harry"
|
||||
}))
|
||||
public static class RolesServlet extends HttpServlet
|
||||
{
|
||||
}
|
||||
|
||||
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = {
|
||||
"tom", "dick", "harry"
|
||||
}), httpMethodConstraints = {@HttpMethodConstraint(value = "GET")})
|
||||
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed =
|
||||
{
|
||||
"tom", "dick", "harry"
|
||||
}), httpMethodConstraints = {@HttpMethodConstraint(value = "GET")})
|
||||
public static class Method1Servlet extends HttpServlet
|
||||
{
|
||||
}
|
||||
|
||||
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = {
|
||||
"tom", "dick", "harry"
|
||||
}), httpMethodConstraints = {@HttpMethodConstraint(value = "GET", transportGuarantee = TransportGuarantee.CONFIDENTIAL)})
|
||||
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed =
|
||||
{
|
||||
"tom", "dick", "harry"
|
||||
}), httpMethodConstraints = {
|
||||
@HttpMethodConstraint(value = "GET", transportGuarantee = TransportGuarantee.CONFIDENTIAL)
|
||||
})
|
||||
public static class Method2Servlet extends HttpServlet
|
||||
{
|
||||
}
|
||||
|
|
|
@ -37,11 +37,11 @@ public class HttpCookie
|
|||
private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
|
||||
|
||||
/**
|
||||
*If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true
|
||||
* If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true
|
||||
**/
|
||||
public static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
|
||||
/**
|
||||
*These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment
|
||||
* These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment
|
||||
**/
|
||||
private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
|
||||
public static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
|
||||
|
@ -474,10 +474,10 @@ public class HttpCookie
|
|||
LOG.debug("No default value for SameSite");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (o instanceof SameSite)
|
||||
return (SameSite)o;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
SameSite samesite = Enum.valueOf(SameSite.class, o.toString().trim().toUpperCase(Locale.ENGLISH));
|
||||
|
|
|
@ -53,7 +53,7 @@ public class TestJettyOSGiBootWithJsp
|
|||
public static Option[] configure()
|
||||
{
|
||||
ArrayList<Option> options = new ArrayList<>();
|
||||
|
||||
|
||||
options.addAll(TestOSGiUtil.configurePaxExamLogging());
|
||||
|
||||
options.add(CoreOptions.junitBundles());
|
||||
|
|
|
@ -39,12 +39,12 @@ public class LifeCycleCallbackCollectionTest
|
|||
{
|
||||
public static int postConstructCount = 0;
|
||||
public static int preDestroyCount = 0;
|
||||
|
||||
|
||||
public void postconstruct()
|
||||
{
|
||||
++postConstructCount;
|
||||
}
|
||||
|
||||
|
||||
public void predestroy()
|
||||
{
|
||||
++preDestroyCount;
|
||||
|
@ -53,7 +53,6 @@ public class LifeCycleCallbackCollectionTest
|
|||
|
||||
/**
|
||||
* An unsupported lifecycle callback type
|
||||
*
|
||||
*/
|
||||
public class TestLifeCycleCallback extends LifeCycleCallback
|
||||
{
|
||||
|
@ -77,7 +76,6 @@ public class LifeCycleCallbackCollectionTest
|
|||
/**
|
||||
* A class that we can use to simulate having PostConstruct and
|
||||
* PreDestroy annotations on.
|
||||
*
|
||||
*/
|
||||
public class SomeTestClass
|
||||
{
|
||||
|
@ -175,7 +173,7 @@ public class LifeCycleCallbackCollectionTest
|
|||
//expected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testServletPostConstructPreDestroy() throws Exception
|
||||
{
|
||||
|
@ -184,7 +182,7 @@ public class LifeCycleCallbackCollectionTest
|
|||
context.setResourceBase(MavenTestingUtils.getTargetTestingDir("predestroy-test").toURI().toURL().toString());
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
|
||||
//add a non-async servlet
|
||||
ServletHolder notAsync = new ServletHolder();
|
||||
notAsync.setHeldClass(TestServlet.class);
|
||||
|
@ -192,7 +190,7 @@ public class LifeCycleCallbackCollectionTest
|
|||
notAsync.setAsyncSupported(false);
|
||||
notAsync.setInitOrder(1);
|
||||
context.getServletHandler().addServletWithMapping(notAsync, "/notasync/*");
|
||||
|
||||
|
||||
//add an async servlet
|
||||
ServletHolder async = new ServletHolder();
|
||||
async.setHeldClass(TestServlet.class);
|
||||
|
@ -200,7 +198,7 @@ public class LifeCycleCallbackCollectionTest
|
|||
async.setAsyncSupported(true);
|
||||
async.setInitOrder(1);
|
||||
context.getServletHandler().addServletWithMapping(async, "/async/*");
|
||||
|
||||
|
||||
//add a run-as servlet
|
||||
ServletHolder runas = new ServletHolder();
|
||||
runas.setHeldClass(TestServlet.class);
|
||||
|
@ -208,7 +206,7 @@ public class LifeCycleCallbackCollectionTest
|
|||
runas.setRunAsRole("admin");
|
||||
runas.setInitOrder(1);
|
||||
context.getServletHandler().addServletWithMapping(runas, "/runas/*");
|
||||
|
||||
|
||||
//add both run-as and non async servlet
|
||||
ServletHolder both = new ServletHolder();
|
||||
both.setHeldClass(TestServlet.class);
|
||||
|
@ -217,7 +215,7 @@ public class LifeCycleCallbackCollectionTest
|
|||
both.setAsyncSupported(false);
|
||||
both.setInitOrder(1);
|
||||
context.getServletHandler().addServletWithMapping(both, "/both/*");
|
||||
|
||||
|
||||
//Make fake lifecycle callbacks for all servlets
|
||||
LifeCycleCallbackCollection collection = new LifeCycleCallbackCollection();
|
||||
context.setAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION, collection);
|
||||
|
@ -225,28 +223,28 @@ public class LifeCycleCallbackCollectionTest
|
|||
collection.add(pcNotAsync);
|
||||
PreDestroyCallback pdNotAsync = new PreDestroyCallback(TestServlet.class, "predestroy");
|
||||
collection.add(pdNotAsync);
|
||||
|
||||
|
||||
PostConstructCallback pcAsync = new PostConstructCallback(TestServlet.class, "postconstruct");
|
||||
collection.add(pcAsync);
|
||||
PreDestroyCallback pdAsync = new PreDestroyCallback(TestServlet.class, "predestroy");
|
||||
collection.add(pdAsync);
|
||||
|
||||
|
||||
PostConstructCallback pcRunAs = new PostConstructCallback(TestServlet.class, "postconstruct");
|
||||
collection.add(pcRunAs);
|
||||
PreDestroyCallback pdRunAs = new PreDestroyCallback(TestServlet.class, "predestroy");
|
||||
collection.add(pdRunAs);
|
||||
|
||||
|
||||
PostConstructCallback pcBoth = new PostConstructCallback(TestServlet.class, "postconstruct");
|
||||
collection.add(pcBoth);
|
||||
PreDestroyCallback pdBoth = new PreDestroyCallback(TestServlet.class, "predestroy");
|
||||
collection.add(pdBoth);
|
||||
|
||||
|
||||
server.start();
|
||||
|
||||
|
||||
assertEquals(4, TestServlet.postConstructCount);
|
||||
|
||||
|
||||
server.stop();
|
||||
|
||||
|
||||
assertEquals(4, TestServlet.preDestroyCount);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ import jakarta.servlet.MultipartConfigElement;
|
|||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletMapping;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
|
|
@ -414,7 +414,7 @@ public class ResponseTest
|
|||
|
||||
@Test
|
||||
public void testResponseCharacterEncoding() throws Exception
|
||||
{
|
||||
{
|
||||
_server.stop();
|
||||
ContextHandler handler = new CharEncodingContextHandler();
|
||||
_server.setHandler(handler);
|
||||
|
@ -429,7 +429,7 @@ public class ResponseTest
|
|||
|
||||
_channel.getRequest().setContext(null, "/");
|
||||
response.recycle();
|
||||
|
||||
|
||||
//test that explicit overrides default
|
||||
response = getResponse();
|
||||
_channel.getRequest().setContext(handler.getServletContext(), "/");
|
||||
|
@ -438,10 +438,10 @@ public class ResponseTest
|
|||
//getWriter should not change explicit character encoding
|
||||
response.getWriter();
|
||||
assertThat("ascii", Matchers.equalTo(response.getCharacterEncoding()));
|
||||
|
||||
|
||||
_channel.getRequest().setContext(null, "/");
|
||||
response.recycle();
|
||||
|
||||
|
||||
//test that assumed overrides default
|
||||
response = getResponse();
|
||||
_channel.getRequest().setContext(handler.getServletContext(), "/");
|
||||
|
@ -450,10 +450,10 @@ public class ResponseTest
|
|||
response.getWriter();
|
||||
//getWriter should not have modified character encoding
|
||||
assertThat("utf-8", Matchers.equalTo(response.getCharacterEncoding()));
|
||||
|
||||
|
||||
_channel.getRequest().setContext(null, "/");
|
||||
response.recycle();
|
||||
|
||||
|
||||
//test that inferred overrides default
|
||||
response = getResponse();
|
||||
_channel.getRequest().setContext(handler.getServletContext(), "/");
|
||||
|
@ -462,10 +462,10 @@ public class ResponseTest
|
|||
//getWriter should not have modified character encoding
|
||||
response.getWriter();
|
||||
assertThat("utf-8", Matchers.equalTo(response.getCharacterEncoding()));
|
||||
|
||||
|
||||
_channel.getRequest().setContext(null, "/");
|
||||
response.recycle();
|
||||
|
||||
|
||||
//test that without a default or any content type, use iso-8859-1
|
||||
response = getResponse();
|
||||
assertThat("iso-8859-1", Matchers.equalTo(response.getCharacterEncoding()));
|
||||
|
@ -473,7 +473,7 @@ public class ResponseTest
|
|||
response.getWriter();
|
||||
assertThat("iso-8859-1", Matchers.equalTo(response.getCharacterEncoding()));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLocaleAndContentTypeEncoding() throws Exception
|
||||
{
|
||||
|
@ -488,7 +488,7 @@ public class ResponseTest
|
|||
|
||||
Response response = getResponse();
|
||||
response.getHttpChannel().getRequest().setContext(handler.getServletContext(), "/");
|
||||
|
||||
|
||||
response.setContentType("text/html");
|
||||
assertEquals("iso-8859-1", response.getCharacterEncoding());
|
||||
|
||||
|
@ -562,14 +562,14 @@ public class ResponseTest
|
|||
response.setCharacterEncoding("ISO-8859-1");
|
||||
assertEquals("text/xml;charset=utf-8", response.getContentType());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testContentEncodingViaContentTypeChange() throws Exception
|
||||
{
|
||||
Response response = getResponse();
|
||||
response.setContentType("text/html;charset=Shift_Jis");
|
||||
assertEquals("Shift_Jis", response.getCharacterEncoding());
|
||||
|
||||
|
||||
response.setContentType("text/xml");
|
||||
assertEquals("Shift_Jis", response.getCharacterEncoding());
|
||||
}
|
||||
|
@ -1111,7 +1111,7 @@ public class ResponseTest
|
|||
|
||||
assertEquals("name=value; Path=/path; Domain=domain; Secure; HttpOnly", set);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAddCookieInInclude() throws Exception
|
||||
{
|
||||
|
@ -1128,7 +1128,7 @@ public class ResponseTest
|
|||
|
||||
assertNull(response.getHttpFields().get("Set-Cookie"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAddCookieSameSiteDefault() throws Exception
|
||||
{
|
||||
|
@ -1145,12 +1145,12 @@ public class ResponseTest
|
|||
response.addCookie(cookie);
|
||||
String set = response.getHttpFields().get("Set-Cookie");
|
||||
assertEquals("name=value; Path=/path; Domain=domain; Secure; HttpOnly; SameSite=Strict", set);
|
||||
|
||||
|
||||
response.getHttpFields().remove("Set-Cookie");
|
||||
|
||||
|
||||
//test bad default samesite value
|
||||
context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "FooBar");
|
||||
|
||||
|
||||
assertThrows(IllegalStateException.class,
|
||||
() -> response.addCookie(cookie));
|
||||
}
|
||||
|
@ -1225,7 +1225,7 @@ public class ResponseTest
|
|||
|
||||
response.setContentType("some/type");
|
||||
response.setContentLength(3);
|
||||
response.setHeader(HttpHeader.EXPIRES,"never");
|
||||
response.setHeader(HttpHeader.EXPIRES, "never");
|
||||
|
||||
response.setHeader("SomeHeader", "SomeValue");
|
||||
|
||||
|
@ -1293,7 +1293,7 @@ public class ResponseTest
|
|||
List<String> actual = Collections.list(response.getHttpFields().getValues("Set-Cookie"));
|
||||
assertThat("HttpCookie order", actual, hasItems(expected));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReplaceHttpCookieSameSite()
|
||||
{
|
||||
|
@ -1335,7 +1335,7 @@ public class ResponseTest
|
|||
actual = Collections.list(response.getHttpFields().getValues("Set-Cookie"));
|
||||
assertThat(actual, hasItems(new String[]{"Foo=replaced; Path=/path; Domain=Bah"}));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReplaceParsedHttpCookieSiteDefault()
|
||||
{
|
||||
|
@ -1378,7 +1378,7 @@ public class ResponseTest
|
|||
super(handler, new SessionData(id, "", "0.0.0.0", 0, 0, 0, 300));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestServletContextHandler extends ContextHandler
|
||||
{
|
||||
private class Context extends ContextHandler.Context
|
||||
|
@ -1400,7 +1400,7 @@ public class ResponseTest
|
|||
@Override
|
||||
public void setAttribute(String name, Object object)
|
||||
{
|
||||
_attributes.put(name,object);
|
||||
_attributes.put(name, object);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -447,7 +447,7 @@ public class ContextHandlerTest
|
|||
assertThat(connector.getResponse("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
|
||||
assertThat(connector.getResponse("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo/bar'"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testContextInitializationDestruction() throws Exception
|
||||
{
|
||||
|
@ -858,12 +858,12 @@ public class ContextHandlerTest
|
|||
writer.println("ctx='" + request.getContextPath() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestServletContextListener implements ServletContextListener
|
||||
{
|
||||
public int initialized = 0;
|
||||
public int destroyed = 0;
|
||||
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce)
|
||||
{
|
||||
|
|
|
@ -38,7 +38,6 @@ import java.util.stream.Stream;
|
|||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletException;
|
||||
|
|
|
@ -110,12 +110,12 @@ public class ServletContextHandlerTest
|
|||
private static final AtomicInteger __testServlets = new AtomicInteger();
|
||||
private static int __initIndex = 0;
|
||||
private static int __destroyIndex = 0;
|
||||
|
||||
|
||||
public class StopTestFilter implements Filter
|
||||
{
|
||||
int _initIndex;
|
||||
int _destroyIndex;
|
||||
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException
|
||||
{
|
||||
|
@ -136,7 +136,7 @@ public class ServletContextHandlerTest
|
|||
}
|
||||
|
||||
public class StopTestServlet extends GenericServlet
|
||||
{
|
||||
{
|
||||
int _initIndex;
|
||||
int _destroyIndex;
|
||||
|
||||
|
@ -182,18 +182,18 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
boolean callSessionTimeouts;
|
||||
int timeout;
|
||||
|
||||
|
||||
public MySCI(boolean callSessionTimeouts, int timeout)
|
||||
{
|
||||
this.callSessionTimeouts = callSessionTimeouts;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
|
||||
public MySCI()
|
||||
{
|
||||
this(false, -1);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
|
||||
{
|
||||
|
@ -274,24 +274,24 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
boolean callSessionTimeouts;
|
||||
int timeout;
|
||||
|
||||
|
||||
public MyContextListener(boolean callSessionTimeouts, int timeout)
|
||||
{
|
||||
this.callSessionTimeouts = callSessionTimeouts;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
|
||||
public MyContextListener()
|
||||
{
|
||||
this(false, -1);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce)
|
||||
{
|
||||
assertNull(sce.getServletContext().getAttribute("MyContextListener.contextInitialized"));
|
||||
sce.getServletContext().setAttribute("MyContextListener.contextInitialized", Boolean.TRUE);
|
||||
|
||||
|
||||
assertNull(sce.getServletContext().getAttribute("MyContextListener.defaultSessionTrackingModes"));
|
||||
try
|
||||
{
|
||||
|
@ -301,9 +301,9 @@ public class ServletContextHandlerTest
|
|||
catch (UnsupportedOperationException e)
|
||||
{
|
||||
//Should NOT be able to call getDefaultSessionTrackingModes from programmatic SCL
|
||||
sce.getServletContext().setAttribute("MyContextListener.defaultSessionTrackingModes", Boolean.TRUE);
|
||||
sce.getServletContext().setAttribute("MyContextListener.defaultSessionTrackingModes", Boolean.TRUE);
|
||||
}
|
||||
|
||||
|
||||
assertNull(sce.getServletContext().getAttribute("MyContextListener.effectiveSessionTrackingModes"));
|
||||
try
|
||||
{
|
||||
|
@ -313,9 +313,9 @@ public class ServletContextHandlerTest
|
|||
catch (UnsupportedOperationException e)
|
||||
{
|
||||
//Should NOT be able to call getEffectiveSessionTrackingModes from programmatic SCL
|
||||
sce.getServletContext().setAttribute("MyContextListener.effectiveSessionTrackingModes", Boolean.TRUE);
|
||||
sce.getServletContext().setAttribute("MyContextListener.effectiveSessionTrackingModes", Boolean.TRUE);
|
||||
}
|
||||
|
||||
|
||||
assertNull(sce.getServletContext().getAttribute("MyContextListener.setSessionTrackingModes"));
|
||||
try
|
||||
{
|
||||
|
@ -325,9 +325,9 @@ public class ServletContextHandlerTest
|
|||
catch (UnsupportedOperationException e)
|
||||
{
|
||||
//Should NOT be able to call setSessionTrackingModes from programmatic SCL
|
||||
sce.getServletContext().setAttribute("MyContextListener.setSessionTrackingModes", Boolean.TRUE);
|
||||
sce.getServletContext().setAttribute("MyContextListener.setSessionTrackingModes", Boolean.TRUE);
|
||||
}
|
||||
|
||||
|
||||
if (callSessionTimeouts)
|
||||
{
|
||||
try
|
||||
|
@ -338,9 +338,9 @@ public class ServletContextHandlerTest
|
|||
catch (UnsupportedOperationException e)
|
||||
{
|
||||
//Should NOT be able to call setSessionTimeout from this SCL
|
||||
sce.getServletContext().setAttribute("MyContextListener.setSessionTimeout", Boolean.TRUE);
|
||||
sce.getServletContext().setAttribute("MyContextListener.setSessionTimeout", Boolean.TRUE);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
sce.getServletContext().getSessionTimeout();
|
||||
|
@ -349,7 +349,7 @@ public class ServletContextHandlerTest
|
|||
catch (UnsupportedOperationException e)
|
||||
{
|
||||
//Should NOT be able to call getSessionTimeout from this SCL
|
||||
sce.getServletContext().setAttribute("MyContextListener.getSessionTimeout", Boolean.TRUE);
|
||||
sce.getServletContext().setAttribute("MyContextListener.getSessionTimeout", Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -384,7 +384,7 @@ public class ServletContextHandlerTest
|
|||
}
|
||||
|
||||
public static class MyTestSessionListener implements HttpSessionAttributeListener, HttpSessionListener
|
||||
{
|
||||
{
|
||||
@Override
|
||||
public void sessionCreated(HttpSessionEvent se)
|
||||
{
|
||||
|
@ -410,13 +410,13 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class MySCAListener implements ServletContextAttributeListener
|
||||
{
|
||||
public static int adds = 0;
|
||||
public static int removes = 0;
|
||||
public static int replaces = 0;
|
||||
|
||||
|
||||
@Override
|
||||
public void attributeAdded(ServletContextAttributeEvent event)
|
||||
{
|
||||
|
@ -443,7 +443,7 @@ public class ServletContextHandlerTest
|
|||
|
||||
@Override
|
||||
public void requestDestroyed(ServletRequestEvent sre)
|
||||
{
|
||||
{
|
||||
++destroys;
|
||||
}
|
||||
|
||||
|
@ -453,13 +453,13 @@ public class ServletContextHandlerTest
|
|||
++inits;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class MyRAListener implements ServletRequestAttributeListener
|
||||
{
|
||||
public static int adds = 0;
|
||||
public static int removes = 0;
|
||||
public static int replaces = 0;
|
||||
|
||||
|
||||
@Override
|
||||
public void attributeAdded(ServletRequestAttributeEvent srae)
|
||||
{
|
||||
|
@ -478,7 +478,7 @@ public class ServletContextHandlerTest
|
|||
++replaces;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class MySListener implements HttpSessionListener
|
||||
{
|
||||
public static int creates = 0;
|
||||
|
@ -495,15 +495,14 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
++destroys;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class MySAListener implements HttpSessionAttributeListener
|
||||
{
|
||||
public static int adds = 0;
|
||||
public static int removes = 0;
|
||||
public static int replaces = 0;
|
||||
|
||||
|
||||
@Override
|
||||
public void attributeAdded(HttpSessionBindingEvent event)
|
||||
{
|
||||
|
@ -520,20 +519,20 @@ public class ServletContextHandlerTest
|
|||
public void attributeReplaced(HttpSessionBindingEvent event)
|
||||
{
|
||||
++replaces;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class MySIListener implements HttpSessionIdListener
|
||||
{
|
||||
public static int changes = 0;
|
||||
|
||||
|
||||
@Override
|
||||
public void sessionIdChanged(HttpSessionEvent event, String oldSessionId)
|
||||
{
|
||||
++changes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ServletContextListener that is designed to be added programmatically,
|
||||
* which should make all of the createListener, createServlet, createFilter
|
||||
|
@ -558,7 +557,7 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
fail(e);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
sce.getServletContext().createServlet(HelloServlet.class);
|
||||
|
@ -572,7 +571,7 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
fail(e);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
sce.getServletContext().createListener(MyContextListener.class);
|
||||
|
@ -587,13 +586,13 @@ public class ServletContextHandlerTest
|
|||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce)
|
||||
{
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class InitialListener implements ServletContextListener
|
||||
{
|
||||
@Override
|
||||
|
@ -639,16 +638,15 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
fail(x);
|
||||
}
|
||||
|
||||
|
||||
sce.getServletContext().setAttribute("foo", "bar");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
|
@ -667,7 +665,7 @@ public class ServletContextHandlerTest
|
|||
_server.stop();
|
||||
_server.join();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInitParams() throws Exception
|
||||
{
|
||||
|
@ -681,7 +679,7 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
sce.getServletContext().setInitParameter("foo", "bar");
|
||||
assertEquals("bar", sce.getServletContext().getInitParameter("foo"));
|
||||
assertThrows(NullPointerException.class,
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> sce.getServletContext().setInitParameter(null, "bad")
|
||||
);
|
||||
assertThrows(NullPointerException.class,
|
||||
|
@ -689,11 +687,11 @@ public class ServletContextHandlerTest
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
root.getServletHandler().addListener(initialListener);
|
||||
_server.start();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetSetSessionTimeout() throws Exception
|
||||
{
|
||||
|
@ -706,7 +704,7 @@ public class ServletContextHandlerTest
|
|||
root.getSessionHandler().setMaxInactiveInterval((int)TimeUnit.MINUTES.toSeconds(startMin));
|
||||
root.addBean(new MySCIStarter(root.getServletContext(), new MySCI(true, timeout.intValue())), true);
|
||||
_server.start();
|
||||
|
||||
|
||||
//test starting value of setSessionTimeout
|
||||
assertEquals(startMin, (Integer)root.getServletContext().getAttribute("MYSCI.startSessionTimeout"));
|
||||
//test can set session timeout from ServletContainerInitializer
|
||||
|
@ -741,7 +739,7 @@ public class ServletContextHandlerTest
|
|||
root.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
_server.start();
|
||||
_server.stop();
|
||||
|
||||
|
||||
assertEquals(0, stopTestListener._initIndex); //listeners contextInitialized called first
|
||||
assertEquals(1, stopTestFilter._initIndex); //filters init
|
||||
assertEquals(2, stopTestServlet._initIndex); //servlets init
|
||||
|
@ -750,7 +748,7 @@ public class ServletContextHandlerTest
|
|||
assertEquals(1, stopTestServlet._destroyIndex); //servlets destroyed next
|
||||
assertEquals(2, stopTestListener._destroyIndex); //listener contextDestroyed last
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAddSessionListener() throws Exception
|
||||
{
|
||||
|
@ -787,7 +785,7 @@ public class ServletContextHandlerTest
|
|||
assertTrue((Boolean)root.getServletContext().getAttribute("MyContextListener.effectiveSessionTrackingModes"));
|
||||
assertTrue((Boolean)root.getServletContext().getAttribute("MyContextListener.setSessionTrackingModes"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testContextInitializationDestruction() throws Exception
|
||||
{
|
||||
|
@ -800,7 +798,7 @@ public class ServletContextHandlerTest
|
|||
{
|
||||
public int initialized = 0;
|
||||
public int destroyed = 0;
|
||||
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce)
|
||||
{
|
||||
|
@ -813,7 +811,7 @@ public class ServletContextHandlerTest
|
|||
destroyed++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TestServletContextListener listener = new TestServletContextListener();
|
||||
root.addEventListener(listener);
|
||||
server.start();
|
||||
|
@ -822,7 +820,7 @@ public class ServletContextHandlerTest
|
|||
server.stop();
|
||||
assertEquals(1, listener.destroyed);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testListenersFromContextListener() throws Exception
|
||||
{
|
||||
|
@ -835,7 +833,7 @@ public class ServletContextHandlerTest
|
|||
root.getServletHandler().addListener(initialListener);
|
||||
ServletHolder holder0 = root.addServlet(TestServlet.class, "/test");
|
||||
_server.start();
|
||||
|
||||
|
||||
ListenerHolder[] listenerHolders = root.getServletHandler().getListeners();
|
||||
assertNotNull(listenerHolders);
|
||||
for (ListenerHolder l : listenerHolders)
|
||||
|
@ -852,22 +850,24 @@ public class ServletContextHandlerTest
|
|||
|
||||
List<String> listenerClassNames = new ArrayList<>();
|
||||
for (EventListener l : root.getEventListeners())
|
||||
{
|
||||
listenerClassNames.add(l.getClass().getName());
|
||||
|
||||
}
|
||||
|
||||
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MySCAListener"));
|
||||
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MyRequestListener"));
|
||||
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MyRAListener"));
|
||||
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MySListener"));
|
||||
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MySAListener"));
|
||||
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MySIListener"));
|
||||
|
||||
|
||||
//test ServletRequestAttributeListener
|
||||
String response = _connector.getResponse("GET /test?req=all HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("200 OK"));
|
||||
assertEquals(1, MyRAListener.adds);
|
||||
assertEquals(1, MyRAListener.replaces);
|
||||
assertEquals(1, MyRAListener.removes);
|
||||
|
||||
|
||||
//test HttpSessionAttributeListener
|
||||
response = _connector.getResponse("GET /test?session=create HTTP/1.0\r\n\r\n");
|
||||
String sessionid = response.substring(response.indexOf("JSESSIONID"), response.indexOf(";"));
|
||||
|
@ -898,7 +898,7 @@ public class ServletContextHandlerTest
|
|||
assertEquals(1, MySAListener.adds);
|
||||
assertEquals(1, MySAListener.replaces);
|
||||
assertEquals(1, MySAListener.removes);
|
||||
|
||||
|
||||
//test HttpSessionIdListener.sessionIdChanged
|
||||
request = new StringBuffer();
|
||||
request.append("GET /test?session=change HTTP/1.0\n");
|
||||
|
@ -909,7 +909,7 @@ public class ServletContextHandlerTest
|
|||
assertThat(response, Matchers.containsString("200 OK"));
|
||||
assertEquals(1, MySIListener.changes);
|
||||
sessionid = response.substring(response.indexOf("JSESSIONID"), response.indexOf(";"));
|
||||
|
||||
|
||||
//test HttpServletListener.sessionDestroyed
|
||||
request = new StringBuffer();
|
||||
request.append("GET /test?session=delete HTTP/1.0\n");
|
||||
|
@ -919,7 +919,7 @@ public class ServletContextHandlerTest
|
|||
response = _connector.getResponse(request.toString());
|
||||
assertThat(response, Matchers.containsString("200 OK"));
|
||||
assertEquals(1, MySListener.destroys);
|
||||
|
||||
|
||||
//test ServletContextAttributeListener
|
||||
//attribute was set when context listener registered
|
||||
assertEquals(1, MySCAListener.adds);
|
||||
|
@ -1018,10 +1018,10 @@ public class ServletContextHandlerTest
|
|||
assertTrue(e.getCause() instanceof IllegalStateException);
|
||||
}
|
||||
else
|
||||
fail(e);
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateMethodsFromSCI() throws Exception
|
||||
{
|
||||
|
@ -1063,11 +1063,11 @@ public class ServletContextHandlerTest
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
root.addBean(new MySCIStarter(root.getServletContext(), new FilterCreatingSCI()), true);
|
||||
_server.start();
|
||||
_server.start();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateMethodsFromSCL() throws Exception
|
||||
{
|
||||
|
@ -1084,7 +1084,7 @@ public class ServletContextHandlerTest
|
|||
ctx.addListener(new CreatingSCL());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
root.addBean(new MySCIStarter(root.getServletContext(), new ListenerCreatingSCI()), true);
|
||||
_server.start();
|
||||
assertTrue((Boolean)root.getServletContext().getAttribute("CreatingSCL.filter"));
|
||||
|
@ -1148,9 +1148,8 @@ public class ServletContextHandlerTest
|
|||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
});
|
||||
context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
context.getServletHandler().setStartWithUnavailable(false);
|
||||
|
@ -1200,9 +1199,8 @@ public class ServletContextHandlerTest
|
|||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
});
|
||||
context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
context.getServletHandler().setStartWithUnavailable(false);
|
||||
|
@ -1252,9 +1250,8 @@ public class ServletContextHandlerTest
|
|||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
});
|
||||
context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
context.getServletHandler().setStartWithUnavailable(false);
|
||||
|
@ -1275,7 +1272,7 @@ public class ServletContextHandlerTest
|
|||
fail(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddServletFromSCL() throws Exception
|
||||
|
@ -1296,9 +1293,8 @@ public class ServletContextHandlerTest
|
|||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce)
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
});
|
||||
_server.setHandler(context);
|
||||
_server.start();
|
||||
|
@ -1329,10 +1325,10 @@ public class ServletContextHandlerTest
|
|||
rego.addMapping("/hello/*");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
root.addBean(new MySCIStarter(root.getServletContext(), new ServletAddingSCI()), true);
|
||||
_server.start();
|
||||
|
||||
|
||||
StringBuffer request = new StringBuffer();
|
||||
request.append("GET /hello HTTP/1.0\n");
|
||||
request.append("Host: localhost\n");
|
||||
|
@ -1455,7 +1451,7 @@ public class ServletContextHandlerTest
|
|||
assertNotNull(mappedServlet.getServletHolder());
|
||||
assertEquals("some.jsp", mappedServlet.getServletHolder().getName());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAddServletAfterStart() throws Exception
|
||||
{
|
||||
|
@ -1483,7 +1479,7 @@ public class ServletContextHandlerTest
|
|||
response = _connector.getResponse(request.toString());
|
||||
assertThat("Response", response, containsString("Hello World"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testServletRegistrationByClass() throws Exception
|
||||
{
|
||||
|
@ -1523,7 +1519,7 @@ public class ServletContextHandlerTest
|
|||
String response = _connector.getResponse(request.toString());
|
||||
assertThat("Response", response, containsString("Test"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPartialServletRegistrationByName() throws Exception
|
||||
{
|
||||
|
@ -1532,7 +1528,7 @@ public class ServletContextHandlerTest
|
|||
ServletHolder partial = new ServletHolder();
|
||||
partial.setName("test");
|
||||
context.addServlet(partial, "/test");
|
||||
|
||||
|
||||
//complete partial servlet registration by providing name of the servlet class
|
||||
ServletRegistration reg = context.getServletContext().addServlet("test", TestServlet.class.getName());
|
||||
assertNotNull(reg);
|
||||
|
@ -1549,7 +1545,7 @@ public class ServletContextHandlerTest
|
|||
String response = _connector.getResponse(request.toString());
|
||||
assertThat("Response", response, containsString("Test"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPartialServletRegistrationByClass() throws Exception
|
||||
{
|
||||
|
@ -1558,7 +1554,7 @@ public class ServletContextHandlerTest
|
|||
ServletHolder partial = new ServletHolder();
|
||||
partial.setName("test");
|
||||
context.addServlet(partial, "/test");
|
||||
|
||||
|
||||
//complete partial servlet registration by providing the servlet class
|
||||
ServletRegistration reg = context.getServletContext().addServlet("test", TestServlet.class);
|
||||
assertNotNull(reg);
|
||||
|
@ -1576,7 +1572,7 @@ public class ServletContextHandlerTest
|
|||
String response = _connector.getResponse(request.toString());
|
||||
assertThat("Response", response, containsString("Test"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNullServletRegistration() throws Exception
|
||||
{
|
||||
|
@ -1586,7 +1582,7 @@ public class ServletContextHandlerTest
|
|||
full.setName("test");
|
||||
full.setHeldClass(TestServlet.class);
|
||||
context.addServlet(full, "/test");
|
||||
|
||||
|
||||
//Must return null if the servlet has been fully defined previously
|
||||
ServletRegistration reg = context.getServletContext().addServlet("test", TestServlet.class);
|
||||
assertNull(reg);
|
||||
|
@ -1602,7 +1598,7 @@ public class ServletContextHandlerTest
|
|||
String response = _connector.getResponse(request.toString());
|
||||
assertThat("Response", response, containsString("Test"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHandlerBeforeServletHandler() throws Exception
|
||||
{
|
||||
|
@ -1701,23 +1697,23 @@ public class ServletContextHandlerTest
|
|||
@Override
|
||||
protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response,
|
||||
Object constraintInfo, UserIdentity userIdentity)
|
||||
throws IOException
|
||||
throws IOException
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//check the linking order
|
||||
context.setSecurityHandler(myHandler);
|
||||
assertSame(myHandler, context.getSecurityHandler());
|
||||
|
||||
|
||||
h = (HandlerWrapper)context.getHandler();
|
||||
assertSame(h, sessionHandler);
|
||||
|
||||
h = (HandlerWrapper)h.getHandler();
|
||||
assertSame(h, myHandler);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReplaceServletHandlerWithoutServlet() throws Exception
|
||||
{
|
||||
|
@ -1857,13 +1853,13 @@ public class ServletContextHandlerTest
|
|||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
{
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
PrintWriter writer = resp.getWriter();
|
||||
writer.write("Hello World");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class MyFilter implements Filter
|
||||
{
|
||||
|
||||
|
@ -1931,10 +1927,10 @@ public class ServletContextHandlerTest
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class FakeJspServlet extends HttpServlet
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class ServletAddingServlet extends HttpServlet
|
||||
|
@ -1953,7 +1949,7 @@ public class ServletContextHandlerTest
|
|||
dynamic.addMapping("/added/*");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class FilterAddingServlet extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
|
@ -2006,7 +2002,7 @@ public class ServletContextHandlerTest
|
|||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
PrintWriter writer = resp.getWriter();
|
||||
writer.write("Test");
|
||||
|
||||
|
||||
String action = req.getParameter("session");
|
||||
if (!Objects.isNull(action))
|
||||
{
|
||||
|
@ -2038,7 +2034,7 @@ public class ServletContextHandlerTest
|
|||
}
|
||||
else
|
||||
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2049,10 +2045,10 @@ public class ServletContextHandlerTest
|
|||
req.setAttribute("some", "value");
|
||||
req.setAttribute("some", "other");
|
||||
req.removeAttribute("some");
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
action = req.getParameter("ctx");
|
||||
if (!Objects.isNull(action))
|
||||
{
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.io.IOException;
|
|||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.websocket.core.Configuration;
|
||||
import org.eclipse.jetty.websocket.core.server.internal.HandshakerSelector;
|
||||
|
||||
|
|
|
@ -48,14 +48,10 @@ public class JakartaWebSocketClientFrameHandlerFactory extends JakartaWebSocketF
|
|||
public JakartaWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass, EndpointConfig endpointConfig)
|
||||
{
|
||||
if (jakarta.websocket.Endpoint.class.isAssignableFrom(endpointClass))
|
||||
{
|
||||
return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig);
|
||||
}
|
||||
|
||||
if (endpointClass.getAnnotation(ClientEndpoint.class) == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
|
||||
return discoverJakartaFrameHandlerMetadata(endpointClass, metadata);
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package org.eclipse.jetty.websocket.jakarta.common;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
|
@ -46,6 +45,7 @@ import org.eclipse.jetty.websocket.core.OpCode;
|
|||
import org.eclipse.jetty.websocket.core.exception.ProtocolException;
|
||||
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedBinaryMessageSink;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedBinaryStreamMessageSink;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextMessageSink;
|
||||
|
@ -95,9 +95,9 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
private MethodHandle openHandle;
|
||||
private MethodHandle closeHandle;
|
||||
private MethodHandle errorHandle;
|
||||
private JakartaWebSocketFrameHandlerMetadata.MessageMetadata textMetadata;
|
||||
private JakartaWebSocketFrameHandlerMetadata.MessageMetadata binaryMetadata;
|
||||
private MethodHandle pongHandle;
|
||||
private JakartaWebSocketMessageMetadata textMetadata;
|
||||
private JakartaWebSocketMessageMetadata binaryMetadata;
|
||||
|
||||
private UpgradeRequest upgradeRequest;
|
||||
|
||||
|
@ -114,8 +114,8 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
public JakartaWebSocketFrameHandler(JakartaWebSocketContainer container,
|
||||
Object endpointInstance,
|
||||
MethodHandle openHandle, MethodHandle closeHandle, MethodHandle errorHandle,
|
||||
JakartaWebSocketFrameHandlerMetadata.MessageMetadata textMetadata,
|
||||
JakartaWebSocketFrameHandlerMetadata.MessageMetadata binaryMetadata,
|
||||
JakartaWebSocketMessageMetadata textMetadata,
|
||||
JakartaWebSocketMessageMetadata binaryMetadata,
|
||||
MethodHandle pongHandle,
|
||||
EndpointConfig endpointConfig)
|
||||
{
|
||||
|
@ -170,26 +170,32 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
errorHandle = InvokerUtils.bindTo(errorHandle, session);
|
||||
pongHandle = InvokerUtils.bindTo(pongHandle, session);
|
||||
|
||||
JakartaWebSocketFrameHandlerMetadata.MessageMetadata actualTextMetadata = JakartaWebSocketFrameHandlerMetadata.MessageMetadata.copyOf(textMetadata);
|
||||
JakartaWebSocketMessageMetadata actualTextMetadata = JakartaWebSocketMessageMetadata.copyOf(textMetadata);
|
||||
if (actualTextMetadata != null)
|
||||
{
|
||||
if (actualTextMetadata.isMaxMessageSizeSet())
|
||||
session.setMaxTextMessageBufferSize(actualTextMetadata.maxMessageSize);
|
||||
session.setMaxTextMessageBufferSize(actualTextMetadata.getMaxMessageSize());
|
||||
|
||||
MethodHandle methodHandle = actualTextMetadata.getMethodHandle();
|
||||
methodHandle = InvokerUtils.bindTo(methodHandle, endpointInstance, endpointConfig, session);
|
||||
methodHandle = JakartaWebSocketFrameHandlerFactory.wrapNonVoidReturnType(methodHandle, session);
|
||||
actualTextMetadata.setMethodHandle(methodHandle);
|
||||
|
||||
actualTextMetadata.handle = InvokerUtils.bindTo(actualTextMetadata.handle, endpointInstance, endpointConfig, session);
|
||||
actualTextMetadata.handle = JakartaWebSocketFrameHandlerFactory.wrapNonVoidReturnType(actualTextMetadata.handle, session);
|
||||
textSink = JakartaWebSocketFrameHandlerFactory.createMessageSink(session, actualTextMetadata);
|
||||
textMetadata = actualTextMetadata;
|
||||
}
|
||||
|
||||
JakartaWebSocketFrameHandlerMetadata.MessageMetadata actualBinaryMetadata = JakartaWebSocketFrameHandlerMetadata.MessageMetadata.copyOf(binaryMetadata);
|
||||
JakartaWebSocketMessageMetadata actualBinaryMetadata = JakartaWebSocketMessageMetadata.copyOf(binaryMetadata);
|
||||
if (actualBinaryMetadata != null)
|
||||
{
|
||||
if (actualBinaryMetadata.isMaxMessageSizeSet())
|
||||
session.setMaxBinaryMessageBufferSize(actualBinaryMetadata.maxMessageSize);
|
||||
session.setMaxBinaryMessageBufferSize(actualBinaryMetadata.getMaxMessageSize());
|
||||
|
||||
MethodHandle methodHandle = actualBinaryMetadata.getMethodHandle();
|
||||
methodHandle = InvokerUtils.bindTo(methodHandle, endpointInstance, endpointConfig, session);
|
||||
methodHandle = JakartaWebSocketFrameHandlerFactory.wrapNonVoidReturnType(methodHandle, session);
|
||||
actualBinaryMetadata.setMethodHandle(methodHandle);
|
||||
|
||||
actualBinaryMetadata.handle = InvokerUtils.bindTo(actualBinaryMetadata.handle, endpointInstance, endpointConfig, session);
|
||||
actualBinaryMetadata.handle = JakartaWebSocketFrameHandlerFactory.wrapNonVoidReturnType(actualBinaryMetadata.handle, session);
|
||||
binarySink = JakartaWebSocketFrameHandlerFactory.createMessageSink(session, actualBinaryMetadata);
|
||||
binaryMetadata = actualBinaryMetadata;
|
||||
}
|
||||
|
@ -350,64 +356,42 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
return messageHandlerMap;
|
||||
}
|
||||
|
||||
public JakartaWebSocketFrameHandlerMetadata.MessageMetadata getBinaryMetadata()
|
||||
public JakartaWebSocketMessageMetadata getBinaryMetadata()
|
||||
{
|
||||
return binaryMetadata;
|
||||
}
|
||||
|
||||
public JakartaWebSocketFrameHandlerMetadata.MessageMetadata getTextMetadata()
|
||||
public JakartaWebSocketMessageMetadata getTextMetadata()
|
||||
{
|
||||
return textMetadata;
|
||||
}
|
||||
|
||||
private void assertBasicTypeNotRegistered(byte basicWebSocketType, Object messageImpl, String replacement)
|
||||
{
|
||||
if (messageImpl != null)
|
||||
{
|
||||
throw new IllegalStateException(
|
||||
"Cannot register " + replacement + ": Basic WebSocket type " + OpCode.name(basicWebSocketType) + " is already registered");
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void addMessageHandler(JakartaWebSocketSession session, Class<T> clazz, MessageHandler.Partial<T> handler)
|
||||
public <T> void addMessageHandler(Class<T> clazz, MessageHandler.Partial<T> handler)
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodHandles.Lookup lookup = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup();
|
||||
MethodHandle partialMessageHandler = lookup
|
||||
.findVirtual(MessageHandler.Partial.class, "onMessage", MethodType.methodType(void.class, Object.class, boolean.class));
|
||||
partialMessageHandler = partialMessageHandler.bindTo(handler);
|
||||
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
|
||||
.findVirtual(MessageHandler.Partial.class, "onMessage", MethodType.methodType(void.class, Object.class, boolean.class))
|
||||
.bindTo(handler);
|
||||
|
||||
JakartaWebSocketMessageMetadata metadata = new JakartaWebSocketMessageMetadata();
|
||||
metadata.setMethodHandle(methodHandle);
|
||||
byte basicType;
|
||||
// MessageHandler.Partial has no decoder support!
|
||||
if (byte[].class.isAssignableFrom(clazz))
|
||||
{
|
||||
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
|
||||
MessageSink messageSink = new PartialByteArrayMessageSink(coreSession, partialMessageHandler);
|
||||
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
|
||||
JakartaWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JakartaWebSocketFrameHandlerMetadata.MessageMetadata();
|
||||
metadata.handle = partialMessageHandler;
|
||||
metadata.sinkClass = PartialByteArrayMessageSink.class;
|
||||
this.binaryMetadata = metadata;
|
||||
basicType = OpCode.BINARY;
|
||||
metadata.setSinkClass(PartialByteArrayMessageSink.class);
|
||||
}
|
||||
else if (ByteBuffer.class.isAssignableFrom(clazz))
|
||||
{
|
||||
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
|
||||
MessageSink messageSink = new PartialByteBufferMessageSink(coreSession, partialMessageHandler);
|
||||
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
|
||||
JakartaWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JakartaWebSocketFrameHandlerMetadata.MessageMetadata();
|
||||
metadata.handle = partialMessageHandler;
|
||||
metadata.sinkClass = PartialByteBufferMessageSink.class;
|
||||
this.binaryMetadata = metadata;
|
||||
basicType = OpCode.BINARY;
|
||||
metadata.setSinkClass(PartialByteBufferMessageSink.class);
|
||||
}
|
||||
else if (String.class.isAssignableFrom(clazz))
|
||||
{
|
||||
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
|
||||
MessageSink messageSink = new PartialStringMessageSink(coreSession, partialMessageHandler);
|
||||
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
|
||||
JakartaWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JakartaWebSocketFrameHandlerMetadata.MessageMetadata();
|
||||
metadata.handle = partialMessageHandler;
|
||||
metadata.sinkClass = PartialStringMessageSink.class;
|
||||
this.textMetadata = metadata;
|
||||
basicType = OpCode.TEXT;
|
||||
metadata.setSinkClass(PartialStringMessageSink.class);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -415,6 +399,9 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
"Unable to add " + handler.getClass().getName() + " with type " + clazz + ": only supported types byte[], " + ByteBuffer.class.getName() +
|
||||
", " + String.class.getName());
|
||||
}
|
||||
|
||||
// Register the Metadata as a MessageHandler.
|
||||
registerMessageHandler(clazz, handler, basicType, metadata);
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
|
@ -426,75 +413,62 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
}
|
||||
}
|
||||
|
||||
public <T> void addMessageHandler(JakartaWebSocketSession session, Class<T> clazz, MessageHandler.Whole<T> handler)
|
||||
public <T> void addMessageHandler(Class<T> clazz, MessageHandler.Whole<T> handler)
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodHandles.Lookup lookup = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup();
|
||||
MethodHandle wholeMsgMethodHandle = lookup.findVirtual(MessageHandler.Whole.class, "onMessage", MethodType.methodType(void.class, Object.class));
|
||||
wholeMsgMethodHandle = wholeMsgMethodHandle.bindTo(handler);
|
||||
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
|
||||
.findVirtual(MessageHandler.Whole.class, "onMessage", MethodType.methodType(void.class, Object.class))
|
||||
.bindTo(handler);
|
||||
|
||||
if (PongMessage.class.isAssignableFrom(clazz))
|
||||
{
|
||||
assertBasicTypeNotRegistered(OpCode.PONG, this.pongHandle, handler.getClass().getName());
|
||||
this.pongHandle = wholeMsgMethodHandle;
|
||||
assertBasicTypeNotRegistered(OpCode.PONG, handler);
|
||||
this.pongHandle = methodHandle;
|
||||
registerMessageHandler(OpCode.PONG, clazz, handler, null);
|
||||
return;
|
||||
}
|
||||
|
||||
AvailableDecoders availableDecoders = session.getDecoders();
|
||||
RegisteredDecoder registeredDecoder = availableDecoders.getFirstRegisteredDecoder(clazz);
|
||||
if (registeredDecoder == null)
|
||||
throw new IllegalStateException("Unable to find Decoder for type: " + clazz);
|
||||
|
||||
// Create the message metadata specific to the MessageHandler type.
|
||||
JakartaWebSocketMessageMetadata metadata = new JakartaWebSocketMessageMetadata();
|
||||
metadata.setMethodHandle(methodHandle);
|
||||
byte basicType;
|
||||
if (registeredDecoder.implementsInterface(Decoder.Binary.class))
|
||||
{
|
||||
basicType = OpCode.BINARY;
|
||||
metadata.setRegisteredDecoders(availableDecoders.getBinaryDecoders(clazz));
|
||||
metadata.setSinkClass(DecodedBinaryMessageSink.class);
|
||||
}
|
||||
else if (registeredDecoder.implementsInterface(Decoder.BinaryStream.class))
|
||||
{
|
||||
basicType = OpCode.BINARY;
|
||||
metadata.setRegisteredDecoders(availableDecoders.getBinaryStreamDecoders(clazz));
|
||||
metadata.setSinkClass(DecodedBinaryStreamMessageSink.class);
|
||||
}
|
||||
else if (registeredDecoder.implementsInterface(Decoder.Text.class))
|
||||
{
|
||||
basicType = OpCode.TEXT;
|
||||
metadata.setRegisteredDecoders(availableDecoders.getTextDecoders(clazz));
|
||||
metadata.setSinkClass(DecodedTextMessageSink.class);
|
||||
}
|
||||
else if (registeredDecoder.implementsInterface(Decoder.TextStream.class))
|
||||
{
|
||||
basicType = OpCode.TEXT;
|
||||
metadata.setRegisteredDecoders(availableDecoders.getTextStreamDecoders(clazz));
|
||||
metadata.setSinkClass(DecodedTextStreamMessageSink.class);
|
||||
}
|
||||
else
|
||||
{
|
||||
AvailableDecoders availableDecoders = session.getDecoders();
|
||||
|
||||
AvailableDecoders.RegisteredDecoder registeredDecoder = availableDecoders.getRegisteredDecoderFor(clazz);
|
||||
if (registeredDecoder == null)
|
||||
{
|
||||
throw new IllegalStateException("Unable to find Decoder for type: " + clazz);
|
||||
}
|
||||
|
||||
JakartaWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JakartaWebSocketFrameHandlerMetadata.MessageMetadata();
|
||||
metadata.handle = wholeMsgMethodHandle;
|
||||
metadata.registeredDecoder = registeredDecoder;
|
||||
|
||||
if (registeredDecoder.implementsInterface(Decoder.Binary.class))
|
||||
{
|
||||
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
|
||||
Decoder.Binary<T> decoder = availableDecoders.getInstanceOf(registeredDecoder);
|
||||
MessageSink messageSink = new DecodedBinaryMessageSink(coreSession, decoder, wholeMsgMethodHandle);
|
||||
metadata.sinkClass = messageSink.getClass();
|
||||
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
|
||||
this.binaryMetadata = metadata;
|
||||
}
|
||||
else if (registeredDecoder.implementsInterface(Decoder.BinaryStream.class))
|
||||
{
|
||||
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
|
||||
Decoder.BinaryStream<T> decoder = availableDecoders.getInstanceOf(registeredDecoder);
|
||||
MessageSink messageSink = new DecodedBinaryStreamMessageSink(coreSession, decoder, wholeMsgMethodHandle);
|
||||
metadata.sinkClass = messageSink.getClass();
|
||||
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
|
||||
this.binaryMetadata = metadata;
|
||||
}
|
||||
else if (registeredDecoder.implementsInterface(Decoder.Text.class))
|
||||
{
|
||||
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
|
||||
Decoder.Text<T> decoder = availableDecoders.getInstanceOf(registeredDecoder);
|
||||
MessageSink messageSink = new DecodedTextMessageSink(coreSession, decoder, wholeMsgMethodHandle);
|
||||
metadata.sinkClass = messageSink.getClass();
|
||||
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
|
||||
this.textMetadata = metadata;
|
||||
}
|
||||
else if (registeredDecoder.implementsInterface(Decoder.TextStream.class))
|
||||
{
|
||||
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
|
||||
Decoder.TextStream<T> decoder = availableDecoders.getInstanceOf(registeredDecoder);
|
||||
MessageSink messageSink = new DecodedTextStreamMessageSink(coreSession, decoder, wholeMsgMethodHandle);
|
||||
metadata.sinkClass = messageSink.getClass();
|
||||
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
|
||||
this.textMetadata = metadata;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Unable to add " + handler.getClass().getName() + ": type " + clazz + " is unrecognized by declared decoders");
|
||||
}
|
||||
throw new RuntimeException("Unable to add " + handler.getClass().getName() + ": type " + clazz + " is unrecognized by declared decoders");
|
||||
}
|
||||
|
||||
// Register the Metadata as a MessageHandler.
|
||||
registerMessageHandler(clazz, handler, basicType, metadata);
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
|
@ -506,6 +480,50 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
}
|
||||
}
|
||||
|
||||
private void assertBasicTypeNotRegistered(byte basicWebSocketType, MessageHandler replacement)
|
||||
{
|
||||
Object messageImpl;
|
||||
switch (basicWebSocketType)
|
||||
{
|
||||
case OpCode.TEXT:
|
||||
messageImpl = textSink;
|
||||
break;
|
||||
case OpCode.BINARY:
|
||||
messageImpl = binarySink;
|
||||
break;
|
||||
case OpCode.PONG:
|
||||
messageImpl = pongHandle;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
if (messageImpl != null)
|
||||
{
|
||||
throw new IllegalStateException("Cannot register " + replacement.getClass().getName() +
|
||||
": Basic WebSocket type " + OpCode.name(basicWebSocketType) + " is already registered");
|
||||
}
|
||||
}
|
||||
|
||||
private void registerMessageHandler(Class<?> clazz, MessageHandler handler, byte basicMessageType, JakartaWebSocketMessageMetadata metadata)
|
||||
{
|
||||
assertBasicTypeNotRegistered(basicMessageType, handler);
|
||||
MessageSink messageSink = JakartaWebSocketFrameHandlerFactory.createMessageSink(session, metadata);
|
||||
switch (basicMessageType)
|
||||
{
|
||||
case OpCode.TEXT:
|
||||
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
|
||||
this.textMetadata = metadata;
|
||||
break;
|
||||
case OpCode.BINARY:
|
||||
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
|
||||
this.binaryMetadata = metadata;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private <T> MessageSink registerMessageHandler(byte basicWebSocketMessageType, Class<T> handlerType, MessageHandler handler, MessageSink messageSink)
|
||||
{
|
||||
synchronized (messageHandlerMap)
|
||||
|
@ -552,7 +570,7 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
this.binarySink = null;
|
||||
break;
|
||||
default:
|
||||
break; // TODO ISE?
|
||||
throw new IllegalStateException("Invalid MessageHandler type " + OpCode.name(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -610,7 +628,6 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
|
|||
|
||||
// Use JSR356 PongMessage interface
|
||||
JakartaWebSocketPongMessage pongMessage = new JakartaWebSocketPongMessage(payload);
|
||||
|
||||
pongHandle.invoke(pongMessage);
|
||||
}
|
||||
catch (Throwable cause)
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.jakarta.common;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
@ -35,6 +33,7 @@ import java.util.function.Function;
|
|||
|
||||
import jakarta.websocket.CloseReason;
|
||||
import jakarta.websocket.Decoder;
|
||||
import jakarta.websocket.Endpoint;
|
||||
import jakarta.websocket.EndpointConfig;
|
||||
import jakarta.websocket.OnClose;
|
||||
import jakarta.websocket.OnError;
|
||||
|
@ -44,90 +43,33 @@ import jakarta.websocket.PongMessage;
|
|||
import jakarta.websocket.Session;
|
||||
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
|
||||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.AbstractDecodedMessageSink;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedBinaryMessageSink;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedBinaryStreamMessageSink;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedMessageSink;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextMessageSink;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextStreamMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.InvalidSignatureException;
|
||||
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
|
||||
import org.eclipse.jetty.websocket.util.InvokerUtils;
|
||||
import org.eclipse.jetty.websocket.util.ReflectUtils;
|
||||
import org.eclipse.jetty.websocket.util.messages.ByteArrayMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.ByteBufferMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.InputStreamMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.PartialByteArrayMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.PartialByteBufferMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.PartialStringMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.ReaderMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.StringMessageSink;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerMetadata.MessageMetadata;
|
||||
|
||||
public abstract class JakartaWebSocketFrameHandlerFactory
|
||||
{
|
||||
private static final MethodHandle FILTER_RETURN_TYPE_METHOD;
|
||||
|
||||
// The different kind of @OnMessage method parameter signatures expected.
|
||||
private static final InvokerUtils.Arg[] textCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(String.class).required()
|
||||
};
|
||||
|
||||
private static final InvokerUtils.Arg[] textPartialCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(String.class).required(),
|
||||
new InvokerUtils.Arg(boolean.class).required()
|
||||
};
|
||||
|
||||
private static final InvokerUtils.Arg[] binaryBufferCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(ByteBuffer.class).required()
|
||||
};
|
||||
|
||||
private static final InvokerUtils.Arg[] binaryPartialBufferCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(ByteBuffer.class).required(),
|
||||
new InvokerUtils.Arg(boolean.class).required()
|
||||
};
|
||||
|
||||
private static final InvokerUtils.Arg[] binaryArrayCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(byte[].class).required()
|
||||
};
|
||||
|
||||
private static final InvokerUtils.Arg[] binaryPartialArrayCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(byte[].class).required(),
|
||||
new InvokerUtils.Arg(boolean.class).required()
|
||||
};
|
||||
|
||||
private static final InvokerUtils.Arg[] inputStreamCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(InputStream.class).required()
|
||||
};
|
||||
|
||||
private static final InvokerUtils.Arg[] readerCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(Reader.class).required()
|
||||
};
|
||||
|
||||
private static final InvokerUtils.Arg[] pongCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(PongMessage.class).required()
|
||||
};
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
FILTER_RETURN_TYPE_METHOD = getServerMethodHandleLookup()
|
||||
.findVirtual(JakartaWebSocketSession.class, "filterReturnType", MethodType.methodType(void.class, Object.class));
|
||||
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
|
@ -135,6 +77,34 @@ public abstract class JakartaWebSocketFrameHandlerFactory
|
|||
}
|
||||
}
|
||||
|
||||
static InvokerUtils.Arg[] getArgsFor(Class<?> objectType)
|
||||
{
|
||||
return new InvokerUtils.Arg[]{new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(objectType).required()};
|
||||
}
|
||||
|
||||
static final InvokerUtils.Arg[] textPartialCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(String.class).required(),
|
||||
new InvokerUtils.Arg(boolean.class).required()
|
||||
};
|
||||
|
||||
static final InvokerUtils.Arg[] binaryPartialBufferCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(ByteBuffer.class).required(),
|
||||
new InvokerUtils.Arg(boolean.class).required()
|
||||
};
|
||||
|
||||
static final InvokerUtils.Arg[] binaryPartialArrayCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(byte[].class).required(),
|
||||
new InvokerUtils.Arg(boolean.class).required()
|
||||
};
|
||||
|
||||
static final InvokerUtils.Arg[] pongCallingArgs = new InvokerUtils.Arg[]{
|
||||
new InvokerUtils.Arg(Session.class),
|
||||
new InvokerUtils.Arg(PongMessage.class).required()
|
||||
};
|
||||
|
||||
protected final JakartaWebSocketContainer container;
|
||||
protected final InvokerUtils.ParamIdentifier paramIdentifier;
|
||||
|
||||
|
@ -174,8 +144,8 @@ public abstract class JakartaWebSocketFrameHandlerFactory
|
|||
MethodHandle errorHandle = metadata.getErrorHandle();
|
||||
MethodHandle pongHandle = metadata.getPongHandle();
|
||||
|
||||
MessageMetadata textMetadata = MessageMetadata.copyOf(metadata.getTextMetadata());
|
||||
MessageMetadata binaryMetadata = MessageMetadata.copyOf(metadata.getBinaryMetadata());
|
||||
JakartaWebSocketMessageMetadata textMetadata = JakartaWebSocketMessageMetadata.copyOf(metadata.getTextMetadata());
|
||||
JakartaWebSocketMessageMetadata binaryMetadata = JakartaWebSocketMessageMetadata.copyOf(metadata.getBinaryMetadata());
|
||||
|
||||
UriTemplatePathSpec templatePathSpec = metadata.getUriTemplatePathSpec();
|
||||
if (templatePathSpec != null)
|
||||
|
@ -190,9 +160,9 @@ public abstract class JakartaWebSocketFrameHandlerFactory
|
|||
pongHandle = bindTemplateVariables(pongHandle, namedVariables, pathParams);
|
||||
|
||||
if (textMetadata != null)
|
||||
textMetadata.handle = bindTemplateVariables(textMetadata.handle, namedVariables, pathParams);
|
||||
textMetadata.setMethodHandle(bindTemplateVariables(textMetadata.getMethodHandle(), namedVariables, pathParams));
|
||||
if (binaryMetadata != null)
|
||||
binaryMetadata.handle = bindTemplateVariables(binaryMetadata.handle, namedVariables, pathParams);
|
||||
binaryMetadata.setMethodHandle(bindTemplateVariables(binaryMetadata.getMethodHandle(), namedVariables, pathParams));
|
||||
}
|
||||
|
||||
openHandle = InvokerUtils.bindTo(openHandle, endpoint);
|
||||
|
@ -209,6 +179,318 @@ public abstract class JakartaWebSocketFrameHandlerFactory
|
|||
config);
|
||||
}
|
||||
|
||||
public static MessageSink createMessageSink(JakartaWebSocketSession session, JakartaWebSocketMessageMetadata msgMetadata)
|
||||
{
|
||||
if (msgMetadata == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
MethodHandles.Lookup lookup = getServerMethodHandleLookup();
|
||||
if (AbstractDecodedMessageSink.class.isAssignableFrom(msgMetadata.getSinkClass()))
|
||||
{
|
||||
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.getSinkClass(),
|
||||
MethodType.methodType(void.class, CoreSession.class, MethodHandle.class, List.class));
|
||||
List<RegisteredDecoder> registeredDecoders = msgMetadata.getRegisteredDecoders();
|
||||
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle(), registeredDecoders);
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.getSinkClass(),
|
||||
MethodType.methodType(void.class, CoreSession.class, MethodHandle.class));
|
||||
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle());
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
throw new RuntimeException("Missing expected MessageSink constructor found at: " + msgMetadata.getSinkClass().getName(), e);
|
||||
}
|
||||
catch (IllegalAccessException | InstantiationException | InvocationTargetException e)
|
||||
{
|
||||
throw new RuntimeException("Unable to create MessageSink: " + msgMetadata.getSinkClass().getName(), e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static MethodHandle wrapNonVoidReturnType(MethodHandle handle, JakartaWebSocketSession session)
|
||||
{
|
||||
if (handle == null)
|
||||
return null;
|
||||
|
||||
if (handle.type().returnType() == Void.TYPE)
|
||||
return handle;
|
||||
|
||||
// Technique from https://stackoverflow.com/questions/48505787/methodhandle-with-general-non-void-return-filter
|
||||
|
||||
// Change the return type of the to be Object so it will match exact with JakartaWebSocketSession.filterReturnType(Object)
|
||||
handle = handle.asType(handle.type().changeReturnType(Object.class));
|
||||
|
||||
// Filter the method return type to a call to JakartaWebSocketSession.filterReturnType() bound to this session
|
||||
handle = MethodHandles.filterReturnValue(handle, FILTER_RETURN_TYPE_METHOD.bindTo(session));
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
private MethodHandle toMethodHandle(MethodHandles.Lookup lookup, Method method)
|
||||
{
|
||||
try
|
||||
{
|
||||
return lookup.unreflect(method);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
throw new RuntimeException("Unable to access method " + method, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected JakartaWebSocketFrameHandlerMetadata createEndpointMetadata(Class<? extends Endpoint> endpointClass, EndpointConfig endpointConfig)
|
||||
{
|
||||
|
||||
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
|
||||
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
|
||||
|
||||
Method openMethod = ReflectUtils.findMethod(endpointClass, "onOpen", Session.class, EndpointConfig.class);
|
||||
MethodHandle open = toMethodHandle(lookup, openMethod);
|
||||
metadata.setOpenHandler(open, openMethod);
|
||||
|
||||
Method closeMethod = ReflectUtils.findMethod(endpointClass, "onClose", Session.class, CloseReason.class);
|
||||
MethodHandle close = toMethodHandle(lookup, closeMethod);
|
||||
metadata.setCloseHandler(close, closeMethod);
|
||||
|
||||
Method errorMethod = ReflectUtils.findMethod(endpointClass, "onError", Session.class, Throwable.class);
|
||||
MethodHandle error = toMethodHandle(lookup, errorMethod);
|
||||
metadata.setErrorHandler(error, errorMethod);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
protected JakartaWebSocketFrameHandlerMetadata discoverJakartaFrameHandlerMetadata(Class<?> endpointClass, JakartaWebSocketFrameHandlerMetadata metadata)
|
||||
{
|
||||
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
|
||||
Method onmethod;
|
||||
|
||||
// OnOpen [0..1]
|
||||
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnOpen.class);
|
||||
if (onmethod != null)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onmethod, OnOpen.class);
|
||||
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
|
||||
final InvokerUtils.Arg ENDPOINT_CONFIG = new InvokerUtils.Arg(EndpointConfig.class);
|
||||
MethodHandle methodHandle = InvokerUtils
|
||||
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, ENDPOINT_CONFIG);
|
||||
metadata.setOpenHandler(methodHandle, onmethod);
|
||||
}
|
||||
|
||||
// OnClose [0..1]
|
||||
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnClose.class);
|
||||
if (onmethod != null)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onmethod, OnClose.class);
|
||||
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
|
||||
final InvokerUtils.Arg CLOSE_REASON = new InvokerUtils.Arg(CloseReason.class);
|
||||
MethodHandle methodHandle = InvokerUtils
|
||||
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, CLOSE_REASON);
|
||||
metadata.setCloseHandler(methodHandle, onmethod);
|
||||
}
|
||||
|
||||
// OnError [0..1]
|
||||
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnError.class);
|
||||
if (onmethod != null)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onmethod, OnError.class);
|
||||
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
|
||||
final InvokerUtils.Arg CAUSE = new InvokerUtils.Arg(Throwable.class).required();
|
||||
MethodHandle methodHandle = InvokerUtils
|
||||
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, CAUSE);
|
||||
metadata.setErrorHandler(methodHandle, onmethod);
|
||||
}
|
||||
|
||||
// OnMessage [0..2]
|
||||
Method[] onMessages = ReflectUtils.findAnnotatedMethods(endpointClass, OnMessage.class);
|
||||
if (onMessages != null && onMessages.length > 0)
|
||||
{
|
||||
for (Method onMsg : onMessages)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onMsg, OnMessage.class);
|
||||
OnMessage onMessageAnno = onMsg.getAnnotation(OnMessage.class);
|
||||
|
||||
long annotationMaxMessageSize = onMessageAnno.maxMessageSize();
|
||||
if (annotationMaxMessageSize > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new InvalidWebSocketException(String.format("Value too large: %s#%s - @OnMessage.maxMessageSize=%,d > Integer.MAX_VALUE",
|
||||
endpointClass.getName(), onMsg.getName(), annotationMaxMessageSize));
|
||||
}
|
||||
|
||||
// Create MessageMetadata and set annotated maxMessageSize if it is not the default value.
|
||||
JakartaWebSocketMessageMetadata msgMetadata = new JakartaWebSocketMessageMetadata();
|
||||
if (annotationMaxMessageSize != -1)
|
||||
msgMetadata.setMaxMessageSize((int)annotationMaxMessageSize);
|
||||
|
||||
// Function to search for matching MethodHandle for the endpointClass given a signature.
|
||||
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle = (signature) ->
|
||||
InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, paramIdentifier, metadata.getNamedTemplateVariables(), signature);
|
||||
|
||||
// Try to match from available decoders (includes primitive types).
|
||||
if (matchDecoders(onMsg, metadata, msgMetadata, getMethodHandle))
|
||||
continue;
|
||||
|
||||
// No decoders matched try partial signatures and pong signatures.
|
||||
if (matchOnMessage(onMsg, metadata, msgMetadata, getMethodHandle))
|
||||
continue;
|
||||
|
||||
// Not a valid @OnMessage declaration signature.
|
||||
throw InvalidSignatureException.build(endpointClass, OnMessage.class, onMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private boolean matchOnMessage(Method onMsg, JakartaWebSocketFrameHandlerMetadata metadata, JakartaWebSocketMessageMetadata msgMetadata,
|
||||
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
|
||||
{
|
||||
// Partial Text Message.
|
||||
MethodHandle methodHandle = getMethodHandle.apply(textPartialCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.setSinkClass(PartialStringMessageSink.class);
|
||||
msgMetadata.setMethodHandle(methodHandle);
|
||||
metadata.setTextMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Partial ByteBuffer Binary Message.
|
||||
methodHandle = getMethodHandle.apply(binaryPartialBufferCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.setSinkClass(PartialByteBufferMessageSink.class);
|
||||
msgMetadata.setMethodHandle(methodHandle);
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Partial byte[] Binary Message.
|
||||
methodHandle = getMethodHandle.apply(binaryPartialArrayCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.setSinkClass(PartialByteArrayMessageSink.class);
|
||||
msgMetadata.setMethodHandle(methodHandle);
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pong Message.
|
||||
MethodHandle pongHandle = getMethodHandle.apply(pongCallingArgs);
|
||||
if (pongHandle != null)
|
||||
{
|
||||
metadata.setPongHandle(pongHandle, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matchDecoders(Method onMsg, JakartaWebSocketFrameHandlerMetadata metadata, JakartaWebSocketMessageMetadata msgMetadata,
|
||||
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
|
||||
{
|
||||
// Get the first decoder match.
|
||||
RegisteredDecoder firstDecoder = metadata.getAvailableDecoders().stream()
|
||||
.filter(registeredDecoder -> getMethodHandle.apply(getArgsFor(registeredDecoder.objectType)) != null)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (firstDecoder == null)
|
||||
return false;
|
||||
|
||||
// Assemble a list of matching decoders which implement the interface type of the first matching decoder found.
|
||||
List<RegisteredDecoder> decoders = new ArrayList<>();
|
||||
Class<? extends Decoder> interfaceType = firstDecoder.interfaceType;
|
||||
metadata.getAvailableDecoders().stream().filter(decoder ->
|
||||
decoder.interfaceType.equals(interfaceType) && (getMethodHandle.apply(getArgsFor(decoder.objectType)) != null))
|
||||
.forEach(decoders::add);
|
||||
msgMetadata.setRegisteredDecoders(decoders);
|
||||
|
||||
// Get the general methodHandle which applies to all the decoders in the list.
|
||||
Class<?> objectType = firstDecoder.objectType;
|
||||
for (RegisteredDecoder decoder : decoders)
|
||||
{
|
||||
if (decoder.objectType.isAssignableFrom(objectType))
|
||||
objectType = decoder.objectType;
|
||||
}
|
||||
MethodHandle methodHandle = getMethodHandle.apply(getArgsFor(objectType));
|
||||
msgMetadata.setMethodHandle(methodHandle);
|
||||
|
||||
// Set the sinkClass and then set the MessageMetadata on the FrameHandlerMetadata
|
||||
if (interfaceType.equals(Decoder.Text.class))
|
||||
{
|
||||
msgMetadata.setSinkClass(DecodedTextMessageSink.class);
|
||||
metadata.setTextMetadata(msgMetadata, onMsg);
|
||||
}
|
||||
else if (interfaceType.equals(Decoder.Binary.class))
|
||||
{
|
||||
msgMetadata.setSinkClass(DecodedBinaryMessageSink.class);
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
}
|
||||
else if (interfaceType.equals(Decoder.TextStream.class))
|
||||
{
|
||||
msgMetadata.setSinkClass(DecodedTextStreamMessageSink.class);
|
||||
metadata.setTextMetadata(msgMetadata, onMsg);
|
||||
}
|
||||
else if (interfaceType.equals(Decoder.BinaryStream.class))
|
||||
{
|
||||
msgMetadata.setSinkClass(DecodedBinaryStreamMessageSink.class);
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void assertSignatureValid(Class<?> endpointClass, Method method, Class<? extends Annotation> annotationClass)
|
||||
{
|
||||
// Test modifiers
|
||||
int mods = method.getModifiers();
|
||||
if (!Modifier.isPublic(mods))
|
||||
{
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("@").append(annotationClass.getSimpleName());
|
||||
err.append(" method must be public: ");
|
||||
ReflectUtils.append(err, endpointClass, method);
|
||||
throw new InvalidSignatureException(err.toString());
|
||||
}
|
||||
|
||||
if (Modifier.isStatic(mods))
|
||||
{
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("@").append(annotationClass.getSimpleName());
|
||||
err.append(" method must not be static: ");
|
||||
ReflectUtils.append(err, endpointClass, method);
|
||||
throw new InvalidSignatureException(err.toString());
|
||||
}
|
||||
|
||||
// Test return type
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if ((returnType == Void.TYPE) || (returnType == Void.class))
|
||||
{
|
||||
// Void is 100% valid, always
|
||||
return;
|
||||
}
|
||||
|
||||
if (!OnMessage.class.isAssignableFrom(annotationClass))
|
||||
{
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("@").append(annotationClass.getSimpleName());
|
||||
err.append(" return must be void: ");
|
||||
ReflectUtils.append(err, endpointClass, method);
|
||||
throw new InvalidSignatureException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the URI Template Variables to their provided values, converting to the type
|
||||
* that the MethodHandle target has declared.
|
||||
|
@ -315,398 +597,6 @@ public abstract class JakartaWebSocketFrameHandlerFactory
|
|||
return retHandle;
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public static MessageSink createMessageSink(JakartaWebSocketSession session, MessageMetadata msgMetadata)
|
||||
{
|
||||
if (msgMetadata == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
MethodHandles.Lookup lookup = getServerMethodHandleLookup();
|
||||
if (DecodedMessageSink.class.isAssignableFrom(msgMetadata.sinkClass))
|
||||
{
|
||||
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.sinkClass,
|
||||
MethodType.methodType(void.class, CoreSession.class, msgMetadata.registeredDecoder.interfaceType, MethodHandle.class));
|
||||
Decoder decoder = session.getDecoders().getInstanceOf(msgMetadata.registeredDecoder);
|
||||
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), decoder, msgMetadata.handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.sinkClass,
|
||||
MethodType.methodType(void.class, CoreSession.class, MethodHandle.class));
|
||||
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.handle);
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
throw new RuntimeException("Missing expected MessageSink constructor found at: " + msgMetadata.sinkClass.getName(), e);
|
||||
}
|
||||
catch (IllegalAccessException | InstantiationException | InvocationTargetException e)
|
||||
{
|
||||
throw new RuntimeException("Unable to create MessageSink: " + msgMetadata.sinkClass.getName(), e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static MethodHandle wrapNonVoidReturnType(MethodHandle handle, JakartaWebSocketSession session)
|
||||
{
|
||||
if (handle == null)
|
||||
return null;
|
||||
|
||||
if (handle.type().returnType() == Void.TYPE)
|
||||
return handle;
|
||||
|
||||
// Technique from https://stackoverflow.com/questions/48505787/methodhandle-with-general-non-void-return-filter
|
||||
|
||||
// Change the return type of the to be Object so it will match exact with JakartaWebSocketSession.filterReturnType(Object)
|
||||
handle = handle.asType(handle.type().changeReturnType(Object.class));
|
||||
|
||||
// Filter the method return type to a call to JakartaWebSocketSession.filterReturnType() bound to this session
|
||||
handle = MethodHandles.filterReturnValue(handle, FILTER_RETURN_TYPE_METHOD.bindTo(session));
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
private MethodHandle toMethodHandle(MethodHandles.Lookup lookup, Method method)
|
||||
{
|
||||
try
|
||||
{
|
||||
return lookup.unreflect(method);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
throw new RuntimeException("Unable to access method " + method, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected JakartaWebSocketFrameHandlerMetadata createEndpointMetadata(Class<? extends jakarta.websocket.Endpoint> endpointClass, EndpointConfig endpointConfig)
|
||||
{
|
||||
|
||||
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
|
||||
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
|
||||
|
||||
Method openMethod = ReflectUtils.findMethod(endpointClass, "onOpen",
|
||||
jakarta.websocket.Session.class, jakarta.websocket.EndpointConfig.class);
|
||||
MethodHandle open = toMethodHandle(lookup, openMethod);
|
||||
metadata.setOpenHandler(open, openMethod);
|
||||
|
||||
Method closeMethod = ReflectUtils.findMethod(endpointClass, "onClose",
|
||||
jakarta.websocket.Session.class, jakarta.websocket.CloseReason.class);
|
||||
MethodHandle close = toMethodHandle(lookup, closeMethod);
|
||||
metadata.setCloseHandler(close, closeMethod);
|
||||
|
||||
Method errorMethod = ReflectUtils.findMethod(endpointClass, "onError",
|
||||
jakarta.websocket.Session.class, Throwable.class);
|
||||
MethodHandle error = toMethodHandle(lookup, errorMethod);
|
||||
metadata.setErrorHandler(error, errorMethod);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
protected JakartaWebSocketFrameHandlerMetadata discoverJakartaFrameHandlerMetadata(Class<?> endpointClass, JakartaWebSocketFrameHandlerMetadata metadata)
|
||||
{
|
||||
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
|
||||
Method onmethod;
|
||||
|
||||
// OnOpen [0..1]
|
||||
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnOpen.class);
|
||||
if (onmethod != null)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onmethod, OnOpen.class);
|
||||
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
|
||||
final InvokerUtils.Arg ENDPOINT_CONFIG = new InvokerUtils.Arg(EndpointConfig.class);
|
||||
MethodHandle methodHandle = InvokerUtils
|
||||
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, ENDPOINT_CONFIG);
|
||||
metadata.setOpenHandler(methodHandle, onmethod);
|
||||
}
|
||||
|
||||
// OnClose [0..1]
|
||||
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnClose.class);
|
||||
if (onmethod != null)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onmethod, OnClose.class);
|
||||
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
|
||||
final InvokerUtils.Arg CLOSE_REASON = new InvokerUtils.Arg(CloseReason.class);
|
||||
MethodHandle methodHandle = InvokerUtils
|
||||
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, CLOSE_REASON);
|
||||
metadata.setCloseHandler(methodHandle, onmethod);
|
||||
}
|
||||
|
||||
// OnError [0..1]
|
||||
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnError.class);
|
||||
if (onmethod != null)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onmethod, OnError.class);
|
||||
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
|
||||
final InvokerUtils.Arg CAUSE = new InvokerUtils.Arg(Throwable.class).required();
|
||||
MethodHandle methodHandle = InvokerUtils
|
||||
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, CAUSE);
|
||||
metadata.setErrorHandler(methodHandle, onmethod);
|
||||
}
|
||||
|
||||
// OnMessage [0..2]
|
||||
Method[] onMessages = ReflectUtils.findAnnotatedMethods(endpointClass, OnMessage.class);
|
||||
if (onMessages != null && onMessages.length > 0)
|
||||
{
|
||||
for (Method onMsg : onMessages)
|
||||
{
|
||||
assertSignatureValid(endpointClass, onMsg, OnMessage.class);
|
||||
|
||||
MessageMetadata msgMetadata = new MessageMetadata();
|
||||
OnMessage onMessageAnno = onMsg.getAnnotation(OnMessage.class);
|
||||
if (onMessageAnno.maxMessageSize() > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new InvalidWebSocketException(String.format("Value too large: %s#%s - @OnMessage.maxMessageSize=%,d > Integer.MAX_VALUE",
|
||||
endpointClass.getName(), onMsg.getName(), onMessageAnno.maxMessageSize()));
|
||||
}
|
||||
msgMetadata.maxMessageSize = (int)onMessageAnno.maxMessageSize();
|
||||
|
||||
// Function to search for matching MethodHandle for the endpointClass given a signature.
|
||||
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle = (signature) ->
|
||||
InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, paramIdentifier, metadata.getNamedTemplateVariables(), signature);
|
||||
|
||||
// Try to match from available decoders.
|
||||
if (matchDecoders(onMsg, metadata, msgMetadata, getMethodHandle))
|
||||
continue;
|
||||
|
||||
// No decoders matched try basic signatures to call onMessage directly.
|
||||
if (matchOnMessage(onMsg, metadata, msgMetadata, getMethodHandle))
|
||||
continue;
|
||||
|
||||
// Not a valid @OnMessage declaration signature.
|
||||
throw InvalidSignatureException.build(endpointClass, OnMessage.class, onMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private boolean matchOnMessage(Method onMsg, JakartaWebSocketFrameHandlerMetadata metadata, MessageMetadata msgMetadata,
|
||||
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
|
||||
{
|
||||
// Whole Text Message.
|
||||
MethodHandle methodHandle = getMethodHandle.apply(textCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = StringMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
metadata.setTextMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Partial Text Message.
|
||||
methodHandle = getMethodHandle.apply(textPartialCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = PartialStringMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
metadata.setTextMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Whole ByteBuffer Binary Message.
|
||||
methodHandle = getMethodHandle.apply(binaryBufferCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = ByteBufferMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Partial ByteBuffer Binary Message.
|
||||
methodHandle = getMethodHandle.apply(binaryPartialBufferCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = PartialByteBufferMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Whole byte[] Binary Message.
|
||||
methodHandle = getMethodHandle.apply(binaryArrayCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = ByteArrayMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Partial byte[] Binary Message.
|
||||
methodHandle = getMethodHandle.apply(binaryPartialArrayCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = PartialByteArrayMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// InputStream Binary Message.
|
||||
methodHandle = getMethodHandle.apply(inputStreamCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = InputStreamMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reader Text Message.
|
||||
methodHandle = getMethodHandle.apply(readerCallingArgs);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = ReaderMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
metadata.setTextMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pong Message.
|
||||
MethodHandle pongHandle = getMethodHandle.apply(pongCallingArgs);
|
||||
if (pongHandle != null)
|
||||
{
|
||||
metadata.setPongHandle(pongHandle, onMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matchDecoders(Method onMsg, JakartaWebSocketFrameHandlerMetadata metadata, MessageMetadata msgMetadata,
|
||||
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
|
||||
{
|
||||
// TODO: we should be able to get this information directly from the AvailableDecoders in the metadata.
|
||||
List<DecodedArgs> decodedTextCallingArgs = new ArrayList<>();
|
||||
List<DecodedArgs> decodedTextStreamCallingArgs = new ArrayList<>();
|
||||
List<DecodedArgs> decodedBinaryCallingArgs = new ArrayList<>();
|
||||
List<DecodedArgs> decodedBinaryStreamCallingArgs = new ArrayList<>();
|
||||
for (AvailableDecoders.RegisteredDecoder decoder : metadata.getAvailableDecoders())
|
||||
{
|
||||
InvokerUtils.Arg[] args = {new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(decoder.objectType).required()};
|
||||
DecodedArgs decodedArgs = new DecodedArgs(decoder, args);
|
||||
|
||||
if (decoder.implementsInterface(Decoder.Text.class))
|
||||
decodedTextCallingArgs.add(decodedArgs);
|
||||
if (decoder.implementsInterface(Decoder.TextStream.class))
|
||||
decodedTextStreamCallingArgs.add(decodedArgs);
|
||||
if (decoder.implementsInterface(Decoder.Binary.class))
|
||||
decodedBinaryCallingArgs.add(decodedArgs);
|
||||
if (decoder.implementsInterface(Decoder.BinaryStream.class))
|
||||
decodedBinaryStreamCallingArgs.add(decodedArgs);
|
||||
}
|
||||
|
||||
MethodHandle methodHandle;
|
||||
|
||||
// Decoder.Text
|
||||
for (DecodedArgs decodedArgs : decodedTextCallingArgs)
|
||||
{
|
||||
methodHandle = getMethodHandle.apply(decodedArgs.args);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = DecodedTextMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
msgMetadata.registeredDecoder = decodedArgs.registeredDecoder;
|
||||
metadata.setTextMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Decoder.Binary
|
||||
for (DecodedArgs decodedArgs : decodedBinaryCallingArgs)
|
||||
{
|
||||
methodHandle = getMethodHandle.apply(decodedArgs.args);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = DecodedBinaryMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
msgMetadata.registeredDecoder = decodedArgs.registeredDecoder;
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to match Text Stream decoders.
|
||||
for (DecodedArgs decodedArgs : decodedTextStreamCallingArgs)
|
||||
{
|
||||
methodHandle = getMethodHandle.apply(decodedArgs.args);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = DecodedTextStreamMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
msgMetadata.registeredDecoder = decodedArgs.registeredDecoder;
|
||||
metadata.setTextMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Decoder.BinaryStream
|
||||
for (DecodedArgs decodedArgs : decodedBinaryStreamCallingArgs)
|
||||
{
|
||||
methodHandle = getMethodHandle.apply(decodedArgs.args);
|
||||
if (methodHandle != null)
|
||||
{
|
||||
msgMetadata.sinkClass = DecodedBinaryStreamMessageSink.class;
|
||||
msgMetadata.handle = methodHandle;
|
||||
msgMetadata.registeredDecoder = decodedArgs.registeredDecoder;
|
||||
metadata.setBinaryMetadata(msgMetadata, onMsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void assertSignatureValid(Class<?> endpointClass, Method method, Class<? extends Annotation> annotationClass)
|
||||
{
|
||||
// Test modifiers
|
||||
int mods = method.getModifiers();
|
||||
if (!Modifier.isPublic(mods))
|
||||
{
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("@").append(annotationClass.getSimpleName());
|
||||
err.append(" method must be public: ");
|
||||
ReflectUtils.append(err, endpointClass, method);
|
||||
throw new InvalidSignatureException(err.toString());
|
||||
}
|
||||
|
||||
if (Modifier.isStatic(mods))
|
||||
{
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("@").append(annotationClass.getSimpleName());
|
||||
err.append(" method must not be static: ");
|
||||
ReflectUtils.append(err, endpointClass, method);
|
||||
throw new InvalidSignatureException(err.toString());
|
||||
}
|
||||
|
||||
// Test return type
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if ((returnType == Void.TYPE) || (returnType == Void.class))
|
||||
{
|
||||
// Void is 100% valid, always
|
||||
return;
|
||||
}
|
||||
|
||||
if (!OnMessage.class.isAssignableFrom(annotationClass))
|
||||
{
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("@").append(annotationClass.getSimpleName());
|
||||
err.append(" return must be void: ");
|
||||
ReflectUtils.append(err, endpointClass, method);
|
||||
throw new InvalidSignatureException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gives a {@link MethodHandles.Lookup} instance to be used to find methods in server classes.
|
||||
|
@ -755,16 +645,4 @@ public abstract class JakartaWebSocketFrameHandlerFactory
|
|||
{
|
||||
return MethodHandles.publicLookup().in(lookupClass);
|
||||
}
|
||||
|
||||
private static class DecodedArgs
|
||||
{
|
||||
public final AvailableDecoders.RegisteredDecoder registeredDecoder;
|
||||
public final InvokerUtils.Arg[] args;
|
||||
|
||||
public DecodedArgs(AvailableDecoders.RegisteredDecoder registeredDecoder, InvokerUtils.Arg... args)
|
||||
{
|
||||
this.registeredDecoder = registeredDecoder;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,32 +20,27 @@ package org.eclipse.jetty.websocket.jakarta.common;
|
|||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
|
||||
import jakarta.websocket.Decoder;
|
||||
import jakarta.websocket.Encoder;
|
||||
import jakarta.websocket.EndpointConfig;
|
||||
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.encoders.AvailableEncoders;
|
||||
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
|
||||
public class JakartaWebSocketFrameHandlerMetadata
|
||||
{
|
||||
private static final String[] NO_VARIABLES = new String[0];
|
||||
|
||||
// EndpointConfig entries
|
||||
private final EndpointConfig endpointConfig;
|
||||
private final AvailableDecoders availableDecoders;
|
||||
private final AvailableEncoders availableEncoders;
|
||||
|
||||
private MethodHandle openHandle;
|
||||
private MethodHandle closeHandle;
|
||||
private MethodHandle errorHandle;
|
||||
|
||||
private MessageMetadata textMetadata;
|
||||
private MessageMetadata binaryMetadata;
|
||||
|
||||
private MethodHandle pongHandle;
|
||||
private JakartaWebSocketMessageMetadata textMetadata;
|
||||
private JakartaWebSocketMessageMetadata binaryMetadata;
|
||||
|
||||
/**
|
||||
* For {@code @ServerEndpoint} or {@code ServerEndpointConfig} based endpoints, this
|
||||
|
@ -76,7 +71,6 @@ public class JakartaWebSocketFrameHandlerMetadata
|
|||
|
||||
public JakartaWebSocketFrameHandlerMetadata(EndpointConfig endpointConfig)
|
||||
{
|
||||
this.endpointConfig = endpointConfig;
|
||||
this.availableDecoders = new AvailableDecoders(endpointConfig);
|
||||
this.availableEncoders = new AvailableEncoders(endpointConfig);
|
||||
}
|
||||
|
@ -91,7 +85,7 @@ public class JakartaWebSocketFrameHandlerMetadata
|
|||
return availableEncoders;
|
||||
}
|
||||
|
||||
public MessageMetadata getBinaryMetadata()
|
||||
public JakartaWebSocketMessageMetadata getBinaryMetadata()
|
||||
{
|
||||
return binaryMetadata;
|
||||
}
|
||||
|
@ -133,7 +127,7 @@ public class JakartaWebSocketFrameHandlerMetadata
|
|||
return pongHandle;
|
||||
}
|
||||
|
||||
public MessageMetadata getTextMetadata()
|
||||
public JakartaWebSocketMessageMetadata getTextMetadata()
|
||||
{
|
||||
return textMetadata;
|
||||
}
|
||||
|
@ -148,7 +142,7 @@ public class JakartaWebSocketFrameHandlerMetadata
|
|||
return (textMetadata != null);
|
||||
}
|
||||
|
||||
public void setBinaryMetadata(MessageMetadata metadata, Object origin)
|
||||
public void setBinaryMetadata(JakartaWebSocketMessageMetadata metadata, Object origin)
|
||||
{
|
||||
assertNotSet(this.binaryMetadata, "BINARY Message Metadata", origin);
|
||||
this.binaryMetadata = metadata;
|
||||
|
@ -160,11 +154,6 @@ public class JakartaWebSocketFrameHandlerMetadata
|
|||
this.closeHandle = close;
|
||||
}
|
||||
|
||||
public void setDecoders(Class<? extends Decoder>[] decoders)
|
||||
{
|
||||
this.availableDecoders.registerAll(decoders);
|
||||
}
|
||||
|
||||
public void setEncoders(Class<? extends Encoder>[] encoders)
|
||||
{
|
||||
this.availableEncoders.registerAll(encoders);
|
||||
|
@ -188,7 +177,7 @@ public class JakartaWebSocketFrameHandlerMetadata
|
|||
this.pongHandle = pong;
|
||||
}
|
||||
|
||||
public void setTextMetadata(MessageMetadata metadata, Object origin)
|
||||
public void setTextMetadata(JakartaWebSocketMessageMetadata metadata, Object origin)
|
||||
{
|
||||
assertNotSet(this.textMetadata, "TEXT Messsage Metadata", origin);
|
||||
this.textMetadata = metadata;
|
||||
|
@ -219,33 +208,4 @@ public class JakartaWebSocketFrameHandlerMetadata
|
|||
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
public static class MessageMetadata
|
||||
{
|
||||
private static final int UNSET = -1;
|
||||
|
||||
public MethodHandle handle;
|
||||
public Class<? extends MessageSink> sinkClass;
|
||||
public AvailableDecoders.RegisteredDecoder registeredDecoder;
|
||||
public int maxMessageSize = UNSET;
|
||||
|
||||
public static MessageMetadata copyOf(MessageMetadata metadata)
|
||||
{
|
||||
if (metadata == null)
|
||||
return null;
|
||||
|
||||
MessageMetadata copy = new MessageMetadata();
|
||||
copy.handle = metadata.handle;
|
||||
copy.sinkClass = metadata.sinkClass;
|
||||
copy.registeredDecoder = metadata.registeredDecoder;
|
||||
copy.maxMessageSize = metadata.maxMessageSize;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public boolean isMaxMessageSizeSet()
|
||||
{
|
||||
return maxMessageSize != UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.jakarta.common;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
|
||||
public class JakartaWebSocketMessageMetadata
|
||||
{
|
||||
private MethodHandle methodHandle;
|
||||
private Class<? extends MessageSink> sinkClass;
|
||||
private List<RegisteredDecoder> registeredDecoders;
|
||||
|
||||
private int maxMessageSize = -1;
|
||||
private boolean maxMessageSizeSet = false;
|
||||
|
||||
public static JakartaWebSocketMessageMetadata copyOf(JakartaWebSocketMessageMetadata metadata)
|
||||
{
|
||||
if (metadata == null)
|
||||
return null;
|
||||
|
||||
JakartaWebSocketMessageMetadata copy = new JakartaWebSocketMessageMetadata();
|
||||
copy.methodHandle = metadata.methodHandle;
|
||||
copy.sinkClass = metadata.sinkClass;
|
||||
copy.registeredDecoders = metadata.registeredDecoders;
|
||||
copy.maxMessageSize = metadata.maxMessageSize;
|
||||
copy.maxMessageSizeSet = metadata.maxMessageSizeSet;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public boolean isMaxMessageSizeSet()
|
||||
{
|
||||
return maxMessageSizeSet;
|
||||
}
|
||||
|
||||
public int getMaxMessageSize()
|
||||
{
|
||||
return maxMessageSize;
|
||||
}
|
||||
|
||||
public void setMaxMessageSize(int maxMessageSize)
|
||||
{
|
||||
this.maxMessageSize = maxMessageSize;
|
||||
this.maxMessageSizeSet = true;
|
||||
}
|
||||
|
||||
public MethodHandle getMethodHandle()
|
||||
{
|
||||
return methodHandle;
|
||||
}
|
||||
|
||||
public void setMethodHandle(MethodHandle methodHandle)
|
||||
{
|
||||
this.methodHandle = methodHandle;
|
||||
}
|
||||
|
||||
public Class<? extends MessageSink> getSinkClass()
|
||||
{
|
||||
return sinkClass;
|
||||
}
|
||||
|
||||
public void setSinkClass(Class<? extends MessageSink> sinkClass)
|
||||
{
|
||||
this.sinkClass = sinkClass;
|
||||
}
|
||||
|
||||
public List<RegisteredDecoder> getRegisteredDecoders()
|
||||
{
|
||||
return registeredDecoders;
|
||||
}
|
||||
|
||||
public void setRegisteredDecoders(List<RegisteredDecoder> decoders)
|
||||
{
|
||||
this.registeredDecoders = decoders;
|
||||
}
|
||||
}
|
|
@ -59,12 +59,11 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
|
|||
private final JakartaWebSocketContainer container;
|
||||
private final CoreSession coreSession;
|
||||
private final JakartaWebSocketFrameHandler frameHandler;
|
||||
private final EndpointConfig config;
|
||||
private final AvailableDecoders availableDecoders;
|
||||
private final AvailableEncoders availableEncoders;
|
||||
private final Map<String, String> pathParameters;
|
||||
private final String sessionId;
|
||||
private Map<String, Object> userProperties;
|
||||
private final Map<String, Object> userProperties;
|
||||
|
||||
private List<Extension> negotiatedExtensions;
|
||||
private JakartaWebSocketAsyncRemote asyncRemote;
|
||||
|
@ -75,17 +74,17 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
|
|||
JakartaWebSocketFrameHandler frameHandler,
|
||||
EndpointConfig endpointConfig)
|
||||
{
|
||||
Objects.requireNonNull(endpointConfig);
|
||||
this.container = container;
|
||||
this.coreSession = coreSession;
|
||||
this.frameHandler = frameHandler;
|
||||
this.sessionId = UUID.randomUUID().toString();
|
||||
this.config = Objects.requireNonNull(endpointConfig);
|
||||
this.availableDecoders = new AvailableDecoders(this.config);
|
||||
this.availableEncoders = new AvailableEncoders(this.config);
|
||||
this.availableDecoders = new AvailableDecoders(endpointConfig);
|
||||
this.availableEncoders = new AvailableEncoders(endpointConfig);
|
||||
|
||||
if (this.config instanceof PathParamProvider)
|
||||
if (endpointConfig instanceof PathParamProvider)
|
||||
{
|
||||
PathParamProvider pathParamProvider = (PathParamProvider)this.config;
|
||||
PathParamProvider pathParamProvider = (PathParamProvider)endpointConfig;
|
||||
this.pathParameters = new HashMap<>(pathParamProvider.getPathParams());
|
||||
}
|
||||
else
|
||||
|
@ -93,7 +92,7 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
|
|||
this.pathParameters = Collections.emptyMap();
|
||||
}
|
||||
|
||||
this.userProperties = this.config.getUserProperties();
|
||||
this.userProperties = endpointConfig.getUserProperties();
|
||||
}
|
||||
|
||||
public CoreSession getCoreSession()
|
||||
|
@ -116,7 +115,7 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
|
|||
LOG.debug("Add MessageHandler.Partial: {}", handler);
|
||||
}
|
||||
|
||||
frameHandler.addMessageHandler(this, clazz, handler);
|
||||
frameHandler.addMessageHandler(clazz, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,7 +133,7 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
|
|||
LOG.debug("Add MessageHandler.Whole: {}", handler);
|
||||
}
|
||||
|
||||
frameHandler.addMessageHandler(this, clazz, handler);
|
||||
frameHandler.addMessageHandler(clazz, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,7 +33,7 @@ public class SessionTracker extends AbstractLifeCycle implements JakartaWebSocke
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SessionTracker.class);
|
||||
|
||||
private CopyOnWriteArraySet<JakartaWebSocketSession> sessions = new CopyOnWriteArraySet<>();
|
||||
private final CopyOnWriteArraySet<JakartaWebSocketSession> sessions = new CopyOnWriteArraySet<>();
|
||||
|
||||
public Set<Session> getSessions()
|
||||
{
|
||||
|
|
|
@ -20,82 +20,30 @@ package org.eclipse.jetty.websocket.jakarta.common.decoders;
|
|||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.websocket.Decoder;
|
||||
import jakarta.websocket.EndpointConfig;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.InitException;
|
||||
import org.eclipse.jetty.websocket.util.InvalidSignatureException;
|
||||
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
|
||||
import org.eclipse.jetty.websocket.util.ReflectUtils;
|
||||
|
||||
public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredDecoder>
|
||||
public class AvailableDecoders implements Iterable<RegisteredDecoder>
|
||||
{
|
||||
public static class RegisteredDecoder
|
||||
{
|
||||
// The user supplied Decoder class
|
||||
public final Class<? extends Decoder> decoder;
|
||||
// The jakarta.websocket.Decoder.* type (eg: Decoder.Binary, Decoder.BinaryStream, Decoder.Text, Decoder.TextStream)
|
||||
public final Class<? extends Decoder> interfaceType;
|
||||
public final Class<?> objectType;
|
||||
public final boolean primitive;
|
||||
public Decoder instance;
|
||||
|
||||
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType)
|
||||
{
|
||||
this(decoder, interfaceType, objectType, false);
|
||||
}
|
||||
|
||||
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, boolean primitive)
|
||||
{
|
||||
this.decoder = decoder;
|
||||
this.interfaceType = interfaceType;
|
||||
this.objectType = objectType;
|
||||
this.primitive = primitive;
|
||||
}
|
||||
|
||||
public boolean implementsInterface(Class<? extends Decoder> type)
|
||||
{
|
||||
return interfaceType.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
public boolean isType(Class<?> type)
|
||||
{
|
||||
return objectType.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append(RegisteredDecoder.class.getSimpleName());
|
||||
str.append('[').append(decoder.getName());
|
||||
str.append(',').append(interfaceType.getName());
|
||||
str.append(',').append(objectType.getName());
|
||||
if (primitive)
|
||||
{
|
||||
str.append(",PRIMITIVE");
|
||||
}
|
||||
str.append(']');
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private final List<RegisteredDecoder> registeredDecoders = new ArrayList<>();
|
||||
private final EndpointConfig config;
|
||||
private LinkedList<RegisteredDecoder> registeredDecoders;
|
||||
|
||||
public AvailableDecoders(EndpointConfig config)
|
||||
{
|
||||
Objects.requireNonNull(config);
|
||||
this.config = config;
|
||||
registeredDecoders = new LinkedList<>();
|
||||
// Register the Config Based Decoders.
|
||||
this.config = Objects.requireNonNull(config);
|
||||
registerAll(config.getDecoders());
|
||||
|
||||
// TEXT based [via Class reference]
|
||||
registerPrimitive(BooleanDecoder.class, Decoder.Text.class, Boolean.class);
|
||||
|
@ -125,17 +73,14 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
|
|||
// STREAMING based
|
||||
registerPrimitive(ReaderDecoder.class, Decoder.TextStream.class, Reader.class);
|
||||
registerPrimitive(InputStreamDecoder.class, Decoder.BinaryStream.class, InputStream.class);
|
||||
|
||||
// Config Based
|
||||
registerAll(config.getDecoders());
|
||||
}
|
||||
|
||||
private void registerPrimitive(Class<? extends Decoder> decoderClass, Class<? extends Decoder> interfaceType, Class<?> type)
|
||||
{
|
||||
registeredDecoders.add(new RegisteredDecoder(decoderClass, interfaceType, type, true));
|
||||
registeredDecoders.add(new RegisteredDecoder(decoderClass, interfaceType, type, config, true));
|
||||
}
|
||||
|
||||
public void register(Class<? extends Decoder> decoder)
|
||||
private void register(Class<? extends Decoder> decoder)
|
||||
{
|
||||
if (!ReflectUtils.isDefaultConstructable(decoder))
|
||||
{
|
||||
|
@ -175,23 +120,10 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: consider removing (if not used)
|
||||
public void registerAll(Class<? extends Decoder>[] decoders)
|
||||
private void registerAll(List<Class<? extends Decoder>> decoders)
|
||||
{
|
||||
if (decoders == null)
|
||||
return;
|
||||
|
||||
for (Class<? extends Decoder> decoder : decoders)
|
||||
{
|
||||
register(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerAll(List<Class<? extends Decoder>> decoders)
|
||||
{
|
||||
if (decoders == null)
|
||||
return;
|
||||
|
||||
decoders.forEach(this::register);
|
||||
}
|
||||
|
||||
|
@ -200,52 +132,34 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
|
|||
Class<?> objectType = ReflectUtils.findGenericClassFor(decoder, interfaceClass);
|
||||
if (objectType == null)
|
||||
{
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("Unknown Decoder Object type declared for interface ");
|
||||
err.append(interfaceClass.getName());
|
||||
err.append(" on class ");
|
||||
err.append(decoder);
|
||||
throw new InvalidWebSocketException(err.toString());
|
||||
String err = "Unknown Decoder Object type declared for interface " +
|
||||
interfaceClass.getName() + " on class " + decoder;
|
||||
throw new InvalidWebSocketException(err);
|
||||
}
|
||||
|
||||
try
|
||||
// Validate the decoder to be added against the existing registered decoders.
|
||||
for (RegisteredDecoder registered : registeredDecoders)
|
||||
{
|
||||
RegisteredDecoder conflicts = registeredDecoders.stream()
|
||||
.filter(registered -> registered.isType(objectType))
|
||||
.filter(registered -> !registered.primitive)
|
||||
.findFirst()
|
||||
.get();
|
||||
|
||||
if (conflicts.decoder.equals(decoder) && conflicts.implementsInterface(interfaceClass))
|
||||
if (!registered.primitive && objectType.equals(registered.objectType))
|
||||
{
|
||||
// Same decoder as what is there already, don't bother adding it again.
|
||||
return;
|
||||
// Streaming decoders can only have one decoder per object type.
|
||||
if (interfaceClass.equals(Decoder.TextStream.class) || interfaceClass.equals(Decoder.BinaryStream.class))
|
||||
throw new InvalidWebSocketException("Multiple decoders for objectType" + objectType);
|
||||
|
||||
// If we have the same objectType, then the interfaceTypes must be the same to form a decoder list.
|
||||
if (!registered.interfaceType.equals(interfaceClass))
|
||||
throw new InvalidWebSocketException("Multiple decoders with different interface types for objectType " + objectType);
|
||||
}
|
||||
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("Duplicate Decoder Object type ");
|
||||
err.append(objectType.getName());
|
||||
err.append(" in ");
|
||||
err.append(decoder.getName());
|
||||
err.append(", previously declared in ");
|
||||
err.append(conflicts.decoder.getName());
|
||||
throw new InvalidWebSocketException(err.toString());
|
||||
}
|
||||
catch (NoSuchElementException e)
|
||||
{
|
||||
registeredDecoders.addFirst(new RegisteredDecoder(decoder, interfaceClass, objectType));
|
||||
// If this decoder is already registered for this interface type we can skip adding a duplicate.
|
||||
if (registered.decoder.equals(decoder) && registered.interfaceType.equals(interfaceClass))
|
||||
return;
|
||||
}
|
||||
|
||||
registeredDecoders.add(new RegisteredDecoder(decoder, interfaceClass, objectType, config));
|
||||
}
|
||||
|
||||
// TODO: consider removing (if not used)
|
||||
public List<RegisteredDecoder> supporting(Class<? extends Decoder> interfaceType)
|
||||
{
|
||||
return registeredDecoders.stream()
|
||||
.filter(registered -> registered.implementsInterface(interfaceType))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public RegisteredDecoder getRegisteredDecoderFor(Class<?> type)
|
||||
public RegisteredDecoder getFirstRegisteredDecoder(Class<?> type)
|
||||
{
|
||||
return registeredDecoders.stream()
|
||||
.filter(registered -> registered.isType(type))
|
||||
|
@ -253,49 +167,39 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
|
|||
.orElse(null);
|
||||
}
|
||||
|
||||
// TODO: consider removing (if not used)
|
||||
public Class<? extends Decoder> getDecoderFor(Class<?> type)
|
||||
public List<RegisteredDecoder> getRegisteredDecoders(Class<?> returnType)
|
||||
{
|
||||
try
|
||||
{
|
||||
return getRegisteredDecoderFor(type).decoder;
|
||||
}
|
||||
catch (NoSuchElementException e)
|
||||
{
|
||||
throw new InvalidWebSocketException("No Decoder found for type " + type);
|
||||
}
|
||||
return registeredDecoders.stream()
|
||||
.filter(registered -> registered.isType(returnType))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public <T extends Decoder> T getInstanceOf(RegisteredDecoder registeredDecoder)
|
||||
public List<RegisteredDecoder> getRegisteredDecoders(Class<? extends Decoder> interfaceType, Class<?> returnType)
|
||||
{
|
||||
if (registeredDecoder.instance != null)
|
||||
{
|
||||
return (T)registeredDecoder.instance;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
registeredDecoder.instance = registeredDecoder.decoder.getConstructor().newInstance();
|
||||
registeredDecoder.instance.init(this.config);
|
||||
return (T)registeredDecoder.instance;
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e)
|
||||
{
|
||||
throw new InitException("Unable to init Decoder for type:" + registeredDecoder.decoder.getName(), e);
|
||||
}
|
||||
return registeredDecoders.stream()
|
||||
.filter(registered -> registered.interfaceType.equals(interfaceType))
|
||||
.filter(registered -> registered.isType(returnType))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public <T extends Decoder> T getInstanceFor(Class<?> type)
|
||||
public List<RegisteredDecoder> getTextDecoders(Class<?> returnType)
|
||||
{
|
||||
try
|
||||
{
|
||||
RegisteredDecoder registeredDecoder = getRegisteredDecoderFor(type);
|
||||
return getInstanceOf(registeredDecoder);
|
||||
}
|
||||
catch (NoSuchElementException e)
|
||||
{
|
||||
throw new InvalidWebSocketException("No Decoder found for type " + type);
|
||||
}
|
||||
return getRegisteredDecoders(Decoder.Text.class, returnType);
|
||||
}
|
||||
|
||||
public List<RegisteredDecoder> getBinaryDecoders(Class<?> returnType)
|
||||
{
|
||||
return getRegisteredDecoders(Decoder.Binary.class, returnType);
|
||||
}
|
||||
|
||||
public List<RegisteredDecoder> getTextStreamDecoders(Class<?> returnType)
|
||||
{
|
||||
return getRegisteredDecoders(Decoder.TextStream.class, returnType);
|
||||
}
|
||||
|
||||
public List<RegisteredDecoder> getBinaryStreamDecoders(Class<?> returnType)
|
||||
{
|
||||
return getRegisteredDecoders(Decoder.BinaryStream.class, returnType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -303,4 +207,9 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
|
|||
{
|
||||
return registeredDecoders.iterator();
|
||||
}
|
||||
|
||||
public Stream<RegisteredDecoder> stream()
|
||||
{
|
||||
return registeredDecoders.stream();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.jakarta.common.decoders;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import jakarta.websocket.DecodeException;
|
||||
import jakarta.websocket.Decoder;
|
||||
import jakarta.websocket.PongMessage;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
public class PongMessageDecoder extends AbstractDecoder implements Decoder.Binary<PongMessage>
|
||||
{
|
||||
private static class PongMsg implements PongMessage
|
||||
{
|
||||
private final ByteBuffer bytes;
|
||||
|
||||
public PongMsg(ByteBuffer buf)
|
||||
{
|
||||
int len = buf.remaining();
|
||||
this.bytes = ByteBuffer.allocate(len);
|
||||
BufferUtil.put(buf, this.bytes);
|
||||
BufferUtil.flipToFlush(this.bytes, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getApplicationData()
|
||||
{
|
||||
return this.bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PongMessage decode(ByteBuffer bytes) throws DecodeException
|
||||
{
|
||||
return new PongMsg(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willDecode(ByteBuffer bytes)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.jakarta.common.decoders;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import jakarta.websocket.Decoder;
|
||||
import jakarta.websocket.EndpointConfig;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.InitException;
|
||||
|
||||
public class RegisteredDecoder
|
||||
{
|
||||
// The user supplied Decoder class
|
||||
public final Class<? extends Decoder> decoder;
|
||||
// The jakarta.websocket.Decoder.* type (eg: Decoder.Binary, Decoder.BinaryStream, Decoder.Text, Decoder.TextStream)
|
||||
public final Class<? extends Decoder> interfaceType;
|
||||
public final Class<?> objectType;
|
||||
public final boolean primitive;
|
||||
public final EndpointConfig config;
|
||||
|
||||
private Decoder instance;
|
||||
|
||||
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, EndpointConfig endpointConfig)
|
||||
{
|
||||
this(decoder, interfaceType, objectType, endpointConfig, false);
|
||||
}
|
||||
|
||||
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, EndpointConfig endpointConfig, boolean primitive)
|
||||
{
|
||||
this.decoder = decoder;
|
||||
this.interfaceType = interfaceType;
|
||||
this.objectType = objectType;
|
||||
this.primitive = primitive;
|
||||
this.config = endpointConfig;
|
||||
}
|
||||
|
||||
public boolean implementsInterface(Class<? extends Decoder> type)
|
||||
{
|
||||
return interfaceType.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
public boolean isType(Class<?> type)
|
||||
{
|
||||
return objectType.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
public <T extends Decoder> T getInstance()
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
instance = decoder.getConstructor().newInstance();
|
||||
instance.init(config);
|
||||
return (T)instance;
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e)
|
||||
{
|
||||
throw new InitException("Unable to init Decoder for type:" + decoder.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return (T)instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append(RegisteredDecoder.class.getSimpleName());
|
||||
str.append('[').append(decoder.getName());
|
||||
str.append(',').append(interfaceType.getName());
|
||||
str.append(',').append(objectType.getName());
|
||||
if (primitive)
|
||||
{
|
||||
str.append(",PRIMITIVE");
|
||||
}
|
||||
str.append(']');
|
||||
return str.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.jakarta.common.messages;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.websocket.CloseReason;
|
||||
import jakarta.websocket.Decoder;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.exception.CloseException;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AbstractDecodedMessageSink implements MessageSink
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractDecodedMessageSink.class);
|
||||
|
||||
private final MethodHandle _methodHandle;
|
||||
private final MessageSink _messageSink;
|
||||
|
||||
public AbstractDecodedMessageSink(CoreSession coreSession, MethodHandle methodHandle)
|
||||
{
|
||||
_methodHandle = methodHandle;
|
||||
|
||||
try
|
||||
{
|
||||
_messageSink = newMessageSink(coreSession);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Throwing from here is an error implementation of the DecodedMessageSink.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the MessageSink with the decoded message.
|
||||
* @param message the decoded message.
|
||||
*/
|
||||
void invoke(Object message)
|
||||
{
|
||||
try
|
||||
{
|
||||
_methodHandle.invoke(message);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a message sink which will first decode the message then pass it to {@link #_methodHandle}.
|
||||
* @throws Exception for any error in creating the message sink.
|
||||
*/
|
||||
abstract MessageSink newMessageSink(CoreSession coreSession) throws Exception;
|
||||
|
||||
@Override
|
||||
public void accept(Frame frame, Callback callback)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("accepting frame {} for {}", frame, _messageSink);
|
||||
_messageSink.accept(frame, callback);
|
||||
}
|
||||
|
||||
public abstract static class Basic<T extends Decoder> extends AbstractDecodedMessageSink
|
||||
{
|
||||
protected final List<T> _decoders;
|
||||
|
||||
public Basic(CoreSession coreSession, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
|
||||
{
|
||||
super(coreSession, methodHandle);
|
||||
if (decoders.isEmpty())
|
||||
throw new IllegalArgumentException("Require at least one decoder for " + this.getClass());
|
||||
_decoders = decoders.stream()
|
||||
.map(RegisteredDecoder::<T>getInstance)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class Stream<T extends Decoder> extends AbstractDecodedMessageSink
|
||||
{
|
||||
protected final T _decoder;
|
||||
|
||||
public Stream(CoreSession coreSession, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
|
||||
{
|
||||
super(coreSession, methodHandle);
|
||||
if (decoders.size() != 1)
|
||||
throw new IllegalArgumentException("Require exactly one decoder for " + this.getClass());
|
||||
_decoder = decoders.get(0).getInstance();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.jakarta.common.messages;
|
|||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.websocket.CloseReason;
|
||||
import jakarta.websocket.DecodeException;
|
||||
|
@ -28,54 +29,49 @@ import jakarta.websocket.Decoder;
|
|||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.exception.CloseException;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.util.messages.ByteBufferMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DecodedBinaryMessageSink<T> extends DecodedMessageSink<Decoder.Binary<T>>
|
||||
public class DecodedBinaryMessageSink<T> extends AbstractDecodedMessageSink.Basic<Decoder.Binary<T>>
|
||||
{
|
||||
public DecodedBinaryMessageSink(CoreSession session,
|
||||
Decoder.Binary<T> decoder,
|
||||
MethodHandle methodHandle)
|
||||
throws NoSuchMethodException, IllegalAccessException
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DecodedBinaryMessageSink.class);
|
||||
|
||||
public DecodedBinaryMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
|
||||
{
|
||||
super(session, decoder, methodHandle);
|
||||
super(session, methodHandle, decoders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException
|
||||
MessageSink newMessageSink(CoreSession coreSession) throws Exception
|
||||
{
|
||||
return JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedBinaryMessageSink.class,
|
||||
"onWholeMessage", MethodType.methodType(void.class, ByteBuffer.class))
|
||||
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
|
||||
.findVirtual(DecodedBinaryMessageSink.class, "onWholeMessage", MethodType.methodType(void.class, ByteBuffer.class))
|
||||
.bindTo(this);
|
||||
return new ByteBufferMessageSink(coreSession, methodHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
|
||||
{
|
||||
return new ByteBufferMessageSink(session, rawMethodHandle);
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public void onWholeMessage(ByteBuffer wholeMessage)
|
||||
{
|
||||
if (!getDecoder().willDecode(wholeMessage))
|
||||
for (Decoder.Binary<T> decoder : _decoders)
|
||||
{
|
||||
logger.warn("Message lost, decoder " + getDecoder().getClass().getName() + "#willDecode() has rejected it.");
|
||||
return;
|
||||
if (decoder.willDecode(wholeMessage))
|
||||
{
|
||||
try
|
||||
{
|
||||
T obj = decoder.decode(wholeMessage);
|
||||
invoke(obj);
|
||||
return;
|
||||
}
|
||||
catch (DecodeException e)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
T obj = getDecoder().decode(wholeMessage);
|
||||
methodHandle.invoke(obj);
|
||||
}
|
||||
catch (DecodeException e)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
|
||||
}
|
||||
LOG.warn("Message lost, willDecode() has returned false for all decoders in the decoder list.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.jakarta.common.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.websocket.CloseReason;
|
||||
import jakarta.websocket.DecodeException;
|
||||
|
@ -28,48 +30,36 @@ import jakarta.websocket.Decoder;
|
|||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.exception.CloseException;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.util.messages.InputStreamMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
|
||||
public class DecodedBinaryStreamMessageSink<T> extends DecodedMessageSink<Decoder.BinaryStream<T>>
|
||||
public class DecodedBinaryStreamMessageSink<T> extends AbstractDecodedMessageSink.Stream<Decoder.BinaryStream<T>>
|
||||
{
|
||||
public DecodedBinaryStreamMessageSink(CoreSession session,
|
||||
Decoder.BinaryStream<T> decoder,
|
||||
MethodHandle methodHandle)
|
||||
throws NoSuchMethodException, IllegalAccessException
|
||||
public DecodedBinaryStreamMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
|
||||
{
|
||||
super(session, decoder, methodHandle);
|
||||
super(session, methodHandle, decoders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException
|
||||
MessageSink newMessageSink(CoreSession coreSession) throws Exception
|
||||
{
|
||||
return JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedBinaryStreamMessageSink.class,
|
||||
"onStreamStart", MethodType.methodType(void.class, InputStream.class))
|
||||
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
|
||||
.findVirtual(DecodedBinaryStreamMessageSink.class, "onStreamStart", MethodType.methodType(void.class, InputStream.class))
|
||||
.bindTo(this);
|
||||
return new InputStreamMessageSink(coreSession, methodHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
|
||||
{
|
||||
return new InputStreamMessageSink(session, rawMethodHandle);
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public void onStreamStart(InputStream stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
T obj = getDecoder().decode(stream);
|
||||
methodHandle.invoke(obj);
|
||||
T obj = _decoder.decode(stream);
|
||||
invoke(obj);
|
||||
}
|
||||
catch (DecodeException e)
|
||||
catch (DecodeException | IOException e)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.jakarta.common.messages;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
|
||||
import jakarta.websocket.Decoder;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.util.messages.AbstractMessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class DecodedMessageSink<T extends Decoder> extends AbstractMessageSink
|
||||
{
|
||||
protected final Logger logger;
|
||||
private final T decoder;
|
||||
private final MethodHandle rawMethodHandle;
|
||||
private final MessageSink rawMessageSink;
|
||||
|
||||
public DecodedMessageSink(CoreSession session, T decoder, MethodHandle methodHandle)
|
||||
throws NoSuchMethodException, IllegalAccessException
|
||||
{
|
||||
super(session, methodHandle);
|
||||
this.logger = LoggerFactory.getLogger(this.getClass());
|
||||
this.decoder = decoder;
|
||||
this.rawMethodHandle = newRawMethodHandle();
|
||||
this.rawMessageSink = newRawMessageSink(session, rawMethodHandle);
|
||||
}
|
||||
|
||||
protected abstract MethodHandle newRawMethodHandle()
|
||||
throws NoSuchMethodException, IllegalAccessException;
|
||||
|
||||
protected abstract MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle);
|
||||
|
||||
public T getDecoder()
|
||||
{
|
||||
return decoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Frame frame, Callback callback)
|
||||
{
|
||||
this.rawMessageSink.accept(frame, callback);
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.jakarta.common.messages;
|
|||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.websocket.CloseReason;
|
||||
import jakarta.websocket.DecodeException;
|
||||
|
@ -27,54 +28,49 @@ import jakarta.websocket.Decoder;
|
|||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.exception.CloseException;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.StringMessageSink;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DecodedTextMessageSink<T> extends DecodedMessageSink<Decoder.Text<T>>
|
||||
public class DecodedTextMessageSink<T> extends AbstractDecodedMessageSink.Basic<Decoder.Text<T>>
|
||||
{
|
||||
public DecodedTextMessageSink(CoreSession session,
|
||||
Decoder.Text<T> decoder,
|
||||
MethodHandle methodHandle)
|
||||
throws NoSuchMethodException, IllegalAccessException
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DecodedTextMessageSink.class);
|
||||
|
||||
public DecodedTextMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
|
||||
{
|
||||
super(session, decoder, methodHandle);
|
||||
super(session, methodHandle, decoders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException
|
||||
MessageSink newMessageSink(CoreSession coreSession) throws NoSuchMethodException, IllegalAccessException
|
||||
{
|
||||
return JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedTextMessageSink.class,
|
||||
"onWholeMessage", MethodType.methodType(void.class, String.class))
|
||||
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
|
||||
.findVirtual(getClass(), "onMessage", MethodType.methodType(void.class, String.class))
|
||||
.bindTo(this);
|
||||
return new StringMessageSink(coreSession, methodHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
|
||||
public void onMessage(String wholeMessage)
|
||||
{
|
||||
return new StringMessageSink(session, rawMethodHandle);
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public void onWholeMessage(String wholeMessage)
|
||||
{
|
||||
if (!getDecoder().willDecode(wholeMessage))
|
||||
for (Decoder.Text<T> decoder : _decoders)
|
||||
{
|
||||
logger.warn("Message lost, decoder " + getDecoder().getClass().getName() + "#willDecode() has rejected it.");
|
||||
return;
|
||||
if (decoder.willDecode(wholeMessage))
|
||||
{
|
||||
try
|
||||
{
|
||||
T obj = decoder.decode(wholeMessage);
|
||||
invoke(obj);
|
||||
return;
|
||||
}
|
||||
catch (DecodeException e)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
T obj = getDecoder().decode(wholeMessage);
|
||||
methodHandle.invoke(obj);
|
||||
}
|
||||
catch (DecodeException e)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
|
||||
}
|
||||
LOG.warn("Message lost, willDecode() has returned false for all decoders in the decoder list.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.jakarta.common.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.websocket.CloseReason;
|
||||
import jakarta.websocket.DecodeException;
|
||||
|
@ -28,48 +30,36 @@ import jakarta.websocket.Decoder;
|
|||
import org.eclipse.jetty.websocket.core.CoreSession;
|
||||
import org.eclipse.jetty.websocket.core.exception.CloseException;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.util.messages.MessageSink;
|
||||
import org.eclipse.jetty.websocket.util.messages.ReaderMessageSink;
|
||||
|
||||
public class DecodedTextStreamMessageSink<T> extends DecodedMessageSink<Decoder.TextStream<T>>
|
||||
public class DecodedTextStreamMessageSink<T> extends AbstractDecodedMessageSink.Stream<Decoder.TextStream<T>>
|
||||
{
|
||||
public DecodedTextStreamMessageSink(CoreSession session,
|
||||
Decoder.TextStream<T> decoder,
|
||||
MethodHandle methodHandle)
|
||||
throws NoSuchMethodException, IllegalAccessException
|
||||
public DecodedTextStreamMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
|
||||
{
|
||||
super(session, decoder, methodHandle);
|
||||
super(session, methodHandle, decoders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException
|
||||
MessageSink newMessageSink(CoreSession coreSession) throws Exception
|
||||
{
|
||||
return JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedTextStreamMessageSink.class,
|
||||
"onStreamStart", MethodType.methodType(void.class, Reader.class))
|
||||
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
|
||||
.findVirtual(DecodedTextStreamMessageSink.class, "onStreamStart", MethodType.methodType(void.class, Reader.class))
|
||||
.bindTo(this);
|
||||
return new ReaderMessageSink(coreSession, methodHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
|
||||
{
|
||||
return new ReaderMessageSink(session, rawMethodHandle);
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public void onStreamStart(Reader reader)
|
||||
{
|
||||
try
|
||||
{
|
||||
T obj = getDecoder().decode(reader);
|
||||
methodHandle.invoke(obj);
|
||||
T obj = _decoder.decode(reader);
|
||||
invoke(obj);
|
||||
}
|
||||
catch (DecodeException e)
|
||||
catch (DecodeException | IOException e)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,13 +20,34 @@ package org.eclipse.jetty.websocket.jakarta.common.messages;
|
|||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.websocket.ClientEndpointConfig;
|
||||
import jakarta.websocket.Decoder;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.AbstractSessionTest;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
|
||||
public abstract class AbstractMessageSinkTest extends AbstractSessionTest
|
||||
{
|
||||
public List<RegisteredDecoder> toRegisteredDecoderList(Class<? extends Decoder> clazz, Class<?> objectType)
|
||||
{
|
||||
Class<? extends Decoder> interfaceType;
|
||||
if (Decoder.Text.class.isAssignableFrom(clazz))
|
||||
interfaceType = Decoder.Text.class;
|
||||
else if (Decoder.Binary.class.isAssignableFrom(clazz))
|
||||
interfaceType = Decoder.Binary.class;
|
||||
else if (Decoder.TextStream.class.isAssignableFrom(clazz))
|
||||
interfaceType = Decoder.TextStream.class;
|
||||
else if (Decoder.BinaryStream.class.isAssignableFrom(clazz))
|
||||
interfaceType = Decoder.BinaryStream.class;
|
||||
else
|
||||
throw new IllegalStateException();
|
||||
|
||||
return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build()));
|
||||
}
|
||||
|
||||
public <T> MethodHandle getAcceptHandle(Consumer<T> copy, Class<T> type)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.lang.invoke.MethodHandle;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -34,6 +35,7 @@ import org.eclipse.jetty.util.FutureCallback;
|
|||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.OpCode;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.AbstractSessionTest;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -49,8 +51,8 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest
|
|||
CompletableFuture<Calendar> copyFuture = new CompletableFuture<>();
|
||||
DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture);
|
||||
MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class);
|
||||
Decoder.Binary<Calendar> decoder = new GmtDecoder();
|
||||
DecodedBinaryMessageSink sink = new DecodedBinaryMessageSink(AbstractSessionTest.session.getCoreSession(), decoder, copyHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
|
||||
DecodedBinaryMessageSink<Calendar> sink = new DecodedBinaryMessageSink<>(AbstractSessionTest.session.getCoreSession(), copyHandle, decoders);
|
||||
|
||||
FutureCallback finCallback = new FutureCallback();
|
||||
ByteBuffer data = ByteBuffer.allocate(16);
|
||||
|
@ -72,8 +74,8 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest
|
|||
CompletableFuture<Calendar> copyFuture = new CompletableFuture<>();
|
||||
DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture);
|
||||
MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class);
|
||||
Decoder.Binary<Calendar> decoder = new GmtDecoder();
|
||||
DecodedBinaryMessageSink sink = new DecodedBinaryMessageSink(AbstractSessionTest.session.getCoreSession(), decoder, copyHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
|
||||
DecodedBinaryMessageSink<Calendar> sink = new DecodedBinaryMessageSink<>(AbstractSessionTest.session.getCoreSession(), copyHandle, decoders);
|
||||
|
||||
FutureCallback callback1 = new FutureCallback();
|
||||
FutureCallback callback2 = new FutureCallback();
|
||||
|
@ -130,7 +132,6 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest
|
|||
@SuppressWarnings("Duplicates")
|
||||
public static class GmtDecoder implements Decoder.Binary<Calendar>
|
||||
{
|
||||
|
||||
@Override
|
||||
public Calendar decode(ByteBuffer buffer) throws DecodeException
|
||||
{
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.lang.invoke.MethodHandle;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -36,6 +37,7 @@ import org.eclipse.jetty.util.BufferUtil;
|
|||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.OpCode;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -51,8 +53,8 @@ public class DecodedBinaryStreamMessageSinkTest extends AbstractMessageSinkTest
|
|||
CompletableFuture<Calendar> copyFuture = new CompletableFuture<>();
|
||||
DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture);
|
||||
MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class);
|
||||
Decoder.BinaryStream<Calendar> decoder = new GmtDecoder();
|
||||
DecodedBinaryStreamMessageSink sink = new DecodedBinaryStreamMessageSink(session.getCoreSession(), decoder, copyHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
|
||||
DecodedBinaryStreamMessageSink<Calendar> sink = new DecodedBinaryStreamMessageSink<>(session.getCoreSession(), copyHandle, decoders);
|
||||
|
||||
FutureCallback finCallback = new FutureCallback();
|
||||
ByteBuffer data = ByteBuffer.allocate(16);
|
||||
|
@ -74,8 +76,8 @@ public class DecodedBinaryStreamMessageSinkTest extends AbstractMessageSinkTest
|
|||
CompletableFuture<Calendar> copyFuture = new CompletableFuture<>();
|
||||
DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture);
|
||||
MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class);
|
||||
Decoder.BinaryStream<Calendar> decoder = new GmtDecoder();
|
||||
DecodedBinaryStreamMessageSink sink = new DecodedBinaryStreamMessageSink(session.getCoreSession(), decoder, copyHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
|
||||
DecodedBinaryStreamMessageSink<Calendar> sink = new DecodedBinaryStreamMessageSink<>(session.getCoreSession(), copyHandle, decoders);
|
||||
|
||||
FutureCallback callback1 = new FutureCallback();
|
||||
FutureCallback callback2 = new FutureCallback();
|
||||
|
|
|
@ -21,7 +21,9 @@ package org.eclipse.jetty.websocket.jakarta.common.messages;
|
|||
import java.lang.invoke.MethodHandle;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -33,6 +35,7 @@ import jakarta.websocket.EndpointConfig;
|
|||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.OpCode;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -48,8 +51,8 @@ public class DecodedTextMessageSinkTest extends AbstractMessageSinkTest
|
|||
CompletableFuture<Date> copyFuture = new CompletableFuture<>();
|
||||
DecodedDateCopy copy = new DecodedDateCopy(copyFuture);
|
||||
MethodHandle copyHandle = getAcceptHandle(copy, Date.class);
|
||||
Decoder.Text<Date> decoder = new GmtDecoder();
|
||||
DecodedTextMessageSink sink = new DecodedTextMessageSink(session.getCoreSession(), decoder, copyHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
|
||||
DecodedTextMessageSink<Calendar> sink = new DecodedTextMessageSink<>(session.getCoreSession(), copyHandle, decoders);
|
||||
|
||||
FutureCallback finCallback = new FutureCallback();
|
||||
sink.accept(new Frame(OpCode.TEXT).setPayload("2018.02.13").setFin(true), finCallback);
|
||||
|
@ -66,8 +69,8 @@ public class DecodedTextMessageSinkTest extends AbstractMessageSinkTest
|
|||
CompletableFuture<Date> copyFuture = new CompletableFuture<>();
|
||||
DecodedDateCopy copy = new DecodedDateCopy(copyFuture);
|
||||
MethodHandle copyHandle = getAcceptHandle(copy, Date.class);
|
||||
Decoder.Text<Date> decoder = new GmtDecoder();
|
||||
DecodedTextMessageSink sink = new DecodedTextMessageSink(session.getCoreSession(), decoder, copyHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
|
||||
DecodedTextMessageSink<Calendar> sink = new DecodedTextMessageSink<>(session.getCoreSession(), copyHandle, decoders);
|
||||
|
||||
FutureCallback callback1 = new FutureCallback();
|
||||
FutureCallback callback2 = new FutureCallback();
|
||||
|
|
|
@ -23,7 +23,9 @@ import java.io.Reader;
|
|||
import java.lang.invoke.MethodHandle;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -36,6 +38,7 @@ import org.eclipse.jetty.util.FutureCallback;
|
|||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.core.OpCode;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -51,8 +54,8 @@ public class DecodedTextStreamMessageSinkTest extends AbstractMessageSinkTest
|
|||
CompletableFuture<Date> copyFuture = new CompletableFuture<>();
|
||||
DecodedDateCopy copy = new DecodedDateCopy(copyFuture);
|
||||
MethodHandle copyHandle = getAcceptHandle(copy, Date.class);
|
||||
Decoder.TextStream<Date> decoder = new GmtDecoder();
|
||||
DecodedTextStreamMessageSink sink = new DecodedTextStreamMessageSink(session.getCoreSession(), decoder, copyHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
|
||||
DecodedTextStreamMessageSink<Calendar> sink = new DecodedTextStreamMessageSink<>(session.getCoreSession(), copyHandle, decoders);
|
||||
|
||||
FutureCallback finCallback = new FutureCallback();
|
||||
sink.accept(new Frame(OpCode.TEXT).setPayload("2018.02.13").setFin(true), finCallback);
|
||||
|
@ -69,8 +72,8 @@ public class DecodedTextStreamMessageSinkTest extends AbstractMessageSinkTest
|
|||
CompletableFuture<Date> copyFuture = new CompletableFuture<>();
|
||||
DecodedDateCopy copy = new DecodedDateCopy(copyFuture);
|
||||
MethodHandle copyHandle = getAcceptHandle(copy, Date.class);
|
||||
Decoder.TextStream<Date> decoder = new GmtDecoder();
|
||||
DecodedTextStreamMessageSink sink = new DecodedTextStreamMessageSink(session.getCoreSession(), decoder, copyHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
|
||||
DecodedTextStreamMessageSink<Calendar> sink = new DecodedTextStreamMessageSink<>(session.getCoreSession(), copyHandle, decoders);
|
||||
|
||||
FutureCallback callback1 = new FutureCallback();
|
||||
FutureCallback callback2 = new FutureCallback();
|
||||
|
|
|
@ -41,15 +41,11 @@ public class JakartaWebSocketServerFrameHandlerFactory extends JakartaWebSocketC
|
|||
public JakartaWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass, EndpointConfig endpointConfig)
|
||||
{
|
||||
if (jakarta.websocket.Endpoint.class.isAssignableFrom(endpointClass))
|
||||
{
|
||||
return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig);
|
||||
}
|
||||
|
||||
ServerEndpoint anno = endpointClass.getAnnotation(ServerEndpoint.class);
|
||||
if (anno == null)
|
||||
{
|
||||
return super.getMetadata(endpointClass, endpointConfig);
|
||||
}
|
||||
|
||||
UriTemplatePathSpec templatePathSpec = new UriTemplatePathSpec(anno.value());
|
||||
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
|
||||
|
|
|
@ -22,7 +22,7 @@ import jakarta.websocket.Decoder;
|
|||
import jakarta.websocket.MessageHandler;
|
||||
import jakarta.websocket.PongMessage;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketSession;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.jakarta.tests.MessageType;
|
||||
import org.eclipse.jetty.websocket.util.ReflectUtils;
|
||||
import org.hamcrest.Description;
|
||||
|
@ -77,7 +77,7 @@ public class IsMessageHandlerType extends TypeSafeMatcher<MessageHandler>
|
|||
return false;
|
||||
}
|
||||
|
||||
AvailableDecoders.RegisteredDecoder registeredDecoder = session.getDecoders().getRegisteredDecoderFor(onMessageClass);
|
||||
RegisteredDecoder registeredDecoder = session.getDecoders().getFirstRegisteredDecoder(onMessageClass);
|
||||
if (registeredDecoder == null)
|
||||
{
|
||||
return false;
|
||||
|
|
|
@ -25,7 +25,7 @@ import jakarta.websocket.MessageHandler;
|
|||
import jakarta.websocket.PongMessage;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketSession;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.RegisteredMessageHandler;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.jakarta.tests.MessageType;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
@ -60,7 +60,7 @@ public class IsMessageHandlerTypeRegistered extends TypeSafeMatcher<JakartaWebSo
|
|||
{
|
||||
Class<?> onMessageType = registeredMessageHandler.getHandlerType();
|
||||
|
||||
AvailableDecoders.RegisteredDecoder registeredDecoder = session.getDecoders().getRegisteredDecoderFor(onMessageType);
|
||||
RegisteredDecoder registeredDecoder = session.getDecoders().getFirstRegisteredDecoder(onMessageType);
|
||||
if (registeredDecoder == null)
|
||||
{
|
||||
continue;
|
||||
|
@ -130,7 +130,7 @@ public class IsMessageHandlerTypeRegistered extends TypeSafeMatcher<JakartaWebSo
|
|||
|
||||
mismatchDescription.appendText("<" + onMessageType.getName() + ">");
|
||||
|
||||
AvailableDecoders.RegisteredDecoder registeredDecoder = session.getDecoders().getRegisteredDecoderFor(onMessageType);
|
||||
RegisteredDecoder registeredDecoder = session.getDecoders().getFirstRegisteredDecoder(onMessageType);
|
||||
if (registeredDecoder == null)
|
||||
{
|
||||
mismatchDescription.appendText("(!NO-DECODER!)");
|
||||
|
|
|
@ -20,19 +20,21 @@ package org.eclipse.jetty.websocket.jakarta.tests.coders;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import jakarta.websocket.ClientEndpointConfig;
|
||||
import jakarta.websocket.DecodeException;
|
||||
import jakarta.websocket.Decoder;
|
||||
import jakarta.websocket.EndpointConfig;
|
||||
import org.eclipse.jetty.toolchain.test.Hex;
|
||||
import org.eclipse.jetty.websocket.jakarta.client.BasicClientEndpointConfig;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.IntegerDecoder;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -44,169 +46,198 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||
|
||||
public class AvailableDecodersTest
|
||||
{
|
||||
private static EndpointConfig testConfig;
|
||||
private AvailableDecoders availableDecoders;
|
||||
|
||||
@BeforeAll
|
||||
public static void initConfig()
|
||||
@SafeVarargs
|
||||
public final void init(Class<? extends Decoder>... decoder)
|
||||
{
|
||||
testConfig = new BasicClientEndpointConfig();
|
||||
EndpointConfig testConfig = ClientEndpointConfig.Builder.create()
|
||||
.decoders(Arrays.asList(decoder))
|
||||
.build();
|
||||
this.availableDecoders = new AvailableDecoders(testConfig);
|
||||
}
|
||||
|
||||
private AvailableDecoders decoders = new AvailableDecoders(testConfig);
|
||||
|
||||
private <T> void assertTextDecoder(Class<T> type, String value, T expectedDecoded) throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public <T extends Decoder> T getInstanceFor(Class<?> type)
|
||||
{
|
||||
Decoder.Text<T> decoder = (Decoder.Text<T>)decoders.getInstanceFor(type);
|
||||
try
|
||||
{
|
||||
RegisteredDecoder registeredDecoder = availableDecoders.getFirstRegisteredDecoder(type);
|
||||
return registeredDecoder.getInstance();
|
||||
}
|
||||
catch (NoSuchElementException e)
|
||||
{
|
||||
throw new InvalidWebSocketException("No Decoder found for type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void assertTextDecoder(Class<T> type, String value, T expectedDecoded) throws DecodeException
|
||||
{
|
||||
Decoder.Text<T> decoder = getInstanceFor(type);
|
||||
assertThat("Decoder instance", decoder, notNullValue());
|
||||
T decoded = decoder.decode(value);
|
||||
assertThat("Decoded", decoded, is(expectedDecoded));
|
||||
}
|
||||
|
||||
private <T> void assertBinaryDecoder(Class<T> type, ByteBuffer value, T expectedDecoded)
|
||||
throws IllegalAccessException, InstantiationException, DecodeException
|
||||
throws DecodeException
|
||||
{
|
||||
Decoder.Binary<T> decoder = (Decoder.Binary<T>)decoders.getInstanceFor(type);
|
||||
Decoder.Binary<T> decoder = getInstanceFor(type);
|
||||
assertThat("Decoder Class", decoder, notNullValue());
|
||||
T decoded = decoder.decode(value);
|
||||
assertThat("Decoded", decoded, equalTo(expectedDecoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeBoolean() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeBoolean() throws DecodeException
|
||||
{
|
||||
init();
|
||||
Boolean expected = Boolean.TRUE;
|
||||
assertTextDecoder(Boolean.class, "true", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeboolean() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeboolean() throws DecodeException
|
||||
{
|
||||
init();
|
||||
boolean expected = false;
|
||||
assertTextDecoder(Boolean.TYPE, "false", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeByte() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeByte() throws DecodeException
|
||||
{
|
||||
init();
|
||||
Byte expected = (byte)0x21;
|
||||
assertTextDecoder(Byte.class, "33", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodebyte() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodebyte() throws DecodeException
|
||||
{
|
||||
init();
|
||||
byte expected = 0x21;
|
||||
assertTextDecoder(Byte.TYPE, "33", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeCharacter() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeCharacter() throws DecodeException
|
||||
{
|
||||
init();
|
||||
Character expected = '!';
|
||||
assertTextDecoder(Character.class, "!", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodechar() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodechar() throws DecodeException
|
||||
{
|
||||
init();
|
||||
char expected = '!';
|
||||
assertTextDecoder(Character.TYPE, "!", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeDouble() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeDouble() throws DecodeException
|
||||
{
|
||||
init();
|
||||
Double expected = 123.45D;
|
||||
assertTextDecoder(Double.class, "123.45", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodedouble() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodedouble() throws DecodeException
|
||||
{
|
||||
init();
|
||||
double expected = 123.45D;
|
||||
assertTextDecoder(Double.TYPE, "123.45", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeFloat() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeFloat() throws DecodeException
|
||||
{
|
||||
init();
|
||||
Float expected = 123.4567F;
|
||||
assertTextDecoder(Float.class, "123.4567", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodefloat() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodefloat() throws DecodeException
|
||||
{
|
||||
init();
|
||||
float expected = 123.4567F;
|
||||
assertTextDecoder(Float.TYPE, "123.4567", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeInteger() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeInteger() throws DecodeException
|
||||
{
|
||||
init();
|
||||
Integer expected = 1234;
|
||||
assertTextDecoder(Integer.class, "1234", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeint() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeint() throws DecodeException
|
||||
{
|
||||
init();
|
||||
int expected = 1234;
|
||||
assertTextDecoder(Integer.TYPE, "1234", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeLong() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeLong() throws DecodeException
|
||||
{
|
||||
init();
|
||||
Long expected = 123_456_789L;
|
||||
assertTextDecoder(Long.class, "123456789", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodelong() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodelong() throws DecodeException
|
||||
{
|
||||
init();
|
||||
long expected = 123_456_789L;
|
||||
assertTextDecoder(Long.TYPE, "123456789", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeString() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeString() throws DecodeException
|
||||
{
|
||||
init();
|
||||
String expected = "Hello World";
|
||||
assertTextDecoder(String.class, "Hello World", expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeByteBuffer() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeByteBuffer() throws DecodeException
|
||||
{
|
||||
init();
|
||||
ByteBuffer val = Hex.asByteBuffer("112233445566778899");
|
||||
ByteBuffer expected = Hex.asByteBuffer("112233445566778899");
|
||||
assertBinaryDecoder(ByteBuffer.class, val, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoreDecodeByteArray() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCoreDecodeByteArray() throws DecodeException
|
||||
{
|
||||
init();
|
||||
ByteBuffer val = Hex.asByteBuffer("112233445566778899");
|
||||
byte[] expected = Hex.asByteArray("112233445566778899");
|
||||
assertBinaryDecoder(byte[].class, val, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomDecoderInteger() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCustomDecoderInteger() throws DecodeException
|
||||
{
|
||||
decoders.register(IntegerDecoder.class);
|
||||
|
||||
init(IntegerDecoder.class);
|
||||
String val = "11223344";
|
||||
int expected = 11223344;
|
||||
assertTextDecoder(Integer.class, val, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomDecoderTime() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCustomDecoderTime() throws DecodeException
|
||||
{
|
||||
decoders.register(TimeDecoder.class);
|
||||
|
||||
init(TimeDecoder.class);
|
||||
String val = "12:34:56 GMT";
|
||||
|
||||
Date epoch = Date.from(Instant.EPOCH);
|
||||
|
@ -222,10 +253,9 @@ public class AvailableDecodersTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCustomDecoderDate() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCustomDecoderDate() throws DecodeException
|
||||
{
|
||||
decoders.register(DateDecoder.class);
|
||||
|
||||
init(DateDecoder.class);
|
||||
String val = "2016.08.22";
|
||||
|
||||
Date epoch = Date.from(Instant.EPOCH);
|
||||
|
@ -241,10 +271,9 @@ public class AvailableDecodersTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCustomDecoderDateTime() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCustomDecoderDateTime() throws DecodeException
|
||||
{
|
||||
decoders.register(DateTimeDecoder.class);
|
||||
|
||||
init(DateTimeDecoder.class);
|
||||
String val = "2016.08.22 AD at 12:34:56 GMT";
|
||||
|
||||
Date epoch = Date.from(Instant.EPOCH);
|
||||
|
@ -264,11 +293,10 @@ public class AvailableDecodersTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCustomDecoderValidDualText() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCustomDecoderValidDualText() throws DecodeException
|
||||
{
|
||||
decoders.register(ValidDualDecoder.class);
|
||||
|
||||
AvailableDecoders.RegisteredDecoder registered = decoders.getRegisteredDecoderFor(Integer.class);
|
||||
init(ValidDualDecoder.class);
|
||||
RegisteredDecoder registered = availableDecoders.getFirstRegisteredDecoder(Integer.class);
|
||||
assertThat("Registered Decoder for Integer", registered.decoder.getName(), is(ValidDualDecoder.class.getName()));
|
||||
|
||||
String val = "[1,234,567]";
|
||||
|
@ -278,11 +306,10 @@ public class AvailableDecodersTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCustomDecoderValidDualBinary() throws IllegalAccessException, InstantiationException, DecodeException
|
||||
public void testCustomDecoderValidDualBinary() throws DecodeException
|
||||
{
|
||||
decoders.register(ValidDualDecoder.class);
|
||||
|
||||
AvailableDecoders.RegisteredDecoder registered = decoders.getRegisteredDecoderFor(Long.class);
|
||||
init(ValidDualDecoder.class);
|
||||
RegisteredDecoder registered = availableDecoders.getFirstRegisteredDecoder(Long.class);
|
||||
assertThat("Registered Decoder for Long", registered.decoder.getName(), is(ValidDualDecoder.class.getName()));
|
||||
|
||||
ByteBuffer val = ByteBuffer.allocate(16);
|
||||
|
@ -299,18 +326,16 @@ public class AvailableDecodersTest
|
|||
public void testCustomDecoderRegisterDuplicate()
|
||||
{
|
||||
// has duplicated support for the same target Type
|
||||
Exception e = assertThrows(InvalidWebSocketException.class, () -> decoders.register(BadDualDecoder.class));
|
||||
assertThat(e.getMessage(), containsString("Duplicate"));
|
||||
Exception e = assertThrows(InvalidWebSocketException.class, () -> init(BadDualDecoder.class));
|
||||
assertThat(e.getMessage(), containsString("Multiple decoders with different interface types"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomDecoderRegisterOtherDuplicate()
|
||||
{
|
||||
// This duplicate of decoders is of the same interface type so will form a decoder list.
|
||||
// Register DateDecoder (decodes java.util.Date)
|
||||
decoders.register(DateDecoder.class);
|
||||
|
||||
// Register TimeDecoder (which also wants to decode java.util.Date)
|
||||
Exception e = assertThrows(InvalidWebSocketException.class, () -> decoders.register(TimeDecoder.class));
|
||||
assertThat(e.getMessage(), containsString("Duplicate"));
|
||||
init(DateDecoder.class, TimeDecoder.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,422 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.jakarta.tests.coders;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.websocket.Decoder;
|
||||
import jakarta.websocket.DeploymentException;
|
||||
import jakarta.websocket.Endpoint;
|
||||
import jakarta.websocket.EndpointConfig;
|
||||
import jakarta.websocket.MessageHandler;
|
||||
import jakarta.websocket.Session;
|
||||
import jakarta.websocket.server.ServerContainer;
|
||||
import jakarta.websocket.server.ServerEndpointConfig;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.websocket.jakarta.client.JakartaWebSocketClientContainer;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.AbstractDecoder;
|
||||
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
|
||||
import org.eclipse.jetty.websocket.jakarta.tests.EventSocket;
|
||||
import org.eclipse.jetty.websocket.jakarta.tests.WSURI;
|
||||
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class DecoderListTest
|
||||
{
|
||||
private Server server;
|
||||
private ServletContextHandler contextHandler;
|
||||
private URI serverUri;
|
||||
private JakartaWebSocketClientContainer client;
|
||||
|
||||
@BeforeEach
|
||||
public void before() throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
contextHandler = new ServletContextHandler();
|
||||
contextHandler.setContextPath("/");
|
||||
server.setHandler(contextHandler);
|
||||
|
||||
client = new JakartaWebSocketClientContainer();
|
||||
client.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
client.stop();
|
||||
}
|
||||
|
||||
public interface CheckedConsumer<T>
|
||||
{
|
||||
void accept(T t) throws DeploymentException;
|
||||
}
|
||||
|
||||
public void start(CheckedConsumer<ServerContainer> containerConsumer) throws Exception
|
||||
{
|
||||
JakartaWebSocketServletContainerInitializer.configure(contextHandler, (context, container) ->
|
||||
containerConsumer.accept(container));
|
||||
server.start();
|
||||
serverUri = WSURI.toWebsocket(server.getURI());
|
||||
}
|
||||
|
||||
public static Stream<Arguments> getTextArguments()
|
||||
{
|
||||
return Stream.of(
|
||||
Arguments.of("=DecodeEquals", "DecodeEquals="),
|
||||
Arguments.of("+DecodePlus", "DecodePlus+"),
|
||||
Arguments.of("-DecodeMinus", "DecodeMinus-"),
|
||||
Arguments.of("DecodeNoMatch", "DecodeNoMatch")
|
||||
);
|
||||
}
|
||||
|
||||
public static Stream<Arguments> getBinaryArguments()
|
||||
{
|
||||
return Stream.of(
|
||||
Arguments.of("=DecodeEquals", "DecodeEquals="),
|
||||
Arguments.of("+DecodePlus", "DecodePlus+"),
|
||||
Arguments.of("-DecodeMinus", "DecodeMinus-"),
|
||||
// No decoder accepts this message and we have no default decoder for this type, so we get no response.
|
||||
Arguments.of("DecodeNoMatch", null)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("getTextArguments")
|
||||
public void testTextDecoderList(String request, String expected) throws Exception
|
||||
{
|
||||
start(container ->
|
||||
{
|
||||
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(TextDecoderListEndpoint.class, "/")
|
||||
.decoders(List.of(EqualsTextDecoder.class, PlusTextDecoder.class, MinusTextDecoder.class))
|
||||
.build();
|
||||
container.addEndpoint(endpointConfig);
|
||||
});
|
||||
|
||||
EventSocket clientEndpoint = new EventSocket();
|
||||
Session session = client.connectToServer(clientEndpoint, serverUri);
|
||||
session.getBasicRemote().sendText(request);
|
||||
String response = clientEndpoint.textMessages.poll(3, TimeUnit.SECONDS);
|
||||
assertThat(response, is(expected));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("getBinaryArguments")
|
||||
public void testBinaryDecoderList(String request, String expected) throws Exception
|
||||
{
|
||||
start(container ->
|
||||
{
|
||||
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(BinaryDecoderListEndpoint.class, "/")
|
||||
.decoders(List.of(EqualsBinaryDecoder.class, PlusBinaryDecoder.class, MinusBinaryDecoder.class))
|
||||
.build();
|
||||
container.addEndpoint(endpointConfig);
|
||||
});
|
||||
|
||||
EventSocket clientEndpoint = new EventSocket();
|
||||
Session session = client.connectToServer(clientEndpoint, serverUri);
|
||||
session.getBasicRemote().sendBinary(BufferUtil.toBuffer(request));
|
||||
ByteBuffer response = clientEndpoint.binaryMessages.poll(3, TimeUnit.SECONDS);
|
||||
assertThat(BufferUtil.toString(response), is(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecoderOrder() throws Exception
|
||||
{
|
||||
start(container ->
|
||||
{
|
||||
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(TextDecoderListEndpoint.class, "/")
|
||||
.decoders(List.of(AppendingPlusDecoder.class, AppendingMinusDecoder.class))
|
||||
.build();
|
||||
container.addEndpoint(endpointConfig);
|
||||
});
|
||||
|
||||
// The AppendingPlusDecoder should be the one used as it was first in the list.
|
||||
EventSocket clientEndpoint = new EventSocket();
|
||||
Session session = client.connectToServer(clientEndpoint, serverUri);
|
||||
session.getBasicRemote().sendText("");
|
||||
String response = clientEndpoint.textMessages.poll(3, TimeUnit.SECONDS);
|
||||
assertThat(response, is("+"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamDecoders()
|
||||
{
|
||||
// Stream decoders will not be able to form a decoder list as they don't implement willDecode().
|
||||
Throwable error = assertThrows(Throwable.class, () ->
|
||||
start(container ->
|
||||
{
|
||||
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(TextDecoderListEndpoint.class, "/")
|
||||
.decoders(List.of(TextStreamDecoder1.class, TextStreamDecoder2.class))
|
||||
.build();
|
||||
container.addEndpoint(endpointConfig);
|
||||
})
|
||||
);
|
||||
|
||||
assertThat(error, instanceOf(RuntimeException.class));
|
||||
Throwable cause = error.getCause();
|
||||
assertThat(cause, instanceOf(DeploymentException.class));
|
||||
Throwable invalidWebSocketException = cause.getCause();
|
||||
assertThat(invalidWebSocketException, instanceOf(InvalidWebSocketException.class));
|
||||
assertThat(invalidWebSocketException.getMessage(), containsString("Multiple decoders for objectTypeclass java.lang.String"));
|
||||
}
|
||||
|
||||
public static class TextDecoderListEndpoint extends Endpoint
|
||||
{
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config)
|
||||
{
|
||||
session.addMessageHandler(new PartialTextHandler(session));
|
||||
}
|
||||
}
|
||||
|
||||
public static class BinaryDecoderListEndpoint extends Endpoint
|
||||
{
|
||||
@Override
|
||||
public void onOpen(Session session, EndpointConfig config)
|
||||
{
|
||||
session.addMessageHandler(new PartialBinaryHandler(session));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Session session, Throwable t)
|
||||
{
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static class PartialTextHandler implements MessageHandler.Whole<String>
|
||||
{
|
||||
private final Session _session;
|
||||
|
||||
public PartialTextHandler(Session session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message)
|
||||
{
|
||||
_session.getAsyncRemote().sendText(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PartialBinaryHandler implements MessageHandler.Whole<ByteBufferWrapper>
|
||||
{
|
||||
private final Session _session;
|
||||
|
||||
public PartialBinaryHandler(Session session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteBufferWrapper message)
|
||||
{
|
||||
_session.getAsyncRemote().sendBinary(message.getByteBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
public static class ByteBufferWrapper
|
||||
{
|
||||
private final ByteBuffer byteBuffer;
|
||||
|
||||
public ByteBufferWrapper(ByteBuffer byteBuffer)
|
||||
{
|
||||
this.byteBuffer = byteBuffer;
|
||||
}
|
||||
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return byteBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EqualsBinaryDecoder extends ByteBufferWrapperDecoder
|
||||
{
|
||||
public EqualsBinaryDecoder()
|
||||
{
|
||||
super("=");
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlusBinaryDecoder extends ByteBufferWrapperDecoder
|
||||
{
|
||||
public PlusBinaryDecoder()
|
||||
{
|
||||
super("+");
|
||||
}
|
||||
}
|
||||
|
||||
public static class MinusBinaryDecoder extends ByteBufferWrapperDecoder
|
||||
{
|
||||
public MinusBinaryDecoder()
|
||||
{
|
||||
super("-");
|
||||
}
|
||||
}
|
||||
|
||||
public static class EqualsTextDecoder extends PrefixStringDecoder
|
||||
{
|
||||
public EqualsTextDecoder()
|
||||
{
|
||||
super("=");
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlusTextDecoder extends PrefixStringDecoder
|
||||
{
|
||||
public PlusTextDecoder()
|
||||
{
|
||||
super("+");
|
||||
}
|
||||
}
|
||||
|
||||
public static class MinusTextDecoder extends PrefixStringDecoder
|
||||
{
|
||||
public MinusTextDecoder()
|
||||
{
|
||||
super("-");
|
||||
}
|
||||
}
|
||||
|
||||
public static class AppendingPlusDecoder extends AppendingStringDecoder
|
||||
{
|
||||
public AppendingPlusDecoder()
|
||||
{
|
||||
super("+");
|
||||
}
|
||||
}
|
||||
|
||||
public static class AppendingMinusDecoder extends AppendingStringDecoder
|
||||
{
|
||||
public AppendingMinusDecoder()
|
||||
{
|
||||
super("-");
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextStreamDecoder1 extends AbstractDecoder implements Decoder.TextStream<String>
|
||||
{
|
||||
@Override
|
||||
public String decode(Reader reader) throws IOException
|
||||
{
|
||||
return "Decoder1: " + IO.toString(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextStreamDecoder2 extends AbstractDecoder implements Decoder.TextStream<String>
|
||||
{
|
||||
@Override
|
||||
public String decode(Reader reader) throws IOException
|
||||
{
|
||||
return "Decoder2: " + IO.toString(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ByteBufferWrapperDecoder extends AbstractDecoder implements Decoder.Binary<ByteBufferWrapper>
|
||||
{
|
||||
private final String prefix;
|
||||
|
||||
public ByteBufferWrapperDecoder(String prefix)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufferWrapper decode(ByteBuffer bytes)
|
||||
{
|
||||
String s = BufferUtil.toString(bytes).substring(prefix.length()) + prefix;
|
||||
return new ByteBufferWrapper(BufferUtil.toBuffer(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willDecode(ByteBuffer bytes)
|
||||
{
|
||||
return BufferUtil.toString(bytes).startsWith(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PrefixStringDecoder extends AbstractDecoder implements Decoder.Text<String>
|
||||
{
|
||||
private final String prefix;
|
||||
|
||||
public PrefixStringDecoder(String prefix)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(String s)
|
||||
{
|
||||
return s.substring(prefix.length()) + prefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willDecode(String s)
|
||||
{
|
||||
return s.startsWith(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AppendingStringDecoder extends AbstractDecoder implements Decoder.Text<String>
|
||||
{
|
||||
private final String s;
|
||||
|
||||
public AppendingStringDecoder(String prefix)
|
||||
{
|
||||
this.s = prefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(String message)
|
||||
{
|
||||
return message + s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willDecode(String message)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,10 +28,12 @@ import java.util.concurrent.CompletableFuture;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.websocket.ClientEndpointConfig;
|
||||
import jakarta.websocket.Decoder;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.websocket.core.Frame;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
|
||||
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextStreamMessageSink;
|
||||
import org.eclipse.jetty.websocket.jakarta.tests.FunctionMethod;
|
||||
import org.eclipse.jetty.websocket.jakarta.tests.client.AbstractClientSessionTest;
|
||||
|
@ -64,7 +66,6 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
|
|||
@Test
|
||||
public void testQuotesDecodedReaderMessageSink() throws Exception
|
||||
{
|
||||
Decoder.TextStream<Quotes> decoder = new QuotesDecoder();
|
||||
CompletableFuture<Quotes> futureQuotes = new CompletableFuture<>();
|
||||
MethodHandle functionHandle = FunctionMethod.getFunctionApplyMethodHandle();
|
||||
MethodHandle quoteHandle = functionHandle.bindTo((Function<Quotes, Void>)(quotes) ->
|
||||
|
@ -80,7 +81,8 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
|
|||
return null;
|
||||
});
|
||||
|
||||
DecodedTextStreamMessageSink sink = new DecodedTextStreamMessageSink(session.getCoreSession(), decoder, quoteHandle);
|
||||
List<RegisteredDecoder> decoders = toRegisteredDecoderList(QuotesDecoder.class, Quotes.class);
|
||||
DecodedTextStreamMessageSink<Quotes> sink = new DecodedTextStreamMessageSink<>(session.getCoreSession(), quoteHandle, decoders);
|
||||
|
||||
List<FutureCallback> callbacks = new ArrayList<>();
|
||||
FutureCallback finCallback = null;
|
||||
|
@ -107,4 +109,21 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
|
|||
assertThat("Quotes.author", quotes.getAuthor(), is("Benjamin Franklin"));
|
||||
assertThat("Quotes.count", quotes.getQuotes().size(), is(3));
|
||||
}
|
||||
|
||||
public List<RegisteredDecoder> toRegisteredDecoderList(Class<? extends Decoder> clazz, Class<?> objectType)
|
||||
{
|
||||
Class<? extends Decoder> interfaceType;
|
||||
if (Decoder.Text.class.isAssignableFrom(clazz))
|
||||
interfaceType = Decoder.Text.class;
|
||||
else if (Decoder.Binary.class.isAssignableFrom(clazz))
|
||||
interfaceType = Decoder.Binary.class;
|
||||
else if (Decoder.TextStream.class.isAssignableFrom(clazz))
|
||||
interfaceType = Decoder.TextStream.class;
|
||||
else if (Decoder.BinaryStream.class.isAssignableFrom(clazz))
|
||||
interfaceType = Decoder.BinaryStream.class;
|
||||
else
|
||||
throw new IllegalStateException();
|
||||
|
||||
return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build()));
|
||||
}
|
||||
}
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -18,7 +18,7 @@
|
|||
<jetty.url>http://www.eclipse.org/jetty</jetty.url>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<build-support.version>1.4</build-support.version>
|
||||
<checkstyle.version>8.20</checkstyle.version>
|
||||
<checkstyle.version>8.29</checkstyle.version>
|
||||
<slf4j.version>2.0.0-alpha1</slf4j.version>
|
||||
<log4j2.version>2.13.0</log4j2.version>
|
||||
<logback.version>1.3.0-alpha5</logback.version>
|
||||
|
|
|
@ -64,10 +64,10 @@ public class MongoTestHelper
|
|||
{
|
||||
long start = System.currentTimeMillis();
|
||||
mongo.start();
|
||||
String containerIpAddress = mongo.getContainerIpAddress();
|
||||
String containerIpAddress = mongo.getContainerIpAddress();
|
||||
int mongoPort = mongo.getMappedPort(27017);
|
||||
LOG.info("Mongo container started for {}:{} - {}ms", containerIpAddress, mongoPort,
|
||||
System.currentTimeMillis() - start);
|
||||
System.currentTimeMillis() - start);
|
||||
System.setProperty("embedmongoHost", containerIpAddress);
|
||||
System.setProperty("embedmongoPort", Integer.toString(mongoPort));
|
||||
}
|
||||
|
|
|
@ -85,14 +85,14 @@ public class DuplicateCookieTest
|
|||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMultipleSessionCookiesOnlyOneValid() throws Exception
|
||||
{
|
||||
String contextPath = "";
|
||||
String servletMapping = "/server";
|
||||
HttpClient client = null;
|
||||
|
||||
|
||||
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
|
||||
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
|
||||
|
||||
|
@ -114,7 +114,7 @@ public class DuplicateCookieTest
|
|||
createInvalidSession(contextHandler.getSessionHandler().getSessionCache(),
|
||||
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
|
||||
"2233");
|
||||
|
||||
|
||||
client = new HttpClient();
|
||||
client.start();
|
||||
|
||||
|
@ -132,7 +132,7 @@ public class DuplicateCookieTest
|
|||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMultipleSessionCookiesMultipleExists() throws Exception
|
||||
{
|
||||
|
@ -163,7 +163,7 @@ public class DuplicateCookieTest
|
|||
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
|
||||
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
|
||||
"9111");
|
||||
|
||||
|
||||
client = new HttpClient();
|
||||
client.start();
|
||||
|
||||
|
@ -190,14 +190,14 @@ public class DuplicateCookieTest
|
|||
cache.add(id, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
public Session createInvalidSession(SessionCache cache, SessionDataStore store, String id) throws Exception
|
||||
{
|
||||
Session session = createUnExpiredSession(cache, store, id);
|
||||
session._state = Session.State.INVALID;
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
public static class TestServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
|
|
@ -95,7 +95,7 @@ public class SessionInvalidationTest
|
|||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class TestServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -117,7 +117,7 @@ public class SessionInvalidationTest
|
|||
|
||||
//invalidate existing session
|
||||
session.invalidate();
|
||||
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> session.invalidate());
|
||||
assertThrows(IllegalStateException.class, () -> session.getLastAccessedTime());
|
||||
assertThrows(IllegalStateException.class, () -> session.getCreationTime());
|
||||
|
@ -130,7 +130,6 @@ public class SessionInvalidationTest
|
|||
assertThrows(IllegalStateException.class, () -> session.removeValue("foo"));
|
||||
assertThrows(IllegalStateException.class, () -> session.setAttribute("a", "b"));
|
||||
assertDoesNotThrow(() -> session.getId());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue