Search in sources :

Example 1 with MethodBasedRouteMatch

use of io.micronaut.web.router.MethodBasedRouteMatch in project micronaut-core by micronaut-projects.

the class RouteExecutor method createResponseForBody.

private Flux<MutableHttpResponse<?>> createResponseForBody(HttpRequest<?> request, Object body, RouteInfo<?> routeInfo) {
    return Flux.<MutableHttpResponse<?>>defer(() -> {
        Mono<MutableHttpResponse<?>> outgoingResponse;
        if (body == null) {
            if (routeInfo.isVoid()) {
                MutableHttpResponse<Object> data = forStatus(routeInfo);
                if (HttpMethod.permitsRequestBody(request.getMethod())) {
                    data.header(HttpHeaders.CONTENT_LENGTH, "0");
                }
                outgoingResponse = Mono.just(data);
            } else {
                outgoingResponse = Mono.just(newNotFoundError(request));
            }
        } else {
            HttpStatus defaultHttpStatus = routeInfo.isErrorRoute() ? HttpStatus.INTERNAL_SERVER_ERROR : HttpStatus.OK;
            boolean isReactive = routeInfo.isAsyncOrReactive() || Publishers.isConvertibleToPublisher(body);
            if (isReactive) {
                Class<?> bodyClass = body.getClass();
                boolean isSingle = isSingle(routeInfo, bodyClass);
                boolean isCompletable = !isSingle && routeInfo.isVoid() && Publishers.isCompletable(bodyClass);
                if (isSingle || isCompletable) {
                    // full response case
                    Publisher<Object> publisher = Publishers.convertPublisher(body, Publisher.class);
                    Supplier<MutableHttpResponse<?>> emptyResponse = () -> {
                        MutableHttpResponse<?> singleResponse;
                        if (isCompletable || routeInfo.isVoid()) {
                            singleResponse = forStatus(routeInfo, HttpStatus.OK).header(HttpHeaders.CONTENT_LENGTH, "0");
                        } else {
                            singleResponse = newNotFoundError(request);
                        }
                        return singleResponse;
                    };
                    return Flux.from(publisher).flatMap(o -> {
                        MutableHttpResponse<?> singleResponse;
                        if (o instanceof Optional) {
                            Optional optional = (Optional) o;
                            if (optional.isPresent()) {
                                o = ((Optional<?>) o).get();
                            } else {
                                return Flux.just(emptyResponse.get());
                            }
                        }
                        if (o instanceof HttpResponse) {
                            singleResponse = toMutableResponse((HttpResponse<?>) o);
                            final Argument<?> bodyArgument = // Mono
                            routeInfo.getReturnType().getFirstTypeVariable().orElse(// HttpResponse
                            Argument.OBJECT_ARGUMENT).getFirstTypeVariable().orElse(// Mono
                            Argument.OBJECT_ARGUMENT);
                            if (bodyArgument.isAsyncOrReactive()) {
                                return processPublisherBody(request, singleResponse, routeInfo);
                            }
                        } else if (o instanceof HttpStatus) {
                            singleResponse = forStatus(routeInfo, (HttpStatus) o);
                        } else {
                            singleResponse = forStatus(routeInfo, defaultHttpStatus).body(o);
                        }
                        return Flux.just(singleResponse);
                    }).switchIfEmpty(Mono.fromSupplier(emptyResponse));
                } else {
                    // streaming case
                    Argument<?> typeArgument = routeInfo.getReturnType().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                    if (HttpResponse.class.isAssignableFrom(typeArgument.getType())) {
                        // a response stream
                        Publisher<HttpResponse<?>> bodyPublisher = Publishers.convertPublisher(body, Publisher.class);
                        Flux<MutableHttpResponse<?>> response = Flux.from(bodyPublisher).map(this::toMutableResponse);
                        Argument<?> bodyArgument = typeArgument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                        if (bodyArgument.isAsyncOrReactive()) {
                            return response.flatMap((resp) -> processPublisherBody(request, resp, routeInfo));
                        }
                        return response;
                    } else {
                        MutableHttpResponse<?> response = forStatus(routeInfo, defaultHttpStatus).body(body);
                        return processPublisherBody(request, response, routeInfo);
                    }
                }
            }
            // now we have the raw result, transform it as necessary
            if (body instanceof HttpStatus) {
                outgoingResponse = Mono.just(HttpResponse.status((HttpStatus) body));
            } else {
                if (routeInfo.isSuspended()) {
                    boolean isKotlinFunctionReturnTypeUnit = routeInfo instanceof MethodBasedRouteMatch && isKotlinFunctionReturnTypeUnit(((MethodBasedRouteMatch) routeInfo).getExecutableMethod());
                    final Supplier<CompletableFuture<?>> supplier = ContinuationArgumentBinder.extractContinuationCompletableFutureSupplier(request);
                    if (isKotlinCoroutineSuspended(body)) {
                        return Mono.fromCompletionStage(supplier).<MutableHttpResponse<?>>flatMap(obj -> {
                            MutableHttpResponse<?> response;
                            if (obj instanceof HttpResponse) {
                                response = toMutableResponse((HttpResponse<?>) obj);
                                final Argument<?> bodyArgument = routeInfo.getReturnType().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                                if (bodyArgument.isAsyncOrReactive()) {
                                    return processPublisherBody(request, response, routeInfo);
                                }
                            } else {
                                response = forStatus(routeInfo, defaultHttpStatus);
                                if (!isKotlinFunctionReturnTypeUnit) {
                                    response = response.body(obj);
                                }
                            }
                            return Mono.just(response);
                        }).switchIfEmpty(createNotFoundErrorResponsePublisher(request));
                    } else {
                        Object suspendedBody;
                        if (isKotlinFunctionReturnTypeUnit) {
                            suspendedBody = Mono.empty();
                        } else {
                            suspendedBody = body;
                        }
                        outgoingResponse = toMutableResponse(request, routeInfo, defaultHttpStatus, suspendedBody);
                    }
                } else {
                    outgoingResponse = toMutableResponse(request, routeInfo, defaultHttpStatus, body);
                }
            }
        }
        // for head request we never emit the body
        if (request != null && request.getMethod().equals(HttpMethod.HEAD)) {
            outgoingResponse = outgoingResponse.map(r -> {
                final Object o = r.getBody().orElse(null);
                if (o instanceof ReferenceCounted) {
                    ((ReferenceCounted) o).release();
                }
                r.body(null);
                return r;
            });
        }
        return outgoingResponse;
    }).doOnNext((response) -> {
        applyConfiguredHeaders(response.getHeaders());
        if (routeInfo instanceof RouteMatch) {
            response.setAttribute(HttpAttributes.ROUTE_MATCH, routeInfo);
        }
        response.setAttribute(HttpAttributes.ROUTE_INFO, routeInfo);
    });
}
Also used : Publishers(io.micronaut.core.async.publisher.Publishers) ServerFilterChain(io.micronaut.http.filter.ServerFilterChain) BeanContext(io.micronaut.context.BeanContext) KotlinExecutableMethodUtils.isKotlinFunctionReturnTypeUnit(io.micronaut.inject.util.KotlinExecutableMethodUtils.isKotlinFunctionReturnTypeUnit) LoggerFactory(org.slf4j.LoggerFactory) HttpHeaders(io.micronaut.http.HttpHeaders) Internal(io.micronaut.core.annotation.Internal) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) HttpStatus(io.micronaut.http.HttpStatus) MediaType(io.micronaut.http.MediaType) ReferenceCounted(io.micronaut.core.io.buffer.ReferenceCounted) HttpResponse(io.micronaut.http.HttpResponse) MethodReference(io.micronaut.inject.MethodReference) MethodBasedRouteMatch(io.micronaut.web.router.MethodBasedRouteMatch) MutableHttpResponse(io.micronaut.http.MutableHttpResponse) Qualifiers(io.micronaut.inject.qualifiers.Qualifiers) Singleton(jakarta.inject.Singleton) MutableHttpHeaders(io.micronaut.http.MutableHttpHeaders) CompletionException(java.util.concurrent.CompletionException) HttpFilter(io.micronaut.http.filter.HttpFilter) RequestArgumentSatisfier(io.micronaut.http.server.binding.RequestArgumentSatisfier) List(java.util.List) RouteInfo(io.micronaut.web.router.RouteInfo) BeanCreationException(io.micronaut.context.exceptions.BeanCreationException) Optional(java.util.Optional) HttpAttributes(io.micronaut.http.HttpAttributes) Pattern(java.util.regex.Pattern) RouteMatch(io.micronaut.web.router.RouteMatch) UnsatisfiedRouteException(io.micronaut.web.router.exceptions.UnsatisfiedRouteException) LocalDateTime(java.time.LocalDateTime) CompletableFuture(java.util.concurrent.CompletableFuture) Scheduler(reactor.core.scheduler.Scheduler) AtomicReference(java.util.concurrent.atomic.AtomicReference) Function(java.util.function.Function) Supplier(java.util.function.Supplier) ExceptionHandler(io.micronaut.http.server.exceptions.ExceptionHandler) ExecutableMethod(io.micronaut.inject.ExecutableMethod) ArrayList(java.util.ArrayList) ErrorContext(io.micronaut.http.server.exceptions.response.ErrorContext) Nullable(io.micronaut.core.annotation.Nullable) ReturnType(io.micronaut.core.type.ReturnType) Schedulers(reactor.core.scheduler.Schedulers) Argument(io.micronaut.core.type.Argument) HttpRequest(io.micronaut.http.HttpRequest) ServerRequestContext(io.micronaut.http.context.ServerRequestContext) ErrorResponseProcessor(io.micronaut.http.server.exceptions.response.ErrorResponseProcessor) ExecutorService(java.util.concurrent.ExecutorService) HttpStatusException(io.micronaut.http.exceptions.HttpStatusException) HttpMethod(io.micronaut.http.HttpMethod) Logger(org.slf4j.Logger) Iterator(java.util.Iterator) ExecutorSelector(io.micronaut.scheduling.executor.ExecutorSelector) Publisher(org.reactivestreams.Publisher) Mono(reactor.core.publisher.Mono) IOException(java.io.IOException) BeanType(io.micronaut.inject.BeanType) ExecutionException(java.util.concurrent.ExecutionException) NonNull(io.micronaut.core.annotation.NonNull) Flux(reactor.core.publisher.Flux) ContinuationArgumentBinder(io.micronaut.http.bind.binders.ContinuationArgumentBinder) KotlinUtils.isKotlinCoroutineSuspended(io.micronaut.core.util.KotlinUtils.isKotlinCoroutineSuspended) BeanDefinition(io.micronaut.inject.BeanDefinition) Router(io.micronaut.web.router.Router) Collections(java.util.Collections) MutableHttpResponse(io.micronaut.http.MutableHttpResponse) Optional(java.util.Optional) Argument(io.micronaut.core.type.Argument) HttpStatus(io.micronaut.http.HttpStatus) MethodBasedRouteMatch(io.micronaut.web.router.MethodBasedRouteMatch) Mono(reactor.core.publisher.Mono) Flux(reactor.core.publisher.Flux) HttpResponse(io.micronaut.http.HttpResponse) MutableHttpResponse(io.micronaut.http.MutableHttpResponse) Publisher(org.reactivestreams.Publisher) MethodBasedRouteMatch(io.micronaut.web.router.MethodBasedRouteMatch) RouteMatch(io.micronaut.web.router.RouteMatch) Supplier(java.util.function.Supplier) ReferenceCounted(io.micronaut.core.io.buffer.ReferenceCounted)

Example 2 with MethodBasedRouteMatch

use of io.micronaut.web.router.MethodBasedRouteMatch in project micronaut-core by micronaut-projects.

the class RoutingInBoundHandler method channelRead0.

@Override
protected void channelRead0(ChannelHandlerContext ctx, io.micronaut.http.HttpRequest<?> request) {
    ctx.channel().config().setAutoRead(false);
    io.micronaut.http.HttpMethod httpMethod = request.getMethod();
    String requestPath = request.getUri().getPath();
    ServerRequestContext.set(request);
    if (LOG.isDebugEnabled()) {
        LOG.debug("Request {} {}", httpMethod, request.getUri());
    }
    NettyHttpRequest nettyHttpRequest = (NettyHttpRequest) request;
    io.netty.handler.codec.http.HttpRequest nativeRequest = nettyHttpRequest.getNativeRequest();
    // handle decoding failure
    DecoderResult decoderResult = nativeRequest.decoderResult();
    if (decoderResult.isFailure()) {
        Throwable cause = decoderResult.cause();
        HttpStatus status = cause instanceof TooLongFrameException ? HttpStatus.REQUEST_ENTITY_TOO_LARGE : HttpStatus.BAD_REQUEST;
        handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(status), status.getReason());
        return;
    }
    MediaType contentType = request.getContentType().orElse(null);
    final String requestMethodName = request.getMethodName();
    if (!multipartEnabled && contentType != null && contentType.equals(MediaType.MULTIPART_FORM_DATA_TYPE)) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Multipart uploads have been disabled via configuration. Rejected request for URI {}, method {}, and content type {}", request.getUri(), requestMethodName, contentType);
        }
        handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE), "Content Type [" + contentType + "] not allowed");
        return;
    }
    UriRouteMatch<Object, Object> routeMatch = null;
    List<UriRouteMatch<Object, Object>> uriRoutes = router.findAllClosest(request);
    if (uriRoutes.size() > 1) {
        throw new DuplicateRouteException(requestPath, uriRoutes);
    } else if (uriRoutes.size() == 1) {
        UriRouteMatch<Object, Object> establishedRoute = uriRoutes.get(0);
        request.setAttribute(HttpAttributes.ROUTE, establishedRoute.getRoute());
        request.setAttribute(HttpAttributes.ROUTE_MATCH, establishedRoute);
        request.setAttribute(HttpAttributes.ROUTE_INFO, establishedRoute);
        request.setAttribute(HttpAttributes.URI_TEMPLATE, establishedRoute.getRoute().getUriMatchTemplate().toString());
        routeMatch = establishedRoute;
    }
    RouteMatch<?> route;
    if (routeMatch == null) {
        // Check if there is a file for the route before returning route not found
        Optional<? extends FileCustomizableResponseType> optionalFile = matchFile(requestPath);
        if (optionalFile.isPresent()) {
            filterAndEncodeResponse(ctx, nettyHttpRequest, Flux.just(HttpResponse.ok(optionalFile.get())));
            return;
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("No matching route: {} {}", httpMethod, request.getUri());
        }
        // if there is no route present try to locate a route that matches a different HTTP method
        final List<UriRouteMatch<?, ?>> anyMatchingRoutes = router.findAny(request.getUri().toString(), request).collect(Collectors.toList());
        final Collection<MediaType> acceptedTypes = request.accept();
        final boolean hasAcceptHeader = CollectionUtils.isNotEmpty(acceptedTypes);
        Set<MediaType> acceptableContentTypes = contentType != null ? new HashSet<>(5) : null;
        Set<String> allowedMethods = new HashSet<>(5);
        Set<MediaType> produceableContentTypes = hasAcceptHeader ? new HashSet<>(5) : null;
        for (UriRouteMatch<?, ?> anyRoute : anyMatchingRoutes) {
            final String routeMethod = anyRoute.getRoute().getHttpMethodName();
            if (!requestMethodName.equals(routeMethod)) {
                allowedMethods.add(routeMethod);
            }
            if (contentType != null && !anyRoute.doesConsume(contentType)) {
                acceptableContentTypes.addAll(anyRoute.getRoute().getConsumes());
            }
            if (hasAcceptHeader && !anyRoute.doesProduce(acceptedTypes)) {
                produceableContentTypes.addAll(anyRoute.getRoute().getProduces());
            }
        }
        if (CollectionUtils.isNotEmpty(acceptableContentTypes)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Content type not allowed for URI {}, method {}, and content type {}", request.getUri(), requestMethodName, contentType);
            }
            handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE), "Content Type [" + contentType + "] not allowed. Allowed types: " + acceptableContentTypes);
            return;
        }
        if (CollectionUtils.isNotEmpty(produceableContentTypes)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Content type not allowed for URI {}, method {}, and content type {}", request.getUri(), requestMethodName, contentType);
            }
            handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.NOT_ACCEPTABLE), "Specified Accept Types " + acceptedTypes + " not supported. Supported types: " + produceableContentTypes);
            return;
        }
        if (!allowedMethods.isEmpty()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Method not allowed for URI {} and method {}", request.getUri(), requestMethodName);
            }
            handleStatusError(ctx, nettyHttpRequest, HttpResponse.notAllowedGeneric(allowedMethods), "Method [" + requestMethodName + "] not allowed for URI [" + request.getUri() + "]. Allowed methods: " + allowedMethods);
            return;
        } else {
            handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.NOT_FOUND), "Page Not Found");
        }
        return;
    } else {
        route = routeMatch;
    }
    if (LOG.isTraceEnabled()) {
        if (route instanceof MethodBasedRouteMatch) {
            LOG.trace("Matched route {} - {} to controller {}", requestMethodName, requestPath, route.getDeclaringType());
        } else {
            LOG.trace("Matched route {} - {}", requestMethodName, requestPath);
        }
    }
    // all ok proceed to try and execute the route
    if (route.isWebSocketRoute()) {
        handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.BAD_REQUEST), "Not a WebSocket request");
    } else {
        handleRouteMatch(route, nettyHttpRequest, ctx);
    }
}
Also used : TooLongFrameException(io.netty.handler.codec.TooLongFrameException) MethodBasedRouteMatch(io.micronaut.web.router.MethodBasedRouteMatch) DecoderResult(io.netty.handler.codec.DecoderResult) MediaType(io.micronaut.http.MediaType) HashSet(java.util.HashSet) HttpMethod(io.micronaut.http.HttpMethod) HttpStatus(io.micronaut.http.HttpStatus) DuplicateRouteException(io.micronaut.web.router.exceptions.DuplicateRouteException) UriRouteMatch(io.micronaut.web.router.UriRouteMatch) AbstractNettyHttpRequest(io.micronaut.http.netty.AbstractNettyHttpRequest)

