When your server understands the request and wants to send back the data client requested, you send a 200. When your server understands the request but you will not send back the data the client requested, you send a 422. And that's exactly how my JSON API works. When a model is saved, I send 200. When model contains validation errors, I send 422:
respond_to do |format|
if #user.persisted?
format.json do
render json: { id: #user.id }, status: 200
end
else
format.json do
render json: { error: #user.errors.full_messages }, status: 422
end
end
end
Unfortunately, when I send a 422 to Android, HttpURLConnection throws an exception when trying to access the input stream:
W/System.err: java.io.FileNotFoundException: http://10.0.2.2:3001/users/auth/facebook/callback.json
Now, if I change 422 to 200 in my JSON API, then no exception is raised and I am able to parse the data.
url = new URL(OMNI_AUTH_CALLBACK);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setReadTimeout(10000);
urlConnection.setConnectTimeout(15000);
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
OutputStream os = urlConnection.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
writer.write(getQuery(params[0]));
writer.flush();
writer.close();
os.close();
int status = urlConnection.getResponseCode();
InputStream in = urlConnection.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
int data = reader.read();
while(data != -1) {
char current = (char) data;
result += current;
data = reader.read();
}
But the response should NOT be a 200, because there was an issue saving the data. What is an Android developer to do?
If you get a non-success response code, read the error stream.
HttpURLConnection httpConn = (HttpURLConnection)_urlConnection;
InputStream _is;
if (httpConn.getResponseCode() >= 400) {
_is = httpConn.getInputStream();
} else {
/* error from server */
_is = httpConn.getErrorStream();
}
Here's the bug, closed as WNF:
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4513568
The fact that it's throwing FNFE for a 422 response is seriously messed up. If you look at the source for HttpURLConnection, it doesn't even define 422.
Related
I am having trouble developing a Android app that I want to authenticate with Django REST framework to securely access information. I am being successfully issued a REST token but IsAuthenticated remains false for all of my views.
In Django, I have a class based view that responds if both authentication.TokenAuthentication permissions.IsAuthenticated are valid:
class TestAuthView(APIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request, format=None):
return GetRestData()
In Android, I acquire a token by POSTing my uname and passwd to the default url: /rest-auth/login/which responds with token: {"key":"c03c1238ab99d91301d34567bda9d417d2b48c0c"}
public static String getResponseFromHttpUrl(String... params) throws IOException {
ArrayList<AbstractMap.SimpleEntry<String,String>> paramssss = new ArrayList<>();
paramssss.add(new AbstractMap.SimpleEntry<>("username", "root"));
paramssss.add(new AbstractMap.SimpleEntry<>("password", "mypass"));
URL url = new URL(params[0]);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setReadTimeout(3000);
urlConnection.setConnectTimeout(3000);
urlConnection.setRequestMethod("POST");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
OutputStream os = urlConnection.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
writer.write(getQuery(paramssss));
writer.flush();
writer.close();
os.close();
urlConnection.connect();
try {
InputStream in = urlConnection.getInputStream();
Scanner scanner = new Scanner(in);
scanner.useDelimiter("\\A");
boolean hasInput = scanner.hasNext();
if (hasInput) {
return scanner.next(); //eg. {"key":"c03c1238ab99d91301d34567bda9d417d2b48c0c"}
} else {
return null;
}
} finally {
urlConnection.disconnect();
}
}
I then store the token and later use it to request some data:
#Override
protected String doInBackground(String... sUrl) {
try {
URL url = new URL(sUrl[0]);
URLConnection urlConnection = url.openConnection();
String authToken = "c03c1238ab99d91301d34567bda9d417d2b48c0c"; //just use a constant string for now..
urlConnection.addRequestProperty("Authorization", "Token " + authToken);
urlConnection.addRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
urlConnection.connect();
...
process the response
...
Looking at the Django logs I see that login succeeds but the GET request fails with HTTP_401_UNAUTHORIZED:
[08/Oct/2019 22:18:53] "POST /rest-auth/login/ HTTP/1.1" 200 50
[08/Oct/2019 22:18:53] "GET /update/ HTTP/1.1" 401 58
When I change the permission_classes to AllowAny:
class TestAuthView(APIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.AllowAny,) //Changed this!!!
def get(self, request, format=None):
return GetRestData()
The response contains the expected REST data and everything succeeds:
[08/Oct/2019 22:24:57] "POST /rest-auth/login/ HTTP/1.1" 200 50
[08/Oct/2019 22:25:02] "GET /update/ HTTP/1.1" 200 19451876
I don't understand how I should properly authenticate my Android app so that IsAuthenticated will not always be False?
Currently I submit a username and password to /rest-auth/login/ and am issued a rest token. But must I also login somewhere else to get a CSRF token and use that as well?
I am not familiar with the need for permissions.IsAuthenticated and if it is even valid for Android apps? I mean do I just leave the permission as AllowAny for non-browser Android apps? I feel it's a bad idea..?
I've been plugging at this for a few days and would kindly appreciate any help!
I have this situation: there is a Jersey rest web service working via HTTPS. I'm working on an Android code that will POST Json to that service and read response. I managed to connect, from API log i can see that call has been received and no errors are displayed, i also get data in the "InputStream" of the connection, but data is encrypted! Even when i try using plain HTTP connection and run web service in HTTP mode result is the same.
What confuses me is that equal cURL request produces proper output without any issues... Where am i wrong? This is the code i'm using:
URL url = new URL("https://myServer:8780/api/<apicall>");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// Create the SSL connection
SSLContext sc;
sc = SSLContext.getInstance("TLS");
sc.init(null, null, new java.security.SecureRandom());
conn.setSSLSocketFactory(sc.getSocketFactory());
// set Timeout and method
conn.setReadTimeout(7000);
conn.setConnectTimeout(7000);
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setRequestProperty("Accept-Encoding", "gzip");
conn.setRequestProperty("Accept", "application/json");
// Add any data you wish to post here
String json = "{\"test\":\"hello\"}";
byte[] bytes = json.getBytes("UTF-8");
OutputStream out = conn.getOutputStream();
out.write(bytes);
out.flush();
out.close();
String result = new String();
InputStream is = null;
/*if ("gzip".equals(conn.getContentEncoding()))
is = new GZIPInputStream(conn.getInputStream()); THIS WOULD FAIL WITH ERROR MESSAGE THAT CONTENT IS NOT IN GZIP FORMAT!
else*/
is = conn.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(is));
String inputLine;
while ((inputLine = in.readLine()) != null) {
result += inputLine;
}
In the same time, this cURL call (i'm using Linux) works perfectly and returns expected JSON response from the server:
curl -H 'Accept-Encoding: gzip' -X POST https://myServer:8780/api/<apicall> -d '{"test":"hello"}'
EDIT Issue was caused by my server using both "Content-encoding: gzip" and "Transfer-encoding: gzip". When i removed "Transfer-Encoding" issue was solved!
I am trying to follow the tutorial here https://developer.android.com/reference/java/net/HttpURLConnection.html
to make a post call in android. The issue I am having it I am not sure how to write the post call to the http connection.
URL url = new URL("https://chart.googleapis.com/chart");
HttpURLConnection client = null;
client = (HttpURLConnection) url.openConnection();
client.setRequestMethod("POST");
client.setRequestProperty("cht", "lc");
client.setRequestProperty("chtt", "This is | my chart");
client.setRequestProperty("chs", "300x200");
client.setRequestProperty("chxt", "x");
client.setRequestProperty("chd", "t:40,20,50,20,100");
client.setDoOutput(true);
client.setChunkedStreamingMode(0);
OutputStream outputPost = new BufferedOutputStream(client.getOutputStream());
outputPost.write(client.getRequestProperties().toString().getBytes());
outputPost.flush();
outputPost.close();
InputStream in = new BufferedInputStream(client.getInputStream());
Log.d(TAG, "Input" + in.read());
client.disconnect();
} catch (MalformedURLException error) {
//Handles an incorrectly entered URL
Log.d(TAG, "MalformedURL");
} catch (SocketTimeoutException error) {
//Handles URL access timeout.
Log.d(TAG, "Socket Timeout");
} catch (IOException error) {
//Handles input and output errors
Log.d(TAG, "IOexception");
}
The tutorial uses a custom method to write the stream but I still run into writing an unknown number of bytes for the body of the POST.
Questions to consider:
Does the Google chart API require information to be sent via the header variables?
Does the Google chart API require information to be sent in the body?
Is the information in the body being sent in the correct format?
Missing Content-Type header variable
Why is the same data being set in the header and body?
After reading the Google Chart Guide the following code will successfully make a POST request to the Google Chart API and retrieve the image as bytes.
To write the post data see the following line in the getImage code sample: con.getOutputStream().write(postDataBytes);
Take note of the following line to set the post size: con.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
public byte[] getImage() throws IOException {
URL obj = new URL("https://chart.googleapis.com/chart");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// Store the post data
Map<String,Object> params = new LinkedHashMap<>();
params.put("cht", "lc");
params.put("chtt", "This is | my chart");
params.put("chs", "300x200");
params.put("chxt", "x");
params.put("chd", "t:40,20,50,20,100");
// Build the post data into appropriate format
StringBuilder postData = new StringBuilder();
for (Map.Entry<String,Object> 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");
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
con.setDoOutput(true);
// post the data
con.getOutputStream().write(postDataBytes);
// opens input stream from the HTTP connection
InputStream inputStream = con.getInputStream();
// read the data from response
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] byteChunk = new byte[4096]; // Or whatever size you want to read in at a time.
int n;
while ( (n = inputStream.read(byteChunk)) > 0 ) {
baos.write(byteChunk, 0, n);
}
inputStream.close();
return baos.toByteArray();
}
I am using the code below to fetch data from server. When the data is updated on server, I get the old data from this method. When I use the same method in the web browser i get updated data.
Even when I stop the app and start again it reflects old data but when I have cleaned all my tasks using task manager, I get new data.
Is the data being cached on the device as i am making new request each time
String response = null;
InputStream inputStream = null;
HttpURLConnection urlConnection = null;
try {
URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setReadTimeout(10000);
urlConnection.setConnectTimeout(15000);
if (method == POST) {
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestMethod("POST");
OutputStream os = urlConnection.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
writer.write(params);
writer.flush();
writer.close();
os.close();
} else {
urlConnection.setRequestMethod("GET");
}
int statusCode = urlConnection.getResponseCode();
/* 200 represents HTTP OK */
if (statusCode == HttpURLConnection.HTTP_OK) {
inputStream = new BufferedInputStream(urlConnection.getInputStream());
response = convertInputStreamToString(inputStream);
return response;
}
I searched the web and found that use cache is on by default, so these two line might help
urlConnection.setUseCaches(false);
urlConnection.addRequestProperty("Cache-Control", "no-cache");
Append some random parameter i.e. current timestamp to the URL then it will treat as fresh request.
Change This
URL url = new URL(urlString);
To
URL url = new URL(urlString+new Date().getTime());
I am trying to login through url and i am getting status code 500 in httpurlconnevtion
public static String excutePost(String targetURL, String urlParameters)
{
URL url;
HttpURLConnection connection = null;
try {
//Create connection
url = new URL(targetURL);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Length", "" +
Integer.toString(urlParameters.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setRequestProperty("Accept-Encoding", "");
connection.setUseCaches (false);
connection.setDoInput(true);
connection.setDoOutput(true);
//Send request
DataOutputStream wr = new DataOutputStream (
connection.getOutputStream ());
wr.writeBytes (urlParameters);
wr.flush ();
wr.close ();
System.out.println("status :"+connection.getResponseCode());
System.out.println("getErrorStream() :"+connection.getErrorStream());
//Get Response
InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if(connection != null) {
connection.disconnect();
}
}
}
and my params are
String urlParameters =
"pwd1=" + URLEncoder.encode("DEMO123", "UTF-8") +
"&badge=" + URLEncoder.encode("1233", "UTF-8");
i am getting logcat
status :500
getErrorStream() :libcore.net.http.FixedLengthInputStream#417bc5c0
thank you
**EDITED 2**
I have also try with
DataOutputStream dos = new DataOutputStream(connection.getOutputStream());
// Add badge
dos.writeBytes(LINE_START + BOUNDRY + LINE_END);
dos.writeBytes("Content-Disposition: form-data; name='badge';");
dos.writeBytes(LINE_END + LINE_END);
dos.writeBytes("1233");
dos.writeBytes(LINE_END);
// Add password
dos.writeBytes(LINE_START + BOUNDRY + LINE_END);
dos.writeBytes("Content-Disposition: form-data; name='pwd1';");
dos.writeBytes(LINE_END + LINE_END);
dos.writeBytes("DEMO123");
dos.writeBytes(LINE_END);
500 denotes an Internal Server Error. There is probably no error on your side, it's on the server. Even if you are sending something incorrectly and it's causing the server to return 500, it's still a server problem.
Edit:
Okey, the server should rather return something like 400 Bad Request instead of 500 Internal Server Error but I found your error now:
connection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
...
//Send request
DataOutputStream wr = new DataOutputStream (connection.getOutputStream ());
wr.writeBytes (urlParameters);
The problem here is that your first get the the bytes from urlParameters using getBytes which (quoting javadoc):
Encodes this String into a sequence of bytes using the platform's default charset
And then you write the bytes using DataOutputStream.writeBytes which (quoting javadoc):
Each character in the string is written out, in sequence, by discarding its high eight bits.
In summary, your Content-Length doesn't match the data. So the server returns you the
java.io.IOException: exceeded content-length limit of 20 bytes
Solution:
//consider urlParameters.getBytes("UTF-8") method instead of using default encoding
byte[] bodyData = urlParameters.getBytes();
connection.setRequestProperty("Content-Length", Integer.toString(bodyData.length));
...
//Send request
InputStream out = connection.getOutputStream();
out.write(bodyData);
Edit 2:
Edit 1 is definitely valid, however, looking over the problem again, I believe the error is definitely caused by the server. I think the server is returning a bad Content-Length header and, when the data is read on Android, the system realizes there is more data coming from the server than it should be by the Content-Length and throws an exception, also replacing the status code by 500 because it really is a server error.
Edit 3:
connection.setRequestProperty("Content-Language", "en-US");
connection.setRequestProperty("Accept-Encoding", "");
Instead of setting Content-Language which is not neccessary here, you should set Content-Encoding to UTF-8 and instead of empty Accept-Encoding, you should add the real expected MIME-type. I believe this is a server error, but you maybe it won't appear if you set the request headers correctly.
Status code 500 means Internal Server Error. Why this is thrown at you, only the server behind targetURL knows.
Verify that you're making correct usage of the API. Taking a look at the response's body (besides the status code) may provide a hint.