草庐IT

java - 由于 HAL+JSON 媒体类型不明确,无法使用 Spring HATEOAS 执行 HAL+JSON Level 3 RESTful API

coder 2023-05-12 原文

Level 3例如,RESTful API 的功能自定义媒体类型,如 application/vnd.service.entity.v1+json。在我的情况下,我使用 HAL在我的 JSON 中提供相关资源之间的链接。

我不清楚使用 HAL+JSON 的自定义媒体类型的正确格式。我目前拥有的看起来像 application/vnd.service.entity.v1.hal+json。我最初使用 application/vnd.service.entity.v1+hal+json,但 +hal 后缀未注册,因此违反了 section 4.2.8 of RFC6838 .

现在 Spring HATEOAS 支持开箱即用的 JSON 链接,但对于 HAL-JSON,您需要使用 @EnableHypermediaSupport(type=EnableHypermediaSupport.HypermediaType.HAL)。就我而言,因为我使用的是 Spring Boot,所以我将它附加到我的初始化程序类(即扩展 SpringBootServletInitializer 的类)。但是 Spring Boot 不会立即识别我的自定义媒体类型。因此,为此,我必须弄清楚如何让它知道它需要对 application/vnd.service.entity.v1.hal+json 形式的媒体类型使用 HAL 对象映射器>.

对于我的第一次尝试,我在 Spring Boot 初始化程序中添加了以下内容:

@Bean
public HttpMessageConverters customConverters() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(Arrays.asList(
            new MediaType("application", "json", Charset.defaultCharset()),
            new MediaType("application", "*+json", Charset.defaultCharset()),
            new MediaType("application", "hal+json"),
            new MediaType("application", "*hal+json")
    ));

    CurieProvider curieProvider = getCurieProvider(beanFactory);
    RelProvider relProvider = beanFactory.getBean(DELEGATING_REL_PROVIDER_BEAN_NAME, RelProvider.class);
    ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);

    halObjectMapper.registerModule(new Jackson2HalModule());
    halObjectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider));

    converter.setObjectMapper(halObjectMapper);

    return new HttpMessageConverters(converter);
}

