• spring mvc加了@produces注解后报406


      问题背景:调用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<MediaType>(consumableMediaTypes));
            }
            else if (!producibleMediaTypes.isEmpty()) {
                throw new HttpMediaTypeNotAcceptableException(new ArrayList<MediaType>(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<ProduceMediaTypeExpression> result = new LinkedHashSet<ProduceMediaTypeExpression>(expressions);
            for (Iterator<ProduceMediaTypeExpression> 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<MediaType> acceptedMediaTypes = getAcceptedMediaTypes(request);
                for (MediaType acceptedMediaType : acceptedMediaTypes) {
                    if (getMediaType().isCompatibleWith(acceptedMediaType)) {
                        return true;
                    }
                }
                return false;
            }
    private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
            List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
            return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes;
        }

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

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

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

    public List<MediaType> 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<MediaType> 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没有了,中文也出来了。

      

  • 相关阅读:
    windows下的文件遍历(使用CFindFile)
    hdu 1728 搜索求最少的转向次数
    linux中vsftpd配置文件详解
    QLineEdit 自动完成(使用setCompleter,内含一个ListView)
    Qt 的内部进程通信机制
    qt 获取windows 的消息(通过MFC的DLL的透明窗体转发消息)good
    JS的类型比较与转换图
    使用C#开发ActiveX控件
    MapXtreme+Asp.net 动态轨迹
    MS SQL 日常维护管理常用脚本(二)
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/7729175.html
Copyright © 2020-2023  润新知