Example 3 with MethodBasedRouteMatch

use of io.micronaut.web.router.MethodBasedRouteMatch in project check-ins by objectcomputing.

the class PermissionSecurityRule method check.

@Override
public SecurityRuleResult check(HttpRequest<?> request, @Nullable RouteMatch<?> routeMatch, @Nullable Map<String, Object> claims) {
    if (routeMatch instanceof MethodBasedRouteMatch) {
        MethodBasedRouteMatch methodBasedRouteMatch = (MethodBasedRouteMatch) routeMatch;
        if (methodBasedRouteMatch.hasAnnotation(RequiredPermission.class)) {
            AnnotationValue<RequiredPermission> requiredPermissionAnnotation = methodBasedRouteMatch.getAnnotation(RequiredPermission.class);
            Optional<String> optionalPermission = requiredPermissionAnnotation != null ? requiredPermissionAnnotation.stringValue("value") : Optional.empty();
            if (optionalPermission.isPresent() && claims != null && claims.containsKey("permissions")) {
                final String requiredPermission = optionalPermission.get();
                final String userPermissions = claims.get("permissions").toString();
                return userPermissions.contains(requiredPermission) ? SecurityRuleResult.ALLOWED : SecurityRuleResult.REJECTED;
            }
        }
    }
    return SecurityRuleResult.UNKNOWN;
}
Also used : RequiredPermission(com.objectcomputing.checkins.services.permissions.RequiredPermission) MethodBasedRouteMatch(io.micronaut.web.router.MethodBasedRouteMatch)

