javahttp-headershttprequestandroid-volleyalexa-voice-service

Set simple string header (not key/value pair) in Volley HTTP POST request


EDIT: It looks like the main issue is setting a string-only header without key/value pairs and associated separators, since running a manual curl request with no "="s got me a server response, therefore I've edited the title.

I am trying to send a POST request to authenticate as described in this Amazon Alexa tutorial, from an Android app, using Volley.

For the second request I am sending (which requires a header), I receive a 400 server error, indicating a bad request.

According to the tutorial page, this is what the request header should look like:

The request must include the following headers:

POST /auth/o2/token
Host: api.amazon.com
Content-Type: application/x-www-form-urlencoded

If I use the regular getHeaders() method for the Volley Request class to override the headers, I can only set a hashmap, which results in the following header format:

{Host=api.amazon.com, Content-Type=application/x-www-form-urlencoded}

(or {POST=/auth/o2/token, Host=api.amazon.com, Content-Type=application/x-www-form-urlencoded} if I include another line for the first bit)

Being new to Volley in general, I wonder if I'm missing something really obvious here. This is the request I am sending:

StringRequest tokenPoller = new StringRequest(
        Request.Method.POST,
        "https://api.amazon.com/auth/O2/token",
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.i("volley", response);
            }
        },
        new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                VolleyLog.d("volley", "Error: " + error.getMessage());
                error.printStackTrace();
            }
        }) {

    @Override
    public String getBodyContentType() {
        return "application/x-www-form-urlencoded";
    }

    @Override
    protected Map<String, String> getParams() throws AuthFailureError {
        Map<String, String> params = new HashMap<String, String>();
        params.put("grant_type", "device_code");
        params.put("device_code", {{my device code from previous request}});
        params.put("user_code", {{my user code from previous request}});
        return params;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        Map<String, String>  headers = new HashMap<String, String>();
        headers.put("Host", "api.amazon.com");
        headers.put("Content-Type", "application/x-www-form-urlencoded");

        return headers;
    }
};

I suspect that something about the headers is off, but I really can't put my finger on it. I've tried not overriding the headers too, to no avail. Any pointers will be highly appreciated! Thanks!


Solution

  • The 400 response in this case did not mean that my request was malformed. It was just the response code the server returns with all errors connected to the authorization process, in my case e.g. authorization_pending. Since I wasn't requesting the error message (via Volley or otherwise), I did not see that until much later.

    While the tutorial does mention that several kinds of responses can be delivered while polling for a token, it does not mention that they are in fact error and error description for a 400 response.

    I ended up switching from Volley to HttpsUrlConnection, and used this question's responses to receive the error and error message as a json response and implement the respective reactions.

    In the end, my HTTP request looked like this:

    String stringUrl = "https://api.amazon.com/auth/o2/token";
    URL url = null;
    
    try {
        url = new URL(stringUrl);
    } catch (MalformedURLException exception) {
        Log.e(TAG, "Error with creating URL", exception);
    }
    
    String response = "";
    HttpsURLConnection conn = null;
    HashMap<String, String> params = new HashMap<String, String>();
    String scope_data = null;
    
    
    try {
        params.put("grant_type", "device_code");
        params.put("device_code", mDeviceCode);
        params.put("user_code", mUserCode);
        Set set = params.entrySet();
        Iterator i = set.iterator();
        StringBuilder postData = new StringBuilder();
        for (Map.Entry<String, String> param : params.entrySet()) {
            if (postData.length() != 0) {
                postData.append('&');
            }
            postData.append(URLEncoder.encode(param.getKey(), "UTF-8"));
            postData.append('=');
            postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8"));
        }
        byte[] postDataBytes = postData.toString().getBytes("UTF-8");
    
    
        conn = (HttpsURLConnection) url.openConnection();
    
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    
        conn.setDoOutput(true);
        conn.getOutputStream().write(postDataBytes);
    
        InputStream inputStream = null;
    
        try {
            inputStream = conn.getInputStream();
        } catch(IOException exception) {
            inputStream = conn.getErrorStream();
        }
    
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder builder = new StringBuilder();
    
        for (String line = null; (line = reader.readLine()) != null;) {
            builder.append(line).append("\n");
        }
    
        reader.close();
        conn.disconnect();
    
        response = builder.toString();