简体   繁体   中英

Android app making JSON call gets unexpected data from server, but same call works in browser

I have an Android app that makes a server call which returns JSON.

The server code returns the right string if I enter the URL into a browser. But it creates exceptions in the Java app (different issues with http and https server calls).

https://www.problemio.com/auth/mobile_login.php?login=test.name@gmail.com&password=130989

Returns this string:

[{"user_id":"1601470","email":"test.name@gmail.com","first_name":"TestName","last_name":null}]

And this is the Java call that parses the JSON but gives an Exception:

@Override
protected void onPostExecute(String result) 
{

    try {
        dialog.dismiss();
    } catch (Exception ee) {
        // nothing
    }           

    if ( connectionError == true )
    {
        Toast.makeText(getApplicationContext(), "Please try again. Possible Internet connection error.", Toast.LENGTH_LONG).show();  
    }           

    if ( result != null && result.equals( "no_such_user") )
    {
        Toast.makeText(getApplicationContext(), "Your email and password do not match out records. Please try again or create and account.", Toast.LENGTH_LONG).show(); 

        //final TextView login_error = (TextView) findViewById(R.id.login_error);
    }
    else
    {
        Log.d( "CONNECTION*** ERRORS: " , "ok 3 and result length: " + result.length()  );

        String firstName = null;
        String lastName = null;
        String email = null;
        String user_id = null;

        try
        {
            JSONArray obj = new JSONArray(result);
            JSONObject o = obj.getJSONObject(0);
            firstName = o.getString("first_name");
            lastName = o.getString("last_name");
            email = o.getString("email");
            user_id = o.getString("user_id");
        }
        catch ( Exception e )
        {
            Log.d( "JSON ERRORS: " , "something happened ****" + e.getMessage() );
        }


        // 1) First, write to whatever local session file that the person is logged in
        // - I just really need user id and name and email. And store that.
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( 
                LoginActivity.this);

        if ( user_id != null && user_id.trim().length() > 0 && !user_id.trim().equals("null") )
        {
            prefs.edit()
            .putString("first_name", firstName)
            .putString("last_name", lastName)
            .putString("email", email)              
            .putString("user_id", user_id)
            .putBoolean("working", true)
            .commit();

            if ( user_id.equals("1"))
            {
                prefs.edit()
                .putString("community_subscription", "1")
                .commit();
            }
        }
    }
}    
}

And this is the exception message:

End of input at character 0 of

It just looks like the string is 0 characters long.

Any idea why this is happening? Before I switched my site to https this call used to work without problems.

Also the server makes an http call. If I change it to https it returns a whole bunch of HTML which is weird since I don't actually send that back.

This is my doInBackground method:

