2013-05-03 86 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);