Skip to content
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

java.io.IOException: written 4677 > 0 content-length (Jetty 11 to 12 upgrade) #12541

Closed
rorytorneymf opened this issue Nov 15, 2024 · 6 comments
Labels

Comments

@rorytorneymf
Copy link

rorytorneymf commented Nov 15, 2024

Jetty Version
12.0.14

Jetty Environment
ee10

Java Version
JRE 21

Question
After upgrading my app from Jetty 11.0.24 to Jetty 12.0.14, I am getting the following error when Jetty tries to send a response. It looks like the Content-Length header is getting set to 0, when there are > 0 bytes being written:

o.e.j.ee10.servlet.HttpOutput: {"message":"onWriteComplete(true,java.io.IOException: written 4677 > 0 content-length) s=CLOSING,api=BLOCKED,sc=false,e=null->s=CLOSED,api=BLOCKING,sc=false,e=null c=null cb=Callback@32b98b0b{NON_BLOCKING, org.eclipse.jetty.ee10.servlet.ServletChannel$$Lambda/0x00007fac6f69ada0@3ebbfb77,org.eclipse.jetty.ee10.servlet.ServletChannel$$Lambda/0x00007fac6f69afb8@3bbbe09c} w=false","exception":"java.io.IOException: written 4677 > 0 content-length\n\tat org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1271)\n\tat org.eclipse.jetty.server.Response$Wrapper.write(Response.java:768)\n\tat org.eclipse.jetty.server.handler.gzip.GzipResponseAndCallback.write(GzipResponseAndCallback.java:133)\n\tat org.eclipse.jetty.server.Response$Wrapper.write(Response.java:768)\n\tat org.eclipse.jetty.server.handler.ContextResponse.write(ContextResponse.java:56)\n\tat org.eclipse.jetty.ee10.servlet.ServletContextResponse.write(ServletContextResponse.java:288)\n\tat org.eclipse.jetty.ee10.servlet.HttpOutput.channelWrite(HttpOutput.java:206)\n\tat org.eclipse.jetty.ee10.servlet.HttpOutput.complete(HttpOutput.java:437)\n\tat org.eclipse.jetty.ee10.servlet.ServletContextResponse.completeOutput(ServletContextResponse.java:212)\n\tat org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:576)\n\tat org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:464)\n\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)\n\tat org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)\n\tat org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1060)\n\tat org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)\n\tat io.dropwizard.metrics.jetty12.AbstractInstrumentedHandler.handle(AbstractInstrumentedHandler.java:299)\n\tat io.dropwizard.jetty.RoutingHandler.handle(RoutingHandler.java:41)\n\tat org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:597)\n\tat io.dropwizard.jetty.ZipExceptionHandlingGzipHandler.handle(ZipExceptionHandlingGzipHandler.java:21)\n\tat org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)\n\tat org.eclipse.jetty.server.handler.GracefulHandler.handle(GracefulHandler.java:101)\n\tat org.eclipse.jetty.server.Server.handle(Server.java:182)\n\tat org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:662)\n\tat org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:414)\n\tat org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)\n\tat org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)\n\tat org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.onFillable(SslConnection.java:575)\n\tat org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:390)\n\tat org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:150)\n\tat org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)\n\tat org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164)\n\tat java.base/java.lang.Thread.run(Thread.java:1583)\n"}

I disabled GZIP to determine if it was a GZIP-specific issue, but still got the same error.

Jetty 11 logs (working):

c.m.a.g.SecureHeaderFilter: response headers [CAF-Correlation-Id: cf67034c-1362-4781-8b50-c5ba49505430, X-Frame-Options: SAMEORIGIN, X-Robots-Tag: none, Strict-Transport-Security: max-age=31536000; includeSubDomains, X-Content-Type-Options: nosniff, Content-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';, Vary: Origin, X-XSS-Protection: 1; mode=block, Date: Fri, 15 Nov 2024 12:40:15 GMT]

o.e.jetty.server.HttpOutput: write(array HeapByteBuffer@11fec375[p=0,l=4677,c=8192,r=4677]={<<<<!--\n    This file is her...ct()">\n</BODY>\n\n</HTML>>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00})
o.e.jetty.server.HttpOutput: write(array) s=CLOSING,api=BLOCKED,sc=false,e=null last=true agg=false flush=true async=false, len=4677 null
org.eclipse.jetty.util.Pool: Pool@63e81ac6[inUse=0,size=1,max=1024,closed=false] returning new reserved entry MonoEntry@c9afe3a{PENDING,pooled=null}
o.e.j.s.h.g.GzipHttpOutputInterceptor: org.eclipse.jetty.server.handler.gzip.GzipHttpOutputInterceptor@3e9a7693 compressing org.eclipse.jetty.util.compression.CompressionPool$Entry@7cc6fdfd
o.e.jetty.server.HttpChannel: sendResponse info=null content=DirectByteBuffer@62ed2d07[p=0,l=1381,c=32768,r=1381]={<<<\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xC5Wmo\xDb6\x10\xFe\xEe_\xC1\xA8hb7...H\xFay\xE8\xE8OIm\xA9\x11\xAe\xF94\xFe\x1bC\x7f\xA1\x06E\x12\x00\x00>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00} complete=true committing=true callback=GzipBufferCB@2cfe67df[content=HeapByteBuffer@718d0efa[p=4677,l=4677,c=8192,r=0]={<!--\n    ...</HTML><<<>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00} last=true buffer=DirectByteBuffer@62ed2d07[p=0,l=1381,c=32768,r=1381]={<<<\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xC5Wmo\xDb6\x10\xFe\xEe_\xC1\xA8hb7...H\xFay\xE8\xE8OIm\xA9\x11\xAe\xF94\xFe\x1bC\x7f\xA1\x06E\x12\x00\x00>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00} deflate=null ]
o.e.jetty.server.HttpChannel: {"message":"COMMIT for /gateway/login.html on HttpChannelOverHttp@6cd392a9{s=HttpChannelState@11eb0f8{s=HANDLING rs=BLOCKING os=COMMITTED is=IDLE awp=false se=false i=true al=1},r=3,c=false/false,a=HANDLING,uri=https://example.com:9310/gateway/login.html?TENANT=rorywin1&ts=1731577382989&redirect=https://example.com:9320/rorywin1/admin/,age=345}\n200 null HTTP/1.1\nDate: Thu, 14 Nov 2024 09:43:09 GMT\r\nCAF-Correlation-Id: d674db41-1124-4bec-b70b-29571f67b0fb\r\nVary: Origin, Accept-Encoding\r\nX-Frame-Options: SAMEORIGIN\r\nContent-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';\r\nX-Content-Type-Options: nosniff\r\nX-Robots-Tag: none\r\nX-XSS-Protection: 1; mode=block\r\nStrict-Transport-Security: max-age=31536000; includeSubDomains\r\nAuthorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxeDEzUEwtYmFCVjVIY1Q1X0NGU1BVQ0VGRUNycnZfdVhneXJPT1hLWlhzIn0.eyJleHAiOjE3MzE1Nzc2ODgsImlhdCI6MTczMTU3NzM4OCwiYXV0aF90aW1lIjoxNzMxNTc3Mzg4LCJqdGkiOiI1YTYxNGNhYy0yOGIyLTQ5NDktYmNhOS02ODdlMWFjM2Q5NjYiLCJpc3MiOiJodHRwczovL2xhcnJ5LWV4dDAxLnN3aW5mcmEubmV0OjkwMjUvYXV0aC9yZWFsbXMvcm9yeXdpbjEiLCJhdWQiOlsicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiZmFkNzhjMDktOWQ0Ni00MGNjLTg1MjYtODQ2Mjg2ZDYwMGY0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY2EiLCJzaWQiOiJhNTQ0NjRkZC02ZGRmLTQwNTAtYjk3My1iMzk5MjE2N2NkZTEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVzb3VyY2VfYWNjZXNzIjp7InJlYWxtLW1hbmFnZW1lbnQiOnsicm9sZXMiOlsidmlldy11c2VycyIsInF1ZXJ5LWdyb3VwcyIsInF1ZXJ5LXVzZXJzIl19LCJjYSI6eyJyb2xlcyI6WyJFY2FNYW5hZ2VDdXN0b2RpYW4iLCJFY2FBc3NpZ25Ub1dvcmtib29rIiwiRGF0YU1hbmFnZW1lbnRNYW5hZ2VBZ2VudHMiLCJBbmFseXplUmVzZWFyY2giLCJBc3BlbkFkbWluVmlld1VzYWdlIiwiRWNhQ29sbGVjdERhdGEiLCJFY2FNYW5hZ2VDYXRlZ29yeSIsIkFuYWx5emVWaWV3RW50aXR5VmFsdWVzIiwiQW5hbHl6ZVZpZXdEYXNoYm9hcmRzIiwiRWNhU2VuZFRvVGFyZ2V0IiwiRWNhTWFuYWdlQ2FzZSIsIkVjYUFzc2lnblRvVGFnIiwiRWNhU2VjdXJlIiwiRGF0YU1hbmFnZW1lbnRNYW5hZ2VTZWN1cmVTeXN0ZW1zIiwiRWNhQ29udGVudFNlYXJjaCIsIkVjYVJldmlld0dyYW1tYXJzIiwiRWNhTWFuYWdlRGlzcG9zaXRpb24iLCJFY2FSZWxlYXNlSG9sZCIsIkRhdGFNYW5hZ2VtZW50RGVsZXRlUmVwb3NpdG9yaWVzIiwiQXNwZW5BZG1pbkFwaURldmVsb3BlciIsIkFzcGVuQWRtaW5Vc2VyTWFuYWdlbWVudCIsIkVjYU1hbmFnZUV4cG9ydCIsIkVjYVZpZXdBdWRpdCIsIkRhdGFNYW5hZ2VtZW50TWFuYWdlR3JhbW1hcnMiLCJFY2FNYW5hZ2VIb2xkIiwiRWNhQ29uZmlndXJlIiwiRWNhT2NyIiwiRGF0YU1hbmFnZW1lbnRNYW5hZ2VEZXN0aW5hdGlvbnMiLCJBbmFseXplQXNzaWduVG9UYWciLCJFY2FFeHBvcnREYXRhIiwiRWNhTWFuYWdlV29ya2Jvb2siLCJBbmFseXplUHJldmlldyIsIkVjYUFwcHJvdmVQb2xpY2llcyIsIkFuYWx5emVEb3dubG9hZCIsIkRhdGFNYW5hZ2VtZW50TWFuYWdlVGFncyIsIkFzcGVuQWRtaW5WaWV3QXVkaXQiLCJEYXRhTWFuYWdlbWVudE1hbmFnZVJlcG9zaXRvcmllcyIsIkRhdGFNYW5hZ2VtZW50TWFuYWdlU2VjdXJlUnVsZXMiLCJFY2FBc3NpZ25Ub0hvbGQiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJSb3J5IFRvcm5leSIsInByZWZlcnJlZF91c2VybmFtZSI6InJvcnkudG9ybmV5QG1pY3JvZm9jdXMuY29tIiwiZ2l2ZW5fbmFtZSI6IlJvcnkiLCJmYW1pbHlfbmFtZSI6IlRvcm5leSIsImVtYWlsIjoicm9yeS50b3JuZXlAbWljcm9mb2N1cy5jb20ifQ.Du0J9j1eniMNK66gDey4y5NpJ61hJXI4TR6SsZBwnH0ZopkRGA_MoDvO7eRIolJ8X1_GZRbz9FdWMLG_tVs-GmZ5siSUWjY65holWYJkeYmisX5biwfKj9K1fWjGVOJacSw_gZLETNVPx9FWTfMVd2j7IqvdPibTi_H1AJySGtMn2xNCeIJ5A-0yVDOJJkNiPPWLR77ZcBzJ3IpmIrDwLOT07Te2VRXWWw-E4yf8oh3ru_skYZSXi-xmAqZDYO2jEkJ1GL-B3kcDDlHmixm6rRaWWkhdN5mICK_6R8jkWMjLJ5bKPh_GdN3l4xEn44GDxRsNGbZc0llxsOQHVDnFaA\r\nSet-Cookie: refresh-token=eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjNTYxZGQ2Zi1lYmY3LTQyZTAtYWU1ZC05ZTZjODA1YjM5ZDkifQ.eyJleHAiOjE3MzE1NzkxODgsImlhdCI6MTczMTU3NzM4OCwianRpIjoiYmI0OTA2MjctMzZmZC00YTRhLTgyZGQtMGFlOGJhMWQ1ZGU1IiwiaXNzIjoiaHR0cHM6Ly9sYXJyeS1leHQwMS5zd2luZnJhLm5ldDo5MDI1L2F1dGgvcmVhbG1zL3Jvcnl3aW4xIiwiYXVkIjoiaHR0cHM6Ly9sYXJyeS1leHQwMS5zd2luZnJhLm5ldDo5MDI1L2F1dGgvcmVhbG1zL3Jvcnl3aW4xIiwic3ViIjoiZmFkNzhjMDktOWQ0Ni00MGNjLTg1MjYtODQ2Mjg2ZDYwMGY0IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImNhIiwic2lkIjoiYTU0NDY0ZGQtNmRkZi00MDUwLWI5NzMtYjM5OTIxNjdjZGUxIiwic2NvcGUiOiJvcGVuaWQgYmFzaWMgcHJvZmlsZSBhY3IgZW1haWwgcm9sZXMgd2ViLW9yaWdpbnMifQ.3PgtfZhv_v9s7r60OR5PufrKq1GrVXwnmVuvRnDwZqg30yAnPePriZekLLGQEeOkZOmEWk7nW18nwKEE8klIUA; Path=/v1/auth/cookie/; Secure; HttpOnly\r\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\r\nSet-Cookie: csrf-token=73e3671db0c34386bd097df1ba82eee01015744945297452179; Path=/; Secure\r\nContent-Type: text/html\r\nContent-Encoding: gzip\r\n\r\n"}
o.eclipse.jetty.server.Request: Response Request(GET https://example.com:9310/gateway/login.html?TENANT=rorywin1&ts=1731577382989&redirect=https://example.com:9320/rorywin1/admin/)@778c1821 committing for session Session@32bca397{id=node0v7yrxsrt309014zqxnoips8tm1,x=node0v7yrxsrt309014zqxnoips8tm1.node0,req=1,res=true}
o.e.j.server.HttpConnection: generate: NEED_HEADER for SendCallback@34bcc3d7[PROCESSING][i=HTTP/1.1{s=200,h=15,cl=-1},cb=org.eclipse.jetty.server.HttpChannel$SendCallback@58309332] (null,[p=0,l=1381,c=32768,r=1381],true)@START
o.e.jetty.http.HttpGenerator: generateHeaders HTTP/1.1{s=200,h=15,cl=-1} last=true content=DirectByteBuffer@62ed2d07[p=0,l=1381,c=32768,r=1381]={<<<\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xC5Wmo\xDb6\x10\xFe\xEe_\xC1\xA8hb7...H\xFay\xE8\xE8OIm\xA9\x11\xAe\xF94\xFe\x1bC\x7f\xA1\x06E\x12\x00\x00>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00}
o.e.jetty.http.HttpGenerator: {"message":"Date: Thu, 14 Nov 2024 09:43:09 GMT\r\nCAF-Correlation-Id: d674db41-1124-4bec-b70b-29571f67b0fb\r\nVary: Origin, Accept-Encoding\r\nX-Frame-Options: SAMEORIGIN\r\nContent-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';\r\nX-Content-Type-Options: nosniff\r\nX-Robots-Tag: none\r\nX-XSS-Protection: 1; mode=block\r\nStrict-Transport-Security: max-age=31536000; includeSubDomains\r\nAuthorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxeDEzUEwtYmFCVjVIY1Q1X0NGU1BVQ0VGRUNycnZfdVhneXJPT1hLWlhzIn0.eyJleHAiOjE3MzE1Nzc2ODgsImlhdCI6MTczMTU3NzM4OCwiYXV0aF90aW1lIjoxNzMxNTc3Mzg4LCJqdGkiOiI1YTYxNGNhYy0yOGIyLTQ5NDktYmNhOS02ODdlMWFjM2Q5NjYiLCJpc3MiOiJodHRwczovL2xhcnJ5LWV4dDAxLnN3aW5mcmEubmV0OjkwMjUvYXV0aC9yZWFsbXMvcm9yeXdpbjEiLCJhdWQiOlsicmVhbG0tbWFuYWdlbWVudCIsImFjY291bnQiXSwic3ViIjoiZmFkNzhjMDktOWQ0Ni00MGNjLTg1MjYtODQ2Mjg2ZDYwMGY0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY2EiLCJzaWQiOiJhNTQ0NjRkZC02ZGRmLTQwNTAtYjk3My1iMzk5MjE2N2NkZTEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVzb3VyY2VfYWNjZXNzIjp7InJlYWxtLW1hbmFnZW1lbnQiOnsicm9sZXMiOlsidmlldy11c2VycyIsInF1ZXJ5LWdyb3VwcyIsInF1ZXJ5LXVzZXJzIl19LCJjYSI6eyJyb2xlcyI6WyJFY2FNYW5hZ2VDdXN0b2RpYW4iLCJFY2FBc3NpZ25Ub1dvcmtib29rIiwiRGF0YU1hbmFnZW1lbnRNYW5hZ2VBZ2VudHMiLCJBbmFseXplUmVzZWFyY2giLCJBc3BlbkFkbWluVmlld1VzYWdlIiwiRWNhQ29sbGVjdERhdGEiLCJFY2FNYW5hZ2VDYXRlZ29yeSIsIkFuYWx5emVWaWV3RW50aXR5VmFsdWVzIiwiQW5hbHl6ZVZpZXdEYXNoYm9hcmRzIiwiRWNhU2VuZFRvVGFyZ2V0IiwiRWNhTWFuYWdlQ2FzZSIsIkVjYUFzc2lnblRvVGFnIiwiRWNhU2VjdXJlIiwiRGF0YU1hbmFnZW1lbnRNYW5hZ2VTZWN1cmVTeXN0ZW1zIiwiRWNhQ29udGVudFNlYXJjaCIsIkVjYVJldmlld0dyYW1tYXJzIiwiRWNhTWFuYWdlRGlzcG9zaXRpb24iLCJFY2FSZWxlYXNlSG9sZCIsIkRhdGFNYW5hZ2VtZW50RGVsZXRlUmVwb3NpdG9yaWVzIiwiQXNwZW5BZG1pbkFwaURldmVsb3BlciIsIkFzcGVuQWRtaW5Vc2VyTWFuYWdlbWVudCIsIkVjYU1hbmFnZUV4cG9ydCIsIkVjYVZpZXdBdWRpdCIsIkRhdGFNYW5hZ2VtZW50TWFuYWdlR3JhbW1hcnMiLCJFY2FNYW5hZ2VIb2xkIiwiRWNhQ29uZmlndXJlIiwiRWNhT2NyIiwiRGF0YU1hbmFnZW1lbnRNYW5hZ2VEZXN0aW5hdGlvbnMiLCJBbmFseXplQXNzaWduVG9UYWciLCJFY2FFeHBvcnREYXRhIiwiRWNhTWFuYWdlV29ya2Jvb2siLCJBbmFseXplUHJldmlldyIsIkVjYUFwcHJvdmVQb2xpY2llcyIsIkFuYWx5emVEb3dubG9hZCIsIkRhdGFNYW5hZ2VtZW50TWFuYWdlVGFncyIsIkFzcGVuQWRtaW5WaWV3QXVkaXQiLCJEYXRhTWFuYWdlbWVudE1hbmFnZVJlcG9zaXRvcmllcyIsIkRhdGFNYW5hZ2VtZW50TWFuYWdlU2VjdXJlUnVsZXMiLCJFY2FBc3NpZ25Ub0hvbGQiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJSb3J5IFRvcm5leSIsInByZWZlcnJlZF91c2VybmFtZSI6InJvcnkudG9ybmV5QG1pY3JvZm9jdXMuY29tIiwiZ2l2ZW5fbmFtZSI6IlJvcnkiLCJmYW1pbHlfbmFtZSI6IlRvcm5leSIsImVtYWlsIjoicm9yeS50b3JuZXlAbWljcm9mb2N1cy5jb20ifQ.Du0J9j1eniMNK66gDey4y5NpJ61hJXI4TR6SsZBwnH0ZopkRGA_MoDvO7eRIolJ8X1_GZRbz9FdWMLG_tVs-GmZ5siSUWjY65holWYJkeYmisX5biwfKj9K1fWjGVOJacSw_gZLETNVPx9FWTfMVd2j7IqvdPibTi_H1AJySGtMn2xNCeIJ5A-0yVDOJJkNiPPWLR77ZcBzJ3IpmIrDwLOT07Te2VRXWWw-E4yf8oh3ru_skYZSXi-xmAqZDYO2jEkJ1GL-B3kcDDlHmixm6rRaWWkhdN5mICK_6R8jkWMjLJ5bKPh_GdN3l4xEn44GDxRsNGbZc0llxsOQHVDnFaA\r\nSet-Cookie: refresh-token=eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjNTYxZGQ2Zi1lYmY3LTQyZTAtYWU1ZC05ZTZjODA1YjM5ZDkifQ.eyJleHAiOjE3MzE1NzkxODgsImlhdCI6MTczMTU3NzM4OCwianRpIjoiYmI0OTA2MjctMzZmZC00YTRhLTgyZGQtMGFlOGJhMWQ1ZGU1IiwiaXNzIjoiaHR0cHM6Ly9sYXJyeS1leHQwMS5zd2luZnJhLm5ldDo5MDI1L2F1dGgvcmVhbG1zL3Jvcnl3aW4xIiwiYXVkIjoiaHR0cHM6Ly9sYXJyeS1leHQwMS5zd2luZnJhLm5ldDo5MDI1L2F1dGgvcmVhbG1zL3Jvcnl3aW4xIiwic3ViIjoiZmFkNzhjMDktOWQ0Ni00MGNjLTg1MjYtODQ2Mjg2ZDYwMGY0IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImNhIiwic2lkIjoiYTU0NDY0ZGQtNmRkZi00MDUwLWI5NzMtYjM5OTIxNjdjZGUxIiwic2NvcGUiOiJvcGVuaWQgYmFzaWMgcHJvZmlsZSBhY3IgZW1haWwgcm9sZXMgd2ViLW9yaWdpbnMifQ.3PgtfZhv_v9s7r60OR5PufrKq1GrVXwnmVuvRnDwZqg30yAnPePriZekLLGQEeOkZOmEWk7nW18nwKEE8klIUA; Path=/v1/auth/cookie/; Secure; HttpOnly\r\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\r\nSet-Cookie: csrf-token=73e3671db0c34386bd097df1ba82eee01015744945297452179; Path=/; Secure\r\nContent-Type: text/html\r\nContent-Encoding: gzip\r\n\r\n"}
o.e.jetty.http.HttpGenerator: CONTENT_LENGTH
o.e.j.server.HttpConnection: generate: FLUSH for SendCallback@34bcc3d7[PROCESSING][i=HTTP/1.1{s=200,h=15,cl=-1},cb=org.eclipse.jetty.server.HttpChannel$SendCallback@58309332] ([p=0,l=4004,c=8192,r=4004],[p=0,l=1381,c=32768,r=1381],true)@COMPLETING
o.e.jetty.io.WriteFlusher: write: WriteFlusher@60fae849{IDLE}->null [DirectByteBuffer@508aef64[p=0,l=4004,c=8192,r=4004]={<<<HTTP/1.1 200 OK\r\nDate: Th...ontent-Length: 1381\r\n\r\n>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00},DirectByteBuffer@62ed2d07[p=0,l=1381,c=32768,r=1381]={<<<\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xC5Wmo\xDb6\x10\xFe\xEe_\xC1\xA8hb7...H\xFay\xE8\xE8OIm\xA9\x11\xAe\xF94\xFe\x1bC\x7f\xA1\x06E\x12\x00\x00>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00}]
o.e.jetty.io.WriteFlusher: update WriteFlusher@60fae849{WRITING}->null:IDLE-->WRITING
o.e.jetty.io.ssl.SslConnection: >flush SslConnection@3e30b117::SocketChannelEndPoint@3c5a3f54[{l=/192.168.75.220:8082,r=/192.168.75.1:53597,OPEN,fill=-,flush=-,to=354/30000}{io=0/0,kio=0,kro=1}]->[SslConnection@3e30b117{NOT_HANDSHAKING,eio=-1/-1,di=-1,fill=IDLE,flush=IDLE}~>{l=/192.168.75.220:8082,r=/192.168.75.1:53597,OPEN,fill=-,flush=W,to=546/30000}=>HttpConnection@5d2af0a8[p=HttpParser{s=CONTENT,0 of -1},g=HttpGenerator@4add5f59{s=COMPLETING}]=>HttpChannelOverHttp@6cd392a9{s=HttpChannelState@11eb0f8{s=HANDLING rs=BLOCKING os=COMMITTED is=IDLE awp=false se=false i=true al=1},r=3,c=false/false,a=HANDLING,uri=https://example.com:9310/gateway/login.html?TENANT=rorywin1&ts=1731577382989&redirect=https://example.com:9320/rorywin1/admin/,age=348}]
o.e.jetty.io.ssl.SslConnection: flush b[0]=DirectByteBuffer@508aef64[p=0,l=4004,c=8192,r=4004]={<<<HTTP/1.1 200 OK\r\nDate: Th...ontent-Length: 1381\r\n\r\n>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00}

Jetty 12 logs (fail written 4677 > 0 content-length):

c.m.a.g.SecureHeaderFilter: response headers: [Date: Fri, 15 Nov 2024 12:30:41 GMT, Vary: Accept-Encoding, Content-Length: 0]

o.e.j.ee10.servlet.HttpOutput: write(array HeapByteBuffer@4059e9ad[p=0,l=4677,c=8192,r=4677]={<<<<!--\n    This file is her...ct()">\n</BODY>\n\n</HTML>>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00})
o.e.j.ee10.servlet.HttpOutput: write(array) s=OPEN,api=BLOCKING,sc=false,e=null aggregated !flush ReservedBuffer@9a46aed[rc=1,DirectByteBuffer@50df4a92[p=0,l=4677,c=32768,r=4677]={<<<<!--\n    This file is her...ct()">\n</BODY>\n\n</HTML>>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00}]
o.e.j.e.s.ServletChannelState: unhandle ServletChannelState@13223dfa{s=HANDLING rs=BLOCKING os=OPEN is=IDLE awp=false se=false i=true al=0}
o.e.j.e.s.ServletChannelState: nextAction(false) COMPLETE ServletChannelState@13223dfa{s=HANDLING rs=COMPLETING os=OPEN is=IDLE awp=false se=false i=false al=0}
o.e.j.e.servlet.ServletChannel: action COMPLETE ServletChannel@35ff7cd1{s=ServletChannelState@13223dfa{s=HANDLING rs=COMPLETING os=OPEN is=IDLE awp=false se=false i=false al=0},r=0,c=false/false,a=HANDLING,uri=https://example.com:9310/gateway/login.html?TENANT=rorywin1&ts=1731666841005&redirect=https://example.com:9320/rorywin1/admin/,age=194}
o.e.j.ee10.servlet.HttpOutput: complete(Callback@32b98b0b{NON_BLOCKING, org.eclipse.jetty.ee10.servlet.ServletChannel$$Lambda/0x00007fac6f69ada0@3ebbfb77,org.eclipse.jetty.ee10.servlet.ServletChannel$$Lambda/0x00007fac6f69afb8@3bbbe09c}) s=CLOSING,api=BLOCKED,sc=false,e=null s=false e=null, c=DirectByteBuffer@50df4a92[p=0,l=4677,c=32768,r=4677]={<<<<!--\n    This file is her...ct()">\n</BODY>\n\n</HTML>>>>\x00\x00\x00\x00\x00\x00\x00\x00\x00...\x00\x00\x00\x00\x00\x00\x00}
o.e.j.s.i.HttpChannelState: fail org.eclipse.jetty.server.handler.ContextResponse$1@7fb14948 written 4677 > 0 content-length
o.e.j.u.t.SerializedInvoker: Offering link Link@602f4767{Queued by dw-35 at org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1290) -> null} of HttpChannelSerializedInvoker@74b44d79{name=HttpChannelState_writeInvoker,tail=null,invoker=null}
o.e.j.u.t.SerializedInvoker: Running link Link@602f4767{Queued by dw-35 at org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1290) -> null} of HttpChannelSerializedInvoker@74b44d79{name=HttpChannelState_writeInvoker,tail=Link@602f4767{Queued by dw-35 at org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1290) -> null},invoker=null}
o.e.j.ee10.servlet.HttpOutput: {"message":"onWriteComplete(true,java.io.IOException: written 4677 > 0 content-length) s=CLOSING,api=BLOCKED,sc=false,e=null->s=CLOSED,api=BLOCKING,sc=false,e=null c=null cb=Callback@32b98b0b{NON_BLOCKING, org.eclipse.jetty.ee10.servlet.ServletChannel$$Lambda/0x00007fac6f69ada0@3ebbfb77,org.eclipse.jetty.ee10.servlet.ServletChannel$$Lambda/0x00007fac6f69afb8@3bbbe09c} w=false","exception":"java.io.IOException: written 4677 > 0 content-length\n\tat org.eclipse.jetty.server.internal.HttpChannelState$ChannelResponse.write(HttpChannelState.java:1271)\n\tat org.eclipse.jetty.server.Response$Wrapper.write(Response.java:768)\n\tat org.eclipse.jetty.server.handler.gzip.GzipResponseAndCallback.write(GzipResponseAndCallback.java:133)\n\tat org.eclipse.jetty.server.Response$Wrapper.write(Response.java:768)\n\tat org.eclipse.jetty.server.handler.ContextResponse.write(ContextResponse.java:56)\n\tat org.eclipse.jetty.ee10.servlet.ServletContextResponse.write(ServletContextResponse.java:288)\n\tat org.eclipse.jetty.ee10.servlet.HttpOutput.channelWrite(HttpOutput.java:206)\n\tat org.eclipse.jetty.ee10.servlet.HttpOutput.complete(HttpOutput.java:437)\n\tat org.eclipse.jetty.ee10.servlet.ServletContextResponse.completeOutput(ServletContextResponse.java:212)\n\tat org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:576)\n\tat org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:464)\n\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)\n\tat org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)\n\tat org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1060)\n\tat org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)\n\tat io.dropwizard.metrics.jetty12.AbstractInstrumentedHandler.handle(AbstractInstrumentedHandler.java:299)\n\tat io.dropwizard.jetty.RoutingHandler.handle(RoutingHandler.java:41)\n\tat org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:597)\n\tat io.dropwizard.jetty.ZipExceptionHandlingGzipHandler.handle(ZipExceptionHandlingGzipHandler.java:21)\n\tat org.eclipse.jetty.server.Handler$Wrapper.handle(Handler.java:740)\n\tat org.eclipse.jetty.server.handler.GracefulHandler.handle(GracefulHandler.java:101)\n\tat org.eclipse.jetty.server.Server.handle(Server.java:182)\n\tat org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:662)\n\tat org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:414)\n\tat org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)\n\tat org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)\n\tat org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.onFillable(SslConnection.java:575)\n\tat org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:390)\n\tat org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:150)\n\tat org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)\n\tat org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164)\n\tat java.base/java.lang.Thread.run(Thread.java:1583)\n"}

Some points:

  • I printed out the headers in the SecureHeaderFilter class (show at the top of the log snippets above). In Jetty 11 I see the expected headers, but in Jetty 12 I don't see any of the headers that have just been set in that class, and I also see the Content-Length: 0 header, though I am not sure who or what is setting the Content-Length header
  • In the Jetty 11 logs I seem some references to o.e.jetty.http.HttpGenerator: CONTENT_LENGTH and ontent-Length: 1381 indicating that the header is being set correctly. I am not sure why the header is already present (and set to 0) in Jetty 12 by the time the SecureHeaderFilter is invoked.

Hare are the relevant classes in my app:

ApiGatewayApplication.java

class ApiGatewayApplication{
    @Override
    public void run(final ApiGatewayConfiguration apiGatewayConfiguration, final Environment environment) {

        final String corsOrigins = configuration.getAuthConfiguration().getCorsOrigins();
        if (configuration.getAuthConfiguration().isCors() && !Strings.isNullOrEmpty(corsOrigins)) {
            logger.debug("Adding CORS...");
            final FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class);
            filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
            filter.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,POST,PUT,PATCH,DELETE,OPTIONS");
            filter.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin,If-Modified-Since,If-Unmodified-Since,Cache-Control,Tenant,Csrf-Token,X-TENANT-ID,CA-AGENT-SERVICE,CAF-Correlation-Id");
            filter.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
            filter.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "true");
            filter.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, corsOrigins);
        }

        environment.jersey().register(MultiPartFeature.class);

        final Client httpClient = new JerseyClientBuilder(environment).using(apiGatewayConfiguration.getHttpClientConfiguration()).build(getName());
        httpClient.register(MultiPartWriter.class);
        httpClient.register(FormDataMultiPart.class);
        httpClient.register(new CorrelationIdClientFilter());

        final LoginResource loginResource = new LoginResource(apiGatewayConfiguration);
        environment.jersey().register(loginResource);

        //this adds secure headers to all responses
        environment.servlets().addFilter("SecureHeaderFilter", new SecureHeaderFilter()).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ERROR), true, "/*");

        //this adds the access token to the response header after login, so that the token can be sent back in subsequent rest calls
        environment.servlets().addFilter("CustomServletFilter", new CustomServletFilter(apiGatewayConfiguration.getAuthConfiguration().isSecureSession())).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, ApiGatewayConstants.SWAGGER_URL + "/", ApiGatewayConstants.SWAGGER_URL + ".json", "/gateway/login.html");
        //if need to intercept response, can add class (which implements ContainerResponseFilter) to env.jersey.register()
    }
}

CustomServletFilter.java

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.WebApplicationException;

import org.keycloak.adapters.RefreshableKeycloakSecurityContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//this adds the access token to the response header after login, so that the token can be sent back in subsequent rest calls
public class CustomServletFilter implements jakarta.servlet.Filter {
    private static final Logger logger = LoggerFactory.getLogger(CustomServletFilter.class);

    private boolean isSecureSession = true;

    public CustomServletFilter() {
    }

    public CustomServletFilter(final boolean isSecureSession) {
        this.isSecureSession = isSecureSession;
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            final HttpServletRequest httpRequest = (HttpServletRequest) request;
            final HttpServletResponse httpResponse = (HttpServletResponse) response;

            final RefreshableKeycloakSecurityContext keycloakContext = ApiGatewayHelper.getKeycloakContext(httpRequest);
            if (keycloakContext != null) {
                httpResponse.setHeader(IDMHelper.HEADER_AUTH, IDMHelper.HEADER_AUTH_BEARER + keycloakContext.getTokenString());

                final Cookie refreshTokenCookie = new Cookie(ApiGatewayConstants.REFRESH_TOKEN_KEY, keycloakContext.getRefreshToken());
                refreshTokenCookie.setPath(ApiGatewayConstants.REFRESH_TOKEN_COOKIE_PATH); //only needs to be sent back to cookie methods
                refreshTokenCookie.setHttpOnly(true); //should not be read by client
                refreshTokenCookie.setSecure(isSecureSession);
                refreshTokenCookie.setMaxAge(-1);
                httpResponse.addCookie(refreshTokenCookie);
                logger.debug("Logged in and added refresh token to cookie");
                logger.trace("New Refresh Token: {}", keycloakContext.getRefreshToken());

                if (!httpRequest.getRequestURI().contains(ApiGatewayConstants.SWAGGER_URL)) {
                    final String csrfUuid = UUID.randomUUID().toString().replace("-", "");
                    final BigInteger csrfRandomNum = new BigInteger(64, new SecureRandom());

                    final Cookie csrfTokenCookie = new Cookie(ApiGatewayConstants.CSRF_TOKEN_KEY, csrfUuid + csrfRandomNum);
                    csrfTokenCookie.setPath("/"); //needs to be sent back on all calls
                    csrfTokenCookie.setHttpOnly(false); //needs to be read by client
                    csrfTokenCookie.setSecure(isSecureSession);
                    csrfTokenCookie.setMaxAge(-1);
                    httpResponse.addCookie(csrfTokenCookie);
                }
                else {
                    //check if user has swagger permission
                    final List<String> perms = new ArrayList<>(Arrays.asList(ApplicationPermissions.AdminPermission.AspenAdminApiDeveloper.name(), ApiGatewayConstants.KEYCLOAK_ONBOARD_PERMISSION, ApiGatewayConstants.KEYCLOAK_USAGE_PERMISSION));
                    if (!Authorizer.hasRoles(keycloakContext.getToken(), perms, true)) {
                        logger.error("No API Developer Permission");
                        throw new WebApplicationException("Forbidden");
                    }

                    httpResponse.setHeader("Cache-Control", "no-store");
                }
            }
        }

        chain.doFilter(request, response);
    }

    @Override
    public void init(final FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

SecureHeaderFilter.java

import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//this adds the access token to the response header after login, so that the token can be sent back in subsequent rest calls
public class SecureHeaderFilter implements jakarta.servlet.Filter {
    private static final Logger logger = LoggerFactory.getLogger(SecureHeaderFilter.class);

    public SecureHeaderFilter() {
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            final HttpServletRequest httpRequest = (HttpServletRequest) request;
            final HttpServletResponse httpResponse = (HttpServletResponse) response;

            if (!httpRequest.getMethod().equalsIgnoreCase("OPTIONS")) {
                //these are based on the keycloak secure headers
                httpResponse.setHeader("X-Frame-Options", "SAMEORIGIN");
                httpResponse.setHeader("Content-Security-Policy", "frame-src 'self'; frame-ancestors 'self'; object-src 'none';");
                httpResponse.setHeader("X-Content-Type-Options", "nosniff");
                httpResponse.setHeader("X-Robots-Tag", "none");
                httpResponse.setHeader("X-XSS-Protection", "1; mode=block");
                httpResponse.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
            }

            logger.warn("response headers: " + getHeaders(httpResponse));
        }

        chain.doFilter(request, response);
    }

    @Override
    public void init(final FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }

    private static List<String> getHeaders(HttpServletResponse response) {

        List<String> headerLogs = new ArrayList<>();

        try {
            Collection<String> headerNames = response.getHeaderNames();
            if (headerNames == null) {
                return Collections.emptyList();
            }

            for (String headerName: headerNames) {
                Collection<String> headerValues = response.getHeaders(headerName);
                if (headerValues == null) {
                    headerLogs.add(String.format("%s: <null>", headerName));
                    continue;
                }

                StringBuilder valueBuilder = new StringBuilder();
                for (String headerValue : headerValues) {
                    if (!valueBuilder.isEmpty()) {
                        valueBuilder.append(", ");
                    }
                    valueBuilder.append(headerValue != null ? headerValue : "<null>");
                }

                headerLogs.add(String.format("%s: %s",
                        headerName,
                        !valueBuilder.isEmpty() ? valueBuilder.toString() : "<empty>"));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return headerLogs;
    }
}

LoginResource.java

import io.swagger.v3.oas.annotations.Operation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/gateway/login.html")
public class LoginResource {
    private static final Logger logger = LoggerFactory.getLogger(LoginResource.class);

    private final ApiGatewayConfiguration apiGatewayConfiguration;

    public LoginResource(final ApiGatewayConfiguration apiGatewayConfiguration) {
        this.apiGatewayConfiguration = apiGatewayConfiguration;

        logger.warn("LoginResource invoked");
    }

    @Operation(hidden = true)
    @GET
    @Produces(MediaType.TEXT_HTML)
    public LoginView Login() {
        logger.warn("Login resource called returning LoginView instance");

        return new LoginView(apiGatewayConfiguration.getAuthConfiguration());
    }
}

LoginView.java

import io.dropwizard.views.common.View;

public class LoginView extends View {
    private final AuthConfiguration loginConfig;

    public LoginView(AuthConfiguration loginConfig) {
        super("login.ftl");
        this.loginConfig = loginConfig;
    }

    public AuthConfiguration getLoginConfig() {
        return loginConfig;
    }
}

login.ftl

<HTML>
<HEAD>
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
</HEAD>

<SCRIPT>
    function redirect() {
        var LOCALHOST = "localhost";
        var REDIRECT_KEY = "redirect";
        var REDIRECT_ERROR_KEY = "redirectError";
        var ERROR_KEY = "error";

        var redirectWhitelistString = "${loginConfig.redirectWhitelist}";
        var redirectWhitelist = redirectWhitelistString.split(",");

        var redirect = "";
        var redirectError = "";
        var error = "";
        var serverName, redirectServerName;
        var location = window.location.toString();
        serverName = getServerName(location);
        var startOfParamsPosition = location.indexOf("?");
        if (startOfParamsPosition > -1) {
            var params = location.substring(startOfParamsPosition + 1).split("&");
            for (var i = 0; i < params.length; i++) {
                var param = params[i];
                var splitParam = param.split("=");
                if (splitParam.length > 1) {
                    if (splitParam[0] === REDIRECT_KEY) {
                        redirect = splitParam[1].replace(/%25/g, "%").replace("%23", "#").replace("%3D", "=");
                    }
                    else if (splitParam[0] === REDIRECT_ERROR_KEY) {
                        redirectError = splitParam[1].replace("%25", "%").replace("%23", "#");
                    }
                    else if (splitParam[0] === ERROR_KEY) {
                        error = splitParam[1];
                    }
                }
            }
        }
        if (error.length > 0 && redirectError.length > 0) {
            redirectServerName = getServerName(redirectError);
            if (isValidServer(serverName, redirectServerName) > -1) {
                window.location = redirectError;
            }
            else {
                document.getElementById("pageBody").innerHTML = "Invalid Redirect Server";
            }
        }
        else if (redirect.length > 0) {
            redirectServerName = getServerName(redirect);
            var validServer = isValidServer(serverName, redirectServerName);
            if (validServer === 0) {
                window.location = redirect;
            }
            else if (validServer === 1) {
                //ui and gateway servers are not same so need to pass token in url
                if (document.cookie.indexOf("csrf-token") !== -1) {
                    redirect += "&" + document.cookie;
                }
                window.location = redirect;
            }
            else {
                document.getElementById("pageBody").innerHTML = "Invalid Redirect Server";
            }
        }
        else {
            document.getElementById("pageBody").innerHTML = "No Redirect Specified!";
        }

        function isValidServer(serverName, redirectServerName) {
            if (redirectServerName === serverName) {
                return 0;
            }
            else if (redirectServerName === LOCALHOST || serverName === LOCALHOST || redirectWhitelist.indexOf('*') >= 0 || redirectWhitelist.indexOf(redirectServerName) >= 0) {
                return 1;
            }
            else {
                return -1;
            }
        }

        function getServerName (url) {
            var startOfServerNamePosition = url.indexOf("://");
            if (startOfServerNamePosition > -1) {
                var endOfServerNamePosition = url.indexOf(":", startOfServerNamePosition + 1);
                if (endOfServerNamePosition > -1) {
                    return url.substring(startOfServerNamePosition + 3, endOfServerNamePosition);
                }
            }
            return "";
        }
    }
</SCRIPT>

<BODY id="pageBody" onload="redirect()">
</BODY>

</HTML>
@rorytorneymf rorytorneymf changed the title written 4677 > 0 content-length (Content-Length: 0 after Jetty 11 to 12 upgrade) java.io.IOException: written 4677 > 0 content-length (Content-Length: 0 after Jetty 11 to 12 upgrade) Nov 15, 2024
@rorytorneymf rorytorneymf changed the title java.io.IOException: written 4677 > 0 content-length (Content-Length: 0 after Jetty 11 to 12 upgrade) java.io.IOException: written 4677 > 0 content-length (Jetty 11 to 12 upgrade) Nov 15, 2024
@joakime
Copy link
Contributor

joakime commented Nov 15, 2024

Duplicate of #12481

Fixed in PR #12484

Will be available in release 12.0.16 due out end of November.

@joakime joakime closed this as not planned Won't fix, can't repro, duplicate, stale Nov 15, 2024
@rorytorneymf
Copy link
Author

Thanks, I think that is a different issue to the one I have though, I temporarily reverted to 12.0.2 (released Oct 10, 2023), which is before the change (Dec 6, 2023) that introduced the problem was added, and still get the same error. It is possibly something I'm doing wrong in my app, I'll continue investigating.

@joakime joakime reopened this Nov 18, 2024
@joakime
Copy link
Contributor

joakime commented Nov 18, 2024

I'll reopen.

If you can make your scattered source files into a ready to run project that would be great (just a new project on github should be sufficient).
Use maven or gradle, and some simple instructions on how/what to run to trigger the issue. (also if the issue occurs every time, or occasionally)

From that, we can confirm if this is a duplicate of #12481

@rorytorneymf
Copy link
Author

Apologies, closing this. This was happening because I was calling callback.succeeded() which was causing the response to be committed prematurely.

@joakime
Copy link
Contributor

joakime commented Nov 18, 2024

@rorytorneymf where are you doing that?
The code you presented in your question has no jetty core Handler example present.

@rorytorneymf
Copy link
Author

It was in a separate class I didn't think was the issue, this is the particular method:

@Override
public AuthenticationState validateRequest(Request req, Response res, Callback callback) throws ServerAuthException {
    final ServletContextRequest servletContextRequest = (ServletContextRequest)req;
    Request request = resolveRequest(servletContextRequest.getServletApiRequest());

    final ServletContextResponse servletContextResponse = (ServletContextResponse)res;
    OIDCJettyHttpFacade facade = new OIDCJettyHttpFacade(request, servletContextResponse.getServletApiResponse().getResponse());
    KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
    if (deployment == null || !deployment.isConfigured()) {
        callback.succeeded();
        return AuthenticationState.SEND_FAILURE; // TODO how to send .UNAUTHENTICATED?
    }
    PreAuthActionsHandler handler = new PreAuthActionsHandler(createSessionManagement(request), deploymentContext, facade);
    if (handler.handleRequest()) {
        callback.succeeded();
        return AuthenticationState.SEND_SUCCESS;
    }

    AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
    nodesRegistrationManagement.tryRegister(deployment);

    tokenStore.checkCurrentToken();
    JettyRequestAuthenticator authenticator = createRequestAuthenticator(request, facade, deployment, tokenStore);
    AuthOutcome outcome = authenticator.authenticate();
    if (outcome == AuthOutcome.AUTHENTICATED) {
        if (facade.isEnded()) {
            callback.succeeded();
            return AuthenticationState.SEND_SUCCESS;
        }

        AuthenticationState authenticationState = register(request, authenticator.principal);
        AuthenticatedActionsHandler authenticatedActionsHandler = new AuthenticatedActionsHandler(deployment, facade);
        if (authenticatedActionsHandler.handledRequest()) {
            callback.succeeded();
            return AuthenticationState.SEND_SUCCESS;
        }
        final KeycloakAuthentication ka = (KeycloakAuthentication)authenticationState;
        // Important: do no call callback.succeeded() here as it results in the response being committed too early.
        // E.g. the login.html is returned with a Content-Length: 0 header even though there are >0 bytes in the content.
        return authenticationState;
    }
    AuthChallenge challenge = authenticator.getChallenge();
    if (challenge != null) {
        challenge.challenge(facade);
    }
    callback.succeeded();
    return AuthenticationState.CHALLENGE;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants