博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring mvc加了@produces注解后报406
阅读量:5285 次
发布时间:2019-06-14

本文共 8905 字,大约阅读时间需要 29 分钟。

  问题背景:调用http的post接口返回一个String类型的字符串时中文出现乱码,定位出问题后在@RequestMapping里加produces注解produces = "application/json;charset=utf-8",再次请求http报406,代码发现spring抛出异常:org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation。

  问题代码附上:

/**     * 执行登陆行为     *     * @author wulinfeng     * @param request     * @param user     * @return     * @throws ServletException     * @throws IOException     */    @RequestMapping(value = "/loginAction.html", method = RequestMethod.POST, produces = "application/json;charset=utf-8")    public @ResponseBody String loginAction(HttpServletRequest request, HttpServletResponse response,        @RequestBody UserBean user)        throws ServletException, IOException    {        // 验证码校验        String validateCode = (String)request.getSession().getAttribute("randomString");        if (StringUtils.isEmpty(validateCode) || !validateCode.equals(user.getValidate().toUpperCase()))        {            return PropertiesConfigUtil.getProperty("verify_code_error");        }                // 用户名密码校验        String result = testPillingService.login(user.getUsername(), user.getPassword());                // 校验通过,创建token并放入session中;校验失败,返回错误描述        if ("success".equals(result))        {            String tokenId = UUID.randomUUID().toString();                        // 登陆成功后是使用cookie还是session来存放tokenId            if (IS_COOKIE.equals("1"))            {                Cookie cookie = new Cookie("tokenId", tokenId);                cookie.setMaxAge(3 * 24 * 60 * 60); // 3天过期                response.addCookie(cookie);            }            else            {                request.getSession(true).setAttribute("tokenId", tokenId);            }                        if (user.getUsername().toUpperCase().equals("ADMIN"))            {                return "register";            }        }        return result;    }

  问题定位:spring源码逆向跟踪,我们从异常抛出的地方回溯到问题发生的地方。

  异常所在地:RequestMappingInfoHandlerMapping类235行,标红;producibleMediaTypes实例化处,218行,标红

     if (patternAndMethodMatches.isEmpty()) {            consumableMediaTypes = getConsumableMediaTypes(request, patternMatches);            producibleMediaTypes = getProducibleMediaTypes(request, patternMatches);            paramConditions = getRequestParams(request, patternMatches);        }        else {            consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches);            producibleMediaTypes = getProducibleMediaTypes(request, patternAndMethodMatches);            paramConditions = getRequestParams(request, patternAndMethodMatches);        }        if (!consumableMediaTypes.isEmpty()) {            MediaType contentType = null;            if (StringUtils.hasLength(request.getContentType())) {                try {                    contentType = MediaType.parseMediaType(request.getContentType());                }                catch (InvalidMediaTypeException ex) {                    throw new HttpMediaTypeNotSupportedException(ex.getMessage());                }            }            throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList
(consumableMediaTypes)); } else if (!producibleMediaTypes.isEmpty()) { throw new HttpMediaTypeNotAcceptableException(new ArrayList
(producibleMediaTypes)); } else if (!CollectionUtils.isEmpty(paramConditions)) { throw new UnsatisfiedServletRequestParameterException(paramConditions, request.getParameterMap()); } else { return null; }

  判断请求是否能匹配注解produces配置的Content-Type(即“application/json;charset=utf-8”):类258行

 private Set<MediaType> getProducibleMediaTypes(HttpServletRequest request, Set<RequestMappingInfo> partialMatches) {
    Set<MediaType> result = new HashSet<MediaType>();
    for (RequestMappingInfo partialMatch : partialMatches) {
       if (partialMatch.getProducesCondition().getMatchingCondition(request) == null) {
        result.addAll(partialMatch.getProducesCondition().getProducibleMediaTypes());
       }
    }
    return result;
 }

  匹配逻辑:ProducesRequestCondition类185行

public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {        if (isEmpty()) {            return this;        }        Set
result = new LinkedHashSet
(expressions); for (Iterator
iterator = result.iterator(); iterator.hasNext();) { ProduceMediaTypeExpression expression = iterator.next(); if (!expression.match(request)) { iterator.remove(); } } return (result.isEmpty()) ? null : new ProducesRequestCondition(result, this.contentNegotiationManager); }

  匹配请求的Content-Type:AbstractMediaTypeExpression类75行

