0

I don't understand, why my Twitter read is working when I run a pure Java application and is not working when calling from an android java program. I hope someone can help me. I use the application-authentification method for the 1.1 API of twitter.

I am using the following code when executing Java and this is working:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;

import javax.net.ssl.HttpsURLConnection;

import org.apache.commons.codec.binary.Base64;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;



public class testSomething {

    private final static String getTokenURL = "https://api.twitter.com/oauth2/token";
    private static String bearerToken;

    public static final String CONSUMER_KEY = "<key>";
    public static final String CONSUMER_SECRET= "<secret>";

    /**
     * @param args
     */
    public static void main(String[] args) {

        // encodeKeys(APIKEY, APISECRET);

        new Thread(new Runnable() {

            @Override
            public void run() {
                try {

                    bearerToken = requestBearerToken(getTokenURL);

                    System.out.println("Search = " + "https://api.twitter.com/1.1/search/tweets.json?q=%23PhuseKatja&count=20");
                    System.out.println("Bearer = " + bearerToken);

                    ArrayList<Tweet> tweets = fetchSearchTweet("https://api.twitter.com/1.1/search/tweets.json?q=%23PhuseKatja&count=20",
                                                               bearerToken);

                    System.out.println(tweets.size());

                } catch (IOException e) {
                    System.out.println("IOException e");
                    e.printStackTrace();
                }
            }
        }).start();

    }

    // Encodes the consumer key and secret to create the basic authorization key
    private static String encodeKeys(String consumerKey, String consumerSecret) {
        try {
            String encodedConsumerKey = URLEncoder.encode(consumerKey, "UTF-8");
            String encodedConsumerSecret = URLEncoder.encode(consumerSecret,
                    "UTF-8");

            String fullKey = encodedConsumerKey + ":" + encodedConsumerSecret;
            byte[] encodedBytes = Base64.encodeBase64(fullKey.getBytes());

            return new String(encodedBytes);
        } catch (UnsupportedEncodingException e) {
            return new String();
        }
    }

    // Constructs the request for requesting a bearer token and returns that
    // token as a string
    public static String requestBearerToken(String endPointUrl)
            throws IOException {
        HttpsURLConnection connection = null;
        String encodedCredentials = encodeKeys(CONSUMER_KEY, CONSUMER_SECRET);

        System.out.println("encodedCredentials "+encodedCredentials);
        try {
            URL url = new URL(endPointUrl);
            connection = (HttpsURLConnection) url.openConnection();
            System.out.println(connection);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Host", "api.twitter.com");
            connection.setRequestProperty("User-Agent", "Android Phuse Application");
            connection.setRequestProperty("Authorization", "Basic " + encodedCredentials);
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
            connection.setRequestProperty("Content-Length", "29");
            connection.setUseCaches(false);

            writeRequest(connection, "grant_type=client_credentials");

            // Parse the JSON response into a JSON mapped object to fetch fields
            // from.
            JSONObject obj = (JSONObject) JSONValue.parse(readResponse(connection));

            if (obj != null) {
                String tokenType = (String) obj.get("token_type");
                String token = (String) obj.get("access_token");

                return ((tokenType.equals("bearer")) && (token != null)) ? token
                        : "";
            }
            return new String();
        } catch (MalformedURLException e) {
            throw new IOException("Invalid endpoint URL specified.", e);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }


    // Fetches the first tweet from a given user's timeline
        public static ArrayList<Tweet> fetchSearchTweet(String endPointUrl, String aBearerToken)
                throws IOException {
            HttpsURLConnection connection = null;

            ArrayList<Tweet> tweets = new ArrayList<Tweet>();

            try {
                URL url = new URL(endPointUrl);
                connection = (HttpsURLConnection) url.openConnection();
                connection.setDoOutput(true);
                connection.setDoInput(true);
                connection.setRequestMethod("GET");
                connection.setRequestProperty("Host", "api.twitter.com");
                connection.setRequestProperty("User-Agent", "anyApplication");
                connection.setRequestProperty("Authorization", "Bearer " +  aBearerToken);
                connection.setUseCaches(false);

                String response = readResponse(connection);

                System.out.println("Response = " + response);
                System.out.println(connection.getResponseMessage());
                System.out.println(connection.getResponseCode());
                System.out.println("---------------------------------");

                // Parse the JSON response into a JSON mapped object to fetch fields from.
                JSONObject objSearch = (JSONObject) JSONValue.parse(response);
                JSONArray ja = (JSONArray) objSearch.get("statuses");

                if (ja != null) {
                    for (int i = 0; i < ja.size(); i++)
                    {
                        Tweet tweet = new Tweet((((JSONObject)((JSONObject) ja.get(i)).get("user")).get("screen_name").toString()), 
                                                ((JSONObject) ja.get(i)).get("text").toString(), 
                                                (((JSONObject)((JSONObject) ja.get(i)).get("user")).get("profile_image_url").toString()));
                        tweets.add(tweet);
                    }
                }
                return tweets;
            } catch (MalformedURLException e) {
                throw new IOException("Invalid endpoint URL specified.", e);
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }

    // Writes a request to a connection
    private static boolean writeRequest(HttpURLConnection connection,
            String textBody) {
        try {
            BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(
                    connection.getOutputStream()));
            wr.write(textBody);
            wr.flush();
            wr.close();

            return true;
        } catch (IOException e) {
            return false;
        }
    }

    // Reads a response for a given connection and returns it as a string.
    private static String readResponse(HttpURLConnection connection) {
        try {
            StringBuilder str = new StringBuilder();

            BufferedReader br = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line = "";
            while ((line = br.readLine()) != null) {
                str.append(line + System.getProperty("line.separator"));
            }
            return str.toString();
        } catch (IOException e) {
            return new String();
        }
    }

    public static class Tweet {
        public String username;
        public String message;
        public String image_url;
        //public Bitmap image_bitmap;

        public Tweet(String username, String message, String url) {
            this.username  = username;
            this.message   = message;
            this.image_url = url;

            //this.image_bitmap = getBitmap(url);
        }
    }
}

When I create am Android Java application (where I finally need this), I can call the same code and it is not working. I do get the 400 "Bad response" code for my fetchSearchTweet request. The bearerToken has been received as expected and when printing the token and search string, it's all the same.

For Android I created a complete new project, activating the Internet connection, copy the same testSomething class and tried to run it. But unluckily this does not work (twitter response 400). I have no clue.

import android.os.Bundle;
import android.os.StrictMode;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);

        try {
            String bearerToken = testSomething.requestBearerToken("https://api.twitter.com/oauth2/token");
            testSomething.fetchSearchTweet("https://api.twitter.com/1.1/search/tweets.json?q=%23PhuseKatja&count=20", bearerToken);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}

