[HTTP] add option to only return simple exception messages
Adds a setting to disable detailed error messages and full exception stack traces in HTTP responses. When set to false, the error_trace request parameter will result in a HTTP 400 response. When the error_trace parameter is not present, the message of the first ElasticsearchException will be output and no nested exception messages will be output.
This commit is contained in:
parent
5f1ed47df6
commit
105bdd486a
|
@ -72,6 +72,11 @@ be cached for. Defaults to `1728000` (20 days)
|
||||||
header should be returned. Note: This header is only returned, when the setting is
|
header should be returned. Note: This header is only returned, when the setting is
|
||||||
set to `true`. Defaults to `false`
|
set to `true`. Defaults to `false`
|
||||||
|
|
||||||
|
|`http.detailed_errors.enabled` |Enables or disables the output of detailed error messages
|
||||||
|
and stack traces in response output. Note: When set to `false` and the `error_trace` request
|
||||||
|
parameter is specified, an error will be returned; when `error_trace` is not specified, a
|
||||||
|
simple message will be returned. Defaults to `true`
|
||||||
|
|
||||||
|`http.pipelining` |Enable or disable HTTP pipelining, defaults to `true`.
|
|`http.pipelining` |Enable or disable HTTP pipelining, defaults to `true`.
|
||||||
|
|
||||||
|`http.pipelining.max_events` |The maximum number of events to be queued up in memory before a HTTP connection is closed, defaults to `10000`.
|
|`http.pipelining.max_events` |The maximum number of events to be queued up in memory before a HTTP connection is closed, defaults to `10000`.
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.elasticsearch.rest.RestRequest;
|
||||||
*/
|
*/
|
||||||
public abstract class HttpChannel extends RestChannel {
|
public abstract class HttpChannel extends RestChannel {
|
||||||
|
|
||||||
protected HttpChannel(RestRequest request) {
|
protected HttpChannel(RestRequest request, boolean detailedErrorsEnabled) {
|
||||||
super(request);
|
super(request, detailedErrorsEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,11 +36,13 @@ public class HttpRequestHandler extends SimpleChannelUpstreamHandler {
|
||||||
private final NettyHttpServerTransport serverTransport;
|
private final NettyHttpServerTransport serverTransport;
|
||||||
private final Pattern corsPattern;
|
private final Pattern corsPattern;
|
||||||
private final boolean httpPipeliningEnabled;
|
private final boolean httpPipeliningEnabled;
|
||||||
|
private final boolean detailedErrorsEnabled;
|
||||||
|
|
||||||
public HttpRequestHandler(NettyHttpServerTransport serverTransport) {
|
public HttpRequestHandler(NettyHttpServerTransport serverTransport, boolean detailedErrorsEnabled) {
|
||||||
this.serverTransport = serverTransport;
|
this.serverTransport = serverTransport;
|
||||||
this.corsPattern = RestUtils.getCorsSettingRegex(serverTransport.settings());
|
this.corsPattern = RestUtils.getCorsSettingRegex(serverTransport.settings());
|
||||||
this.httpPipeliningEnabled = serverTransport.pipelining;
|
this.httpPipeliningEnabled = serverTransport.pipelining;
|
||||||
|
this.detailedErrorsEnabled = detailedErrorsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,9 +60,9 @@ public class HttpRequestHandler extends SimpleChannelUpstreamHandler {
|
||||||
// when reading, or using a cumalation buffer
|
// when reading, or using a cumalation buffer
|
||||||
NettyHttpRequest httpRequest = new NettyHttpRequest(request, e.getChannel());
|
NettyHttpRequest httpRequest = new NettyHttpRequest(request, e.getChannel());
|
||||||
if (oue != null) {
|
if (oue != null) {
|
||||||
serverTransport.dispatchRequest(httpRequest, new NettyHttpChannel(serverTransport, httpRequest, corsPattern, oue));
|
serverTransport.dispatchRequest(httpRequest, new NettyHttpChannel(serverTransport, httpRequest, corsPattern, oue, detailedErrorsEnabled));
|
||||||
} else {
|
} else {
|
||||||
serverTransport.dispatchRequest(httpRequest, new NettyHttpChannel(serverTransport, httpRequest, corsPattern));
|
serverTransport.dispatchRequest(httpRequest, new NettyHttpChannel(serverTransport, httpRequest, corsPattern, detailedErrorsEnabled));
|
||||||
}
|
}
|
||||||
super.messageReceived(ctx, e);
|
super.messageReceived(ctx, e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,16 +64,16 @@ public class NettyHttpChannel extends HttpChannel {
|
||||||
private OrderedUpstreamMessageEvent orderedUpstreamMessageEvent = null;
|
private OrderedUpstreamMessageEvent orderedUpstreamMessageEvent = null;
|
||||||
private Pattern corsPattern;
|
private Pattern corsPattern;
|
||||||
|
|
||||||
public NettyHttpChannel(NettyHttpServerTransport transport, NettyHttpRequest request, Pattern corsPattern) {
|
public NettyHttpChannel(NettyHttpServerTransport transport, NettyHttpRequest request, Pattern corsPattern, boolean detailedErrorsEnabled) {
|
||||||
super(request);
|
super(request, detailedErrorsEnabled);
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
this.channel = request.getChannel();
|
this.channel = request.getChannel();
|
||||||
this.nettyRequest = request.request();
|
this.nettyRequest = request.request();
|
||||||
this.corsPattern = corsPattern;
|
this.corsPattern = corsPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NettyHttpChannel(NettyHttpServerTransport transport, NettyHttpRequest request, Pattern corsPattern, OrderedUpstreamMessageEvent orderedUpstreamMessageEvent) {
|
public NettyHttpChannel(NettyHttpServerTransport transport, NettyHttpRequest request, Pattern corsPattern, OrderedUpstreamMessageEvent orderedUpstreamMessageEvent, boolean detailedErrorsEnabled) {
|
||||||
this(transport, request, corsPattern);
|
this(transport, request, corsPattern, detailedErrorsEnabled);
|
||||||
this.orderedUpstreamMessageEvent = orderedUpstreamMessageEvent;
|
this.orderedUpstreamMessageEvent = orderedUpstreamMessageEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
|
||||||
public static final String SETTING_PIPELINING_MAX_EVENTS = "http.pipelining.max_events";
|
public static final String SETTING_PIPELINING_MAX_EVENTS = "http.pipelining.max_events";
|
||||||
public static final String SETTING_HTTP_COMPRESSION = "http.compression";
|
public static final String SETTING_HTTP_COMPRESSION = "http.compression";
|
||||||
public static final String SETTING_HTTP_COMPRESSION_LEVEL = "http.compression_level";
|
public static final String SETTING_HTTP_COMPRESSION_LEVEL = "http.compression_level";
|
||||||
|
public static final String SETTING_HTTP_DETAILED_ERRORS_ENABLED = "http.detailed_errors.enabled";
|
||||||
|
|
||||||
public static final boolean DEFAULT_SETTING_PIPELINING = true;
|
public static final boolean DEFAULT_SETTING_PIPELINING = true;
|
||||||
public static final int DEFAULT_SETTING_PIPELINING_MAX_EVENTS = 10000;
|
public static final int DEFAULT_SETTING_PIPELINING_MAX_EVENTS = 10000;
|
||||||
|
@ -109,6 +110,8 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
|
||||||
|
|
||||||
protected final String publishHost;
|
protected final String publishHost;
|
||||||
|
|
||||||
|
protected final boolean detailedErrorsEnabled;
|
||||||
|
|
||||||
protected int publishPort;
|
protected int publishPort;
|
||||||
|
|
||||||
protected final String tcpNoDelay;
|
protected final String tcpNoDelay;
|
||||||
|
@ -162,6 +165,7 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
|
||||||
this.reuseAddress = settings.getAsBoolean("http.netty.reuse_address", settings.getAsBoolean(TCP_REUSE_ADDRESS, NetworkUtils.defaultReuseAddress()));
|
this.reuseAddress = settings.getAsBoolean("http.netty.reuse_address", settings.getAsBoolean(TCP_REUSE_ADDRESS, NetworkUtils.defaultReuseAddress()));
|
||||||
this.tcpSendBufferSize = settings.getAsBytesSize("http.netty.tcp_send_buffer_size", settings.getAsBytesSize(TCP_SEND_BUFFER_SIZE, TCP_DEFAULT_SEND_BUFFER_SIZE));
|
this.tcpSendBufferSize = settings.getAsBytesSize("http.netty.tcp_send_buffer_size", settings.getAsBytesSize(TCP_SEND_BUFFER_SIZE, TCP_DEFAULT_SEND_BUFFER_SIZE));
|
||||||
this.tcpReceiveBufferSize = settings.getAsBytesSize("http.netty.tcp_receive_buffer_size", settings.getAsBytesSize(TCP_RECEIVE_BUFFER_SIZE, TCP_DEFAULT_RECEIVE_BUFFER_SIZE));
|
this.tcpReceiveBufferSize = settings.getAsBytesSize("http.netty.tcp_receive_buffer_size", settings.getAsBytesSize(TCP_RECEIVE_BUFFER_SIZE, TCP_DEFAULT_RECEIVE_BUFFER_SIZE));
|
||||||
|
this.detailedErrorsEnabled = settings.getAsBoolean(SETTING_HTTP_DETAILED_ERRORS_ENABLED, true);
|
||||||
|
|
||||||
long defaultReceiverPredictor = 512 * 1024;
|
long defaultReceiverPredictor = 512 * 1024;
|
||||||
if (JvmInfo.jvmInfo().mem().directMemoryMax().bytes() > 0) {
|
if (JvmInfo.jvmInfo().mem().directMemoryMax().bytes() > 0) {
|
||||||
|
@ -349,7 +353,7 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelPipelineFactory configureServerChannelPipelineFactory() {
|
public ChannelPipelineFactory configureServerChannelPipelineFactory() {
|
||||||
return new HttpChannelPipelineFactory(this);
|
return new HttpChannelPipelineFactory(this, detailedErrorsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class HttpChannelPipelineFactory implements ChannelPipelineFactory {
|
protected static class HttpChannelPipelineFactory implements ChannelPipelineFactory {
|
||||||
|
@ -357,9 +361,9 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
|
||||||
protected final NettyHttpServerTransport transport;
|
protected final NettyHttpServerTransport transport;
|
||||||
protected final HttpRequestHandler requestHandler;
|
protected final HttpRequestHandler requestHandler;
|
||||||
|
|
||||||
public HttpChannelPipelineFactory(NettyHttpServerTransport transport) {
|
public HttpChannelPipelineFactory(NettyHttpServerTransport transport, boolean detailedErrorsEnabled) {
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
this.requestHandler = new HttpRequestHandler(transport);
|
this.requestHandler = new HttpRequestHandler(transport, detailedErrorsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -120,10 +120,23 @@ public class BytesRestResponse extends RestResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static XContentBuilder convert(RestChannel channel, RestStatus status, Throwable t) throws IOException {
|
private static XContentBuilder convert(RestChannel channel, RestStatus status, Throwable t) throws IOException {
|
||||||
XContentBuilder builder = channel.newBuilder().startObject()
|
XContentBuilder builder = channel.newBuilder().startObject();
|
||||||
.field("error", detailedMessage(t))
|
if (t == null) {
|
||||||
.field("status", status.getStatus());
|
builder.field("error", "Unknown");
|
||||||
if (t != null && channel.request().paramAsBoolean("error_trace", false)) {
|
} else if (channel.detailedErrorsEnabled()) {
|
||||||
|
builder.field("error", detailedMessage(t));
|
||||||
|
if (channel.request().paramAsBoolean("error_trace", false)) {
|
||||||
|
buildErrorTrace(t, builder);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.field("error", simpleMessage(t));
|
||||||
|
}
|
||||||
|
builder.field("status", status.getStatus());
|
||||||
|
builder.endObject();
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void buildErrorTrace(Throwable t, XContentBuilder builder) throws IOException {
|
||||||
builder.startObject("error_trace");
|
builder.startObject("error_trace");
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
|
@ -144,9 +157,6 @@ public class BytesRestResponse extends RestResponse {
|
||||||
}
|
}
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
}
|
}
|
||||||
builder.endObject();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void buildThrowable(Throwable t, XContentBuilder builder) throws IOException {
|
private static void buildThrowable(Throwable t, XContentBuilder builder) throws IOException {
|
||||||
builder.field("message", t.getMessage());
|
builder.field("message", t.getMessage());
|
||||||
|
@ -163,4 +173,20 @@ public class BytesRestResponse extends RestResponse {
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Builds a simple error string from the message of the first ElasticsearchException
|
||||||
|
*/
|
||||||
|
private static String simpleMessage(Throwable t) throws IOException {
|
||||||
|
int counter = 0;
|
||||||
|
Throwable next = t;
|
||||||
|
while (next != null && counter++ < 10) {
|
||||||
|
if (t instanceof ElasticsearchException) {
|
||||||
|
return next.getClass().getSimpleName() + "[" + next.getMessage() + "]";
|
||||||
|
}
|
||||||
|
next = next.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "No ElasticsearchException found";
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -34,11 +34,13 @@ import java.io.IOException;
|
||||||
public abstract class RestChannel {
|
public abstract class RestChannel {
|
||||||
|
|
||||||
protected final RestRequest request;
|
protected final RestRequest request;
|
||||||
|
protected final boolean detailedErrorsEnabled;
|
||||||
|
|
||||||
private BytesStreamOutput bytesOut;
|
private BytesStreamOutput bytesOut;
|
||||||
|
|
||||||
protected RestChannel(RestRequest request) {
|
protected RestChannel(RestRequest request, boolean detailedErrorsEnabled) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
|
this.detailedErrorsEnabled = detailedErrorsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public XContentBuilder newBuilder() throws IOException {
|
public XContentBuilder newBuilder() throws IOException {
|
||||||
|
@ -96,5 +98,9 @@ public abstract class RestChannel {
|
||||||
return this.request;
|
return this.request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean detailedErrorsEnabled() {
|
||||||
|
return detailedErrorsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void sendResponse(RestResponse response);
|
public abstract void sendResponse(RestResponse response);
|
||||||
}
|
}
|
|
@ -161,20 +161,10 @@ public class RestController extends AbstractLifecycleComponent<RestController> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispatchRequest(final RestRequest request, final RestChannel channel) {
|
public void dispatchRequest(final RestRequest request, final RestChannel channel) {
|
||||||
// If JSONP is disabled and someone sends a callback parameter we should bail out before querying
|
if (!checkRequestParameters(request, channel)) {
|
||||||
if (!settings.getAsBoolean(HTTP_JSON_ENABLE, false) && request.hasParam("callback")){
|
|
||||||
try {
|
|
||||||
XContentBuilder builder = channel.newBuilder();
|
|
||||||
builder.startObject().field("error","JSONP is disabled.").endObject().string();
|
|
||||||
RestResponse response = new BytesRestResponse(FORBIDDEN, builder);
|
|
||||||
response.addHeader("Content-Type", "application/javascript");
|
|
||||||
channel.sendResponse(response);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("Failed to send response", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.length == 0) {
|
if (filters.length == 0) {
|
||||||
try {
|
try {
|
||||||
executeHandler(request, channel);
|
executeHandler(request, channel);
|
||||||
|
@ -191,6 +181,44 @@ public class RestController extends AbstractLifecycleComponent<RestController> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the request parameters against enabled settings for JSONP and error trace support
|
||||||
|
* @param request
|
||||||
|
* @param channel
|
||||||
|
* @return true if the request does not have any parameters that conflict with system settings
|
||||||
|
*/
|
||||||
|
boolean checkRequestParameters(final RestRequest request, final RestChannel channel) {
|
||||||
|
// If JSONP is disabled and someone sends a callback parameter we should bail out before querying
|
||||||
|
if (!settings.getAsBoolean(HTTP_JSON_ENABLE, false) && request.hasParam("callback")) {
|
||||||
|
try {
|
||||||
|
XContentBuilder builder = channel.newBuilder();
|
||||||
|
builder.startObject().field("error","JSONP is disabled.").endObject().string();
|
||||||
|
RestResponse response = new BytesRestResponse(FORBIDDEN, builder);
|
||||||
|
response.addHeader("Content-Type", "application/javascript");
|
||||||
|
channel.sendResponse(response);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to send response", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// error_trace cannot be used when we disable detailed errors
|
||||||
|
if (channel.detailedErrorsEnabled() == false && request.paramAsBoolean("error_trace", false)) {
|
||||||
|
try {
|
||||||
|
XContentBuilder builder = channel.newBuilder();
|
||||||
|
builder.startObject().field("error","error traces in responses are disabled.").endObject().string();
|
||||||
|
RestResponse response = new BytesRestResponse(BAD_REQUEST, builder);
|
||||||
|
response.addHeader("Content-Type", "application/json");
|
||||||
|
channel.sendResponse(response);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to send response", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void executeHandler(RestRequest request, RestChannel channel) throws Exception {
|
void executeHandler(RestRequest request, RestChannel channel) throws Exception {
|
||||||
final RestHandler handler = getHandler(request);
|
final RestHandler handler = getHandler(request);
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
|
|
|
@ -147,7 +147,7 @@ public class NettyHttpServerPipeliningTest extends ElasticsearchTestCase {
|
||||||
private final ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
|
|
||||||
public CustomHttpChannelPipelineFactory(NettyHttpServerTransport transport, ExecutorService executorService) {
|
public CustomHttpChannelPipelineFactory(NettyHttpServerTransport transport, ExecutorService executorService) {
|
||||||
super(transport);
|
super(transport, randomBoolean());
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.options.detailederrors;
|
||||||
|
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.http.HttpServerTransport;
|
||||||
|
import org.elasticsearch.http.netty.NettyHttpServerTransport;
|
||||||
|
import org.elasticsearch.node.Node;
|
||||||
|
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||||
|
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||||
|
import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
||||||
|
import org.elasticsearch.test.rest.client.http.HttpDeleteWithEntity;
|
||||||
|
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
|
||||||
|
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that when disabling detailed errors, a request with the error_trace parameter returns a HTTP 400
|
||||||
|
*/
|
||||||
|
@ClusterScope(scope = Scope.TEST, numDataNodes = 1)
|
||||||
|
public class DetailedErrorsDisabledTest extends ElasticsearchIntegrationTest {
|
||||||
|
|
||||||
|
// Build our cluster settings
|
||||||
|
@Override
|
||||||
|
protected Settings nodeSettings(int nodeOrdinal) {
|
||||||
|
return ImmutableSettings.settingsBuilder()
|
||||||
|
.put(super.nodeSettings(nodeOrdinal))
|
||||||
|
.put(Node.HTTP_ENABLED, true)
|
||||||
|
.put(NettyHttpServerTransport.SETTING_HTTP_DETAILED_ERRORS_ENABLED, false)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatErrorTraceParamReturns400() throws Exception {
|
||||||
|
// Make the HTTP request
|
||||||
|
HttpResponse response = new HttpRequestBuilder(HttpClients.createDefault())
|
||||||
|
.httpTransport(internalCluster().getDataNodeInstance(HttpServerTransport.class))
|
||||||
|
.addParam("error_trace", "true")
|
||||||
|
.method(HttpDeleteWithEntity.METHOD_NAME)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertThat(response.getHeaders().get("Content-Type"), is("application/json"));
|
||||||
|
assertThat(response.getBody(), is("{\"error\":\"error traces in responses are disabled.\"}"));
|
||||||
|
assertThat(response.getStatusCode(), is(400));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.options.detailederrors;
|
||||||
|
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.http.HttpServerTransport;
|
||||||
|
import org.elasticsearch.node.Node;
|
||||||
|
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||||
|
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||||
|
import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
||||||
|
import org.elasticsearch.test.rest.client.http.HttpDeleteWithEntity;
|
||||||
|
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
|
||||||
|
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that by default the error_trace parameter can be used to show stacktraces
|
||||||
|
*/
|
||||||
|
@ClusterScope(scope = Scope.TEST, numDataNodes = 1)
|
||||||
|
public class DetailedErrorsEnabledTest extends ElasticsearchIntegrationTest {
|
||||||
|
|
||||||
|
// Build our cluster settings
|
||||||
|
@Override
|
||||||
|
protected Settings nodeSettings(int nodeOrdinal) {
|
||||||
|
return ImmutableSettings.settingsBuilder()
|
||||||
|
.put(super.nodeSettings(nodeOrdinal))
|
||||||
|
.put(Node.HTTP_ENABLED, true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatErrorTraceWorksByDefault() throws Exception {
|
||||||
|
// Make the HTTP request
|
||||||
|
HttpResponse response = new HttpRequestBuilder(HttpClients.createDefault())
|
||||||
|
.httpTransport(internalCluster().getDataNodeInstance(HttpServerTransport.class))
|
||||||
|
.path("/")
|
||||||
|
.addParam("error_trace", "true")
|
||||||
|
.method(HttpDeleteWithEntity.METHOD_NAME)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertThat(response.getHeaders().get("Content-Type"), containsString("application/json"));
|
||||||
|
assertThat(response.getBody(), containsString("\"error_trace\":{\"message\":\"Validation Failed"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,11 @@ import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,11 +38,8 @@ public class BytesRestResponseTests extends ElasticsearchTestCase {
|
||||||
@Test
|
@Test
|
||||||
public void testWithHeaders() throws Exception {
|
public void testWithHeaders() throws Exception {
|
||||||
RestRequest request = new FakeRestRequest();
|
RestRequest request = new FakeRestRequest();
|
||||||
RestChannel channel = new RestChannel(request) {
|
RestChannel channel = randomBoolean() ? new DetailedExceptionRestChannel(request) : new SimpleExceptionRestChannel(request);
|
||||||
@Override
|
|
||||||
public void sendResponse(RestResponse response) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
BytesRestResponse response = new BytesRestResponse(channel, new ExceptionWithHeaders());
|
BytesRestResponse response = new BytesRestResponse(channel, new ExceptionWithHeaders());
|
||||||
assertThat(response.getHeaders().get("n1"), notNullValue());
|
assertThat(response.getHeaders().get("n1"), notNullValue());
|
||||||
assertThat(response.getHeaders().get("n1"), contains("v11", "v12"));
|
assertThat(response.getHeaders().get("n1"), contains("v11", "v12"));
|
||||||
|
@ -46,6 +47,70 @@ public class BytesRestResponseTests extends ElasticsearchTestCase {
|
||||||
assertThat(response.getHeaders().get("n2"), contains("v21", "v22"));
|
assertThat(response.getHeaders().get("n2"), contains("v21", "v22"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleExceptionMessage() throws Exception {
|
||||||
|
RestRequest request = new FakeRestRequest();
|
||||||
|
RestChannel channel = new SimpleExceptionRestChannel(request);
|
||||||
|
|
||||||
|
Throwable t = new ElasticsearchException("an error occurred reading data", new FileNotFoundException("/foo/bar"));
|
||||||
|
BytesRestResponse response = new BytesRestResponse(channel, t);
|
||||||
|
String text = response.content().toUtf8();
|
||||||
|
assertThat(text, containsString("ElasticsearchException[an error occurred reading data]"));
|
||||||
|
assertThat(text, not(containsString("FileNotFoundException")));
|
||||||
|
assertThat(text, not(containsString("/foo/bar")));
|
||||||
|
assertThat(text, not(containsString("error_trace")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDetailedExceptionMessage() throws Exception {
|
||||||
|
RestRequest request = new FakeRestRequest();
|
||||||
|
RestChannel channel = new DetailedExceptionRestChannel(request);
|
||||||
|
|
||||||
|
Throwable t = new ElasticsearchException("an error occurred reading data", new FileNotFoundException("/foo/bar"));
|
||||||
|
BytesRestResponse response = new BytesRestResponse(channel, t);
|
||||||
|
String text = response.content().toUtf8();
|
||||||
|
assertThat(text, containsString("ElasticsearchException[an error occurred reading data]"));
|
||||||
|
assertThat(text, containsString("FileNotFoundException[/foo/bar]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonElasticsearchExceptionIsNotShownAsSimpleMessage() throws Exception {
|
||||||
|
RestRequest request = new FakeRestRequest();
|
||||||
|
RestChannel channel = new SimpleExceptionRestChannel(request);
|
||||||
|
|
||||||
|
Throwable t = new Throwable("an error occurred reading data", new FileNotFoundException("/foo/bar"));
|
||||||
|
BytesRestResponse response = new BytesRestResponse(channel, t);
|
||||||
|
String text = response.content().toUtf8();
|
||||||
|
assertThat(text, not(containsString("Throwable[an error occurred reading data]")));
|
||||||
|
assertThat(text, not(containsString("FileNotFoundException[/foo/bar]")));
|
||||||
|
assertThat(text, not(containsString("error_trace")));
|
||||||
|
assertThat(text, containsString("\"error\":\"No ElasticsearchException found\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testErrorTrace() throws Exception {
|
||||||
|
RestRequest request = new FakeRestRequest();
|
||||||
|
request.params().put("error_trace", "true");
|
||||||
|
RestChannel channel = new DetailedExceptionRestChannel(request);
|
||||||
|
|
||||||
|
Throwable t = new Throwable("an error occurred reading data", new FileNotFoundException("/foo/bar"));
|
||||||
|
BytesRestResponse response = new BytesRestResponse(channel, t);
|
||||||
|
String text = response.content().toUtf8();
|
||||||
|
assertThat(text, containsString("\"error\":\"Throwable[an error occurred reading data]"));
|
||||||
|
assertThat(text, containsString("FileNotFoundException[/foo/bar]"));
|
||||||
|
assertThat(text, containsString("\"error_trace\":{\"message\":\"an error occurred reading data\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNullThrowable() throws Exception {
|
||||||
|
RestRequest request = new FakeRestRequest();
|
||||||
|
RestChannel channel = new SimpleExceptionRestChannel(request);
|
||||||
|
|
||||||
|
BytesRestResponse response = new BytesRestResponse(channel, null);
|
||||||
|
String text = response.content().toUtf8();
|
||||||
|
assertThat(text, containsString("\"error\":\"Unknown\""));
|
||||||
|
assertThat(text, not(containsString("error_trace")));
|
||||||
|
}
|
||||||
|
|
||||||
private static class ExceptionWithHeaders extends ElasticsearchException.WithRestHeaders {
|
private static class ExceptionWithHeaders extends ElasticsearchException.WithRestHeaders {
|
||||||
|
|
||||||
|
@ -53,4 +118,26 @@ public class BytesRestResponseTests extends ElasticsearchTestCase {
|
||||||
super("", header("n1", "v11", "v12"), header("n2", "v21", "v22"));
|
super("", header("n1", "v11", "v12"), header("n2", "v21", "v22"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class SimpleExceptionRestChannel extends RestChannel {
|
||||||
|
|
||||||
|
SimpleExceptionRestChannel(RestRequest request) {
|
||||||
|
super(request, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendResponse(RestResponse response) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DetailedExceptionRestChannel extends RestChannel {
|
||||||
|
|
||||||
|
DetailedExceptionRestChannel(RestRequest request) {
|
||||||
|
super(request, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendResponse(RestResponse response) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ public class RestFilterChainTests extends ElasticsearchTestCase {
|
||||||
AtomicInteger errors = new AtomicInteger();
|
AtomicInteger errors = new AtomicInteger();
|
||||||
|
|
||||||
protected FakeRestChannel(RestRequest request, int responseCount) {
|
protected FakeRestChannel(RestRequest request, int responseCount) {
|
||||||
super(request);
|
super(request, randomBoolean());
|
||||||
this.latch = new CountDownLatch(responseCount);
|
this.latch = new CountDownLatch(responseCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue