android - Android 上通过 SSL 上传的 HttpsURLConnection 卡住(具有不同的故障模式)

标签 android ssl glassfish httpurlconnection okhttp

将不胜感激对此的任何投入。我需要通过来自 Android 的流式 HTTPS 上传大量数据(仅数十 KB 用于测试)。目前在 Kitkat 4.4.2 平板电脑上进行测试。我的问题与 HttpURLConnection 和 OkHttp 2.0 相同(这似乎需要 Java 7,因此最终需要支持 ICS 4.0.3,所以 HUC 可能是我唯一的选择)。

在服务方面,我使用的是 Jersey 和 Glassfish 4.0.1 b10(我认为它有 Jersey 2.11)。我从 Glassfish 4.0 升级以防出现问题,但没有观察到任何行为差异。我想搬到 Wildfly,但现在没有时间。

对于相对少量的数据,一切正常。一旦我输入了大量数据(数十千字节),应用程序就会从服务器收到 400 Bad Request,而不会访问我的服务器代码。服务器需要客户端证书身份验证,这在所有其他情况下都有效,但在这种情况下,我得到 null principal 并且无法授予权限(注意客户端上的错误不是 401)。在其他成功的身份验证中,主体是证书上的 CN。一段时间后,对于这种类型的请求和其他先前有效的请求,我除了 SSL 握手超时外什么也得不到。我在下面记录的超时是在等待服务器响应 5 分钟后出现的。

当我确实得到要调用的服务时(此时我不再记得我做了什么来达到如此幸福的结果),我的服务器代码记录了大约 5668 个输入字符,然后停止了。然后一旦超时到期(30 秒),一切都失败了。但有时它根本无法访问服务,对明显的身份验证失败返回 400 响应。

我已经通读了有关 HttpURLConnection 的 Web 帖子,直到我脸色发青,并排除了明显的可能性。在写入并刷新输出流之前,我不会调用 getInputStream() 。我没有关闭输出流,因为一些帖子声称它关闭了套接字(我确实刷新了它)。我想要 HTTP 缓存但已将其关闭以消除它作为一个原因。我想要流模式,但我没有设置分块流模式来消除它作为一个原因。无论如何,我读过一些帖子,其中人们声称即使使用分块流,HUC 也会在发送之前在内存中构建请求实体。我想我小心地吃掉了所有的 react ,就像过去被推荐的那样。我不认为这是破坏 HTTP 请求语法的 header 问题,此请求类型仅发送与其他请求类型相同的请求 header ,并且它们工作正常(它们也使用 POST)。对于这种请求类型,我不需要来自服务的响应实体,但我添加它只是为了消除它作为一个原因。我确实注意到,当我将 @Produces 添加到资源方法时,然后(在 OkHttp 2.0 上)我开始阻止客户端等待响应实体。然后 SSL 握手超时异常在该超时后出现.....

应用日志(样本):

D/org.foo.main.rest.RequestProcessor(2325): javax.net.ssl.SSLException: Connection closed by peer 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:405) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.Connection.upgradeToTls(Connection.java:146) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.Connection.connect(Connection.java:107) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:503) 09-04 17:24:55.743: D/org.foo.main.rest.RequestProcessor(2325): at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136)

服务器日志:

[2014-09-04T21:19:54.492+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.core.security] [tid: _ThreadID=36 _ThreadName=http-listener-2(5)] [timeMillis: 1409865594492] [levelValue: 500] [CLASSNAME: com.sun.enterprise.security.ssl.J2EEKeyManager] [METHODNAME: getPrivateKey] [[ Getting private key for alias:server-cert]]

[2014-09-04T21:19:54.492+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.core.security] [tid: _ThreadID=36 _ThreadName=http-listener-2(5)] [timeMillis: 1409865594492] [levelValue: 500] [CLASSNAME: com.sun.enterprise.security.ssl.J2EEKeyManager] [METHODNAME: getCertificateChain] [[ Getting certificate chain]]

[2014-09-04T21:19:55.191+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.container.web.com.sun.web.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595191] [levelValue: 500] [CLASSNAME: com.sun.web.security.RealmAdapter] [METHODNAME: hasUserDataPermission] [[ [Web-Security][ hasUserDataPermission ] Principal: null ContextPath: /bar-data]]

[2014-09-04T21:19:55.192+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.container.web.com.sun.web.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595192] [levelValue: 500] [CLASSNAME: com.sun.web.security.RealmAdapter] [METHODNAME: hasUserDataPermission] [[ [Web-Security] request.getRequest().isSecure(): true]]

[2014-09-04T21:19:55.192+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.container.web.com.sun.web.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595192] [levelValue: 500] [CLASSNAME: com.sun.web.security.RealmAdapter] [METHODNAME: invokeWebSecurityManager] [[ [Web-Security] [ hasResourcePermission ] Principal: null ContextPath: /bar-data]]

[2014-09-04T21:19:55.193+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.core.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595193] [levelValue: 500] [CLASSNAME: com.sun.enterprise.security.web.integration.WebSecurityManager] [METHODNAME: setPolicyContext] [[ [Web-Security] Setting Policy Context ID: old = null ctxID = BarAppDemo/barrestcertswebservice-1_0_0_war]]

[2014-09-04T21:19:55.196+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.core.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595196] [levelValue: 500] [CLASSNAME: com.sun.enterprise.security.web.integration.WebSecurityManager] [METHODNAME: checkPermissionWithoutCache] [[ [Web-Security] Codesource with Web URL: file:/BarAppDemo/barrestcertswebservice-1_0_0_war]]

