Downloading a portion of a File using HTTP Requests - java

I am trying to download a portion of a PDF file (just for testing "Range" header). I requested the server for the bytes (0-24) in Range but still, instead of getting first 25 bytes (a portion) out of the content, I am getting the full length content. Moreover, instead of getting response code as 206 (partial content), I'm getting response code as 200.
Here's my code:
public static void main(String a[]) {
try {
URL url = new URL("http://download.oracle.com/otn-pub/java/jdk/7u21-b11/jdk-7u21-windows-x64.exe?AuthParam=1372502269_599691fc0025a1f2da7723b644f44ece");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("Range", "Bytes=0-24");
urlConnection.connect();
System.out.println("Respnse Code: " + urlConnection.getResponseCode());
System.out.println("Content-Length: " + urlConnection.getContentLengthLong());
InputStream inputStream = urlConnection.getInputStream();
long size = 0;
while(inputStream.read() != -1 )
size++;
System.out.println("Downloaded Size: " + size);
}catch(MalformedURLException mue) {
mue.printStackTrace();
}catch(IOException ioe) {
ioe.printStackTrace();
}
}
Here's the output:
Respnse Code: 200
Content-Length: 94973848
Downloaded Size: 94973848
Thanks in Advance.

Try changing following:
urlConnection.setRequestProperty("Range", "Bytes=0-24");
with:
urlConnection.setRequestProperty("Range", "bytes=0-24");
as per the spec 14.35.1 Byte Ranges
Similarly, as per the spec 14.5 Accept-Ranges, you can also check whether your server actually supports partial content retrieval or not using following:
boolean support = urlConnection.getHeaderField("Accept-Ranges").equals("bytes");
System.out.println("Partial content retrieval support = " + (support ? "Yes" : "No));

If the server supports it (and HTTP 1.1 servers should), only then you can use range requests...
and if all you want to do is check, then just send a HEAD request instead of a GET request. Same headers, same everything, just "HEAD" instead of "GET". If you receive a 206 response, you'll know Range is supported, and otherwise you'll get a 200 response.

You have to connect to url before setRequestProperty
Change:
urlConnection.setRequestProperty("Range", "Bytes=0-24");
urlConnection.connect();
To:
urlConnection.connect();
urlConnection.setRequestProperty("Range", "Bytes=0-24");

I think the correct header is "Content-Range", not "Range" as you are using.

Related

ERROR 411 consuming API in java (POST METHOD)

I am working on a personal project to learn how to work with Rest web services.
I have an API web application in Visual, which is my controller and where the connection to Oracle is made, and a web application in JAVA and with a JSON library, in addition to trying to do it in layers according to what I learned in the institute.
When I make the GET request, I have no problem, they bring me the data, but when I make a POST request as a customer's registrar it is when the problems start and I get the error 411 in java.
I Read looking for the solution that some worked for them by placing the "Content-Length" I don't know if I put it right but I still have the problem.
public int insertarCliente(Cliente c){
globalURL += "?rut=" + c.getRut() + "&nom="+ c.getNombre() +"&app=" + c.getApellidoP() + "&apm=" + c.getApellidoM();
try {
HttpURLConnection conn = Conectar(globalURL);
conn.setRequestMethod("POST");
conn.setRequestProperty("ACCEPT", "application/json");
conn.setRequestProperty("Content-Length", "0");
if (conn.getResponseCode() == 200) {
//InputStreamReader in = new InputStreamReader(conn.getInputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String resp = br.readLine();
JSONObject obj = new JSONObject(resp);
return obj.getInt("resp");
}
} catch (Exception e) {
Logger.getLogger(ClienteDAO.class.getName()).log(Level.SEVERE, null, e);
}
return 0;
}
The problem start in the IF.
And the error that shows me is the following:
Glook2 was successfully deployed in 227 milliseconds.
**Grave: java.lang.RuntimeException: Failed : HTTP Error code : 411**
at Controllers.ClienteDAO.insertarCliente(ClienteDAO.java:50)
at Services.cliente.registrar(cliente.java:104)
at Services.cliente.processRequest(cliente.java:46)
at Services.cliente.doPost(cliente.java:77)
I must emphasize that I have proven in the POSTMAN that the web services method works and correctly adds the data to the database.
String globalURL = "http://localhost:60367/api/Cliente";
HttpURLConnection conn;
public ClienteDAO() {
conn = Conectar(this.globalURL);
}
private HttpURLConnection Conectar(String urlRest) {
try {
URL url;
url = new URL(urlRest);
return (HttpURLConnection) url.openConnection();
} catch (Exception e) {
Logger.getLogger(ClienteDAO.class.getName()).log(Level.SEVERE, null, e);
}
return null;
}
Enable logging as shown here: How to enable wire logging for a java HttpURLConnection traffic?
You will then see1 that the Content-Length header is not sent:
FINE: sun.net.www.MessageHeader#4bf558aa5 pairs:
{POST / HTTP/1.1: null}
{ACCEPT: application/json}
{User-Agent: Java/13}
{Host: localhost:8080}
{Connection: keep-alive}
1: Sample log entry, wrapped for easier reading
That is because HttpURLConnection manages that header.
To send a Content-Length: 0 header, send no output, i.e. replace:
conn.setRequestProperty("Content-length", "0");
with:
conn.setDoOutput(true);
conn.getOutputStream().close();
Logging now shows the header:
FINE: sun.net.www.MessageHeader#5fa7e7ff7 pairs:
{POST / HTTP/1.1: null}
{ACCEPT: application/json}
{User-Agent: Java/13}
{Host: localhost:8080}
{Connection: keep-alive}
{Content-type: application/x-www-form-urlencoded}
{Content-Length: 0}
See also: JDK-6997628: HttpURLConnection strips Content-Length header on Post:
Affects Version/s: 6u22
Status: Open
Resolution: Unresolved
BT2:EVALUATION
The fix for CR 6961084 restricts the setting of some potentially security sensitive headers. Since these headers were allowed to be set in previous releases then of course compatibility is effected. A decision was made that compatibility was secondary to the security risk these headers posed. We understand that there may be valid apps out there that will be effected by this, so the sun.net.http.allowRestrictedHeaders property was added to revert to previous behavior.
BT2:WORK AROUND
Run with -Dsun.net.http.allowRestrictedHeaders=true
I would not recommend using that workaround.

Download file in java when Content-Length is incorrectly set?

I have an android app that downloads and uses a file at runtime. The file is valid as I can download it via the browser and open it up, etc. However my app kept reporting that the file is corrupted.
After investigation I discovered the server (which I have no control over) is returning an incorrect "Content-Length:" (~180 vs ~120000). The header is the culprit as I confirmed the issue by downloading the file with curl - which also resulted in a truncated file.
After some research I concluded that my use of BufferedInputStream to append to a ByteArrayBuffer is autosizing the byte array to the url connections content length. To circumvent this, I tried to use ByteArrayOutputStream instead, however this solved nothing.
Anybody know of a way to download a file if the Content-Length is incorrectly set? A browser can.
Here's my latest attempt:
public static void downloadFileFromRemoteUrl(String urlString, String destination){
try {
URL url = new URL(urlString);
File file = new File(destination);
URLConnection urlConnection = url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
byte[] buffer = new byte[1024];
int curLength = 0;
int newLength = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while((newLength = inputStream.read(buffer))>0)
{
curLength += newLength;
byteArrayOutputStream.write(buffer, 0, newLength);
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(byteArrayOutputStream.toByteArray());
fos.close();
android.util.Log.d("DB UPDATE", "Done downloading database. Size: " + byteArrayOutputStream.toByteArray().length);
}
catch (MalformedURLException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
After some research I concluded that my use of BufferedInputStream to append to a ByteArrayBuffer is autosizing the byte array to the url connections content length.
Nonsense. You are crediting those classes with paranormal powers. How could an output stream possibly become aware of the Content-length header? The URLConnection's input stream is being terminated at the content-length. Correctly.
To circumvent this, I tried to use ByteArrayOutputStream instead, however this solved nothing.
Of course not.
Anybody know of a way to download a file if the Content-Length is incorrectly set?
You could use a Socket and engage in HTTP yourself, which is less trivial than it sounds. But the problem is at the server and that's where it should be fixed. Complain. Or else #Zong Yu is correct and the page is HTML containing JavaScript, say.
NB You don't need to read the entire file into memory:
while((newLength = inputStream.read(buffer))>0)
{
curLength += newLength;
fos.write(buffer, 0, newLength);
}
My final "solution" was to realize I was dealing with a 301 redirect response and not the actual resource! I updated the section that handles my url, checking for a 301 and if exists, update the url. The new url contained the Content-Length that corresponded with the file I was downloading.
// start by creating an http url connection object
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
// determine if this is a redirect
boolean redirect = false;
int status = httpURLConnection.getResponseCode();
if (status != HttpURLConnection.HTTP_OK) {
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM
|| status == HttpURLConnection.HTTP_SEE_OTHER)
redirect = true;
}
// if it is, we need a new url
if (redirect) {
String newUrl = httpURLConnection.getHeaderField("Location");
httpURLConnection = (HttpURLConnection) new URL(newUrl).openConnection();
}
Try Fetch. Fetch is an in app download manager for Android. It's very easy to use. Find the GitHub page here. The project comes with several demos that you can try out. Disclaimer: I'm the creator of Fetch, and it is open source.

Sending binary data using http

I need to send a binary message which is divided to 2 parts:
The first part is 4 bytes and it has some information (say an integer)
The second part has an XMLtext stream.
I have never done something like this before, how can I do this?
My code is something like this:
public String serverCall(String link, String data){
HttpURLConnection connection;
OutputStreamWriter writer = null;
URL url = null;
String parameters = data;
try
{
url = new URL(link);
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "text/xml");
connection.setRequestMethod("POST");
writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(parameters);
writer.flush();
writer.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
How do I set the XML to be 4 bytes and how do I have 4 bytes of text before it?
(Info) The HTTP protocol uses method PUT to "transport a file on the server" (instead of POST).
The transport of binary data better not have a content type "text/..." but "application/bin".
You however could send the XML as "text/xml; charset=UTF-8", and use your own header
connection.setHeader("MyCode",
String.format("%02x%02x%02x%02x", bytes[0], bytes[1], bytes[2], bytes[3]));
If you send all as binary, do not use a Writer (converts bytes to some character encoding), but a Stream (BufferedOutputStream). The XML as:
byte[] xmlBytes = xml.getBytes("UTF-8");
UTF-8 if there is no other encoding mentioned in <?xml ...>.
The close() already flushes, so flush() is not needed.
If the binary portion of your message are mere 4 bytes, percent-encode it and send it as an additional url-parameter. alternatively, add it to the existing xml stream.
the java classes Uri and URLEncoder provide the necessary methods.

How to download a part of a file from URL in android?

I am trying to download a part of file given the download URL using setRequestProperty("Range","bytes=" + startbytes + "-" + endbytes); The following code snippet shows what I am trying to do.
protected String doInBackground(String... aurl) {
int count;
Log.d(TAG,"Entered");
try {
URL url = new URL(aurl[0]);
HttpURLConnection connection =(HttpURLConnection) url.openConnection();
int lengthOfFile = connection.getContentLength();
Log.d(TAG,"Length of file: "+ lengthOfFile);
connection.setRequestProperty("Range", "bytes=" + 0 + "-" + 1000);
The problem is that, an exception is being raised, which says "Cannot set request property after connection is made". Please help me resolve this issue.
Option 1
If you do not need to know the content length:
[Beware, do not call the connection.getContentLength(). If you call that, you will get the exception. If you need to call it, then check the second option]
URL url = new URL(aurl[0]);
HttpURLConnection connection =(HttpURLConnection) url.openConnection();
connection.setRequestProperty("Range", "bytes=" + 0 + "-" + 1000);
//Note that, response code will be 206 (Partial Content) instead of usual 200 (OK)
if(connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){
//Your code here to read response data
}
Option 2
If you need to know the content length:
URL url = new URL(aurl[0]);
//First make a HEAD call to get the content length
HttpURLConnection connection =(HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD");
if(connection.getResponseCode() == HttpURLConnection.HTTP_OK){
int lengthOfFile = connection.getContentLength();
Log.d("ERF","Length of file: "+ lengthOfFile);
connection.disconnect();
//Now that we know the content lenght, make the GET call
connection =(HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes=" + 0 + "-" + 1000);
//Note that, response code will be 206 (Partial Content) instead of usual 200 (OK)
if(connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){
//Your code here to read response data
}
}
Assuming you're using HTTP for the download, you'll want to use the HEAD http verb and RANGE http header.
HEAD will give you the filesize (if available), and then RANGE lets you download a byte range.
Once you have the filesize, divide it into roughly equal sized chunks and spawn download thread for each chunk. Once all are done, write the file chunks in the correct order.
If you don't know how to use the RANGE header, here's another SO answer that explains how: https://stackoverflow.com/a/6323043/1355166
[EDIT]
To make file into chunks use this, and start the downloading process,
private void getBytesFromFile(File file) throws IOException {
FileInputStream is = new FileInputStream(file); //videorecorder stores video to file
java.nio.channels.FileChannel fc = is.getChannel();
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocate(10000);
int chunkCount = 0;
byte[] bytes;
while(fc.read(bb) >= 0){
bb.flip();
//save the part of the file into a chunk
bytes = bb.array();
storeByteArrayToFile(bytes, mRecordingFile + "." + chunkCount);//mRecordingFile is the (String)path to file
chunkCount++;
bb.clear();
}
}
private void storeByteArrayToFile(byte[] bytesToSave, String path) throws IOException {
FileOutputStream fOut = new FileOutputStream(path);
try {
fOut.write(bytesToSave);
}
catch (Exception ex) {
Log.e("ERROR", ex.getMessage());
}
finally {
fOut.close();
}
}

FileNotFoundException while getting the InputStream object from HttpURLConnection

I am trying to send a post request to a url using HttpURLConnection (for using cUrl in java).
The content of the request is xml and at the end point, the application processes the xml and stores a record to the database and then sends back a response in form of xml string. The app is hosted on apache-tomcat locally.
When I execute this code from the terminal, a row gets added to the db as expected. But an exception is thrown as follows while getting the InputStream from the connection
java.io.FileNotFoundException: http://localhost:8080/myapp/service/generate
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1401)
at org.kodeplay.helloworld.HttpCurl.main(HttpCurl.java:30)
Here is the code
public class HttpCurl {
public static void main(String [] args) {
HttpURLConnection con;
try {
con = (HttpURLConnection) new URL("http://localhost:8080/myapp/service/generate").openConnection();
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setDoInput(true);
File xmlFile = new File("test.xml");
String xml = ReadWriteTextFile.getContents(xmlFile);
con.getOutputStream().write(xml.getBytes("UTF-8"));
InputStream response = con.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(response));
for (String line ; (line = reader.readLine()) != null;) {
System.out.println(line);
}
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Its confusing because the exception is traced to the line InputStream response = con.getInputStream(); and there doesn't seem to be any file involved for a FileNotFoundException.
When I try to open a connection to an xml file directly, it doesn't throw this exception.
The service app uses spring framework and Jaxb2Marshaller to create the response xml.
The class ReadWriteTextFile is taken from here
Thanks.
Edit:
Well it saves the data in the DB and sends back a 404 response status code at the same time.
I also tried doing a curl using php and print out the CURLINFO_HTTP_CODE which turns out to be 200.
Any ideas on how do I go about debugging this ? Both service and client are on the local server.
Resolved:
I could solve the problem after referring to an answer on SO itself.
It seems HttpURLConnection always returns 404 response when connecting to a url with a non standard port.
Adding these lines solved it
con.setRequestProperty("User-Agent","Mozilla/5.0 ( compatible ) ");
con.setRequestProperty("Accept","*/*");
I don't know about your Spring/JAXB combination, but the average REST webservice won't return a response body on POST/PUT, just a response status. You'd like to determine it instead of the body.
Replace
InputStream response = con.getInputStream();
by
int status = con.getResponseCode();
All available status codes and their meaning are available in the HTTP spec, as linked before. The webservice itself should also come along with some documentation which overviews all status codes supported by the webservice and their special meaning, if any.
If the status starts with 4nn or 5nn, you'd like to use getErrorStream() instead to read the response body which may contain the error details.
InputStream error = con.getErrorStream();
FileNotFound is just an unfortunate exception used to indicate that the web server returned a 404.
To anyone with this problem in the future, the reason is because the status code was a 404 (or in my case was a 500). It appears the InpuStream function will throw an error when the status code is not 200.
In my case I control my own server and was returning a 500 status code to indicate an error occurred. Despite me also sending a body with a string message detailing the error, the inputstream threw an error regardless of the body being completely readable.
If you control your server I suppose this can be handled by sending yourself a 200 status code and then handling whatever the string error response was.
For anybody else stumbling over this, the same happened to me while trying to send a SOAP request header to a SOAP service. The issue was a wrong order in the code, I requested the input stream first before sending the XML body. In the code snipped below, the line InputStream in = conn.getInputStream(); came immediately after ByteArrayOutputStream out = new ByteArrayOutputStream(); which is the incorrect order of things.
ByteArrayOutputStream out = new ByteArrayOutputStream();
// send SOAP request as part of HTTP body
byte[] data = request.getHttpBody().getBytes("UTF-8");
conn.getOutputStream().write(data);
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
Log.d(TAG, "http response code is " + conn.getResponseCode());
return null;
}
InputStream in = conn.getInputStream();
FileNotFound in this case was an unfortunate way to encode HTTP response code 400.
FileNotFound in this case means you got a 404 from your server - could it be that the server does not like "POST" requests?
FileNotFound in this case means you got a 404 from your server
You Have to Set the Request Content-Type Header Parameter
Set “content-type” request header to “application/json” to send the request content in JSON form.
This parameter has to be set to send the request body in JSON format.
Failing to do so, the server returns HTTP status code “400-bad request”.
con.setRequestProperty("Content-Type", "application/json; utf-8");
Full Script ->
public class SendDeviceDetails extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
String data = "";
String url = "";
HttpURLConnection con = null;
try {
// From the above URL object,
// we can invoke the openConnection method to get the HttpURLConnection object.
// We can't instantiate HttpURLConnection directly, as it's an abstract class:
con = (HttpURLConnection)new URL(url).openConnection();
//To send a POST request, we'll have to set the request method property to POST:
con.setRequestMethod("POST");
// Set the Request Content-Type Header Parameter
// Set “content-type” request header to “application/json” to send the request content in JSON form.
// This parameter has to be set to send the request body in JSON format.
//Failing to do so, the server returns HTTP status code “400-bad request”.
con.setRequestProperty("Content-Type", "application/json; utf-8");
//Set Response Format Type
//Set the “Accept” request header to “application/json” to read the response in the desired format:
con.setRequestProperty("Accept", "application/json");
//To send request content, let's enable the URLConnection object's doOutput property to true.
//Otherwise, we'll not be able to write content to the connection output stream:
con.setDoOutput(true);
//JSON String need to be constructed for the specific resource.
//We may construct complex JSON using any third-party JSON libraries such as jackson or org.json
String jsonInputString = params[0];
try(OutputStream os = con.getOutputStream()){
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input, 0, input.length);
}
int code = con.getResponseCode();
System.out.println(code);
//Get the input stream to read the response content.
// Remember to use try-with-resources to close the response stream automatically.
try(BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"))){
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (con != null) {
con.disconnect();
}
}
return data;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
Log.e("TAG", result); // this is expecting a response code to be sent from your server upon receiving the POST data
}
and call it
new SendDeviceDetails().execute("");
you can find more details in this tutorial
https://www.baeldung.com/httpurlconnection-post
The solution:
just change localhost for the IP of your PC
if you want to know this: Windows+r > cmd > ipconfig
example: http://192.168.0.107/directory/service/program.php?action=sendSomething
just replace 192.168.0.107 for your own IP (don't try 127.0.0.1 because it's same as localhost)
Please change
con = (HttpURLConnection) new URL("http://localhost:8080/myapp/service/generate").openConnection();
To
con = (HttpURLConnection) new URL("http://YOUR_IP:8080/myapp/service/generate").openConnection();

Categories

Resources