如何在Jersey客户端中发送封闭数据的DELETE请求?

10

我在Jersey 2.x中有以下的服务器端代码:

@Path("/store/remove/from/group")
@DELETE
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_PLAIN)
public Response removeStoresFromGroup(@FormParam("storeName") List<String> storeNames, @FormParam("groupName") String groupName) {
    //......
}

在客户端,我想使用Jersey 2.x客户端向上述Web服务发送删除请求。然而,从Jersey client API的文档中,我没有找到如何在DELETE请求中包含以下数据的方法:
WebTarget webTarget = client.target("/store/remove/from/group");
MultivaluedMap<String, String> formData = new MultivaluedHashMap<String, String>();
List<String> storeName = new ArrayList<String>();
storeName.add("Store1");
storeName.add("Store2");
storeName.add("Store3");

formData.addAll("storeName", storeName);
formData.add("groupName", "Group A");

Response response = webTarget.request().accept(MediaType.TEXT_PLAIN).delete();   //The delete() method doesn't take any entity body in the request.

从Jersey客户端API来看,SyncInvoker类不支持将实体主体作为参数的delete方法。因此,我只能像下面这样使用POST或PUT将数据发送到服务器(但不能用于DELETE):

Response response = webTarget.request().accept(MediaType.TEXT_PLAIN).post(Entity.form(formData)); 

但是我想使用DELETE请求,因为该请求正在删除某些资源。如何通过Jersey客户端发送带有实体数据的DELETE请求?


1
通常情况下,你不需要在 DELETE 请求中发送实体,因为“DELETE 方法请求源服务器删除由 Request-URI 标识的资源”。你的方法参考听起来更像是更新引用而不是删除资源。 - lefloh
有时候,如果我想删除满足某些条件的一些数据,我需要在 DELETE 请求的实体主体中传递一些参数。 - tonga
5个回答

24
根据 Jersey 2.18 版本中的代码,JerseyInvocation 类使用预定义的 HashMap 来验证 HTTP 方法及其实体,如下所示:
map.put("DELETE", EntityPresence.MUST_BE_NULL);
map.put("GET", EntityPresence.MUST_BE_NULL);
...

这就是为什么我们会收到错误信息“Entity must be null for http method DELETE”。

同时,请注意,它还提供了一个Jersey客户端配置属性ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION,用于确定是否停止REST执行,因此我们可以使用此属性来抑制验证,以便继续发送带实体的DELETE请求。例如:

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
    Client client = ClientBuilder.newClient(config);
    ...

我有类似的需求,尽管在我的情况下,我实际上没有要发送的正文,但我确实需要强制使用DELETE发送“Content-Length: 0”。尝试使用Jersey Client 2.5.2进行此建议时,我会收到ProtocolException:java.net.ProtocolException:HTTP方法DELETE不支持输出,在sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1081)和org.glassfish.jersey.client.HttpUrlConnector $ 3.getOutputStream(HttpUrlConnector.java:267)。 - Kevin Hooke
2
我试图使用ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION设置为true来覆盖org.glassfish.jersey.test.JerseyTest#configureClient,但结果失败了。现在不再有异常,而是收到了400的服务响应。 - Bender
我正在将一个服务器应用程序从Play框架转换为纯JavaEE,并且有一个接受JSON body的DELETE资源。我不能破坏旧的API,所以这对我很有帮助! - matsa

9

需要注意的是:如果您使用resteasy实现的JAX RS客户端API,您可以使用build().invoke()

client.target("$baseUrl$restEndPoint/$entityId")
                .request("application/json")
                .build("DELETE", Entity.entity(entity, MediaType.APPLICATION_JSON))
                .invoke()

但是它不能与jersey一起使用。


JerseyInvocation 中有 validateHttpMethodAndEntity 函数,用于验证方法和实体的存在。如果不存在,则会抛出 IllegalStateException 异常。 - pshirishreddy
@pshirishreddy,我使用resteasy实现的JAX RS客户端API。它很好用。 - Alexandr
同意,但是OP的问题与Jersey客户端有关 :) - pshirishreddy
Resteasy是Jersey客户端。 - legend

5
一个带有实体主体的 DELETE 并不是严格禁止的,但这在某些框架/服务器中很少见,可能被忽略。需要实体主体可能表明 DELETE 没有按照预期使用。
例如:如果 GET /customers/4711 返回一个客户,并且您发送了一个 DELETE /customers/4711,则对此资源的下一个 GET 应该返回 404。您删除了一个由像 规范中定义的 URL 标识的资源。
您的 URL /store/remove/from/group 似乎没有标识资源。使用像 /store/4711/groups/4711 这样的标识符并在其上发送 DELETE 不适合您的需求,因为您想要“从组中移除商店”,而不是删除商店或组。
假设您有一个组资源。
{
  "id" : 4711,
  "stores" : [123, 456, 789]
}

并且你想要像这样的结果

{
  "id" : 4711,
  "stores" : [123, 789]
}

你不是在删除任何东西。你正在修改资源,因此PUTPOSTPATCH是适当的方法。JSON-Patch是描述这些更改的良好格式。请求应该像这样:

PATCH /groups/4711 HTTP/1.1
Content-Type: application/json-patch

[
  {
    "op" : "remove"
    "path" : "stores/1"
  }
]

感谢您的建议。看起来Jersey框架目前不支持PATCH方法。那么像Tomcat或Jetty这样的Java Web服务器是否支持HTTP PATCH方法呢?在这种情况下,我可以使用PUT请求来修改资源。 - tonga
很抱歉,我不清楚哪个容器或框架支持PATCHPUT是一个好主意,但缺点是您需要发送整个修改后的资源 - lefloh
谢谢。为了稳定起见,我现在只会使用http PUT方法,希望http PATCH方法能够在后续的Jersey版本中正式包含。 - tonga

3
你可以使用 webTarget.request().accept(MediaType.TEXT_PLAIN).method("DELETE",yourEntity) 来调用一个包含实体的 DELETE 请求。

5
当我使用这种方法时,它报错:“实体必须为空,以便使用DELETE方法”。为什么会这样? - tonga
嗯,我真的没想到会这样。如果你看一下[1]第9.6节,你会发现DELETE应该是基于URI的。虽然它没有说不能使用主体.. [1]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html - John Ament
我不明白为什么根据文档,DELETE请求不能包含实体主体。但是Javadoc(http://docs.oracle.com/javaee/7/api/javax/ws/rs/client/SyncInvoker.html#method)也没有说明不能使用DELETE作为方法名。 - tonga

0

我也使用了RESTeasy Jersey客户端,并为我的项目工作:

Response response = client.target(yourUrlToCall).request(MediaType.APPLICATION_JSON) .build("DELETE", Entity.entity(yourJsonDTO, MediaType.APPLICATION_JSON)).invoke();

它非常有用,可以解决一些复杂的场景,这些情况无法通过get()、delete()等正常使用方式来实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接