0
点赞
收藏
分享

微信扫一扫

SpringBoot2.x对http请求accept的消息转换处理

以沫的窝 2021-09-25 阅读 50
  1. 起因
  2. 疑问
  3. 实战分析
    3.1 结论
    3.2 源码分析
    3.3 结论

1. 起因

相关代码:

    @GetMapping("/header/t1")
    public Order test1() {
        return new Order("1","2",new Date());
    }

在谷歌浏览器调用,如图1-1所示出现xml的响应报文:


但是在postman中调用,结果如图1-2所示:

出现以上问题的原因推测是在浏览器调用GET请求时,请求header中accept字段的问题。

2. 疑问

但是为什么在别的项目操作,也是使用浏览器进行操作,返回报文却是JSON格式的?

3. 实战分析

3.1 结论

项目中引用了依赖。

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.11.0</version>
            <scope>compile</scope>
        </dependency>

导致Spring MVC的默认会加载xml格式的消息转换器。

而浏览器请求的accept却是text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9格式。

由于浏览器要求服务端返回的xml格式的优先级高,且SpringBoot存在xml格式的消息转换器,于是浏览器最终返回的xml报文。

3.2 源码分析

    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        ...

        List<MediaType> mediaTypesToUse;

        MediaType contentType = outputMessage.getHeaders().getContentType();
        if (contentType != null && contentType.isConcrete()) {
            mediaTypesToUse = Collections.singletonList(contentType);
        }
        else {
            //获取请求对象
            HttpServletRequest request = inputMessage.getServletRequest();
            //获取请求中的accept字段。
            List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
            //获取SpringBoot支持的消息转换器的MediaType类型
            List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

            if (outputValue != null && producibleMediaTypes.isEmpty()) {
                throw new HttpMessageNotWritableException(
                        "No converter found for return value of type: " + valueType);
            }
            //若requestedMediaTypes存在*,则返回producibleMediaTypes,
            //若producibleMediaTypes存在*,则返回requestedMediaTypes
            mediaTypesToUse = new ArrayList<>();
            for (MediaType requestedType : requestedMediaTypes) {
                for (MediaType producibleType : producibleMediaTypes) {
                    if (requestedType.isCompatibleWith(producibleType)) {
                        mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                    }
                }
            }
            if (mediaTypesToUse.isEmpty()) {
                if (outputValue != null) {
                    throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
                }
                return;
            }
            //对mediaTypesToUse进行排序。
        //规则:accept的q越大,则优先级越高,若q相同,则精确匹配优先级高。
            MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
        }

        MediaType selectedMediaType = null;
        for (MediaType mediaType : mediaTypesToUse) {
            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) {
            //获取到优先级最高的MediaType。
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                        (GenericHttpMessageConverter<?>) converter : null);
                if (genericConverter != null ?
                        ((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :
                        converter.canWrite(valueType, selectedMediaType)) {
                    outputValue = getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                            inputMessage, outputMessage);
                    if (outputValue != null) {
                        addContentDispositionHeader(inputMessage, outputMessage);
                        if (genericConverter != null) {
                            genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
                        }
                        else {
                            ((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                                    "\" using [" + converter + "]");
                        }
                    }
                    return;
                }
            }
        }

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

3.3 结论

根据请求header的accept的类型,可要求服务器返回指定格式的消息,若请求的accept字段为null或者*/*,那么则按SpringBoot支持的消息转换器的优先级最高的输出消息(默认JSON格式)。

举报

相关推荐

0 条评论