Skip to content

Commit 39800bc

Browse files
authored
Broken pipe Exception from Jersey layer while closing response in ServerRuntime.writeResponse() not handled or re-thrown #5783 (#5786)
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
1 parent 7903c97 commit 39800bc

File tree

4 files changed

+121
-15
lines changed

4 files changed

+121
-15
lines changed

core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundJaxrsResponse.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -26,6 +26,8 @@
2626
import java.util.Date;
2727
import java.util.HashSet;
2828
import java.util.List;
29+
import java.util.logging.Level;
30+
import java.util.logging.Logger;
2931
import java.util.Locale;
3032
import java.util.Map;
3133
import java.util.Set;
@@ -200,7 +202,12 @@ public boolean bufferEntity() throws ProcessingException {
200202
@Override
201203
public void close() throws ProcessingException {
202204
closed = true;
203-
context.close();
205+
try {
206+
context.close();
207+
} catch (Exception e) {
208+
// Just log the exception
209+
Logger.getLogger(OutboundJaxrsResponse.class.getName()).log(Level.FINE, e.getMessage(), e);
210+
}
204211
if (buffered) {
205212
// release buffer
206213
context.setEntity(null);

core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.io.OutputStream;
21+
import java.io.UncheckedIOException;
2122
import java.lang.annotation.Annotation;
2223
import java.lang.reflect.Type;
2324
import java.util.ArrayList;
@@ -27,8 +28,6 @@
2728
import java.util.Locale;
2829
import java.util.Set;
2930
import java.util.function.Function;
30-
import java.util.logging.Level;
31-
import java.util.logging.Logger;
3231
import java.util.stream.Collectors;
3332

3433
import javax.ws.rs.core.Configuration;
@@ -557,6 +556,7 @@ public boolean isCommitted() {
557556

558557
/**
559558
* Closes the context. Flushes and closes the entity stream.
559+
* @throws UncheckedIOException if IO errors
560560
*/
561561
public void close() {
562562
if (hasEntity()) {
@@ -567,20 +567,15 @@ public void close() {
567567
}
568568
es.close();
569569
} catch (IOException e) {
570-
// Happens when the client closed connection before receiving the full response.
571-
// This is OK and not interesting in the vast majority of the cases
572-
// hence the log level set to FINE to make sure it does not flood the log unnecessarily
573-
// (especially for clients disconnecting from SSE listening, which is very common).
574-
Logger.getLogger(OutboundMessageContext.class.getName()).log(Level.FINE, e.getMessage(), e);
570+
throw new UncheckedIOException(e);
575571
} finally {
576572
// In case some of the output stream wrapper does not delegate close() call we
577573
// close the root stream manually to make sure it commits the data.
578574
if (!committingOutputStream.isClosed()) {
579575
try {
580576
committingOutputStream.close();
581577
} catch (IOException e) {
582-
// Just log the exception
583-
Logger.getLogger(OutboundMessageContext.class.getName()).log(Level.FINE, e.getMessage(), e);
578+
throw new UncheckedIOException(e);
584579
}
585580
}
586581
}

core-server/src/main/java/org/glassfish/jersey/server/ContainerResponse.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -26,6 +26,8 @@
2626
import java.net.URI;
2727
import java.util.Date;
2828
import java.util.Locale;
29+
import java.util.logging.Level;
30+
import java.util.logging.Logger;
2931
import java.util.Map;
3032
import java.util.Set;
3133

@@ -400,9 +402,14 @@ public boolean isCommitted() {
400402
public void close() {
401403
if (!closed) {
402404
closed = true;
403-
messageContext.close();
404-
requestContext.getResponseWriter().commit();
405-
requestContext.setWorkers(null);
405+
try {
406+
messageContext.close();
407+
requestContext.setWorkers(null);
408+
requestContext.getResponseWriter().commit();
409+
} catch (Exception e) {
410+
Logger.getLogger(ContainerResponse.class.getName()).log(Level.FINE, e.getMessage(), e);
411+
requestContext.getResponseWriter().failure(e);
412+
}
406413
}
407414
}
408415

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
17+
package org.glassfish.jersey.tests.e2e.server;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
21+
import java.io.ByteArrayOutputStream;
22+
import java.io.IOException;
23+
import java.lang.reflect.InvocationHandler;
24+
import java.lang.reflect.Method;
25+
import java.lang.reflect.Proxy;
26+
27+
import javax.ws.rs.GET;
28+
import javax.ws.rs.Path;
29+
import javax.ws.rs.container.ContainerRequestContext;
30+
import javax.ws.rs.container.ContainerResponseContext;
31+
import javax.ws.rs.container.ContainerResponseFilter;
32+
import javax.ws.rs.core.Application;
33+
import javax.ws.rs.core.Context;
34+
import javax.ws.rs.core.Response;
35+
import javax.ws.rs.ext.Provider;
36+
37+
import org.glassfish.jersey.server.ContainerRequest;
38+
import org.glassfish.jersey.server.ResourceConfig;
39+
import org.glassfish.jersey.server.spi.ContainerResponseWriter;
40+
import org.glassfish.jersey.test.JerseyTest;
41+
import org.junit.jupiter.api.Test;
42+
43+
public class Issue5783Test extends JerseyTest {
44+
45+
private static final String ERROR = "Intentional issue5783 exception";
46+
private static volatile String exceptionMessage;
47+
48+
@Override
49+
protected Application configure() {
50+
return new ResourceConfig(Resource.class, ResponseFilter.class);
51+
}
52+
53+
@Test
54+
public void closeException() throws InterruptedException {
55+
target("/test").request().get();
56+
assertEquals(ERROR, exceptionMessage);
57+
}
58+
59+
@Path("/test")
60+
public static class Resource {
61+
62+
@GET
63+
public Response closeException(@Context ContainerRequest request) {
64+
// Save the exception when response.getRequestContext().getResponseWriter().failure(e)
65+
ContainerResponseWriter writer = request.getResponseWriter();
66+
ContainerResponseWriter proxy = (ContainerResponseWriter) Proxy.newProxyInstance(
67+
ContainerResponseWriter.class.getClassLoader(),
68+
new Class<?>[]{ContainerResponseWriter.class}, new InvocationHandler() {
69+
@Override
70+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
71+
if ("failure".equals(method.getName())) {
72+
exceptionMessage = ((Throwable) args[0]).getCause().getMessage();
73+
}
74+
return method.invoke(writer, args);
75+
}
76+
});
77+
request.setWriter(proxy);
78+
return Response.ok().build();
79+
}
80+
}
81+
82+
@Provider
83+
public static class ResponseFilter implements ContainerResponseFilter {
84+
@Override
85+
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
86+
throws IOException {
87+
// Hack it to make ContainerResponse#close throws one exception
88+
responseContext.setEntity("something");
89+
responseContext.setEntityStream(new ByteArrayOutputStream() {
90+
@Override
91+
public void close() throws IOException {
92+
throw new IOException(ERROR);
93+
}
94+
});
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)