Spring Webflux 源码阅读之 accept包

Spring Webflux --accept

RequestedContentTypeResolver策略和实现来解析给定请求的请求内容类型。

typeResovle.jpg

接口

RequestedContentTypeResolver

为ServerWebExchange解决请求的媒体类型的策略。


public interface RequestedContentTypeResolver {

    /**
     * Resolve the given request to a list of requested media types. The returned
     * list is ordered by specificity first and by quality parameter second.
     * @param exchange the current exchange
     * @return the requested media types or an empty list
     * @throws NotAcceptableStatusException if the requested media type is invalid
     */
    List<MediaType> resolveMediaTypes(ServerWebExchange exchange);

}

将给定的请求解析为请求的媒体类型列表。返回的列表首先按特异性排序,然后按质量参数排序。

org.springframework.web.reactive.accept.FixedContentTypeResolver

解析器始终解析为媒体类型的固定列表。这可以用作“最后一行”策略,当客户端没有请求任何媒体类型时提供回调


public class FixedContentTypeResolver implements RequestedContentTypeResolver {

    private static final Log logger = LogFactory.getLog(FixedContentTypeResolver.class);


    private final List<MediaType> mediaTypes;


    /**
     * 具有单个默认MediaType的构造函数。.
     */
    public FixedContentTypeResolver(MediaType mediaType) {
        this(Collections.singletonList(mediaType));
    }

    /**
     * 使用默认的MediaType排序列表的构造函数返回用于支持各种内容类型的应用程序。
     如果目标不存在,并且不支持任何其他默认媒体类型,请考虑在最后附加MediaType.ALL。
     */
    public FixedContentTypeResolver(List<MediaType> mediaTypes) {
        this.mediaTypes = Collections.unmodifiableList(mediaTypes);
    }


    /**
     * 返回配置的媒体类型列表。
     */
    public List<MediaType> getContentTypes() {
        return this.mediaTypes;
    }


    @Override
    public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) {
        if (logger.isDebugEnabled()) {
            logger.debug("Requested media types: " + this.mediaTypes);
        }
        return this.mediaTypes;
    }

}

org.springframework.web.reactive.accept.HeaderContentTypeResolver

解析器查看请求的“Accept”标头。



public class HeaderContentTypeResolver implements RequestedContentTypeResolver {

    @Override
    public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException {
        try {
            List<MediaType> mediaTypes = exchange.getRequest().getHeaders().getAccept();
            MediaType.sortBySpecificityAndQuality(mediaTypes);
            return mediaTypes;
        }
        catch (InvalidMediaTypeException ex) {
            String value = exchange.getRequest().getHeaders().getFirst("Accept");
            throw new NotAcceptableStatusException(
                    "Could not parse 'Accept' header [" + value + "]: " + ex.getMessage());
        }
    }

}

org.springframework.web.reactive.accept.ParameterContentTypeResolver

解析查询参数并使用它查找匹配的MediaType的解析器。查找键可以注册,也可以作为一个后备的MediaTypeFactory执行查找。


public class ParameterContentTypeResolver implements RequestedContentTypeResolver {

    /** Primary lookup for media types by key (e.g. "json" -> "application/json") */
    private final Map<String, MediaType> mediaTypes = new ConcurrentHashMap<>(64);

    private String parameterName = "format";


    public ParameterContentTypeResolver(Map<String, MediaType> mediaTypes) {
        mediaTypes.forEach((key, value) -> this.mediaTypes.put(formatKey(key), value));
    }

    private static String formatKey(String key) {
        return key.toLowerCase(Locale.ENGLISH);
    }


    /**
     * Set the name of the parameter to use to determine requested media types.
     * <p>By default this is set to {@literal "format"}.
     */
    public void setParameterName(String parameterName) {
        Assert.notNull(parameterName, "'parameterName' is required");
        this.parameterName = parameterName;
    }

    public String getParameterName() {
        return this.parameterName;
    }


