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

Adds a default send(Throwable) method to ServerResponse.java as the first step in providing an easy facility for reporting exceptions during HTTP processing #1378

Merged
merged 12 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,8 @@

package io.helidon.media.common;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
Expand Down Expand Up @@ -81,6 +83,33 @@ public static Single<DataChunk> writeCharBuffer(CharBuffer buffer, Charset chars
return Single.just(DataChunk.create(false, buffer.encode(charset)));
}

/**
* Create a a publisher {@link DataChunk} with the given
* {@link Throwable} / {@link Charset} and return a {@link Single}.
*
* @param throwable the {@link Throwable}
* @param charset the charset to use to encode the stack trace
* @return Single
*/
public static Single<DataChunk> writeStackTrace(Throwable throwable, Charset charset) {
final StringWriter stringWriter = new StringWriter();
final PrintWriter printWriter = new PrintWriter(stringWriter);
String stackTraceString = null;
try {
throwable.printStackTrace(printWriter);
stackTraceString = stringWriter.toString();
} finally {
printWriter.close();
}
final Single<DataChunk> returnValue;
if (stackTraceString.isEmpty()) {
returnValue = Single.<DataChunk>empty();
} else {
returnValue = writeCharSequence(stackTraceString, charset);
}
return returnValue;
}

/**
* Returns a writer function for {@code byte[]}.
* <p>
Expand Down Expand Up @@ -147,4 +176,5 @@ public static Function<ReadableByteChannel, Publisher<DataChunk>> byteChannelWri
public static Function<ReadableByteChannel, Publisher<DataChunk>> byteChannelWriter() {
return byteChannelWriter(null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ public Builder registerDefaults() {
.registerWriter(CharSequenceBodyWriter.create())
.registerWriter(ByteChannelBodyWriter.create())
.registerWriter(PathBodyWriter.create())
.registerWriter(FileBodyWriter.create());
.registerWriter(FileBodyWriter.create())
.registerWriter(ThrowableBodyWriter.create(false));
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.media.common;

import java.nio.charset.Charset;
import java.util.concurrent.Flow.Publisher;

import io.helidon.common.GenericType;
import io.helidon.common.http.DataChunk;
import io.helidon.common.http.MediaType;
import io.helidon.common.mapper.Mapper;
import io.helidon.common.reactive.Single;

/**
* Message body writer for {@link Throwable}.
*/
public class ThrowableBodyWriter implements MessageBodyWriter<Throwable> {

private final boolean writeStackTrace;

private ThrowableBodyWriter() {
this(false);
}

protected ThrowableBodyWriter(boolean writeStackTrace) {
this.writeStackTrace = writeStackTrace;
}

@Override
public boolean accept(GenericType<?> type, MessageBodyWriterContext context) {
return Throwable.class.isAssignableFrom(type.rawType());
}

@Override
public Publisher<DataChunk> write(Single<Throwable> content,
GenericType<? extends Throwable> type,
MessageBodyWriterContext context) {
context.contentType(MediaType.TEXT_PLAIN);
return content.mapMany(new ThrowableToChunks(context.charset()));
}

/**
* Creates a new {@link ThrowableBodyWriter}.
* @return a new {@link ThrowableBodyWriter}; never {@code null}
* @see #create(boolean)
*/
public static ThrowableBodyWriter create() {
return create(false);
}

/**
* Creates a new {@link ThrowableBodyWriter}.
* @param writeStackTrace whether stack traces are to be written
* @return a new {@link ThrowableBodyWriter}; never {@code null}
*/
public static ThrowableBodyWriter create(boolean writeStackTrace) {
return new ThrowableBodyWriter(writeStackTrace);
}

private static final class ThrowableToChunks implements Mapper<Throwable, Publisher<DataChunk>> {

private final Charset charset;

private ThrowableToChunks(Charset charset) {
this.charset = charset;
}

@Override
public Publisher<DataChunk> map(Throwable throwable) {
return ContentWriters.writeStackTrace(throwable, charset);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,19 @@ private Span createWriteSpan(GenericType<?> type) {
return null;
}

@Override
public Void send(Throwable content) {
if (status() == null) {
if (content instanceof HttpException) {
status(((HttpException) content).status());
} else {
status(Http.Status.INTERNAL_SERVER_ERROR_500);
}
}
send((Object) content);
return null;
}

@Override
public <T> CompletionStage<ServerResponse> send(T content) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ default ServerResponse status(int statusCode) throws AlreadyCompletedException {
*/
MessageBodyWriterContext writerContext();

/**
* Send a {@link Throwable} and close the response.
*
* @param content the {@link Throwable} to send
* @return {@code null} when invoked
* @throws IllegalArgumentException if there is no registered writer for a given type
* @throws IllegalStateException if any {@code send(...)} method was already called
* @see #send(Object)
*/
Void send(Throwable content);

/**
* Send a message and close the response.
*
Expand Down