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
相关产品推荐
相关产品推荐

