springcloud服务后端500错误到了前端返回406

2022-03-28 20:08:14 浏览数 (1)

之前正常的一个接口突然由api网关返回了406的错误,看了下日志发现服务端报了500错误,为什么某个应用端报的500异常到了api网关却返回了406错误,百思不得其解,最终发现406并不是API网关返回的错误,而是具体的服务端(比如某个springcloud服务),找到这个原因是解决这个问题的根本!!

在讲述具体原因之前,先介绍下406这个错误, 406错误表明服务器端返回的数据客户端无法处理,客户端发送请求时会在http请求头里面加上一些必要的字段比如:

跟406状态有关的是下面这几个请求头属性:

  • Accept: 客户端接受的MIME类型,比如text/html等
  • Accept-Charset: 客户端接口的字符集,比如UTF-8
  • Accept-Encoding: 客户端支持的编码格式,比如gzip,deflate等
  • Accept-Language: 客户端接口的语言,比如Engligh、German等
  • Accept-Ranges: 分段传输时使用

AbstractMessageConverterMethodProcessor类writeWithMessageConverters方法

代码语言:javascript复制
	protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object outputValue;
		Class<?> valueType;
		Type declaredType;

		if (value instanceof CharSequence) {
			outputValue = value.toString();
			valueType = String.class;
			declaredType = String.class;
		}
		else {
			outputValue = value;
			valueType = getReturnValueType(outputValue, returnType);
			declaredType = getGenericType(returnType);
		}

		HttpServletRequest request = inputMessage.getServletRequest();
		List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
		List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

		if (outputValue != null && producibleMediaTypes.isEmpty()) {
			throw new IllegalArgumentException("No converter found for return value of type: "   valueType);
		}

		Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
		for (MediaType requestedType : requestedMediaTypes) {
			for (MediaType producibleType : producibleMediaTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
				}
			}
		}
		if (compatibleMediaTypes.isEmpty()) {
			if (outputValue != null) {
				throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
			}
			return;
		}

		List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
		MediaType.sortBySpecificityAndQuality(mediaTypes);

		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypes) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
				if (messageConverter instanceof GenericHttpMessageConverter) {
					if (((GenericHttpMessageConverter) messageConverter).canWrite(
							declaredType, valueType, selectedMediaType)) {
						outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
								(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
								inputMessage, outputMessage);
						if (outputValue != null) {
							addContentDispositionHeader(inputMessage, outputMessage);
							((GenericHttpMessageConverter) messageConverter).write(
									outputValue, declaredType, selectedMediaType, outputMessage);
							if (logger.isDebugEnabled()) {
								logger.debug("Written ["   outputValue   "] as ""   selectedMediaType  
										"" using ["   messageConverter   "]");
							}
						}
						return;
					}
				}
				else if (messageConverter.canWrite(valueType, selectedMediaType)) {
					outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
							inputMessage, outputMessage);
					if (outputValue != null) {
						addContentDispositionHeader(inputMessage, outputMessage);
						((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
						if (logger.isDebugEnabled()) {
							logger.debug("Written ["   outputValue   "] as ""   selectedMediaType  
									"" using ["   messageConverter   "]");
						}
					}
					return;
				}
			}
		}
         
          //走到这里说明没有找到具体的HttpMessageConverter把返回内容返回到客户端,下面抛出的
          //HttpMediaTypeNotAcceptableException就对应着406这个状态码

		if (outputValue != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

好了,上面分析了springcloud服务返回406的问题,下面给出出问题的地方:

代码语言:javascript复制
@RestController
 @RequestMapping(path="/xxxx", produces="text/html; charset=UTF-8")
public class xxxxController{
    @GetMapping("/crash")
     public String crash(HttpServletRequest request){
         Integer a = null;
         a.toString();
         return "hello";
     }
}

由于该类的RequestMapping声明了返回mime类型为mime,但是抛出异常时就没有找到对应可以处理text/html的HttpMessageConverter,所以返回到客户端成了406,而不是500,但是为什么不抛出异常时该controller没有任何问题呢?

问题就出在不出问题之前,比如注释掉//a.toString(),这时返回的是字符串类型,但是当抛出异常时返回的类型却是map类型的,再加上 produces="text/html; charset=UTF-8"导致没有具体的HttpMessageConverter,所以返回406错误!!

0 人点赞