Skip to content

Instantly share code, notes, and snippets.

@tbroyer
Last active May 31, 2022 07:19
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbroyer/509aeae1e0738bcdd28a to your computer and use it in GitHub Desktop.
Save tbroyer/509aeae1e0738bcdd28a to your computer and use it in GitHub Desktop.
Use OkHttp as JAX-RS Client implementation with RESTEasy
/*
* 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 <t.broyer@ltgt.net>
*/
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
}
}
@tbroyer
Copy link
Author

tbroyer commented Mar 29, 2015

To use it:

new ResteasyClientBuilder()
    .httpEngine(new OkHttpClientEngine(okHttpClient))
    .build()

@martyglaubitz
Copy link

martyglaubitz commented May 14, 2020

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

@tbroyer
Copy link
Author

tbroyer commented May 14, 2020

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