feature/undertow #4

Merged
manuel merged 15 commits from feature/undertow into master 2023-04-08 22:40:46 +02:00
2 changed files with 65 additions and 23 deletions
Showing only changes of commit 87fba97672 - Show all commits

View file

@ -56,6 +56,7 @@ public enum OutputType {
private final Function<Labyrinth, byte[]> render; private final Function<Labyrinth, byte[]> render;
@Getter @Getter
private final boolean attachment; private final boolean attachment;
@Getter
@NonNull @NonNull
private final List<String> names; private final List<String> names;

View file

@ -17,9 +17,11 @@ import io.vavr.collection.Set;
import io.vavr.collection.Stream; import io.vavr.collection.Stream;
import io.vavr.control.Option; import io.vavr.control.Option;
import io.vavr.control.Try; import io.vavr.control.Try;
import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.MDC;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.time.Instant; import java.time.Instant;
@ -27,6 +29,7 @@ import java.time.temporal.ChronoUnit;
import java.util.Deque; import java.util.Deque;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.function.Function;
@Slf4j @Slf4j
public class CreateHandler extends AbstractHttpHandler { public class CreateHandler extends AbstractHttpHandler {
@ -39,8 +42,10 @@ public class CreateHandler extends AbstractHttpHandler {
this.createLabyrinthFromRequestParameters(exchange.getQueryParameters()) this.createLabyrinthFromRequestParameters(exchange.getQueryParameters())
.onFailure(e -> { .onFailure(e -> {
log.error("Error creating Labyrinth from request", e); log.error("Error creating Labyrinth from request", e);
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
exchange.setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING); .setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING)
.getResponseSender()
.send(e.getMessage());
}) })
.forEach(tuple -> { .forEach(tuple -> {
final OutputType outputType = tuple._1(); final OutputType outputType = tuple._1();
@ -50,30 +55,47 @@ public class CreateHandler extends AbstractHttpHandler {
bytes = outputType.render(labyrinth); bytes = outputType.render(labyrinth);
} catch (@NonNull final Exception e) { } catch (@NonNull final Exception e) {
log.error("Error rendering Labyrinth", e); log.error("Error rendering Labyrinth", e);
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
exchange.setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING); .setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING)
.getResponseSender()
.send("Error creating the Labyrinth. Please contact the administrator. Request id=%s".formatted(MDC.get("correlationId")));
return; return;
} }
final long durationMillis = start.until(Instant.now(), ChronoUnit.MILLIS);
exchange.getResponseHeaders() exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, outputType.getContentType()) .put(Headers.CONTENT_TYPE, outputType.getContentType())
.put(HttpString.tryFromString("X-Labyrinth-ID"), String.valueOf(labyrinth.getRandomSeed())) .put(HttpString.tryFromString("X-Labyrinth-ID"), String.valueOf(labyrinth.getRandomSeed()))
.put(HttpString.tryFromString("X-Labyrinth-Width"), String.valueOf(labyrinth.getWidth())) .put(HttpString.tryFromString("X-Labyrinth-Width"), String.valueOf(labyrinth.getWidth()))
.put(HttpString.tryFromString("X-Labyrinth-Height"), String.valueOf(labyrinth.getHeight())) .put(HttpString.tryFromString("X-Labyrinth-Height"), String.valueOf(labyrinth.getHeight()))
.put(HttpString.tryFromString("X-Labyrinth-Generation-Duration-millis"), String.valueOf(start.until(Instant.now(), ChronoUnit.MILLIS))); .put(HttpString.tryFromString("X-Labyrinth-Generation-Duration-millis"), String.valueOf(durationMillis));
exchange.getResponseSender().send(ByteBuffer.wrap(bytes)); exchange.getResponseSender().send(ByteBuffer.wrap(bytes));
log.debug("Create request handled."); log.debug("Create request handled in {}ms.", durationMillis);
}); });
} }
private @NonNull Try<Tuple2<OutputType, Labyrinth>> createLabyrinthFromRequestParameters(final Map<String, Deque<String>> queryParameters) { @NonNull
private Try<Tuple2<OutputType, Labyrinth>> createLabyrinthFromRequestParameters(final Map<String, Deque<String>> queryParameters) {
return new ParametersToLabyrinthExtractor(queryParameters).createLabyrinth(); return new ParametersToLabyrinthExtractor(queryParameters).createLabyrinth();
} }
private enum RequestParameter { private enum RequestParameter {
WIDTH("w", "width"), HEIGHT("h", "height"), ID("i", "id"), OUTPUT("o", "output"); WIDTH(p -> Try.of(() -> Integer.parseInt(p)).toOption(), "w", "width"),
HEIGHT(p -> Try.of(() -> Integer.parseInt(p)).toOption(), "h", "height"),
ID(p -> Try.of(() -> Long.parseLong(p)).toOption(), "i", "id"),
OUTPUT(OutputType::ofString, "o", "output");
@NonNull
private final Function<String, Option<?>> extractor;
@Getter
@NonNull
private final Set<String> names; private final Set<String> names;
RequestParameter(@NonNull final String... names) { RequestParameter(@NonNull final String... names) {
this.extractor = null;
this.names = HashSet.of(names);
}
RequestParameter(@NonNull final Function<String, Option<?>> extractor, @NonNull final String... names) {
this.extractor = extractor;
this.names = HashSet.of(names); this.names = HashSet.of(names);
} }
@ -83,6 +105,10 @@ public class CreateHandler extends AbstractHttpHandler {
} }
return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase)); return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase));
} }
@NonNull <T> Option<T> extractParameterValue(@NonNull final String parameter) {
return (Option<T>) this.extractor.apply(parameter);
}
} }
private static class ParametersToLabyrinthExtractor { private static class ParametersToLabyrinthExtractor {
@ -98,22 +124,37 @@ public class CreateHandler extends AbstractHttpHandler {
@NonNull @NonNull
Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() { Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() {
final Option<OutputType> output = getParameterValues(RequestParameter.OUTPUT) final Option<OutputType> output = getParameterValue(RequestParameter.OUTPUT);
.foldLeft(Option.none(), (type, param) -> type.orElse(() -> OutputType.ofString(param))); final Option<Integer> width = getParameterValue(RequestParameter.WIDTH);
final Option<Integer> width = getParameterValues(RequestParameter.WIDTH) final Option<Integer> height = getParameterValue(RequestParameter.HEIGHT);
.foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption())); final Option<Long> id = getParameterValue(RequestParameter.ID);
final Option<Integer> height = getParameterValues(RequestParameter.HEIGHT)
.foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption()));
final Option<Long> id = getParameterValues(RequestParameter.ID)
.foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Long.parseLong(param)).toOption()));
return Try.of(() -> { if (output.isEmpty()) {
final OutputType t1 = output.get(); return Try.failure(new IllegalArgumentException("Path parameter %s is required and must be one of: %s".formatted(
final Integer width1 = width.get(); RequestParameter.OUTPUT.getNames().mkString("'", " / ", "'"),
final Integer height1 = height.get(); Stream.of(OutputType.values())
final Long orElse = id.getOrElse(() -> new Random().nextLong()); .flatMap(OutputType::getNames)
return Tuple.of(t1, new Labyrinth(width1, height1, orElse)); .mkString(", ")
}); )));
}
if (width.isEmpty()) {
return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted(
RequestParameter.WIDTH.getNames().mkString("'", " / ", "'")
)));
}
if (height.isEmpty()) {
return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted(
RequestParameter.HEIGHT.getNames().mkString("'", " / ", "'")
)));
}
return Try.of(() -> Tuple.of(output.get(), new Labyrinth(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong()))));
}
@NonNull
private <T> Option<T> getParameterValue(@NonNull final RequestParameter parameter) {
return this.getParameterValues(parameter)
.foldLeft(Option.none(), (type, param) -> type.orElse(() -> parameter.extractParameterValue(param)));
} }
@NonNull @NonNull