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

Google App Engine跨域调用异常:预检成功但未触发Servlet代码

跨域调用Google App Engine Servlet异常问题

问题描述

从其他域调用基于Jetty的Google App Engine @WebServlet时出现异常:日志显示预检(preflight)请求成功,但Servlet代码未执行(首行日志未打印)。直接浏览器调用正常,无授权校验,已尝试在app.yaml配置CORS但无效,未使用自定义域名。

具体现象

  • 浏览器直接调用正常,但本地localhost应用调用失败
  • 浏览器和本地调用的预检请求都出现在日志中,但只有浏览器调用会触发Servlet首行日志打印
  • 在app.yaml中配置CORS处理器无效
  • App Engine项目无防火墙限制
  • 未使用自定义域名

Angular调用代码

public getNewCatalogObject(id: string): Observable<any> {
    //const cataloginfoshttp = environment.apiURL + '/' + CatalogInfo + '?catalogname=' + id;
    const cataloginfoshttp = environment.apiURL+ '/' + CatalogInfo;
    const params = new HttpParams().set('catalogname',id);
    const headerdata = new HttpHeaders()
        .set('Access-Control-Allow-Origin', '*')
        .set('Access-Control-Allow-Methods','GET')
        .set('Access-Control-Allow-Headers','Content-Type');
    alert("OntologycatalogService without params: " + cataloginfoshttp);
    alert("OntologycatalogService params: " + params.toString());
            return this.httpClient.get(cataloginfoshttp,{ 'params': params, 'headers': headerdata })
}

WebServlet代码

@WebServlet(name = "CatalogInformationServlet", urlPatterns = { "/cataloginfo" })

public class CatalogInformationServlet extends HttpServlet {
    
    private static final Logger logger = Logger.getLogger(CatalogInformationServlet.class.getName());

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        logger.info("CatalogInformationServlet");
        // 剩余代码省略
    }
}

预检请求日志

{
  "protoPayload": {
    "@type": "type.googleapis.com/google.appengine.logging.v1.RequestLog",
    "appId": "e~blurock-database",
    "versionId": "2",
    "requestId": "660fa92c00ff0ba0838e379e6f0001657e626c75726f636b2d6461746162617365000132000100",
    "ip": "212.247.157.170",
    "startTime": "2024-04-05T07:33:00.777537Z",
    "endTime": "2024-04-05T07:33:00.786930Z",
    "latency": "0.009393s",
    "megaCycles": "188",
    "method": "OPTIONS",
    "resource": "/cataloginfo?catalogname=dataset:ThermodynamicContributions",
    "httpVersion": "HTTP/1.1",
    "status": 200,
    "responseSize": "152",
    "referrer": "http://localhost:4200/",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0",
    "urlMapEntry": "auto",
    "host": "blurock-database.ew.r.appspot.com",
    "instanceIndex": -1,
    "finished": true,
    "instanceId": "00a22404dc913688c9e38d2629cbacea000cd13536d1f9f80d60c2b1277a5df1d41f6afb14979e602dd05cb917c3b4e549604721c4b8ed59aefae040a2b7f9e71d67",
    "appEngineRelease": "1.9.71",
    "traceId": "c2a4b7970a7899c6b6b8ddc84334ef73",
    "first": true,
    "spanId": "10870129278550620427"
  },
  "insertId": "660fa92c000c0273cf4bdb2d",
  "httpRequest": {
    "status": 200
  },
  "resource": {
    "type": "gae_app",
    "labels": {
      "zone": "eu6",
      "version_id": "2",
      "module_id": "default",
      "project_id": "blurock-database"
    }
  },
  "timestamp": "2024-04-05T07:33:00.777537Z",
  "labels": {
    "clone_id": "00a22404dc913688c9e38d2629cbacea000cd13536d1f9f80d60c2b1277a5df1d41f6afb14979e602dd05cb917c3b4e549604721c4b8ed59aefae040a2b7f9e71d67"
  },
  "logName": "projects/blurock-database/logs/appengine.googleapis.com%2Frequest_log",
  "operation": {
    "id": "660fa92c00ff0ba0838e379e6f0001657e626c75726f636b2d6461746162617365000132000100",
    "producer": "appengine.googleapis.com/request_id",
    "first": true,
    "last": true
  },
  "trace": "projects/blurock-database/traces/c2a4b7970a7899c6b6b8ddc84334ef73",
  "receiveTimestamp": "2024-04-05T07:33:00.903523715Z",
  "spanId": "10870129278550620427"
}

已尝试的无效解决方案

1. app.yaml中配置CORS

- url: /cataloginfo
  login: optional
  secure: optional
  static_dir: /__static__
  http_headers:
    Access-Control-Allow-Origin: "*"
    Access-Control-Allow-Methods: "GET, POST, PUT, DELETE, OPTIONS"
    Access-Control-Allow-Headers: "Content-Type, Authorization"
    Access-Control-Max-Age: "3600"

2. 添加CORS过滤器

public class CorsFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    // Initialization code, if needed
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    // Allow cross-origin requests from any origin
    httpResponse.setHeader("Access-Control-Allow-Origin", "*");

    // Allow the OPTIONS method
    httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");

    // Allow specific headers
    httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type");

    // Continue the chain
    chain.doFilter(request, response);
}

@Override
public void destroy() {
    // Cleanup code, if needed
}
}

解决方案

1. 修正Angular请求的错误头设置

你在Angular请求里错误地添加了Access-Control-Allow-Origin这类响应头,这些头应该由服务器返回,而非客户端请求时携带。移除这些错误的请求头:

public getNewCatalogObject(id: string): Observable<any> {
    const cataloginfoshttp = environment.apiURL+ '/' + CatalogInfo;
    const params = new HttpParams().set('catalogname',id);
    // 仅保留必要请求头(无需时可完全移除headers配置)
    const headerdata = new HttpHeaders()
        .set('Content-Type', 'application/json');
    alert("OntologycatalogService without params: " + cataloginfoshttp);
    alert("OntologycatalogService params: " + params.toString());
    return this.httpClient.get(cataloginfoshttp,{ 'params': params, 'headers': headerdata })
}

2. 显式处理OPTIONS预检请求

App Engine自动OPTIONS响应可能未匹配Servlet配置,需在Servlet中显式处理预检请求,确保CORS头正确返回:

@WebServlet(name = "CatalogInformationServlet", urlPatterns = { "/cataloginfo" })
public class CatalogInformationServlet extends HttpServlet {
    
    private static final Logger logger = Logger.getLogger(CatalogInformationServlet.class.getName());

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        logger.info("CatalogInformationServlet");
        // 设置CORS响应头,确保跨域请求能接收响应
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        // 原有GET逻辑
    }

    @Override
    protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 处理预检请求,返回CORS头
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type");
        response.setHeader("Access-Control-Max-Age", "3600"); // 缓存预检结果
        response.setStatus(HttpServletResponse.SC_OK);
    }
}

3. 移除app.yaml中无效的静态目录配置

之前给Servlet路径配置的static_dir会导致请求被当作静态资源处理,而非转发到Servlet,删除该行:

- url: /cataloginfo
  login: optional
  secure: optional
  # 移除 static_dir: /__static__
  http_headers:
    Access-Control-Allow-Origin: "*"
    Access-Control-Allow-Methods: "GET, POST, PUT, DELETE, OPTIONS"
    Access-Control-Allow-Headers: "Content-Type, Authorization"
    Access-Control-Max-Age: "3600"

4. 确保CORS过滤器正确注册

使用过滤器时,需通过@WebFilter注解或web.xml注册,确保它在Servlet之前执行:

@WebFilter(urlPatterns = { "/cataloginfo" })
public class CorsFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;

        httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
        httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type");
        httpResponse.setHeader("Access-Control-Max-Age", "3600");

        // 预检请求直接返回200,不继续执行链
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:49:49