amitshekhariitbhu / Fast-Android-Networking

🚀 A Complete Fast Android Networking Library that also supports HTTP/2 🚀
https://outcomeschool.com
Apache License 2.0
5.7k stars 958 forks source link

Server shows empty body when using POST addJSONObjectBody #53

Closed paulpv closed 7 years ago

paulpv commented 8 years ago

Symptoms: When POSTing a valid JSONObject (89 characters deserialized) to an Ubuntu Apache Slim PHP (http://www.slimframework.com, https://github.com/slimphp/Slim) server, both Stetho and AnalyticsListener say that 89 bytes was sent, but the server sees only an empty/null body. When I make what I believe to be the exact same request using Volley the server responds just fine as expected. (I have yet to get Stetho to work w/ Volley, so I cannot confirm how identical the request is or isn't)

I believe that my code is pretty straightforward:

public String requestJSONObject(
        final String tag,
        @NonNull
        Uri uri,
        HashMap<String, String> bodyParameterMap)
{
    JSONObject jsonObjectBody;
    if (bodyParameterMap == null)
    {
        jsonObjectBody = null;
    }
    else
    {
        jsonObjectBody = new JSONObject();
        for (Entry<String, String> entry : bodyParameterMap.entrySet())
        {
            String key = entry.getKey();
            String value = entry.getValue();
            try
            {
                jsonObjectBody.put(key, value);
            }
            catch (JSONException e)
            {
                // ignore
            }
        }
    }

    ANRequest request = AndroidNetworking.post(uri.toString())
            .setTag(tag)
            .addHeaders("User-Agent", mUserAgent)
            .addHeaders("APP-ID", mAppId)
            .addHeaders("Debug", "true")
            .addJSONObjectBody(jsonObjectBody)
            .build();

    request.setAnalyticsListener(new AnalyticsListener()
    {
        @Override
        public void onReceived(long timeTakenInMillis, long bytesSent, long bytesReceived, boolean isFromCache)
        {
            Log.e(TAG, " timeTakenInMillis : " + timeTakenInMillis);
            Log.e(TAG, " bytesSent : " + bytesSent);
            Log.e(TAG, " bytesReceived : " + bytesReceived);
            Log.e(TAG, " isFromCache : " + isFromCache);
        }
    });

    request.getAsOkHttpResponseAndJSONObject(new OkHttpResponseAndJSONObjectRequestListener()
    {
        @Override
        public void onResponse(Response okHttpResponse, JSONObject response)
        {
            Log.i(TAG, "requestJSONObject(tag=" + tag + ") onResponse: response=" + response);
            //...
        }

        @Override
        public void onError(ANError anError)
        {
            Log.w(TAG, "requestJSONObject(tag=" + tag + ") onError: anError=" + Utils.toString(anError));
            //...
        }
    });

    return tag;
}

Usage:

Uri uri = Uri.parse("http://api.redacted.com")
    .buildUpon()
    .appendPath("oauth2")
    .appendPath("token")
    .build();
HashMap<String, String> postBodyParameters = new HashMap<>();
postBodyParameters.put("grant_type", "refresh_token");
postBodyParameters.put("refresh_token", refreshToken);
requestJSONObject("mytag", uri, postBodyParameters);

Stetho sniff:

Request URL:http://api.redacted.com/oauth2/token
Request Method:POST
Status Code:400 Bad Request
Request Headers:
Accept-Encoding:gzip
Connection:Keep-Alive
Content-Encoding:gzip
Content-Length:95
Content-Type:application/json; charset=utf-8
Host:api.redacted.com
APP-ID:redacted
User-Agent:Dalvik/2.1.0 (Linux; U; Android 5.1.1; Nexus 5 Build/LMY48B); com.redacted.testapp 1.0
Request Payload:
{"refresh_token":"redacted","grant_type":"refresh_token"}
Response Headers:
Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection:close
Content-Length:101
Content-Type:application/json; charset=utf-8
Date:Tue, 08 Nov 2016 22:34:26 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT
OkHttp-Received-Millis:1478644376230
OkHttp-Sent-Millis:1478644375903
Pragma:no-cache
Server:Apache/2.4.7 (Ubuntu)
Set-Cookie:PHPSESSID=au8iauf08b2i3qnu4nbrd40ts6; path=/
X-Powered-By:PHP/5.5.9-1ubuntu4.20
Response Payload:
{"error":[{"message":"Post body is empty.","transaction_id":"redacted"}]}{"post_body":null}

(I had my web developer output to the response the post body that he read.)

Any idea why AN doesn't work, but Volley does?

paulpv commented 8 years ago

FYI, here is my working Volley code:

mRequestQueue.add(new JsonObjectRequest(uri.toString(), jsonObjectBody, new Listener<JSONObject>()
{
    @Override
    public void onResponse(JSONObject response)
    {
        Log.i(TAG, "requestJSONObject(tag=" + tag + ") onResponse: response=" + response);
        //...
    }
}, new ErrorListener()
{
    @Override
    public void onErrorResponse(VolleyError error)
    {
        Log.w(TAG, "requestJSONObject(tag=" + tag + ") onError: error=" + error);
        //...
    }
})
{
    @Override
    public String getBodyContentType()
    {
        return "application/json; charset=utf-8";
    }

    @Override
    public Map<String, String> getHeaders()
            throws AuthFailureError
    {
        Map<String, String> headers = super.getHeaders();
        if (headers == null || headers.equals(Collections.emptyMap()))
        {
            headers = new HashMap<>();
        }

        headers.put("User-Agent", mUserAgent);
        headers.put("APP-ID", mAppId);
        headers.put("Debug", "true");

        return headers;
    }
});
amitshekhariitbhu commented 8 years ago

I just created the same situation to test it.

API - which require both and in JSONObject. otherwise it send error with error body as bad parameter

When I make request it is working perfectly. Still trying to figure out the problem you are facing.

        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("firstname", "Amit");
//            jsonObject.put("lastname", "Shekhar");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        AndroidNetworking.post("https://fierce-cove-29863.herokuapp.com/createAnUser")
                .addJSONObjectBody(jsonObject)
                .setTag(this)
                .setPriority(Priority.LOW)
                .build()
                .getAsOkHttpResponseAndJSONObject(new OkHttpResponseAndJSONObjectRequestListener() {
                    @Override
                    public void onResponse(Response okHttpResponse, JSONObject response) {

                    }

                    @Override
                    public void onError(ANError error) {

                    }
                });

Thanks Amit Shekhar

paulpv commented 8 years ago

Thanks for the response. Can you clarify your statement? What causes "otherwise it send error with error body as bad parameter", or is everything "it is working perfectly"?

amitshekhariitbhu commented 8 years ago

When I make request with correct data, it is giving me in onResponse method

 jsonObject.put("firstname", "Amit");
 jsonObject.put("lastname", "Shekhar");

When I make request with wrong data, it is giving me in onError method with errorBody as {"error":"bad parameter"}

 jsonObject.put("firstname", "Amit");
// not setting lastname.
paulpv commented 8 years ago

That sounds to me like a server side logic choice that is probably by design. What happens if you post a single field using Volley? I still can't figure out why Volley works, but AN doesn't. :/

paulpv commented 8 years ago

Today's investigation is leading me to believe that the problem I am seeing is related to the GzipRequestInterceptor.

OkHttpClient.Builder builder = new OkHttpClient.Builder();
//builder.addInterceptor(new GzipRequestInterceptor());
if (BuildConfig.DEBUG)
{
    builder.addNetworkInterceptor(new StethoInterceptor());
}
OkHttpClient httpClient = builder.build();
AndroidNetworking.initialize(applicationContext, httpClient);

When I comment out the GzipRequestInterceptor line, my server logic seems to work. I can confirm that my server used to be set up to accept gzip encoding, but from looking at the headers in my first entry above, the server isn't responding w/ gzip, so perhaps it got disabled. I will confirm w/ my web dev as soon as he recovers from last night's US election...

paulpv commented 8 years ago

Yup! The problem is with adding GzipRequestInterceptor when a server have gzip enabled. gzip capabilities need to be tested as supported on the server before enabled. Which begs a general question, which may end up being an okhttp question: How should this negotiation be tested and enabled on a per-server basis?