aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApi.java16
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java71
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java10
3 files changed, 78 insertions, 19 deletions
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApi.java b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
index 1d875db7adc..08bac710001 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApi.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
@@ -6,11 +6,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.slime.Slime;
-import com.yahoo.slime.SlimeUtils;
-import java.io.IOException;
import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalLong;
@@ -72,6 +69,7 @@ public interface RestApi {
Headers headers();
Attributes attributes();
Optional<RequestContent> requestContent();
+ RequestContent requestContentOrThrow();
interface Parameters {
Optional<String> getString(String name);
@@ -101,13 +99,11 @@ public interface RestApi {
String contentType();
InputStream inputStream();
ObjectMapper jacksonJsonMapper();
- default byte[] consumeByteArray() throws IOException { return inputStream().readAllBytes(); }
- default String consumeString() throws IOException { return new String(consumeByteArray(), StandardCharsets.UTF_8); }
- default JsonNode consumeJsonNode() throws IOException { return jacksonJsonMapper().readTree(inputStream()); }
- default Slime consumeSlime() throws IOException { return SlimeUtils.jsonToSlime(consumeByteArray()); }
- default <T extends JacksonRequestEntity> T consumeJacksonEntity(Class<T> type) throws IOException {
- return jacksonJsonMapper().readValue(inputStream(), type);
- }
+ byte[] consumeByteArray();
+ String consumeString();
+ JsonNode consumeJsonNode();
+ Slime consumeSlime();
+ <T extends JacksonRequestEntity> T consumeJacksonEntity(Class<T> type);
}
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
index b2da9db9458..eeabcc3fc74 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
@@ -1,25 +1,34 @@
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.restapi;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.slime.Slime;
+import com.yahoo.slime.SlimeUtils;
+import com.yahoo.yolean.Exceptions;
+import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* @author bjorncs
*/
class RestApiImpl implements RestApi {
+ private static final Logger log = Logger.getLogger(RestApiImpl.class.getName());
+
private final Route defaultRoute;
private final List<Route> routes;
private final List<ExceptionMapperHolder<?>> exceptionMappers;
@@ -205,6 +214,9 @@ class RestApiImpl implements RestApi {
@Override public Headers headers() { return headers; }
@Override public Attributes attributes() { return attributes; }
@Override public Optional<RequestContent> requestContent() { return Optional.ofNullable(requestContent); }
+ @Override public RequestContent requestContentOrThrow() {
+ return requestContent().orElseThrow(() -> new RestApiException.BadRequest("Request content missing"));
+ }
private class PathParametersImpl implements RestApi.RequestContext.PathParameters {
@Override public Optional<String> getString(String name) { return Optional.ofNullable(pathMatcher.get(name)); }
@@ -234,12 +246,71 @@ class RestApiImpl implements RestApi {
@Override public String contentType() { return request.getHeader("Content-Type"); }
@Override public InputStream inputStream() { return request.getData(); }
@Override public ObjectMapper jacksonJsonMapper() { return jacksonJsonMapper; }
+ @Override public byte[] consumeByteArray() { return convertIoException(() -> inputStream().readAllBytes()); }
+ @Override public String consumeString() { return new String(consumeByteArray(), StandardCharsets.UTF_8); }
+
+ @Override
+ public JsonNode consumeJsonNode() {
+ return convertIoException(() -> {
+ try {
+ if (log.isLoggable(Level.FINE)) {
+ String content = consumeString();
+ log.fine(() -> "Request content: " + content);
+ return jacksonJsonMapper.readTree(content);
+ } else {
+ return jacksonJsonMapper.readTree(request.getData());
+ }
+ } catch (com.fasterxml.jackson.core.JsonParseException e) {
+ log.log(Level.FINE, e.getMessage(), e);
+ throw new RestApiException.BadRequest("Invalid json request content: " + Exceptions.toMessageString(e), e);
+ }
+ });
+ }
+
+ @Override
+ public Slime consumeSlime() {
+ try {
+ String content = consumeString();
+ log.fine(() -> "Request content: " + content);
+ return SlimeUtils.jsonToSlimeOrThrow(content);
+ } catch (com.yahoo.slime.JsonParseException e) {
+ log.log(Level.FINE, e.getMessage(), e);
+ throw new RestApiException.BadRequest("Invalid json request content: " + Exceptions.toMessageString(e), e);
+ }
+ }
+
+ @Override
+ public <T extends JacksonRequestEntity> T consumeJacksonEntity(Class<T> type) {
+ return convertIoException(() -> {
+ try {
+ if (log.isLoggable(Level.FINE)) {
+ String content = consumeString();
+ log.fine(() -> "Request content: " + content);
+ return jacksonJsonMapper.readValue(content, type);
+ } else {
+ return jacksonJsonMapper.readValue(request.getData(), type);
+ }
+ } catch (com.fasterxml.jackson.core.JsonParseException | JsonMappingException e) {
+ log.log(Level.FINE, e.getMessage(), e);
+ throw new RestApiException.BadRequest("Invalid json request content: " + Exceptions.toMessageString(e), e);
+ }
+ });
+ }
}
private class AttributesImpl implements RestApi.RequestContext.Attributes {
@Override public Optional<Object> get(String name) { return Optional.ofNullable(request.getJDiscRequest().context().get(name)); }
@Override public void set(String name, Object value) { request.getJDiscRequest().context().put(name, value); }
}
+
+ @FunctionalInterface private interface SupplierThrowingIoException<T> { T get() throws IOException; }
+ private static <T> T convertIoException(SupplierThrowingIoException<T> supplier) {
+ try {
+ return supplier.get();
+ } catch (IOException e) {
+ throw new RestApiException.InternalServerError("Failed to read request content: " + Exceptions.toMessageString(e), e);
+ }
+ }
}
private class FilterContextImpl implements RestApi.FilterContext {
diff --git a/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java b/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
index 628c25b23db..16cc2353986 100644
--- a/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
+++ b/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
@@ -9,8 +9,6 @@ import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
@@ -96,13 +94,7 @@ class RestApiImplTest {
void method_handler_can_consume_and_produce_json() {
RestApi restApi = RestApi.builder()
.addRoute(route("/api").post(
- ctx -> {
- try {
- return ctx.requestContent().get().consumeJacksonEntity(TestEntity.class);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }))
+ ctx -> ctx.requestContent().get().consumeJacksonEntity(TestEntity.class)))
.build();
String rawJson = "{\"mystring\":\"my-string-value\", \"myinstant\":\"2000-01-01T00:00:00Z\"}";
verifyJsonResponse(restApi, Method.POST, "/api", rawJson, 200, rawJson);