I used the example from a blog and a useful question resource.


The reason why this is not working in Android-Java is within the "HttpsURLConnection" class which differs for pure Java and Android Java. I do not know whether the new Twitter API does not support the HttpsURLConnection provided by Android or whether the HttpsURLConnection from Android is not conform to the required formats.

I am using now the snapshot version of Twitter4J which supports the application-authentification mode as well.

Community
  • 1
  • 1
Katja
  • 15
  • 1
  • 4

2 Answers2

2

You are not supposed to URLEncoder.encode your keys, since it will be tranformed in Base64 afterward. Simply

return Base64.encodeToString((consumerKey + ":" + consumerSecret).getBytes(),
                    Base64.NO_WRAP);

in your encodeKeys method. (I use this exact method and have no problem with it.)

njzk2
  • 38,969
  • 7
  • 69
  • 107
  • Hm, this is not my problem. I can exchange this method and use the "android.util.Base64" package for android instead of "org.apache.commons.codec.binary.Base64". In both cases I get the same string for the bearerToken. I still get the 400 error when calling the fetchSearchTweet method with either bearerToken. – Katja Jul 19 '13 at 10:01
  • did you try to make the request using for example curl? it is faster to tweak the request and see what works. – njzk2 Jul 19 '13 at 11:41
  • I am not sure that curl could help me if it works. It is working using pure Java running on my Windows. It's just not working when using an Android Java app program, where I finally need this. If it works I would need a curl program on Android additionally? Could it be that the android java is too slow to get the response? I really wonder what the difference of a pure Java and an Android Java is in my case. – Katja Jul 19 '13 at 12:25
  • Also, I have a working example of Twitter with application auth here: https://github.com/njzk2/VolleyTwitter . It is with Volley, but you can still compare what headers / content / stuff are used. – njzk2 Jul 19 '13 at 12:35
  • I am mentionning curl because it is easy to make quick requests to an api to make sure which parameter should go where. It is not used in the actual application. – njzk2 Jul 19 '13 at 12:36
  • Thanks. I am going to check this out. Somehow the important classes are not within the github project (com.android.volley.toolbox.JsonObjectRequest and others). – Katja Jul 19 '13 at 13:03
  • the volley classes are in volley (https://android.googlesource.com/platform/frameworks/volley) – njzk2 Jul 19 '13 at 13:29
  • 1
    NO_WRAP is key here. I was using Base64.DEFAULT and it was putting line breaks in the string... – IgorGanapolsky Nov 01 '13 at 20:07
0

I know this is an old thread, however I noticed that there is no accepted answer for this. Looking for an answer to this question lead me to a comment by @IgorGanapolsky.

NO_WRAP is key here. I was using Base64.DEFAULT and it was putting line breaks in the string...

I was receiving a bad request from Twitter with no error message, changing the flag for Base64.encodeBase64(fullKey.getBytes()) to NO_WRAP made Twitter return a 200.

Cheers

Clocker
  • 1,316
  • 2
  • 19
  • 29