一次诡异405 METHOD_NOT_ALLOWED “Request method ‘POST‘ not supported“问题排查记录

news/2024/4/25 9:25:28/文章来源:https://blog.csdn.net/lonelymanontheway/article/details/131020800

概述

任何稍微只要有一点经验的开发者都知道HTTP 405,表示方法不支持。如,本来是定义为POST接口,前端使用GET请求,就会报错。

但是我还真遇上一次405 METHOD_NOT_ALLOWED "Request method 'POST' not supported"问题,并且是在开发7年后,看得我一脸懵逼。本文记录一下。

问题

背景:使用微服务开发模式后,几乎所有服务都是走gateway网关。

某次常规回归测试,发现生产环境的小程序不可用。请求会经过网关鉴权、路由,于是去查找网关服务的报错日志:

gateway-d | [reactor-http-epoll-4] |  | WARN  | com.aba.gateway.exception.JsonErrorWebExceptionHandler | getErrorAttributes | 38 | - 网关异常,path:/api/dx/user/login/mini-program,method:POST,message:405 METHOD_NOT_ALLOWED "Request method 'POST' not supported"
at org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:163)at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:344)at org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:292)at org.springframework.web.reactive.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:174)

报错网关服务代码片段,注意下面标记的报错行:

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;@Slf4j
public class JsonErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {public JsonErrorWebExceptionHandler(ErrorAttributes errorAttributes,ResourceProperties resourceProperties,ErrorProperties errorProperties,ApplicationContext applicationContext) {super(errorAttributes, resourceProperties, errorProperties, applicationContext);}@Overrideprotected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {// 这里其实可以根据异常类型进行定制化逻辑Throwable error = super.getError(request);Map<String, Object> errorAttributes = new HashMap<>(8);errorAttributes.put("message", error.getMessage());errorAttributes.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());errorAttributes.put("method", request.methodName());errorAttributes.put("path", request.path());// 报错行log.warn("网关异常,path:{},method:{},message:{}", request.path(), request.methodName(), error.getMessage());return errorAttributes;}@Overrideprotected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);}@Overrideprotected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {// 这里其实可以根据errorAttributes里面的属性定制HTTP响应码return HttpStatus.INTERNAL_SERVER_ERROR;}
}

配置类:

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;@Slf4j
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class CustomErrorAutoConfiguration {private final ServerProperties serverProperties;private final ApplicationContext applicationContext;private final ResourceProperties resourceProperties;private final List<ViewResolver> viewResolvers;private final ServerCodecConfigurer serverCodecConfigurer;public CustomErrorAutoConfiguration(ServerProperties serverProperties,ResourceProperties resourceProperties,ObjectProvider<ViewResolver> viewResolversProvider,ServerCodecConfigurer serverCodecConfigurer,ApplicationContext applicationContext) {this.serverProperties = serverProperties;this.applicationContext = applicationContext;this.resourceProperties = resourceProperties;this.viewResolvers = viewResolversProvider.orderedStream().collect(Collectors.toList());this.serverCodecConfigurer = serverCodecConfigurer;}@Bean@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)public DefaultErrorAttributes errorAttributes() {return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());}@Bean@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)@Order(-1)public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {JsonErrorWebExceptionHandler exceptionHandler = new JsonErrorWebExceptionHandler(errorAttributes,resourceProperties,this.serverProperties.getError(),applicationContext);exceptionHandler.setViewResolvers(this.viewResolvers);exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());return exceptionHandler;}
}

上面两个类的代码,看不出什么问题,至此仍旧是一脸懵逼。Google肯定是不可能给出满意的答案的,它只会让你去检查前端请求的方法是否正确。

前端和后端肯定都没有动过小程序登录接口/api/dx/user/login/mini-program相关代码,后端此接口在另一个服务:

/*** 微信小程序登录*/
@PostMapping({"/login/mini-program"})
@ApiOperation(value = "微信小程序登录", produces = "application/json", consumes = "application/json")
public AdaResponse<BaseLoginVo> miniProgram(@RequestBody UserSocialParam userSocialParam) {
}

排查

前端和后端都没有改过登录接口,那就只能从gateway网关服务下手。值得一提的是,排查思路很重要,如果把精力放在Google,或者排查服务提供者应用(而不是网关)上,就会浪费精力,增加生产问题影响面。

另外,出现生产问题时,如果可以回滚(不会影响生产环境的数据等),则应该尽快回滚之前的版本。看一下之前的版本是否还有此问题。没有问题,则大吉大利,可以对比两个版本之前的修改记录和区别。

至于回滚哪个服务,还是要根据业务架构设计,服务之间的依赖关系,及最近的发布变更等因素,来分析。

复现

排查问题时,如果能本地复现,比盲目Google强太多。好在本地可以复现问题:
在这里插入图片描述
可以复现问题,还是上面的代码片段,看不出问题,又要怎么解决问题呢?

对比

测试环境里,小程序登录接口正常!那就对比测试和生产环境的代码版本!!

打开Rancher,找到测试环境和生产环境分别使用的版本。