[2014-09-04T21:19:55.200+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.core.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595200] [levelValue: 500] [CLASSNAME: com.sun.enterprise.security.web.integration.WebSecurityManager] [METHODNAME: checkPermissionWithoutCache] [[ [Web-Security] Checking Web Permission with Principals : null]]

[2014-09-04T21:19:55.201+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.core.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595201] [levelValue: 500] [CLASSNAME: com.sun.enterprise.security.web.integration.WebSecurityManager] [METHODNAME: checkPermissionWithoutCache] [[ [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/resources/upload/data/51" "POST")]]

[2014-09-04T21:19:55.201+0000] [glassfish 4.0] [FINEST] [] [javax.enterprise.system.core.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595201] [levelValue: 300] [CLASSNAME: com.sun.enterprise.security.provider.BasePolicyWrapper] [METHODNAME: doImplies] [[ JACC Policy Provider: PolicyWrapper.implies, context (BarAppDemo/barrestcertswebservice-1_0_0_war)- result was(false) permission (("javax.security.jacc.WebResourcePermission" "/resources/upload/data/51" "POST"))]]

[2014-09-04T21:19:55.202+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.core.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595202] [levelValue: 500] [CLASSNAME: com.sun.enterprise.security.web.integration.WebSecurityManager] [METHODNAME: hasResourcePermission] [[ [Web-Security] hasResource isGranted: false]]

[2014-09-04T21:19:55.202+0000] [glassfish 4.0] [FINE] [] [javax.enterprise.system.core.security] [tid: _ThreadID=33 _ThreadName=http-listener-2(2)] [timeMillis: 1409865595202] [levelValue: 500] [CLASSNAME: com.sun.enterprise.security.web.integration.WebSecurityManager] [METHODNAME: hasResourcePermission] [[ [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/resources/upload/data/51" "POST")]]

服务器代码:

    private static final String UPLOAD_RESPONSE_ENTITY = "{ \"status\" : 200 }";

@Path(ServiceUris.UPLOAD_DATA_PATH + "/{id}")
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_JSON)
public Response uploadData(@PathParam("id") String id, 
                           InputStream is) {
    try {
        logger.info("Data upload is starting.");
        byte[] buf = new byte[80];
        int sum = 0;
        int n = is.read(buf);
        while (n >= 0) {
            sum += n;
            logger.info("Data upload so far: "+new String(buf, "UTF-8"));
            n = is.read(buf);
        }
        logger.info("Data upload read "+sum+" bytes.");
        return Response.serverError().entity(UPLOAD_RESPONSE_ENTITY).build();

    } catch (IOException e) {
        logger.log(Level.SEVERE, "Data upload got exception.", e);
        return Response.serverError().build();
    } finally {
    }
  } 

如果不是很明显,我将 OCTET STREAM 用于输入实体类型,以消除更具体类型出现问题的任何可能性。最初它是多部分表单数据。

感谢任何帮助。我将保留我对 HttpURLConnection API 的看法,因为我不想冒犯任何人。

最佳答案

好的进展,这可能对任何其他尝试通过 SSL 上传文件的人有所帮助。现在我的服务请求成功了。有时它仍然失败,但它是进步。我做了一些装饰性的事情,比如删除对 connect()、setDoInput() 和 setDoOutput() 的调用。阅读 SO 帖子,这些似乎只是为了引起困惑。但我认为不同之处在于,在服务器端,在 HTTP 监听器上,将最大帖子大小和最大帖子保存大小提高到 100000。我已经关闭了上传超时,但显然不应该上传几千字节需要五分钟以上。所以看起来它可能是 Glassfish 上的缓冲区溢出,甚至没有被记录下来,只是导致了奇怪的行为。如果有一种方法可以查明这种情况何时发生,那就太好了。

我仍然在 Android 上定期收到此消息(当相同的请求决定失败时):

09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): javax.net.ssl.SSLException: Connection closed by peer 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:405) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.Connection.upgradeToTls(Connection.java:146) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.Connection.connect(Connection.java:107) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:503) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136) 09-04 22:05:09.347: D/org.foo.main.rest.RequestProcessor(3346): at org.foo.main.rest.RestMethod.throwErrors(RestMethod.java:602)

这可能是因为我在尝试解决这个问题时返回了 500 RC,并且失败可能会混淆 SSL 握手(如果这甚至有意义,我只是注意到 SSL 握手超时似乎在我之前的失败之后级联,以所有类型的请求开始获得此握手超时的时间点)。或者在与服务器的 Android SSL 握手中可能存在竞争条件,会定期将其锁定。

现在我想会尝试打开分块.....

关于android - Android 上通过 SSL 上传的 HttpsURLConnection 卡住(具有不同的故障模式),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25675925/

相关文章:

android - 无法使用带有口味和com.google.gms.google-services插件的Gradle构建应用

java - Android:比较 ImageButton 和资源中的图像

mysql - 启动 SSL 和 MySql

java - 使用 Java 发送电子邮件时出现 SSL 握手异常

php - stream_socket_client 在我的服务器上失败,无法了解有关错误的更多信息

java - 如何在 persistence.xml 中找到 "parameterize"JPA 数据库实例?

java - 除index.xhtml 外,JSF 页面未执行

android - 从 Android 内容提供商到我的远程服务器的数据同步

android - 我无法打开 Android SDK 和 AVD 管理器

java - 创建 LDAP 领域时出现 Glassfish 错误 : "Invalid property syntax, "="in value