Android HttpURLConnection.getResponseCode()冻结执行/nots';t超时
我正在编写一个Android应用程序,它连接到一个有密码保护的cPanel服务器(Apache2.2.22)页面。当身份验证凭据正确时,连接就没有问题。但是,当凭据不正确时,我的Android应用程序似乎冻结在Android HttpURLConnection.getResponseCode()冻结执行/nots';t超时,android,authorization,cpanel,httpurlconnection,Android,Authorization,Cpanel,Httpurlconnection,我正在编写一个Android应用程序,它连接到一个有密码保护的cPanel服务器(Apache2.2.22)页面。当身份验证凭据正确时,连接就没有问题。但是,当凭据不正确时,我的Android应用程序似乎冻结在HttpURLConnection.getResponseCode()方法中。服务器上的日志显示从我的Android设备发送的数百个请求,都像预期的那样返回401,但由于某些原因,这没有反映在我的应用程序中 以下是我的代码,在AsyncTask中执行: @Override
HttpURLConnection.getResponseCode()
方法中。服务器上的日志显示从我的Android设备发送的数百个请求,都像预期的那样返回401,但由于某些原因,这没有反映在我的应用程序中
以下是我的代码,在AsyncTask中执行:
@Override
protected Integer doInBackground(String... bookInfoString) {
// Stop if cancelled
if(isCancelled()){
return null;
}
Log.i(getClass().getName(), "SendToDatabase.doInBackground()");
String apiUrlString = getResources().getString(R.string.url_vages_library);
try{
NetworkConnection connection = new NetworkConnection(apiUrlString);
connection.appendPostData(bookInfoString[0]);
int responseCode = connection.getResponseCode();
Log.d(getClass().getName(), "responseCode: " + responseCode);
return responseCode;
} catch(IOException e) {
return null;
}
}
这段代码使用了我自己的类NetworkConnection
,它只是围绕HttpURLConnection的一个基本包装类,以避免重复代码。这是:
public class NetworkConnection {
private String url;
private HttpURLConnection connection;
public NetworkConnection(String urlString) throws IOException{
Log.i(getClass().getName(), "Building NetworkConnection for the URL \"" + urlString + "\"");
url = urlString;
// Build Connection.
try{
URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(1000 /* 1 seconds */);
connection.setConnectTimeout(1000 /* 1 seconds */);
} catch (MalformedURLException e) {
// Impossible: The only two URLs used in the app are taken from string resources.
e.printStackTrace();
} catch (ProtocolException e) {
// Impossible: "GET" is a perfectly valid request method.
e.printStackTrace();
}
}
public void appendPostData(String postData) {
try{
Log.d(getClass().getName(), "appendPostData() called.\n" + postData);
Log.d(getClass().getName(), "connection.getConnectTimeout(): " + connection.getConnectTimeout());
Log.d(getClass().getName(), "connection.getReadTimeout(): " + connection.getReadTimeout());
// Modify connection settings.
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json");
// Get OutputStream and attach POST data.
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
writer.write(postData);
if(writer != null){
writer.flush();
writer.close();
}
} catch (SocketTimeoutException e) {
Log.w(getClass().getName(), "Connection timed out.");
} catch (ProtocolException e) {
// Impossible: "POST" is a perfectly valid request method.
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// Impossible: "UTF-8" is a perfectly valid encoding.
e.printStackTrace();
} catch (IOException e) {
// Pretty sure this is impossible but not 100%.
e.printStackTrace();
}
}
public int getResponseCode() throws IOException{
Log.i(getClass().getName(), "getResponseCode()");
int responseCode = connection.getResponseCode();
Log.i(getClass().getName(), "responseCode: " + responseCode);
return responseCode;
}
public void disconnect(){
Log.i(getClass().getName(), "disconnect()");
connection.disconnect();
}
}
最后,这里是logcat日志的一小部分:
05-03 11:01:16.315: D/vages.library.NetworkConnection(3408): connection.getConnectTimeout(): 1000
05-03 11:01:16.315: D/vages.library.NetworkConnection(3408): connection.getReadTimeout(): 1000
05-03 11:01:16.585: I/vages.library.NetworkConnection(3408): getResponseCode()
05-03 11:04:06.395: I/vages.library.MainActivity$SendToDatabase(3408): SendToDatabase.onPostExecute(null)
您可以看到,该方法似乎只是在经过随机时间后返回null。我最长的等待时间正好是15分钟。在我省略的最后两个信息日志之间,还有来自dalikvm的几个内存日志(GC_并发)
我还应该说,目前我没有使用https,尽管我认为这不会造成任何问题。如果您对此有任何反馈,我将不胜感激,无论是一个完整的答案还是一条评论,告诉我什么不是问题,因为我仍然不确定这个问题是服务器端还是客户端
多谢各位,
威廉
编辑:我之前忘了提到,我用自己的自定义java.net.Authenticator将身份验证凭据附加在一起:
public class CustomAuthenticator extends Authenticator {
Context mContext;
public CustomAuthenticator(Context context){
super();
mContext = context;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String username = sharedPreferences.getString(SettingsActivity.KEY_USERNAME_PREFERENCE, null);
String password = sharedPreferences.getString(SettingsActivity.KEY_PASSWORD_PREFERENCE, null);
return new PasswordAuthentication(username, password.toCharArray());
}
}
public class CustomAuthenticator extends Authenticator {
public static int RETRIES = 3;
int mRetriesLeft;
Context mContext;
public CustomAuthenticator(Context context){
super();
mRetriesLeft = RETRIES;
mContext = context;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
Log.i(getClass().getName(), "getPasswordAuthentication() - mCounter: " + mRetriesLeft);
if(mRetriesLeft > 0){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String username = sharedPreferences.getString(SettingsActivity.KEY_USERNAME_PREFERENCE, null);
String password = sharedPreferences.getString(SettingsActivity.KEY_PASSWORD_PREFERENCE, null);
mRetriesLeft--;
return new PasswordAuthentication(username, password.toCharArray());
} else {
Log.w(getClass().getName(), "No more retries. Returning null");
mRetriesLeft = RETRIES;
return null;
}
}
public void reset(){
mRetriesLeft = RETRIES;
}
}
我在活动的onCreate()
方法中设置:
Authenticator.setDefault(new CustomAuthenticator(mContext));
此外,我还使用curl请求受密码保护的资源,并收到了预期的401。我现在假设问题是客户端的。这似乎是在POST连接中使用验证器的问题。它很老了,所以我不知道它是否还存在
我会尝试两件事:
- 在
Authenticator
的getPasswordAuthentication
中添加一个日志行,以查看它是否被有效调用。如果未打印任何内容,则应检查在调用之前是否添加了默认的验证器。您说您是在onCreate()
中完成的,所以应该可以,但可以肯定这是很好的
- 避免使用验证器(至少出于测试目的),并在HTTP请求中直接发送身份验证信息。我通常是这样做的:
String auth = user + ":" + pass;
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization",
"Basic " + Base64.encode(auth.getBytes()));
// Set other parameters and read the result...
问题在于401 Unauthorized
状态在授权
标头缺失且标头中包含的凭据不正确时发送。因此,我的应用程序不断地反复发送相同的请求,但都无济于事。因此,我通过在我的CustomAuthenticator
中添加一个计数器,找到了解决此问题的方法:
public class CustomAuthenticator extends Authenticator {
Context mContext;
public CustomAuthenticator(Context context){
super();
mContext = context;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String username = sharedPreferences.getString(SettingsActivity.KEY_USERNAME_PREFERENCE, null);
String password = sharedPreferences.getString(SettingsActivity.KEY_PASSWORD_PREFERENCE, null);
return new PasswordAuthentication(username, password.toCharArray());
}
}
public class CustomAuthenticator extends Authenticator {
public static int RETRIES = 3;
int mRetriesLeft;
Context mContext;
public CustomAuthenticator(Context context){
super();
mRetriesLeft = RETRIES;
mContext = context;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
Log.i(getClass().getName(), "getPasswordAuthentication() - mCounter: " + mRetriesLeft);
if(mRetriesLeft > 0){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String username = sharedPreferences.getString(SettingsActivity.KEY_USERNAME_PREFERENCE, null);
String password = sharedPreferences.getString(SettingsActivity.KEY_PASSWORD_PREFERENCE, null);
mRetriesLeft--;
return new PasswordAuthentication(username, password.toCharArray());
} else {
Log.w(getClass().getName(), "No more retries. Returning null");
mRetriesLeft = RETRIES;
return null;
}
}
public void reset(){
mRetriesLeft = RETRIES;
}
}
然而,我应该说,我不喜欢这个解决办法,因此,我没有接受它。无论何时发出新请求(我在AsyncTask.onPreExecute()
中执行),都必须记住重置计数器,否则每三次请求都会失败。另外,我确信一定有一种本地的方法来实现这一点,尽管在浏览了文档之后,我找不到它。如果有人能向我指出这一点,我将不胜感激。我不知道我是否正确,但我的解决方案已经为我工作了一整天,没有出现任何问题
试着这样做
byte[] buf = new byte[4096];
Inputstream is;
do
{
http conn code etc;
is=conn.getInputStream();
if(is.read(buf)==0)
{
flag=1;
}
//u can either is.close(); or leave as is
//code
int serverResponseCode = connection.getResponseCode();
String serverResponseMessage = connection.getResponseMessage();
conn.disconnect();
} while(flag==1);
很抱歉,我可能是瞎子,但我现在看不见您的身份验证凭据发送到哪里。不管怎样,也许您可以使用curl尝试请求,看看您得到了什么样的输出:curl-v”http://yoururl.com“-u用户:密码
。如果它不起作用,问题可能会出现在服务器端。谢谢@Esparver,我在curl中尝试过它,它会像预期的那样返回401。所以我想这是客户端的问题。我正在使用java.net.Authenticator发送身份验证凭据。我会把这个代码附在我的答案上。谢谢你这么详细的回答。我在getPasswordAuthentication
中放置了一个日志,它被多次调用。这让我意识到,当请求没有授权标头且详细信息不正确时,会给出401,因此我的应用程序会自动发送更多具有错误身份验证凭据的请求。根据REST,应该由客户机来处理此问题,因此我在我的CustomAuthenticator
中放置了一个重试计数器。现在可以了,我将发布我的答案,尽管我仍在寻找替代方案。使用HttpURLConneciton.setRequestProperty()
实际上是我以前进行身份验证的方式。它在运行3.0+的设备上运行得非常好,但由于某些原因,在2.3.4及以下版本上,无论凭据是否正确,我都会收到400秒的提示,服务器日志根本不会记录请求。我仍然在寻找这个问题的答案,所以如果您能提出任何其他建议,我将不胜感激,尽管我可能会为此提出一个新问题。我现在有了一个解决这个问题的方法,但我仍然在寻求知识和真正的解决方案。我在使用验证器和PUT
请求时遇到了同样的问题。我将更改我的验证器
,让用户有机会重新输入凭据或完全取消操作。