在 Google App Engine 上通过 OKClient 使用 OkHttp 客户端会引发“java.lang.NoClassDefFoundError: java.net.ProxySele

Posted

技术标签:

【中文标题】在 Google App Engine 上通过 OKClient 使用 OkHttp 客户端会引发“java.lang.NoClassDefFoundError: java.net.ProxySelector”是受限类错误【英文标题】:Using OkHttp client via OKClient on Google App Engine throws a "java.lang.NoClassDefFoundError: java.net.ProxySelector" is a restricted class error 【发布时间】:2015-09-03 21:29:16 【问题描述】:

我正在尝试在谷歌应用引擎 (1.9.22) 上使用 OKHTTP(版本 2.4.0)和改造 (1.9.0)。

这是我的使用方法:

    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();

这会引发以下错误:

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)

我从错误中得知“java.net.ProxySelector”未列入白名单,无法在 google appengine 上使用。

问题 1) 是否可以在谷歌应用引擎(1.9.22)上使用 OKHTTP(版本 2.4.0)和改造(1.9.0)?即,是否有解决此错误的方法

如果没有, 问题2) 有没有其他方法:

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

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

我通过搜索找到的链接: (1)Retrofit timeout configuration for clients (2)Google App Engine URL Fetch Java API

【问题讨论】:

【参考方案1】:

您可以使用 HttpUrlConnection 和 Retrofit2 在 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】:

您可以使用以下代码 sn-p 运行具有 GAE 限制的 Retorifit2。它包含许多可以在生产中免费删除的调试内容,并且没有实现真正的异步调用。

   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】:

您需要使用 Appengine URLFetchClient 而不是 OkHttpClient。像这样:

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

请注意,这仅适用于 Retrofit1,这不适用于 Retrofit2,因为它直接与 OkHttp 耦合,正如 Jake Wharton here 所解释的那样

【讨论】:

未来读者注意:URLFetchClient 在单元测试的情况下将不起作用。如果您正在测试它,而 appengine 正在运行,那么您就可以开始了。

以上是关于在 Google App Engine 上通过 OKClient 使用 OkHttp 客户端会引发“java.lang.NoClassDefFoundError: java.net.ProxySele的主要内容,如果未能解决你的问题,请参考以下文章

Google App Engine 通过内部网络与 Compute Engine 通信

如何从 Python 中的 App Engine 在 Google BigQuery 上创建架构?

在 Google App Engine 上通过 OKClient 使用 OkHttp 客户端会引发“java.lang.NoClassDefFoundError: java.net.ProxySele

从Iphone本机客户端通过Google App Engine进行身份验证

如何在 Google App Engine 中执行全文搜索?

在 Google App Engine 中上传文件