You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetty 10迁移至Jetty 12后嵌入式服务无法提供openapi.json

问题

我维护一个基于嵌入式Jetty+JAX-RS的Java应用,用Swagger v3注解生成接口文档。在Jetty 10时,通过io.swagger.v3.jaxrs2.integration.resources.OpenApiResource可以正常在/cva/api/openapi.json路径返回openapi.json文件。但迁移到Jetty 12.0.12后,Swagger UI页面能访问,但请求/cva/api/openapi.json返回404,导致Swagger UI提示"Failed to load API definition",无法正常工作。

服务器初始化代码如下:

// called like this new CvaServer("/cva", getPort()).run()
CvaServer(String contextPath, int port) throws Exception {
        
        server = new Server(port);

        // Set up resource config
        ResourceConfig resourceConfig = new ResourceConfig();
        resourceConfig.packages("io.swagger.v3.jaxrs2.integration.resources",
                "org.gel.cva.server.rest", // This is the package name for my APIs
                "org.gel.cva.server.exceptionmapper",
                "com.jersey.jaxb",
                "com.fasterxml.jackson.jaxrs.json");
        resourceConfig.register(MultiPartFeature.class);
        resourceConfig.register(RolesAllowedDynamicFeature.class);
        resourceConfig.register(AuthenticatingRequestFilter.class);
        resourceConfig.register(AuditLoggingFilter.class);
        resourceConfig.register(StringNormalisationConverterProvider.class);
        resourceConfig.register(EntityFilteringFeature.class);
        resourceConfig.register(AuthMethodFilter.class);
        EncodingFilter.enableFor(resourceConfig, GZipEncoder.class);

        // Set up ServletContextHandler
        ServletContextHandler servletContextHandler = new ServletContextHandler();
        servletContextHandler.setContextPath(contextPath);

        servletContextHandler.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/api/*");
        servletContextHandler.addEventListener(new CvaContextListener());

        // Set up ResourceHandler
        ResourceHandler resourceHandler = new ResourceHandler();
        resourceHandler.setWelcomeMode(ResourceService.WelcomeMode.REDIRECT);

        servletContextHandler.setContextPath("/");
        servletContextHandler.setWelcomeFiles(new String[] { "index.html" });

        servletContextHandler.setBaseResource(
                ResourceFactory.of(resourceHandler).newClassLoaderResource("static", true)
        );

        // Set up CrossOriginHandler        
        CrossOriginHandler crossOriginHandler = new CrossOriginHandler();

        crossOriginHandler.setAllowedOriginPatterns(Set.of("*"));
        crossOriginHandler.setAllowedMethods(Set.of("GET","POST","PUT","DELETE","OPTIONS","HEAD"));
        crossOriginHandler.setAllowCredentials(true);
        crossOriginHandler.setAllowedHeaders(Set.of("*"));

        // Set up OpenAPI models
        OpenAPI oas = new OpenAPI();
        oas.setServers(List.of(new io.swagger.v3.oas.models.servers.Server().url("/cva/api")));
        Info info = new Info()
                .title("title")
                .description("")
                .version(CvaConfiguration.CVA_VERSION);

        oas.info(info);

        SwaggerConfiguration oasConfig = new SwaggerConfiguration()
                .openAPI(oas)
                .prettyPrint(true)
                .resourcePackages(Set.of("org.gel.cva.server.rest"));

        // Set up JaxrsOpenApiContextBuilder
        try {
            new JaxrsOpenApiContextBuilder()
                    .application(resourceConfig)
                    .openApiConfiguration(oasConfig)
                    .buildContext(true)
                    .read();
        } catch (OpenApiConfigurationException e) {
            throw new RuntimeException(e.getMessage(), e);
        }

        crossOriginHandler.setHandler(servletContextHandler);
        servletContextHandler.addServlet(new ServletHolder(new DefaultServlet()), "/*");

        server.setHandler(servletContextHandler);
        
        // Set up Connectors
        ServerConnector serverConnector = new ServerConnector(
                server,
                new HttpConnectionFactory(),
                new HTTP2CServerConnectionFactory(new HttpConfiguration())
        );
        serverConnector.setPort(port);
        server.setConnectors(new ServerConnector[]{serverConnector});
    }

预期:openapi.json能正常访问,Swagger页面正常工作。

解决方案

问题根源

  1. ContextPath被覆盖:代码先将ServletContextHandler的contextPath设为传入的/cva,后续又重置为/,导致Jersey的/api/*映射到根路径,而非预期的/cva/api/*,所以/cva/api/openapi.json无法匹配路由。
  2. 跨域处理器未生效:创建CrossOriginHandler后未将其设为服务器根处理器,跨域配置实际未起作用。
  3. 路由冲突:静态资源与API接口共用同一个ServletContextHandler,且DefaultServlet拦截/*,覆盖了Jersey的API路由。

修复步骤

1. 拆分处理器,分离API与静态资源

创建两个独立的ServletContextHandler:一个处理API请求(contextPath设为/cva),另一个处理静态资源(contextPath设为/),通过HandlerList组合两者。

2. 保留API的ContextPath

确保API对应的ServletContextHandler的contextPath始终为传入的/cva,避免被后续代码覆盖。

3. 正确配置跨域处理器

将CrossOriginHandler作为服务器根处理器,内部包含组合后的HandlerList,确保所有请求都经过跨域校验。

4. 调整静态资源配置

静态资源的ServletContextHandler中正确设置ResourceHandler和DefaultServlet,避免与API路由冲突。

修复后的完整代码

// called like this new CvaServer("/cva", getPort()).run()
CvaServer(String contextPath, int port) throws Exception {
    server = new Server(port);

    // --------------------------
    // 1. 配置JAX-RS资源(API接口)
    // --------------------------
    ResourceConfig resourceConfig = new ResourceConfig();
    resourceConfig.packages("io.swagger.v3.jaxrs2.integration.resources",
            "org.gel.cva.server.rest",
            "org.gel.cva.server.exceptionmapper",
            "com.jersey.jaxb",
            "com.fasterxml.jackson.jaxrs.json");
    resourceConfig.register(MultiPartFeature.class);
    resourceConfig.register(RolesAllowedDynamicFeature.class);
    resourceConfig.register(AuthenticatingRequestFilter.class);
    resourceConfig.register(AuditLoggingFilter.class);
    resourceConfig.register(StringNormalisationConverterProvider.class);
    resourceConfig.register(EntityFilteringFeature.class);
    resourceConfig.register(AuthMethodFilter.class);
    EncodingFilter.enableFor(resourceConfig, GZipEncoder.class);

    // API对应的ServletContextHandler,contextPath保持为传入的/cva
    ServletContextHandler apiContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
    apiContext.setContextPath(contextPath);
    apiContext.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/api/*");
    apiContext.addEventListener(new CvaContextListener());

    // --------------------------
    // 2. 配置静态资源(Swagger UI等)
    // --------------------------
    ServletContextHandler staticContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
    staticContext.setContextPath("/");
    staticContext.setWelcomeFiles(new String[]{"index.html"});

    ResourceHandler resourceHandler = new ResourceHandler();
    resourceHandler.setWelcomeMode(ResourceService.WelcomeMode.REDIRECT);
    resourceHandler.setBaseResource(ResourceFactory.of(resourceHandler).newClassLoaderResource("static", true));
    staticContext.setHandler(resourceHandler);

    // 添加DefaultServlet处理静态资源默认请求
    staticContext.addServlet(new ServletHolder("default", new DefaultServlet()), "/*");

    // --------------------------
    // 3. 组合API和静态资源处理器
    // --------------------------
    HandlerList handlerList = new HandlerList();
    handlerList.addHandler(apiContext);
    handlerList.addHandler(staticContext);

    // --------------------------
    // 4. 配置跨域处理器
    // --------------------------
    CrossOriginHandler crossOriginHandler = new CrossOriginHandler();
    crossOriginHandler.setAllowedOriginPatterns(Set.of("*"));
    crossOriginHandler.setAllowedMethods(Set.of("GET","POST","PUT","DELETE","OPTIONS","HEAD"));
    crossOriginHandler.setAllowCredentials(true);
    crossOriginHandler.setAllowedHeaders(Set.of("*"));
    crossOriginHandler.setHandler(handlerList);

    // --------------------------
    // 5. 配置OpenAPI
    // --------------------------
    OpenAPI oas = new OpenAPI();
    oas.setServers(List.of(new io.swagger.v3.oas.models.servers.Server().url(contextPath + "/api")));
    Info info = new Info()
            .title("title")
            .description("")
            .version(CvaConfiguration.CVA_VERSION);
    oas.info(info);

    SwaggerConfiguration oasConfig = new SwaggerConfiguration()
            .openAPI(oas)
            .prettyPrint(true)
            .resourcePackages(Set.of("org.gel.cva.server.rest"));

    try {
        new JaxrsOpenApiContextBuilder()
                .application(resourceConfig)
                .openApiConfiguration(oasConfig)
                .buildContext(true)
                .read();
    } catch (OpenApiConfigurationException e) {
        throw new RuntimeException(e.getMessage(), e);
    }

    // --------------------------
    // 6. 设置服务器根处理器和连接器
    // --------------------------
    server.setHandler(crossOriginHandler);

    ServerConnector serverConnector = new ServerConnector(
            server,
            new HttpConnectionFactory(),
            new HTTP2CServerConnectionFactory(new HttpConfiguration())
    );
    serverConnector.setPort(port);
    server.setConnectors(new ServerConnector[]{serverConnector});
}

验证

启动服务器后,访问/cva/api/openapi.json即可正常获取OpenAPI文档,Swagger UI也能加载该文档并正常显示接口信息。

内容的提问来源于stack exchange,提问作者user28027519

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 23:32:02