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;
@Getter
private final boolean attachment;
@Getter
@NonNull
private final List<String> names;

View file

@ -17,9 +17,11 @@ import io.vavr.collection.Set;
import io.vavr.collection.Stream;
import io.vavr.control.Option;
import io.vavr.control.Try;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import org.slf4j.MDC;
import java.nio.ByteBuffer;
import java.time.Instant;
@ -27,6 +29,7 @@ import java.time.temporal.ChronoUnit;
import java.util.Deque;
import java.util.Map;
import java.util.Random;
import java.util.function.Function;
@Slf4j
public class CreateHandler extends AbstractHttpHandler {
@ -39,8 +42,10 @@ public class CreateHandler extends AbstractHttpHandler {
this.createLabyrinthFromRequestParameters(exchange.getQueryParameters())
.onFailure(e -> {
log.error("Error creating Labyrinth from request", e);
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
exchange.setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING);
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
.setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING)
.getResponseSender()
.send(e.getMessage());
})
.forEach(tuple -> {
final OutputType outputType = tuple._1();
@ -50,30 +55,47 @@ public class CreateHandler extends AbstractHttpHandler {
bytes = outputType.render(labyrinth);
} catch (@NonNull final Exception e) {
log.error("Error rendering Labyrinth", e);
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
exchange.setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING);
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
.setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING)
.getResponseSender()
.send("Error creating the Labyrinth. Please contact the administrator. Request id=%s".formatted(MDC.get("correlationId")));
return;
}
final long durationMillis = start.until(Instant.now(), ChronoUnit.MILLIS);
exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, outputType.getContentType())
.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-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));
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();
}
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;
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);
}
@ -83,6 +105,10 @@ public class CreateHandler extends AbstractHttpHandler {
}
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 {
@ -98,22 +124,37 @@ public class CreateHandler extends AbstractHttpHandler {
@NonNull
Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() {
final Option<OutputType> output = getParameterValues(RequestParameter.OUTPUT)
.foldLeft(Option.none(), (type, param) -> type.orElse(() -> OutputType.ofString(param)));
final Option<Integer> width = getParameterValues(RequestParameter.WIDTH)
.foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption()));
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()));
final Option<OutputType> output = getParameterValue(RequestParameter.OUTPUT);
final Option<Integer> width = getParameterValue(RequestParameter.WIDTH);
final Option<Integer> height = getParameterValue(RequestParameter.HEIGHT);
final Option<Long> id = getParameterValue(RequestParameter.ID);
return Try.of(() -> {
final OutputType t1 = output.get();
final Integer width1 = width.get();
final Integer height1 = height.get();
final Long orElse = id.getOrElse(() -> new Random().nextLong());
return Tuple.of(t1, new Labyrinth(width1, height1, orElse));
});
if (output.isEmpty()) {
return Try.failure(new IllegalArgumentException("Path parameter %s is required and must be one of: %s".formatted(
RequestParameter.OUTPUT.getNames().mkString("'", " / ", "'"),
Stream.of(OutputType.values())
.flatMap(OutputType::getNames)
.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