Aggregations

MethodBasedRouteMatch (io.micronaut.web.router.MethodBasedRouteMatch)3 HttpMethod (io.micronaut.http.HttpMethod)2 HttpStatus (io.micronaut.http.HttpStatus)2 MediaType (io.micronaut.http.MediaType)2 RequiredPermission (com.objectcomputing.checkins.services.permissions.RequiredPermission)1 BeanContext (io.micronaut.context.BeanContext)1 BeanCreationException (io.micronaut.context.exceptions.BeanCreationException)1 Internal (io.micronaut.core.annotation.Internal)1 NonNull (io.micronaut.core.annotation.NonNull)1 Nullable (io.micronaut.core.annotation.Nullable)1 Publishers (io.micronaut.core.async.publisher.Publishers)1 ReferenceCounted (io.micronaut.core.io.buffer.ReferenceCounted)1 Argument (io.micronaut.core.type.Argument)1 ReturnType (io.micronaut.core.type.ReturnType)1 KotlinUtils.isKotlinCoroutineSuspended (io.micronaut.core.util.KotlinUtils.isKotlinCoroutineSuspended)1 HttpAttributes (io.micronaut.http.HttpAttributes)1 HttpHeaders (io.micronaut.http.HttpHeaders)1 HttpRequest (io.micronaut.http.HttpRequest)1 HttpResponse (io.micronaut.http.HttpResponse)1 MutableHttpHeaders (io.micronaut.http.MutableHttpHeaders)1