2013-05-03 33 views
4

我正在编写一个连接到受密码保护的cPanel服务器(Apache 2.2.22)页面的Android应用程序。当身份验证凭据正确时,我没有任何问题连接。但是,当凭据不正确时,我的Android应用程序似乎冻结在HttpURLConnection.getResponseCode()方法中。服务器上的日志显示从我的Android设备发送了数百个请求,所有请求都按照预期返回了401,但出于某种原因,这并未反映在我的应用程序中。HttpURLConnection.getResponseCode()冻结执行/不超时

这里是我的代码,从内部的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_CONCURRENT),我省略了它们。

我还应该说,目前我没有使用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()); 
    } 
} 

我在活动的设置onCreate()方法:

Authenticator.setDefault(new CustomAuthenticator(mContext)); 

另外,我已经使用curl来请求密码保护资源,并且收到了预期的401。我现在假设问题是客户端。

+1

对不起,也许我瞎了,但我看不出现在你在哪里发送您的身份验证凭据。无论如何,也许你可以使用curl来尝试请求,看看你会得到什么样的输出:'curl -v“http://yoururl.com”-u user:password'。如果不起作用,问题可能出现在服务器端。 – Esparver 2013-05-03 11:36:22

+0

谢谢@Esparver,我已经在curl中试过了,它按照预期返回了401。所以我想这是客户端问题。 我使用java.net.Authenticator发送我的身份验证凭据。我会附上这段代码给我的答案。 – 2013-05-03 11:54:40

回答

3

在POST连接中使用Authenticator似乎是issue。这是相当古老的,所以我不知道它是否仍然存在。

我将要做两件事情:

  • AuthenticatorgetPasswordAuthentication添加日志行,看看它是否有效地叫。如果没有打印任何内容,则应检查是否在调用之前添加了默认的Authenticator。你说你在onCreate()这样做,所以它应该没问题,但确实很好。
  • 避免使用Authenticator(至少用于测试目的)并直接在HTTP请求中发送auth信息。我通常做这种方式:

    String auth = user + ":" + pass; 
    conn = (HttpURLConnection) url.openConnection(); 
    conn.setRequestProperty("Authorization", 
           "Basic " + Base64.encode(auth.getBytes())); 
    // Set other parameters and read the result... 
    
+0

谢谢你的详细解答。我在'getPasswordAuthentication'中记录了一个日志,并且它被多次调用。它让我意识到,当请求没有授权标头**和**时,如果细节不正确,则会给出401,所以我的应用程序会自动发送更多具有错误授权凭证的请求。根据REST的说法,客户端应该处理这个问题,所以我在我的'CustomAuthenticator'中放置了一个重试计数器。它现在可以工作,我会发布我的答案,但我仍在寻找替代品。 – 2013-05-03 15:40:06

+0

使用'HttpURLConneciton.setRequestProperty()'实际上是我之前进行身份验证的方式。它适用于运行3.0+以上的设备,但由于某些原因,在2.3.4及更低版本中,无论凭证是否正确,我都会收到400秒,并且服务器日志根本不记录请求。我仍然在寻找这个问题的答案,所以如果你能提出任何建议,我会为你提出一个新的问题。我现在对这个问题有一个解决方法,但我仍然在寻求知识和真正的解决方案。 – 2013-05-03 15:44:09

+0

当''Authenticator'与'PUT'请求一起使用时,我遇到了同样的问题。我要改变我的'Authenticator',让用户有机会重新输入凭证或完全取消操作。 – Daniel 2013-07-25 13:34:58

2

的问题是,当Authorization头部丢失当包含在报头中的凭据是不正确的401 Unauthorized状态被发送。因此,我的应用程序不断发送同样的请求无济于事。因此,我已经找到了解决方法的问题通过添加计数器到我CustomAuthenticator

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()中执行)重置计数器,否则每三个请求都会失败。另外,我确信必须有一种本地方式来做到这一点,虽然在搜索完文档后我找不到它。如果有人能指出我的意见,我仍然非常感激。

0

我不知道我是否正确,但我的解决方案已经为我工作了一整天没有一个小故障。

尝试这样做

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