这行得通,我正在以正确的 HAL 格式获取链接。然而,这是巧合。这是因为最终被报告为与 application/vnd.service.entity.v1.hal+json “兼容”的实际媒体类型是 *+json;它不能针对 application/*hal+json 识别它(请参阅后面的解释)。我不喜欢这个解决方案,因为它污染了现有的 JSON 转换器,担心 HAL。所以,我做了一个不同的解决方案:

@Configuration
public class ApplicationConfiguration {

    private static final String HAL_OBJECT_MAPPER_BEAN_NAME = "_halObjectMapper";

    @Autowired
    private BeanFactory beanFactory;

    @Bean
    public HttpMessageConverters customConverters() {
        return new HttpMessageConverters(new HalMappingJackson2HttpMessageConverter());
    }

    private class HalMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
        public HalMappingJackson2HttpMessageConverter() {
            setSupportedMediaTypes(Arrays.asList(
                new MediaType("application", "hal+json"),
                new MediaType("application", "*hal+json")
            ));

            ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
            setObjectMapper(halObjectMapper);
        }
    }
}

此解决方案不起作用;我最终在我的 JSON 中获得了不符合 HAL 的链接。这是因为 application/vnd.service.entity.v1.hal+jsonapplication/*hal+json 识别。发生这种情况的原因是检查媒体类型兼容性的 MimeType 仅将以 *+ 开头的媒体类型识别为子类型的有效通配符媒体类型(例如,application/*+json)。这就是第一个解决方案奏效的原因(巧合)。

所以这里有两个问题:

  • MimeType从不识别 application/vnd.service.entity.v1.hal+json 形式的供应商特定 HAL 媒体类型> 针对application/*hal+json.
  • MimeType 识别格式为 application/vnd.service.entity.v1+hal+json 的供应商特定 HAL 媒体类型针对 application/*+hal+json然而 根据 section 4.2.8 of RFC6838,这些是无效的 mimetypes .

似乎唯一的正确方法是如果 +hal 被识别为有效后缀,在这种情况下,上面的第二个选项就可以了。否则,任何其他类型的通配符媒体类型都无法专门识别供应商特定的 HAL 媒体类型。唯一的选择是用 HAL 问题覆盖现有的 JSON 消息转换器(参见第一个解决方案)。

目前的另一种解决方法是在为消息转换器创建支持的媒体类型列表时指定您正在使用的每个自定义媒体类型。那就是:

@Configuration
public class ApplicationConfiguration {

    private static final String HAL_OBJECT_MAPPER_BEAN_NAME = "_halObjectMapper";

    @Autowired
    private BeanFactory beanFactory;

    @Bean
    public HttpMessageConverters customConverters() {
        return new HttpMessageConverters(new HalMappingJackson2HttpMessageConverter());
    }

    private class HalMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
        public HalMappingJackson2HttpMessageConverter() {
            setSupportedMediaTypes(Arrays.asList(
                new MediaType("application", "hal+json"),
                new MediaType("application", "vnd.service.entity.v1.hal+json"),
                new MediaType("application", "vnd.service.another-entity.v1.hal+json"),
                new MediaType("application", "vnd.service.one-more-entity.v1.hal+json")                       
            ));

            ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
            setObjectMapper(halObjectMapper);
        }
    }
}

这样做的好处是不会污染现有的 JSON 转换器,但看起来不够优雅。有谁知道正确的解决方案?我是不是完全错了?

最佳答案

虽然这个问题有点老了,但我最近偶然发现了同样的问题,所以我想把我的 2 美分献给这个话题。

我认为这里的问题是对 HAL 对 JSON 的理解。正如您已经指出的 here ,所有 HAL 都是 JSON,但并非所有 JSON 都是 HAL。根据我的理解,两者之间的区别在于,HAL 为语义/结构定义了一些约定,例如告诉您在 _links 之类的属性后面您会找到一些链接,而 JSON 只定义了格式像 key: [value] (正如@zeroflagL 已经提到的)

这就是为什么媒体类型被称为application/hal+json的原因。它基本上说它是 JSON 格式的 HAL 样式/语义。这也是存在媒体类型application/hal+xml(source )的原因。

现在使用供应商特定的媒体类型,您可以定义自己的语义,因此您可以替换 application/hal+json 中的 hal 并且不要扩展它。

如果我的理解正确,您基本上想说您有一个自定义媒体类型,它使用 HAL 样式作为 JSON 格式。 (这样,客户端可以使用一些 HAL 库来轻松解析您的 JSON。)

所以,最后我认为您基本上必须决定是否要区分 JSON 和基于 HAL 的 JSON,以及您的 API 是否应该提供其中之一或两者。

如果您想同时提供两者,则必须定义两种不同的媒体类型 vnd.service.entity.v1.hal+jsonvnd.service.entity.v1+ json。对于 vnd.service.entity.v1.hal+json 媒体类型,您必须添加自定义 MappingJackson2HttpMessageConverter 使用 _halObjectMapper 返回基于 HAL 的 JSON,而默认支持 +json 媒体类型,以良好的旧 JSON 返回您的资源。

如果您总是想提供基于 HAL 的 JSON,则必须启用 HAL 作为默认 JSON-Media 类型(例如,通过添加支持 +json 的自定义 MappingJackson2HttpMessageConverter 媒体类型并使用前面提到的 _halObjectMapper),因此对 application/vnd.service.entity.v1+json 的每个请求都由返回 HAL 的转换器处理- 基于 JSON。

在我看来,我认为正确的方法是仅区分 JSON 和其他格式(如 XML),并且在您的媒体类型文档中您会说,您的 JSON 在某种程度上是受 HAL 启发的客户端可以使用 HAL 库来解析响应。


编辑:

要绕过您必须单独添加每个供应商特定媒体类型的问题,您可以覆盖 isCompatibleWith您要添加到自定义 MappingJackson2HttpMessageConverter

的媒体类型的方法
converter.setSupportedMediaTypes(Arrays.asList(
            new MediaType("application", "doesntmatter") {
                @Override
                public boolean isCompatibleWith(final MediaType other) {
                    if (other == null) {
                        return false;
                    }
                    else if (other.getSubtype().startsWith("vnd.") && other.getSubtype().endsWith("+json")) {
                        return true;
                    }
                    return super.isCompatibleWith(other);
                }
            }
));

关于java - 由于 HAL+JSON 媒体类型不明确,无法使用 Spring HATEOAS 执行 HAL+JSON Level 3 RESTful API,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27494779/

有关java - 由于 HAL+JSON 媒体类型不明确,无法使用 Spring HATEOAS 执行 HAL+JSON Level 3 RESTful API的更多相关文章

  1. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  3. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  4. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  5. ruby - Chef 执行非顺序配方 - 2

    我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul

  6. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  7. ruby - 为什么 Ruby 的 each 迭代器先执行? - 2

    我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试

  8. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  9. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  10. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

随机推荐