    @Override
    public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException {
        String key = exchange.getRequest().getQueryParams().getFirst(getParameterName());
        if (!StringUtils.hasText(key)) {
            return Collections.emptyList();
        }
        key = formatKey(key);
        MediaType match = this.mediaTypes.get(key);
        if (match == null) {
            match = MediaTypeFactory.getMediaType("filename." + key)
                    .orElseThrow(() -> {
                        List<MediaType> supported = new ArrayList<>(this.mediaTypes.values());
                        return new NotAcceptableStatusException(supported);
                    });
        }
        this.mediaTypes.putIfAbsent(key, match);
        return Collections.singletonList(match);
    }



org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder

Builder的复合RequestedContentTypeResolver代表其他解析器实现一个不同的策略来确定请求的内容类型,例如Accept标头,查询参数,或其他。
使用生成器方法在所需的顺序中添加解析器。对于给定的请求,他首先解析返回一个非空的列表,并且不包含只是MediaType。将使用。
默认情况下,如果没有显式解析器配置,构建器将添加HeaderContentTypeResolver。


public class RequestedContentTypeResolverBuilder {

    private final List<Supplier<RequestedContentTypeResolver>> candidates = new ArrayList<>();


    /**
     * Add a resolver to get the requested content type from a query parameter.
     * By default the query parameter name is {@code "format"}.
     */
    public ParameterResolverConfigurer parameterResolver() {
        ParameterResolverConfigurer parameterBuilder = new ParameterResolverConfigurer();
        this.candidates.add(parameterBuilder::createResolver);
        return parameterBuilder;
    }

    /**
     * Add resolver to get the requested content type from the
     * {@literal "Accept"} header.
     */
    public void headerResolver() {
        this.candidates.add(HeaderContentTypeResolver::new);
    }

    /**
     * Add resolver that returns a fixed set of media types.
     * @param mediaTypes the media types to use
     */
    public void fixedResolver(MediaType... mediaTypes) {
        this.candidates.add(() -> new FixedContentTypeResolver(Arrays.asList(mediaTypes)));
    }

    /**
     * Add a custom resolver.
     * @param resolver the resolver to add
     */
    public void resolver(RequestedContentTypeResolver resolver) {
        this.candidates.add(() -> resolver);
    }

    /**
     * Build a {@link RequestedContentTypeResolver} that delegates to the list
     * of resolvers configured through this builder.
     */
    public RequestedContentTypeResolver build() {

        List<RequestedContentTypeResolver> resolvers =
                this.candidates.isEmpty() ?
                        Collections.singletonList(new HeaderContentTypeResolver()) :
                        this.candidates.stream().map(Supplier::get).collect(Collectors.toList());

        return exchange -> {
            for (RequestedContentTypeResolver resolver : resolvers) {
                List<MediaType> type = resolver.resolveMediaTypes(exchange);
                if (type.isEmpty() || (type.size() == 1 && type.contains(MediaType.ALL))) {
                    continue;
                }
                return type;
            }
            return Collections.emptyList();
        };
    }


    /**
     * Helper to create and configure {@link ParameterContentTypeResolver}.
     */
    public static class ParameterResolverConfigurer {

        private final Map<String, MediaType> mediaTypes = new HashMap<>();

        @Nullable
        private String parameterName;

        /**
         * Configure a mapping between a lookup key (extracted from a query
         * parameter value) and a corresponding {@code MediaType}.
         * @param key the lookup key
         * @param mediaType the MediaType for that key
         */
        public ParameterResolverConfigurer mediaType(String key, MediaType mediaType) {
            this.mediaTypes.put(key, mediaType);
            return this;
        }

        /**
         * Map-based variant of {@link #mediaType(String, MediaType)}.
         * @param mediaTypes the mappings to copy
         */
        public ParameterResolverConfigurer mediaType(Map<String, MediaType> mediaTypes) {
            this.mediaTypes.putAll(mediaTypes);
            return this;
        }

        /**
         * Set the name of the parameter to use to determine requested media types.
         * <p>By default this is set to {@literal "format"}.
         */
        public ParameterResolverConfigurer parameterName(String parameterName) {
            this.parameterName = parameterName;
            return this;
        }

        /**
         * Private factory method to create the resolver.
         */
        private RequestedContentTypeResolver createResolver() {
            ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(this.mediaTypes);
            if (this.parameterName != null) {
                resolver.setParameterName(this.parameterName);
            }
            return resolver;
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容