Search code examples
javaandroidtwitter

Twitter - Bad Request (400) for Android Java, working in pure Java


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.


Solution

  • 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.)