public final boolean match(HttpServletRequest request) {        try {            boolean match = matchMediaType(request);            return (!this.isNegated ? match : !match);        }        catch (HttpMediaTypeException ex) {            return false;        }    }

  获取请求匹配的Content-Type:ProducesRequestCondition类300行、236行

protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {            List
acceptedMediaTypes = getAcceptedMediaTypes(request); for (MediaType acceptedMediaType : acceptedMediaTypes) { if (getMediaType().isCompatibleWith(acceptedMediaType)) { return true; } } return false; }
private List
getAcceptedMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException { List
mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request)); return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes; }

  解析请求Content-Type:ContentNegotiationManager类109行

public List
resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { for (ContentNegotiationStrategy strategy : this.strategies) { List
mediaTypes = strategy.resolveMediaTypes(request); if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) { continue; } return mediaTypes; } return Collections.emptyList(); }

  好了,到底了,最终解析Content-Type的地方在这里,AbstractMappingContentNegotiationStrategy类

public List
resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest)); } /** * An alternative to {
@link #resolveMediaTypes(NativeWebRequest)} that accepts * an already extracted key. * @since 3.2.16 */ public List
resolveMediaTypeKey(NativeWebRequest webRequest, String key) throws HttpMediaTypeNotAcceptableException { if (StringUtils.hasText(key)) { MediaType mediaType = lookupMediaType(key); if (mediaType != null) { handleMatch(key, mediaType); return Collections.singletonList(mediaType); } mediaType = handleNoMatch(webRequest, key); if (mediaType != null) { addMapping(key, mediaType); return Collections.singletonList(mediaType); } } return Collections.emptyList(); }

  怎么取到html这个后缀的呢?AbstractMappingContentNegotiationStrategy的子类PathExtensionContentNegotiationStrategy类114行

protected String getMediaTypeKey(NativeWebRequest webRequest) {        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);        if (request == null) {            logger.warn("An HttpServletRequest is required to determine the media type key");            return null;        }        String path = this.urlPathHelper.getLookupPathForRequest(request);        String filename = WebUtils.extractFullFilenameFromUrlPath(path);        String extension = StringUtils.getFilenameExtension(filename);        return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null;    }

 

  回到最顶端,我的@RequestMapping匹配的url是“/loginAction.html”,getMediaTypeKey方法就是在取url后缀,拿到html后作为上面resolveMediaTypeKey方法的里key,然后去调用lookupMediaType方法

protected MediaType lookupMediaType(String extension) {        return this.mediaTypes.get(extension.toLowerCase(Locale.ENGLISH));    }

  而这里mediaTypes对象是什么东西呢?它是启动时加载的,我这里取出来是这样子的:{xml=application/xml, html=text/html, json=application/json},所以最终解析出来我的请求竟然是text/html,而实际上我从ajax调用http时是设置了Content-Type为application/json;charset=UTF-8的。

  看到这里,问题已经出来了,url以html结尾,导致请求头设置的Content-Type被覆盖了。那么解决方式相对就简单了,不以html结尾即可,我这里是直接把/loginAction.html改为/loginAction,重新试一下,406没有了,中文也出来了。

  

转载于:https://www.cnblogs.com/wuxun1997/p/7729175.html

你可能感兴趣的文章
JS DOM对象
查看>>
OGR – Merging Multiple SHP files
查看>>
创业公司该不该被收购?(转)
查看>>
sqlserver 行转列、列转行[转]
查看>>
【IScroll深入学习】解决IScroll疑难杂症
查看>>
python 数据类型
查看>>
108-PHP类成员protected和private成员属性不能被查看数值
查看>>
css控制height充满浏览器视口
查看>>
Linux 系统目录结构
查看>>
python学习之 - XML
查看>>
Laravel学习笔记(三)数据库 数据库迁移
查看>>
ORACLE查看并修改最大连接数
查看>>
Python--GIL 详解
查看>>
Oracle数据导入Mysql中
查看>>
MongoDB学习笔记——聚合操作之group,distinct,count
查看>>
大道至简读后感(第四章)
查看>>
IDA IDC Tutorials: Additional Auto-Commenting
查看>>
k8s-存储卷1-十二
查看>>
在Android中Intent的概念及应用(二)——Intent过滤器相关选项
查看>>
第十六章 多态性(一)
查看>>