-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Servlet 4.0 request trailerFields #6917
Support Servlet 4.0 request trailerFields #6917
Conversation
This enables applications and libraries to use trailers according to the servlet 4.0 spec instead of casting to Jetty's custom implementation. Roughly backported from Jetty 10.x, but with @OverRide omitted, and some simple javadoc since none will be inherited. Signed-off-by: Colin Alworth <colin@colinalworth.com>
Jetty 9.4.x is in maintenance mode. Active branches for new functionality is Jetty 10.0.x and Jetty 11.0.x. If you want Servlet 4.0 behavior you have to be using Jetty 10.0.x (which supports Servlet 4.0). Also note that |
Understood, my motivation is only that even today, Java 8 is still widely deployed, with many projects stuck in the chicken/egg situation of updating. I appreciate that Java 10/11 have moved up to Java 11 (and to jakarta.servlet in jetty 11), but am still personally bound to support specific requirements for the time being. My hope was that since Jetty 10/11 do not support setTrailers (and indeed a javax.servlet 4.0 project can't use jetty as a direct dependency), this would smooth the migration path for other projects. |
@niloc132 know that Jakarta EE 10 (due out before the end of the year) and even Spring 6 (due out next year) are setting JDK 17 as the minimum JVM. The struggle to upgrade is becoming even more intense. |
Both Jetty 10 on Servlet 4.0 and Jetty 11 on Servlet 5.0 support the Servlet spec defined |
Yes - I initially wrote the downstream code for the standard setTrailerFields method, and it only failed in Jetty 9.4 (since left unimplemented, the default method fails silently, and h2 streams, otherwise well-supported in jetty 9.4, incorrectly close without delivering trailers). Hacky attempts at an I appreciate you taking the time to discuss this, and understand your perspective. |
Note that this PR doesn't prevent an app from needing to downcast the response instance to a Jetty |
@gregw I'm not sure I understand how your idea would work - were you thinking of something like this?
That doesn't work for Jetty 10/11 since it is expected that super.setTrailerFields is called - adding that makes a certain amount of sense (and would be a no-op for Jetty 9). For Jetty 9 though it still wouldn't work, since Probably I misunderstood you, and there is an easier way you're thinking such that an overridden getTrailerFields is handled correctly in Jetty 10/11, and somehow that Jetty 9 would know to check that method at all? Next in response to your suggestion I tried making a Resonse subclass, so that at least when i call req.startAsync(req, replacementResponse) it would have a real jetty response instance, but this also doesn't work - I can't be certain, but garbage data is coming out for some reason on the other side of the wire (both responses writing to output perhaps?). I specifically wired this up using some reflection to see if a) it was a jetty Response, and b) if Response has a setTrailers method I could try to call in the subclass:
At this point, unless you were suggesting some other way (probably closer to the first method) to intercept and propagate trailers, I'm not sure I understand. My "fix" at this point probably has to be the same reflection code, but specifically at the normal HttpServletResponse.setTrailerFields callsite, and always compile the library against jetty as a compile-only dependency. Could possibly use just Jetty 9 and either a method handle or test if the method is there with reflection, then rely on bytecode for actual runtime dispatch. |
This does appear to work, but as above, requires reflection and a HttpServletResponseWrapper, and including Jetty on the compile-only classpath (but omitting it in the runtime classpath does indeed work as expected).
|
On a standard WebAppContext, using Jetty 9.4.x, if you use the Don't be fooled by the classloaders in your unit test cases, as that is not how a runtime on Jetty 9.4.x functions. Also ... if (!response.getClass().getName().equals("org.eclipse.jetty.server.Response")) { That has no guarantee to work, the You have to identify wrapping and unwrap to the root instance at a minimum. |
I readily acknowledge that this snippet is incomplete, merely that for trivial cases, with only ~2x the lines of code as the PR itself, that it works. The PR does not require casting to the Jetty API - the hacky snippet does, but only after it has done its simple "typecheck", and has been tested to confirm that it works on classpaths with no jetty versions of any kind present. I haven't changed the PR (as there was no feedback to its content), but only put up the PoC workaround that I am currently exploring in lieu of the actual PR. |
This would neither compile nor execute on Jetty 9.4.x, as it's a forced Servlet 3.1.0 environment. |
As a project using this workaround would necessarily assume servlet 4.0, both servlet 4.0 and jetty would be on the compile classpath. None of the code in my comments would make sense to be added to Jetty itself, these are only workarounds if the PR's patch is not acceptable (as the "right" way of doing it, just too late to merge to Jetty 9). The patch attached to the PR itself solved this by avoiding using |
Here's a demonstration. The project for the war can be found at https://github.com/joakime/use-servlet-4 You can easily create a bare-bones [servlet-4-demo-base]$ tree
.
├── start.ini
└── webapps
└── use-servlet-4.war
1 directory, 2 files
[servlet-4-demo-base]$ cat start.ini
--module=deploy
--module=http
[servlet-4-demo-base]$ jar -tvf webapps/use-servlet-4.war
95 Fri Sep 24 16:02:36 CDT 2021 META-INF/MANIFEST.MF
0 Fri Sep 24 16:02:36 CDT 2021 META-INF/
0 Fri Sep 24 16:02:36 CDT 2021 WEB-INF/
0 Fri Sep 24 16:02:36 CDT 2021 WEB-INF/lib/
0 Fri Sep 24 16:02:36 CDT 2021 WEB-INF/classes/
0 Fri Sep 24 16:02:36 CDT 2021 WEB-INF/classes/demo/
82973 Fri Nov 13 13:31:04 CST 2020 WEB-INF/lib/jakarta.servlet-api-4.0.4.jar
563 Fri Sep 24 16:02:32 CDT 2021 WEB-INF/web.xml
3371 Fri Sep 24 16:02:36 CDT 2021 WEB-INF/classes/demo/DumpServlet.class
52 Fri Sep 24 15:55:22 CDT 2021 index.jsp
2019 Fri Sep 24 15:55:50 CDT 2021 META-INF/maven/demo/use-servlet-4/pom.xml
90 Fri Sep 24 16:02:36 CDT 2021 META-INF/maven/demo/use-servlet-4/pom.properties You'll notice that The package demo;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DumpServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
PrintWriter out = resp.getWriter();
Class<?> clazz = resp.getClass();
out.printf("HttpServletResponse = %s (%s)%n", clazz.getName(), getLocation(clazz));
out.printf("HttpServletResponse methods%n");
for (Method method : clazz.getMethods())
{
out.printf(" %s%n", method);
}
}
private String getLocation(Class<?> clazz)
{
return getCodeSourceLocation(clazz).toASCIIString();
}
public static URI getCodeSourceLocation(Class<?> clazz)
{
try
{
ProtectionDomain domain = AccessController.doPrivileged((PrivilegedAction<ProtectionDomain>)() -> clazz.getProtectionDomain());
if (domain != null)
{
CodeSource source = domain.getCodeSource();
if (source != null)
{
URL location = source.getLocation();
if (location != null)
{
return location.toURI();
}
}
}
}
catch (URISyntaxException ignored)
{
}
return null;
}
} Start the server ... [servlet-4-demo-base]$ java -jar ~/distros/jetty-home-9.4.43.v20210629/start.jar
2021-09-24 16:10:41.881:INFO::main: Logging initialized @366ms to org.eclipse.jetty.util.log.StdErrLog
2021-09-24 16:10:42.101:INFO:oejs.Server:main: jetty-9.4.43.v20210629; built: 2021-06-30T11:07:22.254Z; git: 526006ecfa3af7f1a27ef3a288e2bef7ea9dd7e8; jvm 11.0.12+7
2021-09-24 16:10:42.115:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///home/joakim/code/jetty/distros/bases/servlet-4-demo-base/webapps/] at interval 1
2021-09-24 16:10:42.188:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /use-servlet-4, did not find org.eclipse.jetty.jsp.JettyJspServlet
2021-09-24 16:10:42.194:INFO:oejs.session:main: DefaultSessionIdManager workerName=node0
2021-09-24 16:10:42.194:INFO:oejs.session:main: No SessionScavenger set, using defaults
2021-09-24 16:10:42.194:INFO:oejs.session:main: node0 Scavenging every 660000ms
2021-09-24 16:10:42.214:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@359df09a{Servlet 4.0 Demo,/use-servlet-4,file:///tmp/jetty-0_0_0_0-8080-use-servlet-4_war-_use-servlet-4-any-3432559648625761543/webapp/,AVAILABLE}{/home/joakim/code/jetty/distros/bases/servlet-4-demo-base/webapps/use-servlet-4.war}
2021-09-24 16:10:42.230:INFO:oejs.AbstractConnector:main: Started ServerConnector@4dc22657{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2021-09-24 16:10:42.232:INFO:oejs.Server:main: Started @717ms Starts fine. Now hit URL You'll get the following output ...
Notice that the Jetty server didn't use |
The goal of this PR is to reach Trailer feature parity with Servlet 4.0 without casting or using Jetty internal classes. The existing Jetty 9.4.x However, a HttpServlet can be used, with Jetty internal classes, but only in Embedded Jetty, and by using the Here's an example of the Servlet in a branch: Response baseResponse = Request.getBaseRequest(req).getResponse();
baseResponse.setTrailers(() ->
{
HttpFields trailers = new HttpFields();
trailers.put("X-Demo", "Foo");
return trailers;
}); With the testcase using the embedded Note that this Note that Of Note: If you use a WAR or WebAppContext, you can also not cast to |
@niloc132 your PR will not work in jetty-9.4 because it requires users to access jetty's Response object: the api trailer methods do not exist < servlet4, and the servlet4 api is not available in jetty-9.4. So we can't accept this PR. Our suggestion for you is that you write a HttpServletResponseWrapper that works only via reflection (no downcasting) that will call through to jetty's Response object when the servlet4 api is not available. |
Sorry for the delay, I got caught up working on Jakarta support (to add jetty 11 and tomcat 10 to the set of versions that the tool I was writing this for needed): grpc/grpc-java#8596 As above, I certainly understand your perspective, disagreeing that it is appropriate to update Jetty 9, and instead focusing on 10 and 11, but my requirements lead me down this road. The linked patch has a class I likewise recognize that not all users of Jetty use it as an embedded webserver, but I would hazard that only out of curiosity have I ever not deployed jetty as a library embedded in a standalone application. My experience may not be common, but I was first introduced some 10+ years ago to this outstanding product as a handy way to lightly embed Java servlets, and it tends to be the first tool I reach for when serving web content (and as above, always embedded). At least at a quick test (enable http2/c, enable annotations) I am finding that a deployed war in a non-embedded Jetty can't access some jetty types anyway - I tried to use Here's an example of the Servlet in a branch:
Perhaps I missed a step in setting up jetty (I'm unfamiliar with the --add-to-start wiring, as I effectively always use embedded jetty, as above), but this actually seems like the correct behavior, that the child war cannot/shouldn't access the parent classes? |
Seems like you are hitting the standard webapp classloader isolation on the Servlet spec. Server classes are not visible to the webapp. |
I agree - and in embedded situations, it is easy to replace the servlet-api jar with a newer version to support doing this, in cases where it matters. The proposed workaround (or the like) will not work in these cases - reflection could be used, adding some complexity and cost here. My preference is to support this in what seems to be the best implementation of http2 servlets available, while still maintaining Java 8 support. The grpc-java project actually supports Java 7 as its baseline (for android I assume?), but as the servlet-api 4.0 spec requires Java 8 I have only set the minimum to be Java 8, to better keep with those requirements (as the jump from Java 7 to 8 is much smaller than 8 to 11). Tomcat and Undertow support running in Java 8 with h2, and it was my hope to showcase Jetty as well. I do not object to you closing this if you aren't interested in supporting this case. Even in that case, I am not looking for support to build this workaround (I think I have the tools to make examples should I or other downstream users need this). I appreciate the help you've offered here, but I am mostly interested in ensuring that Jetty is in front of users as a first-class option when building a gRPC application which needs more functionality than just a bare gRPC server (as netty offers). If that isn't a priority, I can guard the Jetty integration tests with IE11 checks, and ensure the examples guide users to the most compatible other servers. If there are ways that I can improve the patch such that it might be accepted, please let me know and I will work on them, but if this isn't of interest at all, I understand. Again, I appreciate the work you've done to help with this issue, and understand that maintaining a project such as Jetty is complex and has many competing considerations. |
Android has supported Java 8 since API 26 (roughly the end of 2017) Android supports Java 11 now - https://android-developers.googleblog.com/2020/12/announcing-android-gradle-plugin.html
This is a bad assumption about the Servlet Spec / API jars. The bytecode level of the Spec / API jars have no relationship with implementation and container requirements. If you write against Servlet 3.0 (Java 5), you should be Here's a breakdown of similar decisions in another container. Tomcat 3.3.x - minimum Java 1.1 - Servlet 2.2 (spec at Java 1.0) Take Jakarta EE 10 (due out in a few months). You know that Spring 6 is also going Java 17 minimum version right? Java 7 public support (meaning access to public, non-paid support contract, releases) ended July 2019. Why haven't you dropped support for Java 7 yet? Speaking of HTTP/2, since it pretty much requires TLSv1.2. Every release of Java ratchets down the security of SSL/TLS based on industry recommendations. |
I don't mean to relitigate all of these points, but briefly, I do not use or support Java 7 for any of my projects, and I am not a member of the gRPC team. I am seeking to add servlet support for grpc-java, ideally with Jetty, so I can serve static content, provide gRPC services, and offer filters to handle grpc-web (since browsers don't support enough of http2 to handle grpc proper). While I don't personally use Java 7 on anything, I am still trying to follow the guidelines of the grpc project when submitting patches to them. I do have projects that are still limited to Java 8, but this a business decision made by others, not a deliberate decision on my part. Those others are the product managers I work with, and the downstream consumers they build for. If I were king, we'd all be on Java 17 already (alas, to my knowledge, only a few maintainers have even offered jdk17 builds, adoptopenjdk is not one of them). Not even being minor royalty in the great world of developers, I instead take requirements (even if I disagree with them) and try to produce correct, interoperable, maintainable code. (I haven't used Spring on purpose since about 2009.) |
The thing is, Jetty 9 is Servlet 3.1, on Java 8. The users that can use the features in this PR, are fully embedded-jetty users, thing is, they don't actually don't need this PR, they can just use the features already in the Request/Response objects directly. As for making other open source projects support Jetty 9 and Jetty 10 even if they themselves only use Java 8 (or in some cases still on Java 6, staring at you logback) is possible, many projects do just that. If you don't want any of that, and want grpc with servlet 4, you have to use Jetty 10.
Now to the future. If you are an open source project using the Servlet spec (or any Java EE or Jakarta EE spec, even things as mundane as javax.sql, or javax.annotations), you will have to migrate to continue to have modern support for your project. Jetty will continue to support The next release, Jetty 12, based on Jakarta EE 10 (JDK 17), Servlet 6.0 (on Will Jetty (and Tomcat, and Undertow, and Glassfish, etc) support 5 major versions of the servlet spec for users that don't want to upgrade?
That's a big ask for an open source web container. We've already dropped ...
(Reminder, Jetty versioning since 1995 is We have a near term future of ...
For us, that's a future with 3 mainlines, and 1 maintenance mode branch.
I also do not use Spring, I find it constricting, and often doing things in the most inefficient way possible under the flag of a consistent API for all. But ignoring spring, as an open source project, is a bad idea. This is interesting in itself, from a planning point of view. Servlet 4.x saw barely any adoption, I think due to the slow adoption of HTTP/2, also the most common stated reason for people that did upgrade is the existence of the HTTP/2 push API. (which Jetty 9 championed for in the Servlet spec, and also has an API for in Jetty 9) |
Hi @niloc132. For all the reasons we've listed in prior comments, we will not be able to accept this PR. I hope this doesn't dissuade you from submitting future PRs, as we are keen to involve the jetty user community. |
Very sad an effort to support GRPC on embedded Jetty 9 has been turned shot down |
@atagunov Jetty 9 is now at End of Community Support Jetty 12 supports |
That is sad as well |
@atagunov while I think this can be made to work in Jetty 9, there are other improvements that can be found in 10+ that will be important for a rigorous gRPC implementation to have. I have not tried to use Jetty 12 as described, but the servlet support that landed in https://github/grpc/grpc-java should work in Jetty 10, 11, and 12. None of those options work well for teams that require Java 8 - but at the same time I have to question adding gRPC to any legacy Java 8 project that doesn't intend to update beyond that. That said, feel free to contact me off-list on this if needed - I also understand that there may be additional commercial support available for Jetty (even Jetty 9) from WebTide. |
This enables applications and libraries to use trailers according to the
servlet 4.0 spec instead of casting to Jetty's custom implementation.
Roughly backported from Jetty 10.x, but with @OverRide omitted, and some
simple javadoc since none will be inherited.
Signed-off-by: Colin Alworth colin@colinalworth.com