@Override
protected String doInBackground(String... theParams) 
{
    String myUrl = theParams[0];
    final String myEmail = theParams[1];
    final String myPassword = theParams[2];

    String charset = "UTF-8";           

    Authenticator.setDefault(new Authenticator()
    {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() 
        {
            return new PasswordAuthentication( myEmail, myPassword.toCharArray());
        }
    }); 

Edit

If my doInBackground method is inside the

public class DownloadWebPageTask extends AsyncTask<String, Void, String>   

Can it be that the server is just too slow to return the string and that is why it is getting null?

It is always crashing on this with the result string being empty:

JSONArray obj = new JSONArray(result);

Edit 2

Here is the full code:

package com.problemio;

import java.io.InputStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLEncoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import java.io.InputStreamReader;
import java.io.BufferedReader;

import org.json.JSONArray;
import org.json.JSONObject;

import com.flurry.android.FlurryAgent;

import utils.SendEmail;

import android.app.Dialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class LoginActivity extends BaseActivity 
{
    //private TextView textView;
    private Dialog dialog;

    public static final String REQUEST_METHOD = "GET";
    public static final int READ_TIMEOUT = 15000;
    public static final int CONNECTION_TIMEOUT = 15000;

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        FlurryAgent.onStartSession(this, "8CA5LTZ5M73EG8R35SXG");

        setContentView(R.layout.login);        

        //final TextView emailask = (TextView) findViewById(R.id.email_ask);

        // Show form for login_email
        final EditText loginEmail = (EditText) findViewById(R.id.login_email);  
        //String name = loginEmail.getText().toString();

        // Show field for password  
        final EditText password = (EditText) findViewById(R.id.password);
        //String text = password.getText().toString();
        //Log.d( "First parameters: " , "Login email: " + loginEmail + " AND login password: " +  text);


        // Show button for submit
        Button submit = (Button)findViewById(R.id.submit);   

        submit.setOnClickListener(new Button.OnClickListener() 
        {  
           public void onClick(View v) 
           {
              String email = loginEmail.getText().toString();
              String pass = password.getText().toString();

               //Set the email pattern string
//            Pattern pattern = Pattern.compile(".+@.+\\.[a-z]+");
//            //Match the given string with the pattern
//            Matcher m = pattern.matcher(email);
//            //check whether match is found
//            boolean matchFound = m.matches();               

              // TODO: VALIDATE!!!
              if ( email == null || email.trim().length() < 2 )
              {
                  Toast.makeText(getApplicationContext(), "Please enter a valid email address.", Toast.LENGTH_LONG).show();                       
              }
              else
              if ( pass == null || pass.trim().length() < 2 )
              {
                  Toast.makeText(getApplicationContext(), "Please enter a correct password.", Toast.LENGTH_LONG).show();                          
              }
              else
              {
                 sendFeedback(pass, email); 
              }  
           }
        });        

        // Show button for submit
        Button forgot_password = (Button)findViewById(R.id.forgot_password);   

        forgot_password.setOnClickListener(new Button.OnClickListener() 
        {  
           public void onClick(View v) 
           {
              Toast.makeText(getApplicationContext(), "Please wait...", Toast.LENGTH_LONG).show();  

              Intent intent = new Intent(LoginActivity.this, ForgotPasswordActivity.class);
              LoginActivity.this.startActivity(intent);           
           }
        });                

        // Now add messaging for creating a profile
        final TextView create_profile_message = (TextView) findViewById(R.id.create_profile_message);

        Button create_profile = (Button)findViewById(R.id.create_profile);   

        create_profile.setOnClickListener(new Button.OnClickListener() 
        {  
           public void onClick(View v) 
           {
                //sendEmail("Create Profile Clicked", "From Login screen, someone clicked on the create profile button" );   

                Intent myIntent = new Intent(LoginActivity.this, CreateProfileActivity.class);
                LoginActivity.this.startActivity(myIntent);
           }
        });        

    }

    public void sendFeedback(String pass , String email) 
    {  
        String[] params = new String[] { "http://www.problemio.com/auth/mobile_login.php", email, pass };

        DownloadWebPageTask task = new DownloadWebPageTask();
        task.execute(params);        
    }          

    // Subject , body
    public void sendEmail( String subject , String body )
    {
        String[] params = new String[] { "http://www.problemio.com/problems/send_email_mobile.php", subject, body };

        SendEmail task = new SendEmail();
        task.execute(params);               
    }

    public class DownloadWebPageTask extends AsyncTask<String, Void, String> 
    {
         private boolean connectionError = false;

         @Override
         protected void onPreExecute( ) 
         {
              dialog = new Dialog(LoginActivity.this);

              dialog.setContentView(R.layout.please_wait);
                dialog.setTitle("Logging You In");

              TextView text = (TextView) dialog.findViewById(R.id.please_wait_text);
                text.setText("Please wait while you are being logged in...");
              dialog.show();
         }

         // orig
        @Override
        protected String doInBackground(String... theParams)
        {
            String myUrl = theParams[0];
            final String myEmail = theParams[1];
            final String myPassword = theParams[2];

            String charset = "UTF-8";

            Authenticator.setDefault(new Authenticator()
            {
                @Override
                protected PasswordAuthentication getPasswordAuthentication()
                {
                    return new PasswordAuthentication( myEmail, myPassword.toCharArray());
                }
            });

            String response = null;

            String stringUrl = "https://www.problemio.com/auth/mobile_login.php?login=test.name@gmail.com&password=130989";
            String result = "";
            String inputLine;

            try
            {
                String query = String.format("login=%s&password=%s",
                         URLEncoder.encode(myEmail, charset),
                         URLEncoder.encode(myPassword, charset));

                final URL url = new URL( myUrl + "?" + query );

                final HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                conn.setDoOutput(true);
                conn.setRequestMethod("POST");

                conn.setRequestProperty("login", myEmail);
                conn.setRequestProperty("password", myPassword);
                conn.setDoOutput(true);

                conn.setUseCaches(false);

                conn.connect();

                final InputStream is = conn.getInputStream();
                final byte[] buffer = new byte[8196];
                int readCount;
                final StringBuilder builder = new StringBuilder();
                while ((readCount = is.read(buffer)) > -1)
                {
                    builder.append(new String(buffer, 0, readCount));
                }

                response = builder.toString();

            }
            catch (Exception e)
            {
                  sendEmail ( "Login Activity 1 Network Error" , "Error: " + e.getMessage() );
            }

            return response;
        }



        @Override
        protected void onPostExecute(String result) 
        {
            super.onPostExecute(result);

            try {
                dialog.dismiss();
            } catch (Exception ee) {
                // nothing
            }           

            if ( connectionError == true )
            {
                Toast.makeText(getApplicationContext(), "Please try again. Possible Internet connection error.", Toast.LENGTH_LONG).show();  
            }           

            if ( result != null && result.equals( "no_such_user") )
            {
                Toast.makeText(getApplicationContext(), "Your email and password do not match out records. Please try again or create and account.", Toast.LENGTH_LONG).show(); 

                //final TextView login_error = (TextView) findViewById(R.id.login_error);
            }
            else
            {

                String firstName = null;
                String lastName = null;
                String email = null;
                String user_id = null;

                try
                {
                    JSONArray obj = new JSONArray(result);
                    Log.d( "CONNECTION*** ERRORS: " , ".....5"  );
                    JSONObject o = obj.getJSONObject(0);
                    firstName = o.getString("first_name");
                    lastName = o.getString("last_name");
                    email = o.getString("email");
                    user_id = o.getString("user_id");

                }
                catch ( Exception e )
                {
                    Log.d( "JSON ERRORS: " , "some crap happened ****" + e.getMessage() );
                }


                // 1) First, write to whatever local session file that the person is logged in
                // - I just really need user id and name and email. And store that.
                SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( 
                        LoginActivity.this);

                if ( user_id != null && user_id.trim().length() > 0 && !user_id.trim().equals("null") )
                {
                    prefs.edit()
                    .putString("first_name", firstName)
                    .putString("last_name", lastName)
                    .putString("email", email)              
                    .putString("user_id", user_id)
                    .putBoolean("working", true)
                    .commit();

                    if ( user_id.equals("1"))
                    {
                        prefs.edit()
                        .putString("community_subscription", "1")
                        .commit();
                    }
                }
            }
        }    
    }

    // TODO: see if I can get rid of this
    public void readWebpage(View view) 
    {
        DownloadWebPageTask task = new DownloadWebPageTask();
        task.execute(new String[] { "http://www.problemio.com/auth/mobile_login.php" });
    }       

    @Override
    public void onStop()
    {
       super.onStop();
    }       
}

