1

I'm trying to make a post call with HttpURLConnection & sending JSON data via HttpURLConnection's OutputStream:

See my class below:

package com.app.data.pojos;

import android.os.AsyncTask;
import android.util.Log;
import com.app.xyz.data.GDParser;
import com.app.xyz.model.GDUser;
import com.app.xyz.model.GDUserStatus;
import com.app.xyz.utils.APrefs;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * Created by arynaim on 10/12/17.
 */

public class PostUserStatus extends AsyncTask<String, Void, GDUserStatusResponse> {

    private static final String TAG = "PostUserStatus";
    private GDUser gdUser;

    public PostUserStatus(GDUser gdUser) {
        this.gdUser = gdUser;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();

    }

    @Override
    protected GDUserStatusResponse doInBackground(String... urls) {

        Log.i(TAG,"doInBackground(), gDUserStatusResponse url:"+urls[0]);
        HttpURLConnection httpURLConnection = null;
        GDUserStatus gdUserStatus = null;
        JsonElement jsonElemnt = null;
        JsonObject jsonObject = null;
        GDUserStatusResponse gDUserStatusResponse = null;
        try {
            URL url = new URL(urls[0]);
            httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setRequestProperty("Content-length", "0");
            httpURLConnection.setUseCaches(false);
            httpURLConnection.setAllowUserInteraction(false);
            httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            httpURLConnection.setRequestProperty("Authorization", new APrefs().getNMToken());
            //httpURLConnection.connect();
            JSONObject userIdJsonObject = new JSONObject();
            userIdJsonObject.put("userID",getGdUser().getUserId());
            //post json via OutputStream
            OutputStream os = httpURLConnection.getOutputStream();
            os.write(userIdJsonObject.toString().getBytes());
            os.flush();
            Log.i(TAG,"doInBackground(), http request"+url.toString());


            int status = httpURLConnection.getResponseCode();

            switch (status) {
                case 200:
                    Log.i(TAG,"doInBackground(), http response status:"+status);
                    BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));
                    StringBuilder sb = new StringBuilder();
                    String line;
                    while ((line = br.readLine()) != null) {
                        sb.append(line + "\n");
                    }

                    jsonElemnt = new JsonParser().parse(sb.toString());
                    jsonObject = jsonElemnt.getAsJsonObject();
                    gdUserStatus = new GDParser().parseStatusResponse(jsonObject);
                    gDUserStatusResponse = new GDUserStatusResponse();
                    gDUserStatusResponse.setGdUserStatus(gdUserStatus);
                    br.close();
                    break;
                default:
                    Log.e(TAG,"doInBackground(), http response error status:"+status);
                    //TODO: handle error responses
                    //TODO: if stale token refresh & try again
                    break;

            }



        } catch (IOException ex) {

            ex.printStackTrace();

        } catch (Exception ex) {

            ex.printStackTrace();

        } finally {
            if (httpURLConnection != null) {
                try {
                    httpURLConnection.disconnect();
                } catch (Exception ex) {
                ex.printStackTrace();
                }
            }
        }

        return gDUserStatusResponse;
    }//end doInBackgroud

    protected void onPostExecute(GDUserStatusResponse gDUserStatusResponse) {

        Log.i(TAG,"onPostExecute(), gDUserStatusResponse:"+gDUserStatusResponse);

    }

    public GDUser getGdUser() {
        return gdUser;
    }

    public void setGdUser(GDUser gdUser) {
        this.gdUser = gdUser;
    }
}

However when I write to

os.write(userIdJsonObject.toString().getBytes());

I'm getting this exception:

10-13 16:30:46.737 15395-17467/com.app.xyz I/PostUserStatus: doInBackground(), gDUserStatusResponse url:https://development.xyz.com/api/v1/users/status
10-13 16:30:46.741 15395-17467/com.app.xyz I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
10-13 16:30:46.741 15395-17467/com.app.xyz I/System.out: (HTTPLog)-Static: isSBSettingEnabled false
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err: java.net.ProtocolException: exceeded content-length limit of 0 bytes
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at com.android.okhttp.internal.http.RetryableSink.write(RetryableSink.java:58)
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at com.android.okhttp.okio.RealBufferedSink.flush(RealBufferedSink.java:221)
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at com.android.okhttp.okio.RealBufferedSink$1.flush(RealBufferedSink.java:204)
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at com.app.xyz.data.pojos.PostUserStatus.doInBackground(PostUserStatus.java:63)
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at com.app.xyz.data.pojos.PostUserStatus.doInBackground(PostUserStatus.java:24)
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at android.os.AsyncTask$2.call(AsyncTask.java:304)
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
10-13 16:30:46.833 15395-17467/com.app.xyz W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
10-13 16:30:46.834 15395-17467/com.app.xyz W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
10-13 16:30:46.834 15395-17467/com.app.xyz W/System.err:     at java.lang.Thread.run(Thread.java:762)
10-13 16:30:46.835 15395-15395/com.app.xyz I/PostUserStatus: onPostExecute(), gDUserStatusResponse:null

I don't want to use any Http libraries other than the base provided with Android/Java.

user207421
  • 305,947
  • 44
  • 307
  • 483
cyber101
  • 2,822
  • 14
  • 50
  • 93

2 Answers2

1
httpURLConnection.setRequestProperty("Content-length", "0");

Remove this. You don't need to set the content-length at all with this class, and certainly not to zero if you intend to send a message body.

httpURLConnection.setRequestMethod("POST");

Replace this with

httpURLConnection.setDoOutput(true);

You're not allowed to do any output otherwise.

