I want to implement video Player in Angular using Spring Boot Rest API. I can play the video but I can't make video seeking. Every time the video starts over and over again when I use Chrome or Edge.
I tried this endpoint:
#RequestMapping(value = "/play_video/{video_id}", method = RequestMethod.GET)
#ResponseBody public ResponseEntity<byte[]> getPreview1(#PathVariable("video_id") String video_id, HttpServletResponse response) {
ResponseEntity<byte[]> result = null;
try {
String file = "/opt/videos/" + video_id + ".mp4";
Path path = Paths.get(file);
byte[] image = Files.readAllBytes(path);
response.setStatus(HttpStatus.OK.value());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentLength(image.length);
result = new ResponseEntity<byte[]>(image, headers, HttpStatus.OK);
} catch (java.nio.file.NoSuchFileException e) {
response.setStatus(HttpStatus.NOT_FOUND.value());
} catch (Exception e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
return result;
}
I found this post which gives some ides: How to Implement HTTP byte-range requests in Spring MVC
But currently it's not working. Video is again playing from the start when I try to shift the position.
I use this player: https://github.com/smnbbrv/ngx-plyr
I have configured it this way:
<div class="media">
<div
class="class-video mr-3 mb-1"
plyr
[plyrPlaysInline]="true"
[plyrSources]="gymClass.video"
(plyrInit)="player = $event"
(plyrPlay)="played($event)">
</div>
<div class="media-body">
{{ gymClass.description }}
</div>
</div>
Do you know how I can fix this issue?
First solution: Using FileSystemResource
FileSystemResource internally handles byte-range header support, reading and writing the appropriate headers.
Two problems with this approach.
It uses FileInputStream internally for reading files. This is fine for small files, but not for large files served through byte-range requests. FileInputStream will read the file from the beginning and discard the not needed content until it reches the requested start offset. This can cause slowdowns with larger files.
It sets "application/json" as the "Content-Type" response header. So, I provide my own "Content-Type" header. See this thread
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class Stream3 {
#GetMapping(value = "/play_video/{video_id}")
#ResponseBody
public ResponseEntity<FileSystemResource> stream(#PathVariable("video_id") String video_id) {
String filePathString = "/opt/videos/" + video_id + ".mp4";
final HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "video/mp4");
return new ResponseEntity<>(new FileSystemResource(filePathString), responseHeaders, HttpStatus.OK);
}
}
Second solution: Using HttpServletResponse and RandomAccessFile
With RandomAccessFile you can implement support for byte-range requests. The advantage over FileInputStream, is that you don't need to read the file from the beginning every time there is a new range request, making this method usable also for larger files. RandomAccessFile has a method called seek(long) which calls the C method fseek(), which directly moves the pointer for the file to the requested offset.
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class Stream {
#GetMapping(value = "/play_video/{video_id}")
#ResponseBody
public void stream(
#PathVariable("video_id") String video_id,
#RequestHeader(value = "Range", required = false) String rangeHeader,
HttpServletResponse response) {
try {
OutputStream os = response.getOutputStream();
long rangeStart = 0;
long rangeEnd;
String filePathString = "/opt/videos/" + video_id + ".mp4";
Path filePath = Paths.get(filePathString);
Long fileSize = Files.size(filePath);
byte[] buffer = new byte[1024];
RandomAccessFile file = new RandomAccessFile(filePathString, "r");
try (file) {
if (rangeHeader == null) {
response.setHeader("Content-Type", "video/mp4");
response.setHeader("Content-Length", fileSize.toString());
response.setStatus(HttpStatus.OK.value());
long pos = rangeStart;
file.seek(pos);
while (pos < fileSize - 1) {
file.read(buffer);
os.write(buffer);
pos += buffer.length;
}
os.flush();
return;
}
String[] ranges = rangeHeader.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;
}
String contentLength = String.valueOf((rangeEnd - rangeStart) + 1);
response.setHeader("Content-Type", "video/mp4");
response.setHeader("Content-Length", contentLength);
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Range", "bytes" + " " + rangeStart + "-" + rangeEnd + "/" + fileSize);
response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
long pos = rangeStart;
file.seek(pos);
while (pos < rangeEnd) {
file.read(buffer);
os.write(buffer);
pos += buffer.length;
}
os.flush();
}
} catch (FileNotFoundException e) {
response.setStatus(HttpStatus.NOT_FOUND.value());
} catch (IOException e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
}
}
Third solution: Also using RandomAccessFile, but StreamingResponseBody instead of HttpServletResponse
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
#Controller
public class Stream2 {
#GetMapping(value = "/play_video/{video_id}")
#ResponseBody
public ResponseEntity<StreamingResponseBody> stream(
#PathVariable("video_id") String video_id,
#RequestHeader(value = "Range", required = false) String rangeHeader) {
try {
StreamingResponseBody responseStream;
String filePathString = "/opt/videos/" + video_id + ".mp4";
Path filePath = Paths.get(filePathString);
Long fileSize = Files.size(filePath);
byte[] buffer = new byte[1024];
final HttpHeaders responseHeaders = new HttpHeaders();
if (rangeHeader == null) {
responseHeaders.add("Content-Type", "video/mp4");
responseHeaders.add("Content-Length", fileSize.toString());
responseStream = os -> {
RandomAccessFile file = new RandomAccessFile(filePathString, "r");
try (file) {
long pos = 0;
file.seek(pos);
while (pos < fileSize - 1) {
file.read(buffer);
os.write(buffer);
pos += buffer.length;
}
os.flush();
} catch (Exception e) {}
};
return new ResponseEntity<>(responseStream, responseHeaders, HttpStatus.OK);
}
String[] ranges = rangeHeader.split("-");
Long rangeStart = Long.parseLong(ranges[0].substring(6));
Long rangeEnd;
if (ranges.length > 1) {
rangeEnd = Long.parseLong(ranges[1]);
} else {
rangeEnd = fileSize - 1;
}
if (fileSize < rangeEnd) {
rangeEnd = fileSize - 1;
}
String contentLength = String.valueOf((rangeEnd - rangeStart) + 1);
responseHeaders.add("Content-Type", "video/mp4");
responseHeaders.add("Content-Length", contentLength);
responseHeaders.add("Accept-Ranges", "bytes");
responseHeaders.add("Content-Range", "bytes" + " " + rangeStart + "-" + rangeEnd + "/" + fileSize);
final Long _rangeEnd = rangeEnd;
responseStream = os -> {
RandomAccessFile file = new RandomAccessFile(filePathString, "r");
try (file) {
long pos = rangeStart;
file.seek(pos);
while (pos < _rangeEnd) {
file.read(buffer);
os.write(buffer);
pos += buffer.length;
}
os.flush();
} catch (Exception e) {}
};
return new ResponseEntity<>(responseStream, responseHeaders, HttpStatus.PARTIAL_CONTENT);
} catch (FileNotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} catch (IOException e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
In your component.ts:
You can change the currently displaying video with playVideoFile()
export class AppComponent implements OnInit {
videoSources: Plyr.Source[];
ngOnInit(): void {
const fileName = 'sample';
this.playVideoFile(fileName);
}
playVideoFile(fileName: string) {
this.videoSources = [
{
src: `http://localhost:8080/play_video/${fileName}`,
},
];
}
}
And the html:
<div
#plyr
plyr
[plyrPlaysInline]="false"
[plyrSources]="videoSources"
></div>
If you are using the <video /> element in Chrome, seeking only works if the endpoint implements partial content requests by honouring requests with a Range header and respond with a 206 Partial Content response.
Related
I'm working on a springboot backend server to provide an audio interface to the android frontend. I expose the audio directory to the outside world directly in the application.yaml file. But in this way, I found that the audio playback has a delay of about 5s before it can be played when I use docker to deploy the .jar to the cloud server. But when I went to test the network interface of some music web, I found that they were very fast and could start playing almost instantly. I wonder how this is done? Can this be achieved if springboot is used as the backend?
this is a part of application.yaml, where web.sound-path is the folder path where I store the audio on cloud server.
spring:
web:
resources:
static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:${web.sound-path}
The android frontend directly accesses http://ip:port/audioname.mp3 to play audio.
I am a novice in the development of java network, I do not know how to further improve, I hope someone can give detailed other solutions
my springboot version is 2.7.5
And my cloud server has 10Mbps bandwidth, quad-core CPU and 8G memory.
I've looked for a lot of information online but nothing is of much help, I'd really appreciate if someone could give a good answer.
I've tried two other methods on the web.
One is to use a NonStaticResourceHttpRequestHandler.
the NonStaticResourceHttpRequestHandler is :
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import javax.servlet.http.HttpServletRequest;
import java.nio.file.Path;
#Component
public class NonStaticResourceHttpRequestHandler extends ResourceHttpRequestHandler {
public final static String ATTR_FILE = "NON-STATIC-FILE";
#Override
protected Resource getResource(HttpServletRequest request) {
final Path filePath = (Path) request.getAttribute(ATTR_FILE);
return new FileSystemResource(filePath);
}
}
and the method in controller is :
#GetMapping("/get/{name}")
public void getSound(#ApiParam("name") #PathVariable("name") String name, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
Path path = Paths.get(soundPath + name);
if (Files.exists(path)) {
String mimeType = Files.probeContentType(path);
if (!StringUtils.isEmpty(mimeType)) {
response.setContentType(mimeType);
}
request.setAttribute(NonStaticResourceHttpRequestHandler.ATTR_FILE, path);
nonStaticResourceHttpRequestHandler.handleRequest(request, response);
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
}
}
another solution I have tried is: (The code in it may not be very beautiful, because I rewritten it based on the existing code on the Internet.)
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
#RestController
#RequestMapping("/audio")
public class AudioVideoController {
#Value("${web.sound-path}")
public String AUDIO_PATH;
public static final int BYTE_RANGE = 128; // increase the byterange from here
#GetMapping("/play/{fileName}")
public ResponseEntity<byte[]> streamAudio(#RequestHeader(value = "Range", required = false) String httpRangeList,
#PathVariable("fileName") String fileName) {
return getContent(AUDIO_PATH, fileName, httpRangeList, "audio");
}
private ResponseEntity<byte[]> getContent(String location, String fileName, String range, String contentTypePrefix) {
long rangeStart = 0;
long rangeEnd;
byte[] data;
Long fileSize;
String fileType = fileName.substring(fileName.lastIndexOf(".") + 1);
try {
fileSize = Optional.ofNullable(fileName)
.map(file -> Paths.get(getFilePath(location), file))
.map(this::sizeFromFile)
.orElse(0L);
if (range == null) {
return ResponseEntity.status(HttpStatus.OK)
.header("Content-Type", contentTypePrefix + "/" + fileType)
.header("Content-Length", String.valueOf(fileSize))
.body(readByteRange(location, fileName, rangeStart, fileSize - 1));
}
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;
}
data = readByteRange(location, fileName, rangeStart, rangeEnd);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
String contentLength = String.valueOf((rangeEnd - rangeStart) + 1);
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
.header("Content-Type", contentTypePrefix +
"/" + fileType)
.header("Accept-Ranges", "bytes")
.header("Content-Length", contentLength)
.header("Content-Range", "bytes" + " " + rangeStart + "-" + rangeEnd + "/" + fileSize)
.body(data);
}
public byte[] readByteRange(String location, String filename, long start, long end) throws IOException {
Path path = Paths.get(getFilePath(location), filename);
try (InputStream inputStream = (Files.newInputStream(path));
ByteArrayOutputStream bufferedOutputStream = new ByteArrayOutputStream()) {
byte[] data = new byte[BYTE_RANGE];
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;
}
}
private String getFilePath(String location) {
return location;
}
private Long sizeFromFile(Path path) {
try {
return Files.size(path);
} catch (IOException ex) {
ex.printStackTrace();
}
return 0L;
}
}
I have tried these two methods, and the audio can be obtained normally, but there is still a delay of a few seconds from the time the http request is sent to the audio starts playing.
I was hoping there was a way to get audio files quickly. The ideal situation is to enter an http request, press the Enter key, and then immediately start playing audio.
I have more attachments to download instead of downloading one by one.
I need to download them by zipping these files as batched 2 or 3 files in each zip based on conditions written in the code.
The code is written to download 2 zip files but it downloaded only one zip through response.
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class downloadSert extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=Attachments.ZIP");
response.addHeader("Pragma", "no-cache");
response.addHeader("Expires", "0");
String[] cjFiles = { "C:\\Users\\MW\\Desktop\\Treasury process title and status\\1.txt", "C:\\Users\\MW\\Desktop\\Treasury process title and status\\2.txt", "C:\\Users\\MW\\Desktop\\Treasury process title and status\\3.txt", "C:\\Users\\MW\\Desktop\\Treasury process title and status\\4.txt", "C:\\Users\\MW\\Desktop\\Treasury process title and status\\5.txt" };
int len = cjFiles.length;
int attachmentTotalForEachZip = 0;
int zipBatches = 0;
if (len <= 10) {
attachmentTotalForEachZip = 2;
zipBatches = len / 2;
} else {
attachmentTotalForEachZip = 4;
zipBatches = len / 4;
}
int total = 0;
FileInputStream in = null;
ZipOutputStream zos = null;
List<ZipOutputStream> zips = new ArrayList<ZipOutputStream>();
for (int i = 0; i < cjFiles.length; i++) {
if (total == 0) {
zos = new ZipOutputStream(response.getOutputStream());
}
total = total + 1;
in = new FileInputStream(cjFiles[i]);
File f = new File(cjFiles[i]);
zos.putNextEntry(new ZipEntry((i + 1) + " - " + f.getName().substring(f.getName().indexOf("-") + 1, f.getName().length())));
int length;
byte[] buffer = new byte[1024];
while ((length = in.read(buffer)) > 0) {
zos.write(buffer, 0, length);
}
in.close();
zos.closeEntry();
//download zip
if (total == attachmentTotalForEachZip) {
zips.add(zos);
total = 0;
zipBatches = zipBatches - 1;
}
// handle zip file amount (pdf total in each zip )
if (zipBatches == 1) {
attachmentTotalForEachZip = attachmentTotalForEachZip + (len % attachmentTotalForEachZip);
zipBatches = 0;
}
}
for (ZipOutputStream z : zips) {
z.finish();
z.close();
}
}
}
if there is any way to download all zip file.
When downloading bigger (specially videos) files using Spring-boot and java nio package only download part of the file. But smaller files such as images, pdf ect get downloaded properly and usable.
For example : Let say video size is 3.5MB but when downloaded it only show 160KB and cannot play it in any player(that is because, probably partially downloaded)
Following is the controller
package com.filedownloader_with_nio_package.controllers;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.filedownloader_with_nio_package.model.FileDetails;
import com.filedownloader_with_nio_package.services.FileDownloadService;
#RestController
public class FileDownloadController {
#Autowired
FileDownloadService fileDownloadService;
#RequestMapping(method = RequestMethod.POST, value = "/filedownload")
public String downloadFile(#RequestBody FileDetails fileDetails){
return fileDownloadService.downloadFile(fileDetails);
}
}
Following is Service
package com.filedownloader_with_nio_package.services;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import org.springframework.stereotype.Service;
import com.filedownloader_with_nio_package.exceptions.FileNotDownloadedCorrectlyException;
import com.filedownloader_with_nio_package.model.FileDetails;
import com.filedownloader_with_nio_package.utils.Constants;
#Service
public class FileDownloadService {
public String downloadFile(FileDetails fileDetails) {
try {
URL url = new URL(fileDetails.getFileUrl());
ReadableByteChannel readableByteChannel = Channels.newChannel(url
.openStream());
String downloadedFile = fileDetails.getFileDownloadLocation() + "/"
+ fileDetails.getFileName() + "."
+ fileDetails.getFileType();
FileOutputStream fileOutputStream = new FileOutputStream(
downloadedFile);
WritableByteChannel writableByteChannel = fileOutputStream
.getChannel();
//
//
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (readableByteChannel.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
writableByteChannel.write(buffer);
}
buffer.clear();
}
//
//
fileOutputStream.flush();
fileOutputStream.close();
return downloadedFile;
} catch (MalformedURLException e) {
throw new FileNotDownloadedCorrectlyException(
Constants.FILE_NOT_DOWNLOADED_CORRECTLY, e);
} catch (IOException e) {
throw new FileNotDownloadedCorrectlyException(
Constants.FILE_NOT_DOWNLOADED_CORRECTLY, e);
}
}
}
Following is FileDetails model
package com.filedownloader_with_nio_package.model;
public class FileDetails {
private String fileName;
private String fileUrl;
private String fileType;
private String fileDownloadLocation;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileUrl() {
return fileUrl;
}
public void setFileUrl(String fileUrl) {
this.fileUrl = fileUrl;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public String getFileDownloadLocation() {
return fileDownloadLocation;
}
public void setFileDownloadLocation(String fileDownloadLocation) {
this.fileDownloadLocation = fileDownloadLocation;
}
}
This is request body
{
"fileName": "SB2",
"fileUrl": "https://drive.google.com/open?id=1_gkQK8sAlgTslzfRGOvNtbEAwtoPeyJv",
"fileType":"mp4",
"fileDownloadLocation": "C:/DownloadedFiles"
}
I went trough the related questions and answers but I could not find a proper solution for this.
Can any one help to sort out the issue ? or any idea about this welcome.
you need a url with extension to download large files (videos) when using nio packages.
URL url = new URL("https://elpvideo.s3-us-west-2.amazonaws.com/4445/4445.mp4");
If your hosting service provides an API to download files with a particular token then nio packages don't work properly.
URL url = new URL("https://fv2-1.failiem.lv/down.php?i=sk2j3yuya");
tip
if you are downloading a large size of the file then make sure to set timeout if not then it will be disconnected in the middle.
URL url = new URL("https://elpvideo.s3-us-west-2.amazonaws.com/4445/4445.mp4");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setReadTimeout((1000*30)); //30 mins
ReadableByteChannel readableByteChannel = Channels.newChannel(urlConnection.getInputStream());
I'm trying to set up a server on aws with simple http server and save each http post request headers & payload.
It works locally.
My steps after connection via ssh to the ec2 server:
javac Server.java
sudo nohup java Server
It saves the headers to log file but not the payload and it doesn't returns 204 response.
Server.java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
public class Server {
private static final int PORT = 80;
private static final String FILE_PATH = "/home/ec2-user/logs/";
private static final String UTF8 = "UTF-8";
private static final String DELIMITER = "|||";
private static final String LINE_BREAK = "\n";
private static final String FILE_PREFIX = "dd_MM_YYYY_HH";
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FILE_PREFIX);
private static final String FILE_TYPE = ".txt";
public static void main(String[] args) {
try {
HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);
server.createContext("/", new HttpHandler() {
#Override
public void handle(HttpExchange t) throws IOException {
System.out.println("Req\t" + t.getRemoteAddress());
InputStream initialStream = t.getRequestBody();
byte[] buffer = new byte[initialStream.available()];
initialStream.read(buffer);
File targetFile = new File(FILE_PATH + simpleDateFormat.format(new Date()) + FILE_TYPE);
OutputStream outStream = new FileOutputStream(targetFile, true);
String prefix = LINE_BREAK + t.getRequestHeaders().entrySet().toString() + LINE_BREAK + System.currentTimeMillis() + DELIMITER;
outStream.write(prefix.getBytes());
Map<String, String> queryPairs = new HashMap<>();
String params = new String(buffer);
String[] pairs = params.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String key = pair.substring(0, idx);
String val = pair.substring(idx + 1);
String decodedKey = URLDecoder.decode(key, UTF8);
String decodeVal = URLDecoder.decode(val, UTF8);
queryPairs.put(decodedKey, decodeVal);
}
outStream.write(queryPairs.toString().getBytes());
t.sendResponseHeaders(204, -1);
t.close();
}
});
server.setExecutor(Executors.newCachedThreadPool());
server.start();
} catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
}
}
}
Consider these changes to your handle method. As a starting point, two things are changed:
It reads the complete input and copies that into your file (initialStream.available() might not be the full truth)
catch, log and rethrow IOExceptions (you didn't see your 204 after all)
Consider redirecting your output into files, so you can check what happend on server later:
sudo nohup java Server > server.log 2> server.err &
If you described in more detail the desired target file structure we could figure something out there as well I guess.
#Override
public void handle(HttpExchange t) throws IOException {
try {
System.out.println("Req\t" + t.getRemoteAddress());
InputStream initialStream = t.getRequestBody();
File targetFile = new File(FILE_PATH + simpleDateFormat.format(new Date()) + FILE_TYPE);
OutputStream outStream = new FileOutputStream(targetFile, true);
// This will copy ENTIRE input stream into your target file
IOUtils.copy(initialStream, outStream);
outStream.close();
t.sendResponseHeaders(204, -1);
t.close();
} catch(IOException e) {
e.printStackTrace();
throw e;
}
}
as you have seen before I'm working on a download manager in java, I have asked This Question and I have read This Question But These hadn't solve my problem. now I have wrote another code in java. but there is a problem. when download finishes file is larger than it's size and related software can't read it
This is image of my code execution :
as you see file size is about 9.43 MB
This is My project directory's image:
as you see my downloaded filesize is about 13 MB
So what is my Prooblem?
here is my complete source code
Main Class:
package download.manager;
import java.util.Scanner;
/**
*
* #author Behzad
*/
public class DownloadManager {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter url here : ");
String url = input.nextLine();
DownloadInfo information = new DownloadInfo(url);
}
}
DownloadInfo Class:
package download.manager;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DownloadInfo {
private String downloadUrl;
private String fileName;
private String fileExtension;
private URL nonStringUrl;
private HttpURLConnection connection;
private int fileSize;
private int remainingByte;
private RandomAccessFile outputFile;
public DownloadInfo(String downloadUrl) {
this.downloadUrl = downloadUrl;
initiateInformation();
}
private void initiateInformation(){
fileName = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1, downloadUrl.length());
fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1, fileName.length());
try {
nonStringUrl = new URL(downloadUrl);
connection = (HttpURLConnection) nonStringUrl.openConnection();
fileSize = ((connection.getContentLength()));
System.out.printf("File Size is : %d \n", fileSize);
System.out.printf("Remain File Size is : %d \n", fileSize % 8);
remainingByte = fileSize % 8;
fileSize /= 8;
outputFile = new RandomAccessFile(fileName, "rw");
} catch (MalformedURLException ex) {
Logger.getLogger(DownloadInfo.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(DownloadInfo.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.printf("File Name is : %s\n", fileName);
System.out.printf("File Extension is : %s\n", fileExtension);
System.out.printf("Partition Size is : %d MB\n", fileSize);
int first = 0 , last = fileSize - 1;
ExecutorService thread_pool = Executors.newFixedThreadPool(8);
for(int i=0;i<8;i++){
if(i != 7){
thread_pool.submit(new Downloader(nonStringUrl, first, last, (i+1), outputFile));
}
else{
thread_pool.submit(new Downloader(nonStringUrl, first, last + remainingByte, (i+1), outputFile));
}
first = last + 1;
last += fileSize;
}
thread_pool.shutdown();
try {
thread_pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
Logger.getLogger(DownloadInfo.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
and this is my downloader class:
package download.manager;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* #author Behzad
*/
public class Downloader implements Runnable{
private URL downloadURL;
private int startByte;
private int endByte;
private int threadNum;
private RandomAccessFile outputFile;
private InputStream stream;
public Downloader(URL downloadURL,int startByte, int endByte, int threadNum, RandomAccessFile outputFile) {
this.downloadURL = downloadURL;
this.startByte = startByte;
this.endByte = endByte;
this.threadNum = threadNum;
this.outputFile = outputFile;
}
#Override
public void run() {
download();
}
private void download(){
try {
System.out.printf("Thread %d is working...\n" , threadNum);
HttpURLConnection httpURLConnection = (HttpURLConnection) downloadURL.openConnection();
httpURLConnection.setRequestProperty("Range", "bytes="+startByte+"-"+endByte);
httpURLConnection.connect();
outputFile.seek(startByte);
stream = httpURLConnection.getInputStream();
while(true){
int nextByte = stream.read();
if(nextByte == -1){
break;
}
outputFile.write(endByte);
}
} catch (IOException ex) {
Logger.getLogger(Downloader.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
This file is MP4 for as you seen, but Gom can't play it
Would you please help me?
OoOoOopppps finally I found what is the problem , It's all on seek method. because i have a file and 8 threads. so seek method changes the cursor repeatedly and make larger file and unexecutable file :), But I'm so sorry . I can't show whole code :)