使用 JSON 字符串作为正文创建 JAX-RS 客户端发布请求

Posted

技术标签:

【中文标题】使用 JSON 字符串作为正文创建 JAX-RS 客户端发布请求【英文标题】:Create JAX-RS Client Post Request with JSON String as Body 【发布时间】:2018-09-11 01:13:47 【问题描述】:

我正在为供应商 REST 服务之一编写 REST 客户端。我使用 jersey 2.x 和 JSON-P,下面是我添加的依赖项。

<dependencies>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-processing</artifactId>
    <version>2.26</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.26</version>
</dependency>
</dependencies>

我成功地为 GET 请求编写代码并收到 JSON 输出。我将它保存到一个文件中,并使用 JSON-P 来解释和执行我自己的逻辑,没有任何问题。

但现在我需要编写一个 POST 请求。当我如下使用 CURL 时,我可以做到。并希望使用 Jeresey 2.x 和 JSON-P 实现相同的功能。

curl -v -H "Accept:application/json" -H "Content-Type:application/json" -u user:password -X POST --databinary @./InputParameters.json https://<IP>:<PORT>/Configuration/server

InputParameters.json contents

"ip": "10.197.70.16",
"partNumber": 202067,
"model": "IBM P7"

当我尝试以 JSON 格式 ("ip": "10.197.70.16", "partNumber": 202067, "model": "IBM P7") 将响应正文作为字符串传递时,但没有成功。所以尝试如下的 JsonObject 仍然没有工作。

JsonObject jsonObj = Json.createObjectBuilder()
            .add("ip", "10.197.70.16")
            .add("partNumber", 202067)
            .add("model", "IBM P7")
            .build();

response = invocationBuilder.post(Entity.json(jsonObj));

我知道核心 java,基于那次经验,我开始编写这个程序,并通过 GET 而不是 POST 获得了成功。我怀疑我在 POST 上做错了什么。

【问题讨论】:

嗨极客,请对我的问题提出一些建议并给我建议。 【参考方案1】:

添加以下依赖项后问题已解决。在这一点上,我不确定它的作用。

感谢 Swamy (TCS) 为解决此问题提供的支持。

<dependency>
 <groupId>com.owlike</groupId>
 <artifactId>genson</artifactId>
 <version>1.4</version>
</dependency>

【讨论】:

【参考方案2】:

让我们解开你正在做的事情。首先是这部分:

JsonObject jsonObj = Json.createObjectBuilder()
    .add("ip", "10.197.70.16")
    .add("partNumber", 202067)
    .add("model", "IBM P7")
    .build();

这会创建一个javax.json.JsonObject 实例。 JsonObject 是 JSON-P Java API 的一部分,正如它所说的:一个表示 JSON 对象的类。 JsonObject 实例包含javax.json.JsonValue 实例的层次结构,这些实例符合更具体的类型,如 JsonArray、JsonString、其他 JsonObjects 等。在这方面,它与the classes of the DOM API for representing XML documents 没有什么不同(希望 Oracle 将 API 文档保留在该 URL 上一段时间)。但幸运的是 JSON 比 XML 和 DOM 简单得多。

您的 jsonObj 实例将包含一个 JsonString 实例,其值“10.197.70.16”映射到名称“ip”,一个 JsonNumber 值 202067(可能表示为 BigDecimal)映射到名称“partNumber”等等。

接下来你的代码会执行这个:

Entity.json(jsonObj)

javax.ws.rs.client.Entity.json(something) 基本上表明您要创建一个实体,该实体将为 JAX-RS 客户端调用提供有效负载,内容类型为 application/json。换句话说,您为其创建的something 在发送到 API 时必须转换为 JSON 表示,API 应该期望 JSON 有效负载并知道如何处理它。请注意,Entity.json(...) 有一个泛型类型参数。方法签名是static &lt;T&gt; Entity&lt;t&gt; json(T entity)。因此,您正在使用有效负载实体 jsonObj 创建一个 Entity&lt;JsonObject&gt; 实例。

当这被移交给 javax.ws.rs.client.Invocation.Builder 实例的 post 方法时(post 方法实际上是在其父接口 SyncInvoker 中定义的),客户端实现开始工作。

response = invocationBuilder.post(Entity.json(jsonObj));