打开GitLab,对比两个版本有啥区别:
在这里插入图片描述
右侧的Target选择被对比的版本,也可以是Branch、Tag等。左侧的Source是相对于Target版本,新增的提交commit信息。看一下GitLab URL,其格式如:/compare/Target...Source

git commit提交信息不多,完全看不出哪次提交有问题。那就把本地代码恢复到测试环境的版本。执行命令:git checkout stg20201125v01
在这里插入图片描述
Debug模式启动应用,上面报错的方法,断点还是打开状态,gatew网关并没有走到此方法,也就是说本地代码回滚版本后,不再抛异常。

请求从gateway正常转发到enduser-dx服务!!打印日志如下:

2023-06-01 18:50:26 |  | 10.18.66.118 | 51651 | enduser-provider-dx | [http-nio-8199-exec-9] |  | INFO  | com.aba.enduser.dx.aop.LogAspect | log | 40 | - 请求开始, com.aba.enduser.dx.controller.LoginController#miniProgram() URI: /api/dx/user/login/mini-program, method: POST, URL: http://10.18.66.118:8199/api/dx/user/login/mini-program, params: null

我靠!!

结论:GitLab 对比得出的几次提交,/compare/Target...Source,某个地方的改动引发此问题。

逐个撤销回滚上面几次提交的改动,最终定位到gatew网关服务新增的如下代码引发问题:

@RestController
public class HealthController {@GetMapping({"/health/check"})public String heathCheck() {return "OK";}// 引发问题@GetMapping()public String index() {return "index";}
}

把上面新增的index这个@GetMapping删除解决问题

分析

@GetMapping

为啥要新增如下代码:

@GetMapping()
public String index() {return "index";
}

为了减少生产环境的日志打印:

gateway-d | [reactor-http-epoll-3] |  | ERROR | o.s.b.a.web.reactive.error.AbstractErrorWebExceptionHandler | error | 122 | - [2a2e51e5] 500 Server Error for HTTP GET "/"
org.springframework.web.server.ResponseStatusException: 404 NOT_FOUNDat org.springframework.web.reactive.resource.ResourceWebHandler.lambda$handle$0(ResourceWebHandler.java:325)at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)at reactor.core.publisher.Mono.subscribe(Mono.java:3848)at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75)at reactor.core.publisher.Operators.complete(Operators.java:131)

生产环境正常接受前端的请求,并做鉴权和路由转发等。但是时不时总会打印如上报错信息,也不知道生产环境的某个环节(节点)的服务是否正常,不知道是不是前段的请求响应失败。

为啥405

为啥加个表示/请求路径的@GetMapping,就100%出现HTTP 405问题?

不得而知。

我好菜逼啊。

参考

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_310838.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

考上大学,走进网安

出品&#xff5c;MS08067实验室&#xff08;www.ms08067.com&#xff09; 本文作者&#xff1a;北平靴 01 我和网络安全的相遇&#xff0c;说来很巧。2022年我考入吉林大学计算机学院&#xff0c;正巧我们的导助是学校ctf战队的成员&#xff0c;所以在刚入学的很清闲的日子里&a…

markdown标注

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

红黑树的 概念性质 和 详解实现(插入旋转等)

文章目录 概念满足的条件性质实现红黑树的定义红黑树节点插入操作情况一情况二情况三Insert()总代码 其余操作左右单旋RotateL 左单旋RotateR 右单旋prevCheck 红黑树性质检测isBalance 红黑树平衡判断InOrder 中序遍历 完整代码 概念 红黑树&#xff0c;是一种二叉搜索树&…

OceanBase并行执行中 DTL消息接收处理的逻辑

OceanBase 并行执行的消息处理框架是很有意思的&#xff0c;里面用到了不少面向对象编程思想&#xff0c;值得分析。 DTL 从宏观上看可以分为三大部分&#xff1a; DTL 消息发送DTL 消息缓存DTL 消息处理 本文介绍 DTL 消息处理。 核心组件 DTL 消息缓冲区 DTL 消息缓冲区…

小白如何快速入门?

入门 Web 安全、安卓安全、二进制安全、工控安全还是智能硬件安全等等&#xff0c;每个不同的领域要掌握的技能也不同。当然入门 Web 安全相对难度较低&#xff0c;也是很多人的首选。主要还是看自己的兴趣方向吧。 本文就以下几个问题来说明网络安全大致学习过程&#x1f447…

如何用Python写个网页爬取程序

如何用Python写个网页爬取程序 准备开发工具安装PythonPython安装pipPip安装爬取插件准备好网页地址代码实现 准备开发工具 额&#xff0c;作者用的是vscode。具体怎么安装自行百度哈&#xff0c;这个都不会建议就不要学爬取了。 不忍心藏着也&#xff0c;给你个方法吧 vsc…

Javascript的闭包,匿名函数,自动调用

这里写目录标题 验证文本框HTMLJavascript分析var引起的赋值错误最优的解决方案forEach(function(item){})最简单的方式&#xff0c;const/let 申明一个局部变量直接使用函数通过声明函数变量的方式定义函数申明匿名函数和自动调用函数的区别 在案例的基础上分析。 验证文本框 …

