Using OkHttp client via OKClient on Google App Eng

2020-08-15 04:41发布

问题:

I am trying to use OKHTTP (version 2.4.0) along retrofit (1.9.0) on google app engine (1.9.22).

Here is the how i use it:

    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.setConnectTimeout(COMPOSER_MODULE_CONNECTION_TIMEOUT,  TimeUnit.SECONDS);
    okHttpClient.setReadTimeout(COMPOSER_MODULE_SOCKET_TIMEOUT, TimeUnit.SECONDS);

    RestAdapter restAdapter = new RestAdapter.Builder()
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .setConverter(new JacksonConverter())
                .setEndpoint(ENDPOINT_PATH)
                .setClient(new OkClient(okHttpClient))
                .build();

This throws the following error:

java.lang.NoClassDefFoundError: java.net.ProxySelector is a restricted class. Please see the Google App Engine developer's guide for more details.
at com.google.apphosting.runtime.security.shared.stub.java.net.ProxySelector.<clinit>(ProxySelector.java)
at com.squareup.okhttp.OkHttpClient.copyWithDefaults(OkHttpClient.java:614)
at com.squareup.okhttp.Call.<init>(Call.java:50)
at com.squareup.okhttp.OkHttpClient.newCall(OkHttpClient.java:595)
at retrofit.client.OkClient.execute(OkClient.java:53)

I gather from the error that "java.net.ProxySelector" is not white-listed for use on google appengine.

Question 1) Is it possible to use OKHTTP (version 2.4.0) along retrofit (1.9.0) on google app engine (1.9.22)? i.e, is there a work around for this error

if not, Question 2) Are there any other way to:

(a) use async HTTP calls with google appengine (with URLFetchService, for instance) ?

(b) set connection and socket timeouts for the client used from (a) ?

The links i have come across via search: (1) Retrofit timeout configuration for clients (2) Google App Engine URL Fetch Java API

回答1:

You can use HttpUrlConnection with Retrofit2 to use it in Google APP Engine

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.servlet.http.HttpServletResponse;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;


public class RetrofitCall implements Call {
Request request;

RetrofitCall(Request request) {
    this.request = request;
}

@Override
public Request request() {
    return request;
}

@Override
public Response execute() throws IOException {
    URL url = request.url().url();
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setUseCaches(false);
    connection.setDoOutput(true);
    connection.setRequestMethod(request.method());

    Headers headers = request.headers();
    if (headers != null) {
        for (int i = 0; i < headers.size(); i++) {
            String name = headers.name(i);
            connection.setRequestProperty(name, headers.get(name));
        }
    }

    if (request.body() != null) {
        BufferedSink outbuf;
        outbuf = Okio.buffer(Okio.sink(connection.getOutputStream()));
        request.body().writeTo(outbuf);
        outbuf.close();
    }

    connection.connect();

    final BufferedSource source = Okio.buffer(Okio.source(connection.getInputStream()));
    if (connection.getResponseCode() != HttpServletResponse.SC_OK) {
        throw new IOException("Fail to call " + " :: " + source.readUtf8());
    }
    Response response = new Response.Builder()
            .code(connection.getResponseCode())
            .message(connection.getResponseMessage())
            .request(request)
            .protocol(Protocol.HTTP_1_1)
            .body(new ResponseBody() {
                @Override
                public MediaType contentType() {
                    return MediaType.parse(connection.getContentType());
                }

                @Override
                public long contentLength() {
                    return connection.getContentLengthLong();
                }

                @Override
                public BufferedSource source() {
                    return source;
                }
            })
            .build();
    return response;
}

@Override
public void enqueue(Callback responseCallback) {

}

@Override
public void cancel() {

}

@Override
public boolean isExecuted() {
    return false;
}

@Override
public boolean isCanceled() {
    return false;
}

public static class Factory implements Call.Factory {
    @Override
    public Call newCall(Request request) {
        return new RetrofitCall(request);
    }
}

}



回答2:

You can use the following code snippet to run Retorifit2 with GAE limitations. It contains a lot of debugging stuffs free to remove in production and does not implement real async call.

   okhttp3.Call.Factory gaeCallFactory = new okhttp3.Call.Factory() {
            @Override
            public okhttp3.Call newCall(final Request request) {

                final URL url = request.url().url();
                final String method = url.toString();

                return new okhttp3.Call() {
                    @Override
                    public Request request() {
                        return request;
                    }

                    @Override
                    public Response execute() throws IOException {
                        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                        connection.setUseCaches(false);
                        if (request.body() != null) {
                            //TODO ajust for different needs   
                            connection.setRequestProperty("Content-Type", "application/json");
                            connection.setDoOutput(true);
                            BufferedSink outbuf;
                            ByteArrayOutputStream out = new ByteArrayOutputStream();
                            outbuf = Okio.buffer(Okio.sink(out));
                            request.body().writeTo(outbuf);
                            outbuf.close();
                            logger.info("Calling " + method + "\n" + new String(out.toByteArray()));
                            outbuf = Okio.buffer(Okio.sink(connection.getOutputStream()));
                            request.body().writeTo(outbuf);
                            outbuf.close();
                        } else {
                            logger.info("Calling " + method);
                        }

                        final BufferedSource source = Okio.buffer(Okio.source(connection.getInputStream()));
                        if (connection.getResponseCode() != HttpServletResponse.SC_OK) {
                            throw new IOException("Fail to call " + method + " :: " + source.readUtf8());
                        }
                        Response response = new Response.Builder()
                                .code(connection.getResponseCode())
                                .message(connection.getResponseMessage())
                                .request(request)
                                .protocol(Protocol.HTTP_1_1)
                                .body(new ResponseBody() {
                                    @Override
                                    public MediaType contentType() {
                                        return MediaType.parse(connection.getContentType());
                                    }

                                    @Override
                                    public long contentLength() {
                                        return connection.getContentLengthLong();
                                    }

                                    @Override
                                    public BufferedSource source() {
                                        return source;
                                    }
                                })
                                .build();
                        logger.info("Call response code: " + response.code() + " message: " + response.message());
                        return response;
                    }

                    @Override
                    public void enqueue(Callback responseCallback) {
                        try {
                            responseCallback.onResponse(this, execute());
                        } catch (IOException e) {
                            responseCallback.onFailure(this, e);
                        }
                    }

                    @Override
                    public void cancel() {

                    }

                    @Override
                    public boolean isExecuted() {
                        return false;
                    }

                    @Override
                    public boolean isCanceled() {
                        return false;
                    }
                };
            }
        };
        Retrofit retrofit = new Retrofit.Builder()
                .callFactory(gaeCallFactory)
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(ENDPOINT_URI)
                .build();


回答3:

You need to use the Appengine URLFetchClient instead of the OkHttpClient. Like this:

import retrofit.appengine.UrlFetchClient;
RestAdapter restAdapter = new RestAdapter.Builder()
            .setLogLevel(RestAdapter.LogLevel.FULL)
            .setConverter(new JacksonConverter())
            .setEndpoint(ENDPOINT_PATH)
            .setClient(new UrlFetchClient())
            .build();

Please note this only works with Retrofit1, this will not work with Retrofit2 because it's coupled directly to OkHttp as explained by Jake Wharton here