它获取提供的实体实例及其内容(我们的 jsonObj),检查实体的所需输出(这是 application/json),并寻找可以将给定类型的对象转换为该输出的提供程序。换句话说,必须找到可以给定 javax.json.JsonObject 的某个组件,并将它的表示形式作为 JSON 写入 OutputStream。处理此问题的组件可能是 javax.ws.rs.ext.MessageBodyWriter,声称它可以执行此转换并已注册到 JAX-RS 运行时。各种库都提供此类提供程序,您也可以编写自己的。这使得 JAX-RS 可扩展以处理各种场景、处理非标准输入和输出或让您调整其行为。当多个提供者能够处理给定的实体类型并产生所需的输出时,有一些规则可以确定哪个提供者承担这项工作。请注意,这可能取决于您的类路径中的内容。有一些方法可以明确地强制这样做。

客户端通过其配置,使用正确的 URL、查询参数、HTTP 方法、标头等将调用放在一起。通过将实体以所需格式写入 OutputStream 来创建有效负载。在您的示例中,这会导致服务器出现POST。调用完成后,您会收到一个javax.ws.rs.core.Response,您可以使用它来确定 HTTP 结果代码并检索响应负载(如果有)。 Response 的readEntity(Class&lt;T&gt; entityType) 方法的工作原理与将实体转换为有效负载的相反。它根据response.getMediaType()返回的值搜索可以解释响应流的MessageBodyReader,并可以从中创建Class entityType的实例。

那么在解释了所有这些之后,您的方法到底出了什么问题?好吧,问题在于 JAX-RS 运行时可用的默认实现可能没有专门用于 JsonObject 类型的输入和预期输出 application/json 的编写器。如果服务器需要 JSON,那么您应该能够提供 JsonObject 作为有效负载,这似乎很合乎逻辑。但是,如果 JAX-RS 实现找不到处理该类的东西,那么它充其量只能使用一些默认方法。在这种情况下,它可能会尝试将对象解释为 POJO 并以默认方式将其序列化为 JSON,这可能会导致如下异常:


    "valueMap": 
        "ip": 
            "value": "10.197.70.16"
        ,
        "partNumber": 
            "num": 202067,
            "integral": TRUE
        ,
        ...
    

这就是 JsonObject 实例的字面解释可能看起来的样子,具体取决于它使用的实现以及 JAX-RS 使用什么将其转换为 JSON 输出。当然也有可能对象根本无法序列化为 JSON,要么是找不到合适的 MessageBodyWriter,要么是在创建输出时出错。

第一个解决方案将是一个非常简单的解决方案。只需将 JsonObject 转换为 String 并简单地将其作为实体提供:

StringWriter stringWriter = new StringWriter();
try (JsonWriter jsonWriter = Json.createWriter(stringWriter);) 
    jsonWriter.writeObject(jsonObject);
 // some error handling would be needed here
String jsonPayload = stringWriter.toString();
response = invocationBuilder.post(Entity.json(jsonPayload));

看来你已经尝试过了。一个可能的问题是,当使用 String 作为输出和 application/json 作为所需的内容类型时,使用的 MessageBodyWriter 只需以合适的编码(可能是 UTF-8)输出 String 的字节。有些人可能不会那样做。您可以尝试Entity.entity(jsonPayload, MediaType.TEXT_PLAIN_TYPE.withCharset("UTF-8")),但随后服务器可能会拒绝呼叫。

其他可能的解决方案是

为带有 @javax.ws.rs.Produces(MediaType.APPLICATION_JSON) 注释的 String 对象编写您自己的 MessageBodyWriter。 更好的是,编写这样一个接受 JsonObject 实例的类。 为您的 JSON 结构创建 POJO 类,并让这些类用于从实例生成 JSON 或反序列化对实例的 JSON 响应。 找到一个扩展库,其中包含处理 javax.json 类的合适提供程序。

在您的项目中添加 com.owlike:genson 依赖项正是最后一个建议的应用。 Genson 库提供 Java 对象和 JSON 之间的双向转换以及数据绑定。它的部分代码库专用于 JAX-RS 提供程序,其中有一个类适合接受 JsonObject 作为输入。

【讨论】:

以上是关于使用 JSON 字符串作为正文创建 JAX-RS 客户端发布请求的主要内容,如果未能解决你的问题,请参考以下文章

如何使用JAX-RS和Jersey在Adobe AEM 6.2中发布json数据

我应该通过 HTTP 标头或将正文作为 JSON 传递到 REST Api 吗?

使用 dart 创建发布请求,将信息作为 json 数据通过请求正文传递

具有自定义标头和请求正文作为 JSON 字符串的跨域 jquery ajax 请求

如何在 JAX-RS 客户端中记录请求正文

JAX-RS @CookieParam值从请求变为另一个