ISSUE #1

From your question description:

The server code returns the right string if I enter the URL into a browser.

I'm assuming you are using HTTP GET . However you are using HTTP POST in your code instead:

String query = String.format("login=%s&password=%s",
                         URLEncoder.encode(myEmail, charset),
                         URLEncoder.encode(myPassword, charset));

                final URL url = new URL( myUrl + "?" + query );

                final HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                conn.setDoOutput(true);
                conn.setRequestMethod("POST"); // <----------- replace with "GET"

ISSUE #2

 conn.setDoOutput(true); 

When set to true the request method is changed to POST , since GET or DELETE can't have a request body. To continue with a GET request you must set conn.setDoOutput(false);

Also, comment out those lines:

//conn.setRequestProperty("login", myEmail);
//conn.setRequestProperty("password", myPassword);
//conn.setDoOutput(true);

ISSUE #3

 task.execute(new String[] { "http://www.problemio.com/auth/mobile_login.php" });

From Android 8: Cleartext HTTP traffic not permitted

You must change the URL from http to https or add android:usesCleartextTraffic="true" in the manifest. This will only effect on devices running API level 23+. Before 23+ http is allowed by default.

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        ...
        android:usesCleartextTraffic="true"
        ...>
        ...
    </application>
</manifest>

For me using https worked properly.

