4

我正在编写一个连接到受密码保护的 cPanel 服务器(Apache 2.2.22)页面的 Android 应用程序。当身份验证凭据正确时,连接没有问题。但是,当凭据不正确时,我的 Android 应用程序似乎冻结在该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_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。我现在假设问题出在客户端。

4

3 回答 3

3

在 POST 连接中使用 Authenticator似乎是一个问题。太老了,不知道还有没有。

我会尝试两件事:

  • getPasswordAuthentication在 的中添加一条日志行Authenticator以查看它是否被有效调用。Authenticator如果没有打印任何内容,您应该在调用之前检查是否添加了默认值。你说你是在 中做的onCreate(),所以应该没问题,但可以肯定的是。
  • 避免使用 Authenticator(至少出于测试目的)并直接在 HTTP 请求中发送身份验证信息。我通常这样做:

    String auth = user + ":" + pass;
    conn = (HttpURLConnection) url.openConnection();
    conn.setRequestProperty("Authorization", 
                   "Basic " + Base64.encode(auth.getBytes()));
    // Set other parameters and read the result...
    
于 2013-05-03T12:36:10.440 回答
2

问题是当标头丢失并且标头中包含的凭据不正确时401 Unauthorized会发送状态。因此,我的应用程序不断地反复发送相同的请求,但无济于事。因此,我通过在我的:AuthorizationCustomAuthenticator

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()),否则每第三个请求都会失败。此外,我确信必须有一种本地方式来执行此操作,尽管在搜索文档后我找不到它。如果有人能向我指出这一点,我仍然会非常感激。

于 2013-05-03T16:07:50.083 回答
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);
于 2014-09-10T22:07:32.263 回答