为啥在 webflux 中使用 WebFilter corsFilter 时请求返回 404 状态?

Posted

技术标签:

【中文标题】为啥在 webflux 中使用 WebFilter corsFilter 时请求返回 404 状态?【英文标题】:Why requests are returned 404 status when using WebFilter corsFilter in webflux?为什么在 webflux 中使用 WebFilter corsFilter 时请求返回 404 状态? 【发布时间】:2019-11-23 15:33:52 【问题描述】:

我想使用功能端点在 Spring Webflux 中构建一个 REST api。对于 CORS,我使用 WebFilter corsFilter 方法来设置所需的标头。我确实看到调用了该方法(我看到了来自它的日志消息),并且我看到响应中的标题确实是我在 Webflux api 中设置的标题。但是,一旦我开始使用corsFilter,请求就会返回 404 状态(之前它们会返回 JSON)。我怀疑corsFilter 没有将请求传递给路由器功能。为什么会这样?

具体来说,我想知道这条线是否足以将 cors 配置与路由连接起来:

HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(RouterFunctions.toWebHandler(route))
        .applicationContext(ctx).build();

这是我的主要课程:

package com.mypackage;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RequestPredicates.method;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;

@SpringBootApplication
public class Server 
    private static final Logger log = LogManager.getLogger(Server.class);
    public static final String HOST = "localhost";
    public static final int PORT = 8080;

    public static void main(String[] args) throws Exception 
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(CorsConfiguration.class);
        Server server = new Server();
        server.startReactorServer(ctx);

        System.out.println("Press ENTER to exit.");
        System.in.read();
    

    public RouterFunction<ServerResponse> routingFunction() 
        PersonRepository repository = new DummyPersonRepository();
        PersonHandler handler = new PersonHandler(repository);

        return nest(path("/person"),
                nest(accept(APPLICATION_JSON),
                        route(GET("/id"), handler::getPerson)
                                .andRoute(method(HttpMethod.GET), handler::listPeople)
                ).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson));
    

    public void startReactorServer(AnnotationConfigApplicationContext ctx) 
        RouterFunction<ServerResponse> route = this.routingFunction().filter((request, next) -> 
            log.warn(request.path());

            if (request.path().contains("person")) 
                log.warn("calling next()");
                return next.handle(request);
             else 
                return ServerResponse.status(UNAUTHORIZED).build();
            
        );

        HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(RouterFunctions.toWebHandler(route))
        .applicationContext(ctx).build();

        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        HttpServer server = HttpServer.create(HOST, PORT);
        server.newHandler(adapter).block();
    

这是我的 CORS 配置类:

package com.mypackage;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Configuration
@EnableWebFlux
public class CorsConfiguration 

    private static final Logger log = LogManager.getLogger(CorsConfiguration.class);

    private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN, mode";
    private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
    private static final String ALLOWED_ORIGIN = "*";
    private static final String MAX_AGE = "3600";

    @Bean
    public WebFilter corsFilter() 
        log.warn("from CorsConfiguration!!!");
        return (ServerWebExchange ctx, WebFilterChain chain) -> 
            ServerHttpRequest request = ctx.getRequest();
            log.warn("after ServerHttpRequest");
            if (CorsUtils.isCorsRequest(request)) 
                log.warn("inside isCorsRequest");
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
                headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
                headers.add("Access-Control-Max-Age", MAX_AGE);
                headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
                if (request.getMethod() == HttpMethod.OPTIONS) 
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                
            
            return chain.filter(ctx);
        ;
    

【问题讨论】:

不同之处在于,在上一个问题中,我正在寻找一种在应用程序中实现 CORS 的方法。我找到了一种方法并用答案更新了该问题。现在 Cors 可以工作,但路由不能,这是这个问题的重点。 只是出于好奇,为什么要在 main 函数中手动启动服务器并手动设置所有路由,而不是使用注释和内置的 spring boot 功能 @ThomasAndolf 在 webflux 中有 2 种编程模型:函数式编程模型和基于注释的模型。我想使用基于函数的模型,但我发现这越来越具有挑战性。 您可能可以删除主类中的几乎所有内容。阅读我的回答。 【参考方案1】:

在定义端点时使用函数式方法,Spring Boot 的官方文档有一个非常简单的示例。

FooBarApplication.class 这是我们的主类。

@SpringBootApplication
public class FooBarApplication 

    public static void main(String[] args) 
        SpringApplication.run(FooBarApplication.class, args);
    


RoutingConfiguration.class(或任何你想要的名称)

@Configuration
public class RoutingConfiguration 

    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) 
        return route(GET("/user").and(accept(APPLICATION_JSON)), userHandler::getUser)
                .andRoute(GET("/user/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
                .andRoute(DELETE("/user").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
    



@Component
public class UserHandler 

    public Mono<ServerResponse> getUser(ServerRequest request) 
        // ...
    

    public Mono<ServerResponse> getUserCustomers(ServerRequest request) 
        // ...
    

    public Mono<ServerResponse> deleteUser(ServerRequest request) 
        // ...
    

任何带有@Configuration 注释的类都将在启动时运行并运行所有@Bean 注释方法。所以这将运行monoRouterFunction 并为我们设置所有路线。

示例取自 Spring Boot 官方文档Spring boot webflux 向下滚动一点。

编辑: 作为旁注,@EnableWebFlux 注释意味着您将禁用 webflux 的自动配置并手动设置 upp 配置。如果你刚开始,我不推荐这个(我知道这个名字很容易误导)你可以在这里阅读关于 webflux 自动配置的信息Spring WebFlux Auto-configuration

编辑2: WebFlux 有一个内置的 CorsFilter,您只需对其进行配置即可使用它。

@Bean
CorsWebFilter corsWebFilter() 
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.setAllowedOrigins(Arrays.asList("http://allowed-origin.com"));
    corsConfig.setMaxAge(8000L);
    corsConfig.addAllowedMethod("PUT");
    corsConfig.addAllowedHeader("Baeldung-Allowed");

    UrlBasedCorsConfigurationSource source =
  new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfig);

    return new CorsWebFilter(source);

示例取自Enabling CORS with a WebFilter

【讨论】:

但您的示例缺少我问题的关键部分:使用 CORS 我们每 3 天收到一次关于堆栈溢出的 cors 问题。 baeldung.com/spring-webflux-cors***.com/questions/46978794/…***.com/questions/52689374/… webflux 中已经有一个 CORS 过滤器,你只需要配置它。更新了我的答案。

以上是关于为啥在 webflux 中使用 WebFilter corsFilter 时请求返回 404 状态?的主要内容,如果未能解决你的问题,请参考以下文章

WebFilter bean 在安全的 Spring Boot Webflux 应用程序中调用了两次

Spring WebFlux添加WebFIlter以匹配特定路径

(WebFlux)004WebFilter踩坑记录

(WebFlux)004WebFilter踩坑记录

(WebFlux)004WebFilter踩坑记录

将 WebFilter Spring WebFlux 添加到路径