Eric Stein
Eric Stein

Reputation: 13682

StreamingOutput not throwing WebApplicationException, returning empty response

I'm having difficulty throwing a WebApplicationException from my StreamingOutput implementation. I would expect the code below to return a 501, but curl is reporting curl: (52) Empty reply from server. I can see the 503 in the trace of the call, but Jersey is just replying with an empty body. Does anybody know what gives?

public final class MyStreamingOutput implements StreamingOutput {

    @Override
    public void write(final OutputStream outputStream)
        throws IOException {

        try {
            dataProvider = new DataProvider(); // throws SQLException
        } catch (final SQLException e) {
            throw new WebApplicationException(503);
        }
    }
}

This is the trace I'm seeing:

java.sql.SQLException: Invalid object name 'no_such_table'.
    at net.sourceforge.jtds.jdbc.SQLDiagnostic.addDiagnostic(SQLDiagnostic.java:368) ~[jtds-1.2.4.jar:1.2.4]
...
16:05:22.148 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - MBW_WRITE_TO WriteTo by [org.glassfish.jersey.message.internal.StreamingOutputProvider @1d45d135] [642.98 ms]
16:05:22.148 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - WI_AFTER [org.glassfish.jersey.filter.LoggingFilter @77edc290 #-2147483648] AFTER context.proceed() [ 0.00 ms]
16:05:22.148 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - WI_AFTER [org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor @2a0fded2 #3000] AFTER context.proceed() [ 0.01 ms]
16:05:22.148 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - WI_AFTER [org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor @26c087be #10] AFTER context.proceed() [ 0.01 ms]
16:05:22.148 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - WI_SUMMARY WriteTo summary: 3 interceptors [644.44 ms]
16:05:22.148 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - FINISHED Response status: 200/SUCCESSFUL|OK [ ---- ms]
16:05:22.153 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - EXCEPTION_MAPPING Exception mapper [com.locustec.eim.query.rest.RuntimeExceptionMapper @3a8978c7] maps [javax.ws.rs.WebApplicationException @563625d0 <501/SERVER_ERROR|Not Implemented|-no-entity->] ('Carp') to <501/SERVER_ERROR|Not Implemented> [ 0.02 ms]
16:05:22.153 [http-8080-1] INFO  o.g.jersey.filter.LoggingFilter - 8 * LoggingFilter - Response received on thread http-8080-1
8 < 503

16:05:22.154 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - RESPONSE_FILTER Filter by [org.glassfish.jersey.filter.LoggingFilter @5271b383 #-2147483648] [ 0.13 ms]
16:05:22.154 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - RESPONSE_FILTER_SUMMARY Response summary: 1 filters [ 0.93 ms]
16:05:22.155 [http-8080-1] DEBUG org.glassfish.jersey.tracing.general - FINISHED Response status: 501/SERVER_ERROR|Not Implemented [ ---- ms]
16:05:22.160 [http-8080-1] TRACE o.g.j.process.internal.RequestScope - [DEBUG] Released scope instance Instance{id=021c24a2-c224-4c22-8f18-e5f7f93b0295, referenceCounter=0, store size=0} on thread http-8080-1

Upvotes: 1

Views: 1673

Answers (1)

Xavier
Xavier

Reputation: 1574

Jersey is beginning the HTTP response, thus sending a 200 OK, and only after that starts calling your StreamingOutput.write implementation. So, when your exception is raised, it's already too late to send a different HTTP code.

The best way I found to deal with this kind of issue is trying to do as much as possible outside of the StreamingOutput, or in the constructor of the implementation (these versions will send 500, you can catch the SQLException if you want something else):

@GET
public StreamingOutput getDataWithAnonymousClass() throws SQLException {
    final DataProvider dataProvider = new DataProvider();
    return new StreamingOutput() {
        @Override
        public void write(OutputStream output) {
            dataProvider.writeData(output);
        }
    }
}


public final class MyStreamingOutput implements StreamingOutput {

    private final DataProvider dataProvider;

    public MyStreamingOutput() throws SQLException {
        this.dataProvider = new DataProvider();
    }

    @Override
    public void write(OutputStream output) {
        dataProvider.writeData(output);
    }
}

@GET
public StreamingOutput getDataWithExcInConstructor() throws SQLException {
    return new MyStreamingOutput();
}

That way, the exception is thrown before the HTTP response has been started, and you can have a different status.

Upvotes: 1

Related Questions