EDIT For the sceptics, the following code does not set the content-length and yet 'correctly' returns a 405 response code after having sent a Content-length: 9\r\n header on the wire, provided automatically by HttpURLConnection (and also a request type of POST and a Content-Type: application/x-www-form-urlencoded\r\n header, neither set by this code):

    URL url = new URL("http://google.com");
    HttpURLConnection conn = (HttpURLConnection)url.openConnection();
    conn.setDoOutput(true);
    OutputStream    out = conn.getOutputStream();
    out.write("xxxx=yyyy".getBytes());
    out.flush();
    int responseCode = conn.getResponseCode();
    System.out.println("responseCode="+responseCode);
    InputStream in = (responseCode == 200) ? conn.getInputStream() : conn.getErrorStream();
    int c;
    while ((c = in.read()) != -1)
    {
        System.out.write(c);
    }
    in.close();
    conn.disconnect();
user207421
  • 305,947
  • 44
  • 307
  • 483
  • 1
    I tried removing httpURLConnection.setRequestMethod("POST"); & just using httpURLConnection.setDoOutput(true); , I also tried keeping both, but still the same error. – cyber101 Oct 14 '17 at 00:33
  • Did you remove your invalid `Content-length` setting? Did you read this answer? – user207421 Oct 19 '17 at 09:39
-1

I was not aware that POST + output was feasible. But the error especially mentions the Content-Length 0.

    JSONObject userIdJsonObject = new JSONObject();
    userIdJsonObject.put("userID",getGdUser().getUserId());
    byte[] content = userIdJsonObject.toString().getBytes();

    httpURLConnection.setDoOutput(true);
    httpURLConnection.setRequestProperty("Content-length",
            String.valueOf(content.length));
    ...
    OutputStream os = httpURLConnection.getOutputStream();
    os.write(content);
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • I don't understand your first sentence. How else would you do a POST? You should not set the content length. Java does it for you. – user207421 Oct 14 '17 at 00:21
  • @I was doing POST and JSON using retrofit http API without any issue. How would I write a JSON to the HttpURLConnection it seems the code is pretty straight forward. Thanks – cyber101 Oct 14 '17 at 00:35
  • @StephenC Actually it does, and that is exactly how it does it. That is also why nothing gets sent until you call a method that receives. Unless you're using chunked or fixed length mode. Your last sentence is inconsistent wth your assertion. – user207421 Oct 14 '17 at 00:55
  • No it isn't. Look at the stuff that is sent over the wire. There will be no content-length header at all in the request. Ergo ... NOTHING is setting it for you. – Stephen C Oct 14 '17 at 01:03
  • @StephenC I'm 600k away from being able to do that, but I've seen it many times. If it didn't do that, HTTP connection pooling and keepalive would be impossible, unless everybody used chunked or fixed length transfer mode. I've never set a content-length header in 20 years. There are many accepted answers here saying the same thing. – user207421 Oct 14 '17 at 01:10
  • @EJP - You are not understanding what I am saying. I am saying that your assertion that "Java does it for you" is incorrect. It doesn't set it for you. Instead, the Java client side leaves the header unset, and (maybe) uses chunking instead. But I am not saying the you >>need to<< set the header. That's clearly not true ... unless the remote webapp **requires** a content-length header for some reason. – Stephen C Oct 14 '17 at 01:38
  • Note that there is a qualitative difference between content-length and chunking at the remote end. The webapp (or the server-side HTTP stack) can access a content-length header before reading the content and use this to (say) preallocate space. If there is no content-length, the webapp / stack can't do that ... so if the payload cannot be processed "stream-wise" it has to use a read / grow buffer strategy. – Stephen C Oct 14 '17 at 01:51
  • I would have revoked my answer, were it not for the valuable comments. Unfortunately the OP still has no (accepted) solution at this moment. – Joop Eggen Oct 14 '17 at 09:48
  • @StephenC I did what you said. I POSTed `xxxx=yyyy` to `google.com`, via `HttpURLConnection`, without setting anhy headers at all, and specifcally not the `Content-length` header. I did nothing to the `HttpURLConnection` except call `setDoOutput(true)`, writing to its output stream, and fetching the response code. No chunking, no fixed-length transfer mode. Wireshark showed `Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2`, `Content-length: 9\r\n` and `Content-type: application/x-www-form-urlencoded\r\b` on the request. QED. – user207421 Oct 16 '17 at 02:25
  • Well yes. Now try it with an input stream. Not QED. – Stephen C Oct 16 '17 at 02:39
  • @StephenC You're dreaming. How can reading the input stream after the request has been sent determine whether the `Content-length` header was sent in the request, before the input stream was read? or even obtained? It doesn't begin to make sense. But, for completeness, I did exactly that, read the input stream to EOS, and Wireshark showed exactly the same things in the request. QEF. – user207421 Oct 16 '17 at 02:43
  • I meant output stream. The point is that the header must be written before the data: the HTTP spec says so. Therefore, HttpURLConnection would need to buffer and count all of the bytes to be sent ... before it can send the header. That's ... all kinds of broken. – Stephen C Oct 16 '17 at 02:45
  • @StephenC That's what I did. Read what I wrote. I POSTed `xxxx=yyyy` to by 'writing to its output stream'. Please try this experiment yourself before you comment again. You're just wasting time. – user207421 Oct 16 '17 at 02:46
  • OK. You are right. What is happening is that HttpURLConnection >>is<< buffering the entire output stream contents. When you call getResponseCode() it triggers the sending of the header and the buffered content. But the behavior changes if you enable chunked streaming mode. – Stephen C Oct 16 '17 at 10:45
  • @EJP,@StephenC All this back and forth with actual help on my post, could you guys exchange email and carry out this offline. – cyber101 Oct 16 '17 at 19:19
  • @StephenC All that is *exactly* what I told your disbelieving ears *two days* ago, *including* the part about chunked *and fixed-length* streaming mode. Really this is exasperating. – user207421 Oct 17 '17 at 00:35