Downloading a file from spring controllers with resume support - java

Downloading a file from spring controllers
Above is the original article, however i wish to have resume support, means that i can dowmload 51% 1st, and then download another 49% on other time.
environment tomcat 7.0.39
i tried some, but still failed.
here is my code , or maybe you can share your code
InputStream fis =new FileInputStream(filepath+file_name);
response.setHeader("Accept-Ranges", "bytes");
long length = (int) new File(filepath+file_name).length();
long start = 0;
if (request.getHeader("Range") != null)
{
response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);// 206
start = Long.parseLong(request.getHeader("Range")
.replaceAll("bytes=", "").replaceAll("-", ""));
}
response.setHeader("Content-Length", new Long(length - start).toString());
if (start != 0)
{
response.setHeader("Content-Range", "bytes "
+ new Long(start).toString() + "-"
+ new Long(length - 1).toString() + "/"
+ new Long(length).toString());
}
response.setContentType("application/octet-stream");
fis.skip(start);
byte[] b = new byte[1024];
int i;
while ((i = fis.read(b)) != -1) {
response.getOutputStream().write(b, 0, i);
response.flushBuffer();
}
fis.close();
fixed, this is my edited version
long length = (int) new File(filepath+file_name).length();
long start = 0;
response.setHeader("Accept-Ranges", "bytes");
response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);// 206
if (request.getHeader("Range") != null)
{
int x = request.getHeader("Range").indexOf("-");
start = Long.parseLong(request.getHeader("Range").substring(0, x)
.replaceAll("bytes=", ""));
}
response.setHeader("Content-Length", new Long(length - start).toString());
if(start == 0)
response.setHeader("Content-Range", "bytes 0-" +new Long(length - 1).toString()+"/"+length);
else
response.setHeader("Content-Range", "bytes "+start+"-"+new Long(length - 1).toString()+"/"+length);
fis.skip(start);
byte[] b = new byte[1024];
int i;
while ((i = fis.read(b)) != -1) {
response.getOutputStream().write(b, 0, i);
response.flushBuffer();
}
fis.close();

I've build a solution to use HTTP Byte Range with or without Spring.
If you are interested, check it out at https://gist.github.com/davinkevin/b97e39d7ce89198774b4
That helps me to use it inside a Spring application using mainly #RestController

Related

How to scroll in video spring boot?

I have built a dummy video streaming application in spring boot,I have noticed that i cant skip to another frame or to a certain time in a video.
Here is my code to read videoFile
public byte[] getVideo() throws IOException {
byte[] bytes = new byte[(int) file.length()];
FileInputStream inputStream = new FileInputStream(file);
inputStream.read(bytes);
return bytes;
}
and this is my what my video controller returns
return ResponseEntity.status(HttpStatus.OK)
.header("Content-Type","video/mp4")
.header("Content-length",String.valueOf(streamingService.file.length()))
.body(streamingService.getVideo());
note i am not using any frontend
So after some experimentation, I found that the browser (Chrome) made another GET request to a certain byte range, and my getVideo() method was not well-written enough to accept the byte range and return the required byte range.
After rewriting my VideoStreamingService, the problem was solved for Chrome.
Here is my rewritten code for getVideo():
byte[] data;
Long fileSize = file.length();
String[] ranges = range.split("-");
rangeStart = Long.parseLong(ranges[0].substring(6));
if (ranges.length > 1) {
rangeEnd = Long.parseLong(ranges[1]);
} else {
rangeEnd = fileSize - 1;
}
if (fileSize < rangeEnd) {
rangeEnd = fileSize - 1;
}
contentLength = String.valueOf((rangeEnd - rangeStart) + 1);
data = readByteRange( rangeStart, rangeEnd);
return data;
readRangeByte() method so I can read data appropriately:
public byte[] readByteRange(long start, long end) throws IOException {
try (InputStream inputStream = (Files.newInputStream(Path.of(videoFileName)));
ByteArrayOutputStream bufferedOutputStream = new ByteArrayOutputStream()) {
byte[] data = new byte[128];
int nRead;
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
bufferedOutputStream.write(data, 0, nRead);
}
bufferedOutputStream.flush();
byte[] result = new byte[(int) (end - start) + 1];
System.arraycopy(bufferedOutputStream.toByteArray(), (int) start, result, 0, result.length);
return result;
}
}
and my controller class code:
public ResponseEntity<byte[]> video(#RequestHeader(value = "Range",required = false) String range) throws IOException {
if (range == null) {
return ResponseEntity.status(HttpStatus.OK)
.header("Content-Type", "video/mp4")
.header("Content-Length", String.valueOf(streamingService.file.length()))
.body(streamingService.readByteRange(streamingService.getRangeStart(),streamingService.file.length()-1));
}
byte[] data = streamingService.getVideo(range);
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
.header("Content-Type", "video/mp4")
.header("Accept-Ranges", "bytes")
.header("Content-Length", streamingService.getContentLength())
.header("Content-Range", "bytes" + " " + streamingService.getRangeStart() + "-" + streamingService.getRangeEnd() + "/" + streamingService.file.length())
.body(data);
After #Andreas suggestion the code works very well with both the browsers

How can I chunk xml to multiple parts in java?

I have a code that creates xml.
public void createXML(InputStream in, String fileName) throws IOException {
baos = new ByteArrayOutputStream();
byte[] buf = new byte[BUF_SIZE];
int readNum = 0;
Writer writer = new BufferedWriter(new OutputStreamWriter(FileUtil.getOutputStream(fileName, FileUtil.HDD)));
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
writer.write("\t<" + imageFileName + ">\r\n");
writer.write("\t\t");
try {
while ((readNum = in.read(buf)) >= 0) {
baos.write(buf, 0, readNum);
writer.write(baos.toString());
baos.reset();
}
}
finally {
if (in != null) {
in.close();
baos.close();
}
}
writer.write("\r\n\t<" + imageFileName + ">");
writer.close();
baos = null;
buf = null;
}
I want to create this xml into multiple parts (maximum of 500kb each). How can I do this? Is there any way for me to determine that the created file is already 500kb and write the remaining data to a different file?
I used this but the image after decoding the base64 string, the image produced is corrupted on the portion where it was cut.
for(int i = 1; i <= numberOfFiles; i++){
baos = new ByteArrayOutputStream();
String filePartName = fileName + ".part" + i + ".xml";
Writer writer = new BufferedWriter(new OutputStreamWriter(FileUtil.getOutputStream(filePartName, FileUtil.HDD)));
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
writer.write("\t<" + filePartName + ">\r\n");
writer.write("\t\t");
int size = 0;
while ((readNum = in.read(buf)) >= 0 && size < 512000) {
baos.write(buf, 0, readNum);
size = size + readNum;
writer.write(baos.toString());
baos.reset();
}
writer.write("\r\n\t<" + filePartName + ">");
writer.close();
baos.close();
}
}
in.close();
If you keep a sum total of readNum that is in
while ((readNum = in.read(buf)) >= 0)
then you can test for its value and create new files when ever you want.
500kb = 512000 bytes. Use another ByteArrayOutputStream instanse to control this limit.

Reading JPEG Stream over socket gives Null characters

I am reading a .jpg file over InputStream using this code but I am receiving NULNUL...n stream after some text. Ii am reading this file link to file and link of file that I received , link is Written File link.
while ((ret = input.read(imageCharArray)) != -1) {
packet.append(new String(imageCharArray, 0, ret));
totRead += ret;
imageCharArray = new char[4096];
}
file = new File(
Environment.getExternalStorageDirectory()
+ "/FileName_/"
+ m_httpParser.filename + ".jpg");
PrintWriter printWriter = new PrintWriter(file);
// outputStream = new FileOutputStream(file); //also Used FileoutputStream for writting
// outputStream.write(packet.toString().getBytes());//
// ,
printWriter.write(packet.toString());
// outputStream.close();
printWriter.close();
}
I have also tried FileoutputStream but hardlucj for this too as commented in my code.
Edit
I have used this also. I have a content length field upto which i am reading and writing
byte[] bytes = new byte[1024];
int totalReadLength = 0;
// read untill we have bytes
while ((read = inputStream.read(bytes)) != -1
&& contentLength >= (totalReadLength)) {
outputStream.write(bytes, 0, read);
totalReadLength += read;
System.out.println(" read size ======= "
+ read + " totalReadLength = "
+ totalReadLength);
}
String is not a container for binary data, and PrintWriter isn't a way to write it. Get rid of all, all, the conversions between bytes and String and vice versa, and just transfer the bytes with input and output streams:
while ((count = in.read(buffer)) > 0)
{
out.write(buffer, 0, count);
}
If you need to constrain the number of bytes read from the input, you have to do that before calling read(), and you also have to constrain the read() correctly:
while (total < length && (count = in.read(buffer, 0, length-total > buffer.length ? buffer.length: (int)(length-total))) > 0)
{
total += count;
out.write(buffer, 0, count);
}
I tested it in my Nexus4 and it's working for me. Here is the snippet of code what I tried :
public void saveImage(String urlPath)throws Exception{
String fileName = "kumar.jpg";
File folder = new File("/sdcard/MyImages/");
// have the object build the directory structure, if needed.
folder.mkdirs();
final File output = new File(folder,
fileName);
if (output.exists()) {
output.delete();
}
InputStream stream = null;
FileOutputStream fos = null;
try {
URL url = new URL(urlPath);
stream = url.openConnection().getInputStream();
// InputStreamReader reader = new InputStreamReader(stream);
DataInputStream dis = new DataInputStream(url.openConnection().getInputStream());
byte[] fileData = new byte[url.openConnection().getContentLength()];
for (int x = 0; x < fileData.length; x++) { // fill byte array with bytes from the data input stream
fileData[x] = dis.readByte();
}
dis.close();
fos = new FileOutputStream(output.getPath());
fos.write(fileData);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Just Call the above function in a background thread and pass your url. It'll work for sure. Let me know if it helps.
You can check below code.
destinationFile = new File(
Environment.getExternalStorageDirectory()
+ "/FileName_/"
+ m_httpParser.filename + ".jpg");
BufferedOutputStream buffer = new BufferedOutputStream(new FileOutputStream(destinationFile));
byte byt[] = new byte[1024];
int i;
for (long l = 0L; (i = input.read(byt)) != -1; l += i ) {
buffer.write(byt, 0, i);
}
buffer.close();

writing to a file using java.nio

I have to use java.nio to create a file of any desired size by populating it with data. I am reading through a document, but am confused about when I need to flip, put, or write and am getting errors. I have successfully done this program using .io but I am testing to see if .nio will make it run faster.
This is my code so far. args[0] is the size of the file you want to make and args[1] is the name of the file to be written to
public static void main(String[] args) throws IOException
{
nioOutput fp = new nioOutput();
FileOutputStream fos = new FileOutputStream(args[1]);
FileChannel fc = fos.getChannel();
long sizeOfFile = fp.getFileSize(args[1]);
long desiredSizeOfFile = Long.parseLong(args[0]) * 1073741824; //1 Gigabyte = 1073741824 bytes
int byteLength = 1024;
ByteBuffer b = ByteBuffer.allocate(byteLength);
while(sizeOfFile + byteLength < desiredSizeOfFile)
{
// b.put((byte) byteLength);
b.flip();
fc.write(b);
sizeOfFile += byteLength;
}
int diff = (int) (desiredSizeOfFile - sizeOfFile);
sizeOfFile += diff;
fc.write(b, 0, diff);
fos.close();
System.out.println("Finished at " + sizeOfFile / 1073741824 + " Gigabyte(s)");
}
long getFileSize(String fileName)
{
File file = new File(fileName);
if (!file.exists() || !file.isFile())
{
System.out.println("File does not exist");
return -1;
}
return file.length();
}
If all you want to do is pre-extend a file to a given length with nulls, you can do it in three lines and save all that I/O:
RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.setLength(desiredSizeOfFile);
raf.close();
This will operate several gazzilion times as quickly as what you are trying to do now.
sorry everyone, I figured it out.
while(sizeOfFile + byteLength < desiredSizeOfFile)
{
fc.write(b);
b.rewind();
sizeOfFile += byteLength;
}
int diff = (int) (desiredSizeOfFile - sizeOfFile);
sizeOfFile += diff;
ByteBuffer d = ByteBuffer.allocate(diff);
fc.write(d);
b.rewind();
fos.close();
System.out.println("Finished at " + sizeOfFile / 1073741824 + " Gigabyte(s)");
}

How to send file in parts using header "Range"?

I would like to send big file by dividing it to small parts and send them separately.
I tried to use the hedder "Range" and got "org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity".
// create authenticate client
DefaultHttpClient client = new DefaultHttpClient();
// create HTTP put with the file
HttpPut httpPut = new HttpPut(url);
final File recordingFile = new File(mDir, mName);
long fileLength = recordingFile.length();
for (int i=0; i < fileLength; i += 4096) {
int length = Math.min(4096, (int)recordingFile.length() - i);
InputStreamEntity entity = new InputStreamEntity(inputStream, length);
httpPut.setEntity(entity);
httpPut.addHeader("Connection", "Keep-Alive");
httpPut.addHeader("Range", "bytes=" + i + "-" + (i + length));
// Execute
HttpResponse res = client.execute(httpPut);
int statusCode = res.getStatusLine().getStatusCode();
}
I also tried "Content-Range" header (instead of "Range") and I got the same exception.
httpPut.addHeader("Content-Range", "bytes=" + i + "-" + (i + length) + "/" + fileLength);
httpPut.addHeader("Accept-Ranges", "bytes");
You repeatedly send multiple of 4096 bits. E.g. let's take the first two steps:
i = 0
Send range 0-4096
i = 4096
Send range 4096-8192.
Fix this lines:
for (int i=0; i <= fileLength; i += 4097) {
int length = Math.min(4096, (int)recordingFile.length() - i + 1);
/*...*/
}
and it should work fine.
Update:
Maybe the problem is that for some reasons (e.g. authentication failure) it tries to resend the same chunk again, in which case the inputstream is already consumed.
Try using a ByteArrayEntity instead of InputStreamEntity, something like this:
ByteArrayInputStream bis = new ByteArrayInputStream(recordingFile);
for (int i=0; i <= fileLength; i += 4097) {
int length = Math.min(4096, (int)recordingFile.length() - i + 1);
byte[] bytes = new byte[length];
bis.read(bytes);
ByteArrayEntity entity = ByteArrayEntity(bytes);
/*...*/
}

Categories

Resources