ISSUE #4

Upon providing wrong credentials your server is sending a plain text message

no_such_user

Which needs to be replaced with a valid JSON string.

From my end, the code you provided is working properly after fixing those issues.

I tried your code using HttpURLConnection in async task, It gave me the desired output without error.. But if I give different password in the get url.. the response is not JSONObject. It is coming as String value. may be that causing the issue(u handle that also in postexecute method)

public class HttpGetRequest extends AsyncTask<String, Void, String> {
    public static final String REQUEST_METHOD = "GET";
    public static final int READ_TIMEOUT = 15000;
    public static final int CONNECTION_TIMEOUT = 15000;

    @Override
    protected String doInBackground(String... params) {
        String stringUrl = "https://www.problemio.com/auth/mobile_login.php?login=test.name@gmail.com&password=130989";
        String result = "";
        String inputLine;
        try {
            //Create a URL object holding our url
            URL myUrl = new URL(stringUrl);
            //Create a connection
            HttpURLConnection connection = (HttpURLConnection)
                    myUrl.openConnection();
            //Set methods and timeouts
            connection.setRequestMethod(REQUEST_METHOD);
            connection.setReadTimeout(READ_TIMEOUT);
            connection.setConnectTimeout(CONNECTION_TIMEOUT);

            //Connect to our url
            connection.connect();
            //Create a new InputStreamReader
            InputStreamReader streamReader = new
                    InputStreamReader(connection.getInputStream());
            //Create a new buffered reader and String Builder
            BufferedReader reader = new BufferedReader(streamReader);
            StringBuilder stringBuilder = new StringBuilder();
            //Check if the line we are reading is not null
            while ((inputLine = reader.readLine()) != null) {
                stringBuilder.append(inputLine);
            }
            //Close our InputStream and Buffered reader
            reader.close();
            streamReader.close();
            //Set our result equal to our stringBuilder
            result = stringBuilder.toString();
        } catch (Exception e) {

        }
        return result;
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
       // textView.setText("Response is: " + response);
        try {
            JSONArray obj = new JSONArray(result);
            JSONObject o = obj.getJSONObject(0);
            String firstName = o.getString("first_name");
            String lastName = o.getString("last_name");
            String email = o.getString("email");
            String user_id = o.getString("user_id");
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
}

Looking your Json I can see a null

[
  {
    "user_id": "1601470",
    "email": "test.name@gmail.com",
    "first_name": "TestName",
    "last_name": null
  }
]

Json files accept nulls, but json objects do not accept it.

The application is crashing when you try to get the last_name .

Instead of null use empty.

[
  {
    "user_id": "1601470",
    "email": "test.name@gmail.com",
    "first_name": "TestName",
    "last_name": ""
  }
]

Regards

If calling the API using the HTTP protocol has no problem and changing the protocol to HTTPS makes the issue, it points to the trust management problem. I guess the certificate of the service provider (problemio.com) is not known by the device.

As you're not publishing the doInBackground() method, I guess that you're returning an empty String when an exception is thrown. However, with supposing that and you're using HttpURLConnection , I propose the below code to trust all SSL sockets. (Note that it may be harmful on untrusted networks!)

Call the trust method with passing your HttpURLConnection as its argument, before calling the connect() .

import java.net.HttpURLConnection;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class YourClass {

    // Other class members...

    public void trust(HttpURLConnection connection) throws NoSuchAlgorithmException, KeyManagementException {
        if (connection instanceof HttpsURLConnection) {
            X509TrustManager trustManager = new X509TrustManager() {

                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };

            // Install the all-trusting trust manager
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());

            // Create an ssl socket factory with our all-trusting manager
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
            httpsConnection.setSSLSocketFactory(sslSocketFactory);
            httpsConnection.setHostnameVerifier((s, sslSession) -> true);
        }
    }

    // Other class members...

}

As per google this is old technique Retrieve API data using AsyncTask, I prefer Retrofit + RxJava + RxAndroid + GSON.

It will remove all your boilerplate code. It is so easy to use Retrofit.

Add Below dependency in your app,

//retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.6.1'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
    implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
//Rx android
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.8'

Create MyRetrofit class,

import com.google.gson.GsonBuilder;
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class MyRetrofit {
    private static Retrofit retrofit = null;


    public static Retrofit getInstance() {
        String BASE_URL = "https://www.problemio.com/";
        if (retrofit == null) {
            OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
            httpClient.readTimeout(60, TimeUnit.MINUTES);
            httpClient.connectTimeout(60, TimeUnit.SECONDS);

            if (BuildConfig.DEBUG) {
                HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
                logging.setLevel(HttpLoggingInterceptor.Level.BODY);

                httpClient.addInterceptor(logging);
            }
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()))
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(httpClient.build())
                    .build();

        }
        return retrofit;
    }
}