90后测试员:“入职阿里,这一次,我决定不跳槽了...”

所谓“舒适”生活 记得上一份工作是去年听从了朋友的意见&#xff0c;“你一定要找一份舒适的工作&#xff0c;这样你一天就有好多时间玩&#xff0c;好多时间干自己想干的事情&#xff0c;摸鱼真香&#xff01;” 在这份“教导”下&#xff0c;开始了我的找工作之旅&#xf…

内网穿透技术

文章目录 前言1. 安装JAVA2. MCSManager安装3.局域网访问MCSM4.创建我的世界服务器5.局域网联机测试6.安装cpolar内网穿透7. 配置公网访问地址8.远程联机测试9. 配置固定远程联机端口地址9.1 保留一个固定tcp地址9.2 配置固定公网TCP地址9.3 使用固定公网地址远程联机 转载自内…

【正点原子STM32连载】 第二十五章 TFT-LCD(MCU屏)实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十…

没有硬件资源?免费使用Colab搭建你自己的Stable Diffiusion在线模型!保姆级教程...

部署 Stable Diffusion 需要一定的硬件资源&#xff0c;具体取决于要处理的图像大小和处理速度等因素。一般来说&#xff0c;至少需要一台具有较高计算能力的服务器&#xff0c;而对 GPU 的高要求就限制了我们学习和使用SD来生成我们想要的图像。 GPU是深度学习开发的重要硬件条…

chatgpt赋能python:Python列表分割与排序:完美解决数据处理问题

Python列表分割与排序&#xff1a;完美解决数据处理问题 在Python的开发实践中&#xff0c;数据处理是一项必不可少的操作。列表&#xff08;list&#xff09;是Python语言中常用的数据类型之一&#xff0c;列表中的元素可以是任意类型。列表的分割和排序是Python中常见的操作…

数字孪生:数字世界与现实世界的交汇

数字孪生是一种崭新的技术,指将现实世界中的物理实体、系统或过程通过数字化技术在虚拟数字世界中建立起虚拟模型。数字孪生可以帮助人们以更小的成本地理解和预测现实世界中的物理实体、系统或过程的行为和性能,从而提高生产效率、降低成本、减少风险等。 如今数字孪生技术…

【CSS3系列】第三章 · CSS3新增边框和文本属性

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

【Python 文本分析】零基础也能轻松掌握的学习路线与参考资料

Python 常用的文本分析工具有很多&#xff0c;如 Natural Language Toolkit (NLTK)、TextBlob、spaCy、Jieba等。本文将分别介绍这些工具及其对应的学习路线、参考资料和优秀实践。 Natural Language Toolkit (NLTK) Natural Language Toolkit (NLTK) 是 Python 中文本分析研…

如何申请免费ChatGPT 2500刀初创金

近日OpenAI 推出了OpenAI for Startups项目&#xff0c;那么什么是Startups项目呢&#xff1a; 它是由全球知名的人工智能研究公司 OpenAI 推出的一个开放式的创业计划&#xff0c;旨在为初创公司提供一种新的激励机制和技术推广方式。 也就是说我们可以用自己账号申请&#x…

记一次Java生成SQL脚本文件换行格式为window/unix的笔记

今天在做一个SQL脚本文件生成需求&#xff0c;其中&#xff0c;需要设置&#xff1a; 文件编码为&#xff1a;UTF-8文件换行格式为&#xff1a;UNIX UTF-8这个好说&#xff0c;因为java代码可以指定文件编码&#xff0c;如&#xff1a; 但是Unix换行格式就很神奇了&#xff0…

快手三面全过了,却因为背调时leader手机号造假,导致offer作废了!

这是一个悲伤的故事&#xff1a; 快手本地三面全过了&#xff0c;但因为背调时leader手机号造假&#xff0c;导致offer作废了。 楼主感叹&#xff1a;大家背调填写信息时&#xff0c;一定要慎重再慎重&#xff0c;不要重复他的悲剧&#xff01; 网友愤慨&#xff0c;照这么说&a…

OSPF最优路径选择

路由比较 1、内部区域>区域间路由>NSSA1>Nssa2 2、如果只有Ex1、Ex2或者Nssa1、nNssa2开销类型。则Ex1>Ex2或者Nssa1>Nssa2 3、如果Ex1、Nssa1,Ex2和Nssa2,Ex1和Nssa1优于Ex2和Nssa2 4、如果外部开销加上内部开销,Ex1和Nssa1一样,则Ex1和Nssa1相同负载分担 5、如果外…

京东工作8年,肝到T8就剩这份心得了,已助朋友拿到10个Offer

在京东工作了8年&#xff0c;工作压力大&#xff0c;节奏快&#xff0c;但是从技术上确实得到了成长&#xff0c;尤其是当你维护与大促相关的系统的时候&#xff0c;熬到T7也费了不少心思&#xff0c;小编也是个爱学习的人&#xff0c;把这几年的工作经验整理成了一份完整的笔记…