How to use PATCH method in CXF

2019-06-27 23:55发布

问题:

I am trying to use PATCH method in my client using CXF implementation of JAX-RS. At first I defined the PATCH annotation as

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("PATCH")
public @interface PATCH {
}

Referencing what was written here : How to have a @PATCH annotation for JAX-RS?

Then I found out @PATCH was added into CXF 3.1.2, so I changed version in my maven's pom.xml and indeed there is public @interface PATCH inside of package org.apache.cxf.jaxrs.ext; and the code actually looks exactly as what I posted above.

However, when I try to use this annotation on my service definition as

@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public interface AbcService {

    @PATCH
    @Path("/abc/efg")
    public SomeDTO patchSomething(RequestObject request);
}

I end up with the java.net.ProtocolException: Invalid HTTP method: PATCH as was said in the queston link I posted above. They discuss some solution for this with Jersey, however what I can I do in CXF, so that I can use :

AbcService abcService = JAXRSClientFactory.create(myURI, AbcService.class, myProviders, true);
abcService.patchSomething(new RequestObject('something'));

So I have couple of questions:

  1. How can I make this work ? No I need to write custom CXF interceptor ?
  2. Why did they add the PATCH annotation into CXF if it doesn't work ?
  3. Some guys in the other topic said that the mentioned PATCH annotation definition works for them. How come ? Does it only make trouble on the client side, and if so why is it ?
  4. Why I can't find this annotation in CXF documentation ? I looked into org.apache.cxf.jaxrs.ext package at http://cxf.apache.org/javadoc/latest/ and I don't see any PATCH. Yet in the latest cxf 3.1.2 I really can find it in this package.

回答1:

It turns out it's cause because in JAVA7, HttpURLConnection doesn't support PATCH, the supported methods in that class are defined statically as

   private static final String[] methods = {
        "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
    };

However it is possible to send PATCH request in CXF, but the Conduit object must be of type AsyncHTTPConduit. To make CXF use AsyncHTTPConduit, you can programatically achieve it like this

AbcService service = JAXRSClientFactory.create(myURI, AbcService.class, myProviders, true);
WebClient.getConfig(service).getRequestContext().put("use.async.http.conduit", true);
service.patchEnvironmentParameters(patchRequest);

Or

WebClient client = WebClient.create("http://localhost:53261/v1-0/api/environment/parameters");
WebClient.getConfig(client).getRequestContext().put("use.async.http.conduit", true);
client.invoke("PATCH", "{}");

But beware !! In order to make this work, you have put this dependency into your project

<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-transports-http-hc</artifactId>
  <version>${cxf.version}</version>
</dependency>

Also make sure that you use the same version of cxf-rt-transports-http-hc and cxf.

But as you can see what I described doesn't solve the original issue, this way I just made 1 specific PATCH request. However in my project there are many PATCH services defined using interfaces like I showed originally

public interface AbcService {

    @PATCH
    @Path("/abc/efg")
    public SomeDTO patchSomething(RequestObject request);
}

So in order to use the AsyncHTTPConduit only on PATCH methods, I had to write custom CXF interceptor, about which you can learn more here http://cxf.apache.org/docs/interceptors.html The interceptor I wrote runs in PRE_LOGIC phase and it checks what kind of method is used and in case it PATCH, it defined the conduit property. Then in latter phases of service invocation, CXF uses this property to choose which Conduit implementation should be used, and so after

if ( message.get(Message.HTTP_REQUEST_METHOD).equals("PATCH") {
  message.put("use.async.http.conduit", true);
}

the AsyncHTTPConduit instance will be used with which the PATCH will work.



回答2:

Could you try to replace the use of @PATCH with @POST in your code to see if it works ? Your AbcService interface misses an @Path annotation at the type level (unless it is a subresource ?), so it might be worth trying with a standard HTTP verb first to make sure everything else is properly configured.