Create APIService Class,

    import in.kintanpatel.customrecylerview.model.LoginBean;
    import io.reactivex.Observable;
    import retrofit2.Response;
    import retrofit2.http.GET;
    import retrofit2.http.Query;

    /**
     * Created by kintan on 8/11/18.
     */

    public interface APIService {
//Add All your method here

        @GET("auth/mobile_login.php/")
        Observable<Response<ArrayList<LoginBean>>> doLogin(@Query("login") String login, @Query("password") String password);

    }

That's it now call your API stuff here,

private void doLoginAPI(String userName, String password) {
        //Show Loading here
        MyRetrofit.getInstance().create(APIService.class).doLogin(userName, password)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Response<ArrayList<LoginBean>>>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(Response<ArrayList<LoginBean>> response) {
                        //Hide Loading here
                        if (response.isSuccessful()) { //check HTTP Response
                            LoginBean bean = response.body().get(0);
                            Log.e("USER_INFO", "User ID: " + bean.getUserId());
                            Log.e("USER_INFO", "User First Name : " + bean.getFirstName());
                            Log.e("USER_INFO", "User Last Name : " + bean.getLastName());
                            Log.e("USER_INFO", "User Email : " + bean.getEmail());

                        }
                    }
                    @Override
                    public void onError(Throwable e) {
                        Log.e("USER_INFO", e.getMessage());
                    }

                    @Override
                    public void onComplete() {

                    }
                });

    }

And just call your method where you want:

 doLoginAPI("test.name@gmail.com", "130989");

And Your output like,

11-08 14:44:59.946 25447-25447/com.kintanpatel.baserecyclerview E/USER_INFO: User ID: 1601470
11-08 14:44:59.946 25447-25447/com.kintanpatel.baserecyclerview E/USER_INFO: User First Name : TestName
11-08 14:44:59.946 25447-25447/com.kintanpatel.baserecyclerview E/USER_INFO: User Last Name : null
11-08 14:44:59.946 25447-25447/com.kintanpatel.baserecyclerview E/USER_INFO: User Email : test.name@gmail.com

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM