Last active
May 31, 2022 07:19
-
-
Save tbroyer/509aeae1e0738bcdd28a to your computer and use it in GitHub Desktop.
Use OkHttp as JAX-RS Client implementation with RESTEasy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2015 Thomas Broyer | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
import com.squareup.okhttp.Headers; | |
import com.squareup.okhttp.MediaType; | |
import com.squareup.okhttp.OkHttpClient; | |
import com.squareup.okhttp.Request; | |
import com.squareup.okhttp.RequestBody; | |
import com.squareup.okhttp.Response; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.util.List; | |
import java.util.Map; | |
import javax.annotation.Nullable; | |
import javax.inject.Inject; | |
import javax.net.ssl.HostnameVerifier; | |
import javax.net.ssl.SSLContext; | |
import javax.ws.rs.ProcessingException; | |
import javax.ws.rs.core.HttpHeaders; | |
import javax.ws.rs.core.MultivaluedMap; | |
import okio.Buffer; | |
import okio.BufferedSink; | |
import org.jboss.resteasy.client.jaxrs.ClientHttpEngine; | |
import org.jboss.resteasy.client.jaxrs.internal.ClientInvocation; | |
import org.jboss.resteasy.client.jaxrs.internal.ClientResponse; | |
import org.jboss.resteasy.util.CaseInsensitiveMap; | |
/** | |
* @author Thomas Broyer <[email protected]> | |
*/ | |
public class OkHttpClientEngine implements ClientHttpEngine { | |
private final OkHttpClient client; | |
private SSLContext sslContext; | |
@Inject | |
public OkHttpClientEngine(OkHttpClient client) { | |
this.client = client; | |
} | |
@Override | |
public SSLContext getSslContext() { | |
return sslContext; | |
} | |
public void setSslContext(SSLContext sslContext) { | |
this.sslContext = sslContext; | |
} | |
@Override | |
public HostnameVerifier getHostnameVerifier() { | |
return client.getHostnameVerifier(); | |
} | |
@Override | |
public ClientResponse invoke(ClientInvocation request) { | |
Request req = createRequest(request); | |
Response response; | |
try { | |
response = client.newCall(req).execute(); | |
} catch (IOException e) { | |
throw new ProcessingException("Unable to invoke request", e); | |
} | |
return createResponse(request, response); | |
} | |
private Request createRequest(ClientInvocation request) { | |
Request.Builder builder = new Request.Builder() | |
.method(request.getMethod(), createRequestBody(request)) | |
.url(request.getUri().toString()); | |
for (Map.Entry<String, List<String>> header : request.getHeaders().asMap().entrySet()) { | |
String headerName = header.getKey(); | |
for (String headerValue : header.getValue()) { | |
builder.addHeader(headerName, headerValue); | |
} | |
} | |
return builder.build(); | |
} | |
@Nullable | |
private RequestBody createRequestBody(final ClientInvocation request) { | |
if (request.getEntity() == null) { | |
return null; | |
} | |
// NOTE: this will invoke WriterInterceptors which can possibly change the request, | |
// so it must be done first, before reading any header. | |
final Buffer buffer = new Buffer(); | |
try { | |
request.writeRequestBody(buffer.outputStream()); | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
javax.ws.rs.core.MediaType mediaType = request.getHeaders().getMediaType(); | |
final MediaType contentType = (mediaType == null) ? null : MediaType.parse(mediaType.toString()); | |
return new RequestBody() { | |
@Override | |
public long contentLength() throws IOException { | |
return buffer.size(); | |
} | |
@Override | |
public MediaType contentType() { | |
return contentType; | |
} | |
@Override | |
public void writeTo(BufferedSink sink) throws IOException { | |
sink.write(buffer, buffer.size()); | |
} | |
}; | |
} | |
private ClientResponse createResponse(ClientInvocation request, final Response response) { | |
ClientResponse clientResponse = new ClientResponse(request.getClientConfiguration()) { | |
private InputStream stream; | |
@Override | |
protected InputStream getInputStream() { | |
if (stream == null) { | |
try { | |
stream = response.body().byteStream(); | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
return stream; | |
} | |
@Override | |
protected void setInputStream(InputStream is) { | |
stream = is; | |
} | |
@Override | |
protected void releaseConnection() throws IOException { | |
// Stream might have been entirely replaced, so we need to close it independently from response.body() | |
Throwable primaryExc = null; | |
try { | |
if (stream != null) { | |
stream.close(); | |
} | |
} catch (Throwable t) { | |
primaryExc = t; | |
throw t; | |
} finally { | |
if (primaryExc != null) { | |
try { | |
response.body().close(); | |
} catch (Throwable suppressedExc) { | |
primaryExc.addSuppressed(suppressedExc); | |
} | |
} else { | |
response.body().close(); | |
} | |
} | |
} | |
}; | |
clientResponse.setStatus(response.code()); | |
clientResponse.setHeaders(transformHeaders(response.headers())); | |
return clientResponse; | |
} | |
private MultivaluedMap<String, String> transformHeaders(Headers headers) { | |
MultivaluedMap<String, String> ret = new CaseInsensitiveMap<>(); | |
for (int i = 0, l = headers.size(); i < l; i++) { | |
ret.add(headers.name(i), headers.value(i)); | |
} | |
return ret; | |
} | |
@Override | |
public void close() { | |
// no-op | |
} | |
} |
You might want to use resteasy-client-okhttp or resteasy-client-okhttp3 from https://github.com/tbroyer/jaxrs-utils (https://search.maven.org/search?q=g:net.ltgt.jaxrs); that said, the code there is mostly the same as it is here (only difference AFAICT: tbroyer/jaxrs-utils@6a5ee07#diff-2f027da64d17f1a994b1a6e39f11c65e) so it would have the same bug; but it also has tests so maybe you could do a PR with a failing test (and/or even better, a fix for it 😉; but at a minimum please file an issue with more details about your failing request)
I must say I moved all my projects to using plain OkHttp so I can't promise anything wrt when I could fix the issue.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have found a serious bug in this implementation: when POSTing JSON data, the encoding is somehow broken - the remote Server won't accept it in some instances (the Mailchimp API in my case). I had to use the default Adapter... I have tested it by building a raw call with plain OkHttp and that worked, so it can't be OkHttps fault. Not wanting to say that this is a bad project though - i'd love to use OkHttp for all my Java Networking