[Java 1.5; Eclipse Galileo]
HttpsURLConnection seems to stall when the getInputStream() method is called. I've tried using different websites to no avail (currently https://www.google.com). I should point out I'm using httpS.
The code below has been modified based on what I've learned from other StackOverflow answers. However, no solutions I've tried thus far have worked.
I'd be very grateful for a nudge in the right direction :)
public static void request( URL url, String query )
{
try{
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
//connection.setReadTimeout( 5000 ); //<-- uncommenting this line at least allows a timeout error to be thrown
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
System.setProperty("http.keepAlive", "false");
connection.setRequestMethod( "POST" );
// setting headers
connection.setRequestProperty("Content-length",String.valueOf (query.length()));
connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); //WAS application/x-www- form-urlencoded
connection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)");
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
System.out.println( "THIS line stalls" + connection.getInputStream() );
////////////////////////////////////////////////////////////////////////////////////
}catch( Exception e ) {
System.out.println( e );
e.printStackTrace();
}
Typical errors look like:
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at com.sun.net.ssl.internal.ssl.InputRecord.readFully(InputRecord.java:293)
at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:331)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:782)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:739)
at com.sun.net.ssl.internal.ssl.AppInputStream.read(AppInputStream.java:75)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:256)
at java.io.BufferedInputStream.read(BufferedInputStream.java:313)
at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:681)
at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:626)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:983)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:234)
at https_understanding.HTTPSRequest.request(HTTPSRequest.java:60)
at https_understanding.Main.main(Main.java:17)
connection.setDoOutput(true);
This means that you have to open, write to, and close the connection's output stream before you attempt to read from its input stream. See the docs.
I reproduced the problem in Android 2.2: when downloading from a web-server over wireless and a HTTPS URL, the error is a socket "read time out" at URLConnection.getInputStream()
To fix it, use url.openStream() for the InputStream instead of connection.getInputStream()
Bonus: you can get the length of the file you're downloading so you can show a % complete indicator
code sample:
private final int TIMEOUT_CONNECTION = 5000;//5sec
private final int TIMEOUT_SOCKET = 30000;//30sec
file = new File(strFullPath);
URL url = new URL(strURL);
URLConnection ucon = url.openConnection();
//this timeout affects how long it takes for the app to realize there's a connection problem
ucon.setReadTimeout(TIMEOUT_CONNECTION);
ucon.setConnectTimeout(TIMEOUT_SOCKET);
//IMPORTANT UPDATE:
// ucon.getInputStream() often times-out over wireless
// so, replace it with ucon.connect() and url.openStream()
ucon.connect();
iFileLength = ucon.getContentLength();//returns -1 if not set in response header
if (iFileLength != -1)
{
Log.i(TAG, "Expected Filelength = "+String.valueOf(iFileLength)+" bytes");
}
//Define InputStreams to read from the URLConnection.
// uses 5KB download buffer
InputStream is = url.openStream();//ucon.getInputStream();
BufferedInputStream inStream = new BufferedInputStream(is, 1024 * 5);
outStream = new FileOutputStream(file);
bFileOpen = true;
byte[] buff = new byte[5 * 1024];
//Read bytes (and store them) until there is nothing more to read(-1)
int total=0;
int len;
int percentdone;
int percentdonelast=0;
while ((len = inStream.read(buff)) != -1)
{
//write to file
outStream.write(buff,0,len);
//calculate percent done
if (iFileLength != -1)
{
total+=len;
percentdone=(int)(total*100/iFileLength);
//limit the number of messages to no more than one message every 10%
if ( (percentdone - percentdonelast) > 10)
{
percentdonelast = percentdone;
Log.i(TAG,String.valueOf(percentdone)+"%");
}
}
}
//clean up
outStream.flush();//THIS IS VERY IMPORTANT !
outStream.close();
bFileOpen = false;
inStream.close();
Also don't set the content-length header. Java will do that for you.
Related
I use a Expect 100-continue in the http header like you see in the code below. The HttpUrlConnection when i get the outputstream dont wait the answer of the 100 Expectation or any other 400 error. (In the RFC they tell it's not a problem: https://www.rfc-editor.org/rfc/rfc7231#section-5.1.1).
I've looked at this post: How to wait for Expect 100-continue response in Java using HttpURLConnection in case they have more info on the problem but i have tested the setChuncked method just to see if it's make a difference but no.
The problem is when the server answer with an error and close the connection without let me finish the upload i have a Broken Pipe Exception. It's right i write bytes on a close connection but i can't handle that correctly. I can't read the server answer. I can't know that the connection is close before the write. If i try to get the ErrorStream i get a null reference same for the InputStream, ResponseCode and other methods to know what happen.
Any idea how can make it right or what i miss to make it work?
javax.net.ssl.SSLException: Write error: ssl=0x73de8c02c0: I/O error during system call, Broken pipe
at com.android.org.conscrypt.NativeCrypto.SSL_write(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream.write(OpenSSLSocketImpl.java:824)
at com.android.okhttp.okio.Okio$1.write(Okio.java:76)
at com.android.okhttp.okio.AsyncTimeout$1.write(AsyncTimeout.java:155)
at com.android.okhttp.okio.RealBufferedSink.emitCompleteSegments(RealBufferedSink.java:176)
at com.android.okhttp.okio.RealBufferedSink.write(RealBufferedSink.java:46)
at com.android.okhttp.internal.http.HttpConnection$ChunkedSink.write(HttpConnection.java:339)
at com.android.okhttp.okio.RealBufferedSink.emitCompleteSegments(RealBufferedSink.java:176)
at com.android.okhttp.okio.RealBufferedSink$1.write(RealBufferedSink.java:198)
at out.write(buffer, 0, bytes_read);
In the code below, i've remove some not useful part like a global try on this, or compute for the fileSize.
huc = (HttpURLConnection) new URL(upload_url).openConnection();
huc.setRequestMethod("POST");
huc.setRequestProperty("Content-Length", "" + FILE_SZ);
huc.setRequestProperty("User-Agent", "UA");
huc.setRequestProperty("Expect","100-continue");
huc.setFixedLengthStreamingMode(FILE_SZ);
huc.setUseCaches(false);
huc.setDoInput(true);
huc.setDoOutput(true);
huc.setInstanceFollowRedirects(true);
try {
// read input file chunks + publish progress
OutputStream out = new OutputStream(huc.getOutputStream());
buffer_size = 4 * 1024;
buffer = new byte[buffer_size];
total_bytes = 0;
while ((bytes_read = fis.read(buffer)) != -1) {
out.write(buffer, 0, bytes_read);
total_bytes += bytes_read;
}
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
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.
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();
}
}
I'm trying to do something relatively simple. I need to make a simple PUT request with a file in the body in order to upload a file to a server not in my control. Here's the code I have so far:
connection = ((HttpURLConnection)new URL(ticket.getEndpoint()).openConnection());
connection.setRequestMethod("PUT");
connection.setRequestProperty("Content-Type", "video/mp4");
connection.setRequestProperty("Content-Length", String.valueOf(getStreamFile().length()));
connection.setUseCaches(false);
connection.setDoOutput(true);
connection.connect();
outputStream = connection.getOutputStream();
streamFileInputStream = new FileInputStream(getStreamFile());
streamFileBufferedInputStream = new BufferedInputStream(streamFileInputStream);
byte[] streamFileBytes = new byte[getBufferLength()];
int bytesRead = 0;
int totalBytesRead = 0;
while ((bytesRead = streamFileBufferedInputStream.read(streamFileBytes)) > 0) {
outputStream.write(streamFileBytes, 0, bytesRead);
outputStream.flush();
totalBytesRead += bytesRead;
notifyListenersOnProgress((double)totalBytesRead / (double)getStreamFile().length());
}
outputStream.close();
logger.debug("Wrote {} bytes of {}, ratio: {}",
new Object[]{totalBytesRead, getStreamFile().length(),
(double)totalBytesRead / (double)getStreamFile().length()});
I'm watching my network manager and nothing near the size of my file gets sent. In fact, I don't know if anything is being sent at all, but I don't see any errors thrown.
I need to be able to send this request and also measure the status of the upload synchronously, so as to be able to inform my listeners of the upload progress. How can I modify my existing example to just work�
Try setting the content-type param to multipart/form-data. W3C forms.
I'm writing a simple client-server system and the question is: how to structure my client code in order to get POST request-response working in a loop?
At the moment it looks something like this (and it's is NOT a loop right now):
open HttpURLConnection
set properties
setDoOutput(true)
writing to output stream
closing output stream
new DataInputStream
reading response
exiting method
I'm not sure which objects do I have to save for the next iterations and which ones I should close.
you need to save the connection object and you should make use of setDoInput(true) for reading data but if you just want to read responseCode and responseMessage you dont need InputStream. check the code below.
HttpURLConnection connection =(HttpURLConnection)new URL("url").openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-type", "text/xml"); // depend on you
connection.setRequestProperty("Accept", "text/xml, application/xml"); // depend on you
connection.setRequestMethod("POST");
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(yaml);
writer.close();
int statusCode = connection.getResponseCode();
String message = connection.getResponseMessage();
for InputStreamReader
connection.setDoInput(true);
InputStreamReader reader = new InputStreamReader(connection.getInputStream());
char[] cbuf = new char[100];
reader.read(cbuf);
// there are 3 read method you can choose as per your convenience
//and put a check for end of line in while loop for reading whole content.
reader.close();
After managing my own 'research' on this subject (thanks to Google and Nokia Developer forums) I've come to the final view of my code. It's a file upload loop:
path = Paths.get(requestString);
in = Files.newInputStream(path);
int i = 0;
while ((bytesRead = in.read(buf)) != -1) {
URL u = new URL(defaultURL);
huc =
(HttpURLConnection) u.openConnection();
huc.setRequestMethod("POST");
huc.setDoOutput(true);
huc.setDoInput(true);
os = huc.getOutputStream();
os.write(buf, 0, bytesRead);
os.flush();
os = null;
// thanks to dku.rajkumar for the following block of code !
InputStreamReader reader =
new InputStreamReader(huc.getInputStream());
char[] cbuf = new char[400];
reader.read(cbuf);
reader.close();
String s = new String(cbuf);
messagebuffer.append(s + "\n\n");
huc.disconnect();
Thread.sleep(16);
}