1package dev.rafex.ether.http.jetty12.handler;
29import java.nio.charset.StandardCharsets;
30import java.util.LinkedHashMap;
34import org.eclipse.jetty.server.Handler;
35import org.eclipse.jetty.server.Request;
36import org.eclipse.jetty.server.Response;
37import org.eclipse.jetty.util.Callback;
38import org.eclipse.jetty.util.MultiMap;
39import org.eclipse.jetty.util.UrlEncoded;
41import dev.rafex.ether.http.core.DefaultErrorMapper;
42import dev.rafex.ether.http.core.ErrorMapper;
43import dev.rafex.ether.http.core.HttpResource;
44import dev.rafex.ether.http.core.Route;
45import dev.rafex.ether.http.core.RouteMatcher;
46import dev.rafex.ether.http.jetty12.exchange.JettyHttpExchange;
47import dev.rafex.ether.http.jetty12.response.JettyApiErrorResponses;
48import dev.rafex.ether.http.problem.exception.ProblemException;
49import dev.rafex.ether.json.JsonCodec;
62 this.jsonCodec = jsonCodec;
63 this.errorMapper = errorMapper;
74 public boolean handle(
final Request request,
final Response
response,
final Callback callback) {
75 final var path = request.getHttpURI().getPath();
76 if (!matchesBasePath(path)) {
80 final var relPath = normalizeRelPath(path);
82 if (match.isEmpty()) {
83 errorResponses.notFound(
response, callback, path);
87 final var routeMatch = match.get();
89 parseQueryMap(request), routeMatch.route().allowedMethods(), jsonCodec);
91 final var method = request.getMethod().toUpperCase();
92 if (!routeMatch.route().allows(method) && !
"OPTIONS".equals(method)) {
98 return dispatch(method, x);
100 errorResponses.problem(
response, callback, e.problem());
102 }
catch (
final Exception e) {
103 final var mapped = errorMapper.map(e);
104 errorResponses.error(
response, callback, mapped, path);
109 private boolean dispatch(
final String method,
final JettyHttpExchange x)
throws Exception {
110 return switch (method) {
111 case "GET" ->
get(x);
112 case "POST" ->
post(x);
113 case "PUT" ->
put(x);
114 case "DELETE" ->
delete(x);
115 case "PATCH" ->
patch(x);
118 x.methodNotAllowed();
124 private String normalizeRelPath(
final String absolutePath) {
126 if (absolutePath.length() == base.length()) {
129 final var rel = absolutePath.substring(base.length());
130 return rel.isEmpty() ?
"/" : rel;
133 private boolean matchesBasePath(
final String path) {
135 if (
"/".equals(base)) {
136 return path !=
null && path.startsWith(
"/");
138 if (base.equals(path)) {
141 return path.startsWith(base +
"/");
144 private static Map<String, List<String>> parseQueryMap(
final Request request) {
145 final MultiMap<String> params =
new MultiMap<>();
146 final var rawQuery = request.getHttpURI().getQuery();
147 if (rawQuery !=
null && !rawQuery.isEmpty()) {
148 UrlEncoded.decodeTo(rawQuery, params, StandardCharsets.UTF_8);
151 final var out =
new LinkedHashMap<String, List<String>>();
152 for (
final var key : params.keySet()) {
153 final var values = params.getValues(key);
154 out.put(key, values ==
null ? List.of() : List.copyOf(values));
161 if (value ==
null || value.isBlank()) {
static Optional< RouteMatch > match(final String relPath, final List< Route > routes)
String queryFirst(final String name)
ResourceHandler(final JsonCodec jsonCodec)
boolean handle(final Request request, final Response response, final Callback callback)
static String queryParam(final JettyHttpExchange x, final String key)
ResourceHandler(final JsonCodec jsonCodec, final ErrorMapper errorMapper)
abstract String basePath()
default Set< String > supportedMethods()
default boolean patch(final HttpExchange x)
default boolean delete(final HttpExchange x)
default boolean put(final HttpExchange x)
default boolean options(final HttpExchange x)
default boolean post(final HttpExchange x)