All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.http.server.netty.NettyRequestLifecycle Maven / Gradle / Ivy

/*
 * Copyright 2017-2022 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.http.server.netty;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.execution.ExecutionFlow;
import io.micronaut.http.HttpMethod;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.server.RequestLifecycle;
import io.micronaut.http.server.netty.body.ByteBody;
import io.micronaut.http.server.netty.handler.PipeliningServerHandler;
import io.micronaut.http.server.types.files.FileCustomizableResponseType;
import io.micronaut.http.server.types.files.StreamedFile;
import io.micronaut.http.server.types.files.SystemFile;
import io.micronaut.web.router.RouteMatch;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.TooLongFrameException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Optional;

@Internal
final class NettyRequestLifecycle extends RequestLifecycle {
    private static final Logger LOG = LoggerFactory.getLogger(NettyRequestLifecycle.class);

    private final RoutingInBoundHandler rib;
    private final PipeliningServerHandler.OutboundAccess outboundAccess;

    /**
     * Should only be used where netty-specific stuff is needed, such as reading the body or
     * writing the response. Otherwise, use {@link #request()} which can be updated by filters
     */
    private final NettyHttpRequest nettyRequest;

    NettyRequestLifecycle(RoutingInBoundHandler rib, PipeliningServerHandler.OutboundAccess outboundAccess, NettyHttpRequest request) {
        super(rib.routeExecutor, request);
        this.rib = rib;
        this.outboundAccess = outboundAccess;
        this.nettyRequest = request;

        multipartEnabled(rib.multipartEnabled);
    }

    void handleNormal() {
        if (LOG.isDebugEnabled()) {
            HttpMethod httpMethod = request().getMethod();
            LOG.debug("Request {} {}", httpMethod, request().getUri());
        }

        ExecutionFlow> result;

        try {
            // handle decoding failure
            DecoderResult decoderResult = nettyRequest.getNativeRequest().decoderResult();
            if (decoderResult.isFailure()) {
                Throwable cause = decoderResult.cause();
                HttpStatus status = cause instanceof TooLongFrameException ? HttpStatus.REQUEST_ENTITY_TOO_LARGE : HttpStatus.BAD_REQUEST;
                result = onStatusError(
                    HttpResponse.status(status),
                    status.getReason()
                );
            } else {
                result = normalFlow();
            }
            result.onComplete((response, throwable) -> rib.writeResponse(outboundAccess, nettyRequest, response, throwable));
        } catch (Exception e) {
            handleException(e);
        }
    }

    @Nullable
    @Override
    protected FileCustomizableResponseType findFile() {
        Optional optionalUrl = rib.staticResourceResolver.resolve(request().getUri().getPath());
        if (optionalUrl.isPresent()) {
            try {
                URL url = optionalUrl.get();
                if (url.getProtocol().equals("file")) {
                    File file = Paths.get(url.toURI()).toFile();
                    if (file.exists() && !file.isDirectory() && file.canRead()) {
                        return new SystemFile(file);
                    }
                }
                return new StreamedFile(url);
            } catch (URISyntaxException e) {
                //no-op
            }
        }
        return null;
    }

    @Override
    protected ExecutionFlow> fulfillArguments(RouteMatch routeMatch) {
        // handle decoding failure
        DecoderResult decoderResult = nettyRequest.getNativeRequest().decoderResult();
        if (decoderResult.isFailure()) {
            return ExecutionFlow.error(decoderResult.cause());
        }
        return super.fulfillArguments(routeMatch).flatMap(this::waitForBody);
    }

    /**
     * If necessary (e.g. when there's a {@link Body} parameter), wait for the body to come in.
     * This method also sometimes fulfills more controller parameters with form data.
     */
    private ExecutionFlow> waitForBody(RouteMatch routeMatch) {
        // if there is a binder that needs form content, actually process the body now. We need to
        // do this after all binders are done because all createClaimant calls must be done before
        // the FormRouteCompleter can process data.
        if (nettyRequest.hasFormRouteCompleter()) {
            FormDataHttpContentProcessor processor = new FormDataHttpContentProcessor(nettyRequest, rib.serverConfiguration);
            ByteBody rootBody = nettyRequest.byteBody();
            FormRouteCompleter formRouteCompleter = nettyRequest.formRouteCompleter();
            try {
                rootBody.processMulti(processor).handleForm(formRouteCompleter);
                nettyRequest.addRouteWaitsFor(formRouteCompleter.getExecute());
            } catch (Throwable e) {
                return ExecutionFlow.error(e);
            }
        }
        return nettyRequest.getRouteWaitsFor().map(v -> routeMatch);
    }

    void handleException(Throwable cause) {
        onError(cause).onComplete((response, throwable) -> rib.writeResponse(outboundAccess, nettyRequest, response, throwable));
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy