How to achieve mp3 stream using java Servlet - google-app-engine

Goal: build a servlet so that when I type http://xxx.com/servpage?a.mp3 in browser, I can instantaneously start the playing of this mp3 file. Previously if I put the file on goDaddy as a static file, I can do that. My software can play it right away.
Using Servlet, I can ignore what is after ?, just want this page to return the mp3 dynamically (because in the future I may return any other files). What I got is a long wait (>20 seconds), and then got the player to play it.
I followed some examples, and noticed "attachment" in the example. However, if I remove it, the mp3 won't got played even. I am usign Google App Engine though, but just use the input/outputstream to return the http request. Anyone can help?
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException ,IOException {
res.setContentType("audio/mpeg3");
OutputStream os = res.getOutputStream();
res.setHeader("Content-Disposition", "attachment; filename="" + "a.mp3";");
res.setContentLength(1000000);
FileService fileService = FileServiceFactory.getFileService();
boolean lockForRead = false;
String filename = "/gs/" + BUCKETNAME + "/" + FILENAME;
AppEngineFile readableFile = new AppEngineFile(filename);
try{
FileReadChannel readChannel = fileService.openReadChannel(readableFile, lockForRead);
InputStream is = Channels.newInputStream(readChannel);
int BUFF_SIZE = 1024;
byte[] buffer = new byte[BUFF_SIZE];
try {
do {
int byteCount = is.read(buffer);
if (byteCount == -1)
break;
os.write(buffer, 0, byteCount);
os.flush();
} while (true);
} catch (Exception excp) {
} finally {
os.close();
is.close();
}
readChannel.close();
} catch(Exception e){
}
}

Few notes:
You are not doing "streaming". Just a plain file download.
To do blob (file) serving, you do not need to read the blob from BlobStore as you do with AppEngineFile. Just serve it directly with blobstoreService.serve(blobKey). See Serving a Blob for an example.
You can get the BlobKey needed in 2. via fileService.getBlobKey(readableFile).
Update:
Just realized you are using Google Cloud Storage, not BlobStore.
In GS, if ACLs are properly set, files are publicly visible via: http://commondatastorage.googleapis.com/BUCKETNAME/FILENAME
Since you are not doing any authentication, you could publicly share the file on GS and then in your servlet just do a 301 redirect to public URL of the file.

Related

Get file size of an URL without download

To get the file size of an URL without download, I wrote:
public static long getFileSizeWithoutDownload(String url) {
ConnectionRequest cr = new GZConnectionRequest();
cr.setUrl(url);
cr.setPost(false);
NetworkManager.getInstance().addProgressListener((NetworkEvent evt) -> {
if (cr == evt.getConnectionRequest() && evt.getLength() > 0) {
cr.kill();
}
});
NetworkManager.getInstance().addToQueueAndWait(cr);
return cr.getContentLength();
}
It seems to work on Simulator, Android and iOS with a testing URL of my Spring Boot server.
Anyway, I consider this code as a workaround, as I couldn't find an API that directly gives me the file size without starting the download first. Starting the download and then killing it works, but maybe there may be a better way to get the same result. By the way, the condition && evt.getLength() > 0 may never be satisfied in some cases (depending on the headers received), so it would be better to read only the headers, in which "Content-Length" may be present or absent.
So, my question is if, with Codename One, there is a way to download only the response headers, without starting the download. Thank you.
Using the HTTP head request should give you the content length header that you can then use to get the size of the file without triggering a download. Your code might not follow through on the download but it does physically happen so a head request would be superior.
Unfortunately while there's a nice wrapper to head in Rest. This wrapper isn't very useful since there's no API to query response headers. That would make sense as an enhancement. You would need to derive ConnectionRequest and read the server response headers to get the content length.
Thank you Shai, your answer https://stackoverflow.com/a/62124902/1277576 led me in the right direction. cr.setHttpMethod("HEAD"); simplifies the code and prevents the download from starting:
public static long getFileSizeWithoutDownload(String url) {
ConnectionRequest cr = new GZConnectionRequest();
cr.setUrl(url);
cr.setHttpMethod("HEAD");
cr.setPost(false);
NetworkManager.getInstance().addToQueueAndWait(cr);
return cr.getContentLength();
}
However, as you wrote, I can override ConnectionRequest for a more precise control of the headers. This other method performs the same function as the previous one, but it also guarantees me that the server supports partial downloads. In fact, if the server does not support partial downloads, the information about the content length would be useless for my purposes:
/**
* Returns -2 if the server doesn't accept partial downloads, -1 if the
* content length is unknow, a value greater than 0 if the Content-Length is
* known
*
* #param url
* #return must be interpreted as a boolean value: if greater than zero than
* partial downloads are supported (the returned value is the Content-Length),
* otherwise they are not supported.
*/
public static long getFileSizeWithoutDownload(String url) {
// documentation about the headers: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
Wrapper<Long> result = new Wrapper<>(0l);
ConnectionRequest cr = new GZConnectionRequest() {
#Override
protected void readHeaders(Object connection) throws IOException {
String acceptRanges = getHeader(connection, "Accept-Ranges");
if (acceptRanges == null || !acceptRanges.equals("bytes")) {
Log.p("The partial downloads of " + url + " are not supported.", Log.WARNING);
result.set(-2l);
} else {
String contentLength = getHeader(connection, "Content-Length");
if (contentLength != null) {
result.set(Long.parseLong(contentLength));
} else {
Log.p("The Content-Length of " + url + " is unknown.", Log.WARNING);
result.set(-1l);
}
}
}
};
cr.setUrl(url);
cr.setHttpMethod("HEAD");
cr.setPost(false);
NetworkManager.getInstance().addToQueueAndWait(cr);
return result.get();
}
The readHeaders and getHeader methods are implementation dependent. I have verified that they work as desired on Simulator, Android and iOS.
Lastly, the Wrapper class is so implemented:
/**
* Generic object wrapper, as workaround for the issue "Local variables
* referenced from a lambda expression must be final or effectively final".
*/
public class Wrapper<T> {
private T object;
public Wrapper(T obj) {
this.object = obj;
}
public T get() {
return object;
}
public void set(T obj) {
this.object = obj;
}
}
I hope this detailed answer will help those who need to read HTTP headers with Codename One.

Download File XML with Spring boot

I'm trying to download files with a React front end, but the method in the controller doesn't download it
The method works because it doesn't launch any exception and the byte array works, but when I close the streams the download does not happen
public void downloadFile(#PathVariable("numeroOfferta") String numeroOfferta, #RequestParam(value="file") String file, HttpServletResponse response, HttpServletRequest req) throws IOException {
String filePathToBeServed =
File fileToDownload = new File(filePathToBeServed + file);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename="+file);
response.setStatus(HttpServletResponse.SC_OK);
try(FileInputStream in = new FileInputStream(fileToDownload);
OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[4096];
while ((in.read(buffer, 0, 4096)) != -1) {
out.write(buffer, 0, 4096);
}
out.flush();
out.close();
in.close();
}
NumeroOfferta is just a String that I need to go inside folders and file contains the name of the file I send from the web page.
I can't understand why it doesn't download the file I choose despite the method works. Thank you for every answer
Simply use Spring MVC ResponseEntity:
public void downloadFile(#PathVariable("numeroOfferta") String numeroOfferta,
#RequestParam(value="file") String file) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData(file, file);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
byte[] content = Files.readAllBytes(new File(filePathToBeServed + file).toPath());
return new ResponseEntity(content, headers, HttpStatus.OK);
}
That's it.

Deadline exceeded error when inserting attachments into Google Drive - Java

When there are more attachments and in the process of adding all into the google drive from the code there is an exception with the below description.
com.google.apphosting.api.DeadlineExceededException
A problem was encountered with the process that handled this request, causing it to exit. This is likely to cause a new process to be used for the next request to your application. If you see this message frequently, you may be throwing exceptions during the initialization of your application. (Error code 104)
Using the below code to save the attachments into Google Drive.
public File insertFile(Drive service, String title, String description, String mimeType,String parentId , String filename, byte fileByteArray[],
HttpServletRequest req, HttpServletResponse resp)
throws MessagingException, ParseException, InterruptedException
{
try
{
File body = new File();
body.setTitle(title);
body.setDescription(description);
body.setMimeType(mimeType);
if(parentId != null && parentId.length() > 0)
body.setParents(Arrays.asList(new ParentReference[] {
(new ParentReference()).setId(parentId)
}));
com.google.api.services.drive.Drive.Files.List files = service.files().list();
FileList fileList = (FileList)files.execute();
List fileArray = fileList.getItems();
log.info("fileArray"+fileArray);
log.info("file Array Size"+fileArray.size());
File file=null;
try{
file = (File)service.files().insert(body, new InputStreamContent(mimeType, new ByteArrayInputStream(fileByteArray))).execute();
log.info("-----------File----------"+file);
System.out.println("FileName="+MimeUtility.encodeText(file.getTitle()));
System.out.println("File URL"+file.getAlternateLink());
System.out.println("File ID"+file.getId());
return file;
}
catch(DeadlineExceededException e)
{
log.info((new StringBuilder("An error occured: ")).append(e).toString());
//inserted for attachment saving error
System.out.println("inside exception deadlineexception");
}
catch(Exception e)
{
log.info((new StringBuilder("An error occured: ")).append(e).toString());
}
return null;
}
Can someone suggest a way to optimize the code so that all the attachments say around 10 to 15 can be saved into the google drive without any exception.

How can I e-mail a .csv file in Codename One?

In my app, I create a file with a comma-separated array by writing to an OutputStream. Then I want to be able to share this by e-mail so a user can get the data. This is the code I use to create the file:
public String getLogFile(String logName) {
String path = FileSystemStorage.getInstance().getAppHomePath() + "exp " + logName + ".csv";
Set<Long> keys;
OutputStream os = null;
try {
os = FileSystemStorage.getInstance().openOutputStream(path);
Hashtable<Long, Integer> log = (Hashtable<Long, Integer>) dataStorage
.readObject(logName);
keys = log.keySet();
for (Long key : keys) {
String outString = (key + "," + log.get(key) + "\n");
System.out.println(outString);
byte[] buffer = outString.getBytes();
os.write(buffer);
}
} catch (IOException e) {
AnalyticsService.sendCrashReport(e, "Error writing log", false);
e.printStackTrace();
} finally {
try {
os.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
return path;
}
Then, I've created a button that when pressed passes the path of the file to share. I've tried to use MIME types such as "text/plain" and "text/comma-separated-values", but that causes errors. Here is the code executed when the button is pressed.
public void exportLog(String logName) {
String path = dataBuffer.getLogFile(logName);
EmailShare email = new EmailShare();
// email.share("Here is your log.", path, "text/plain");
email.share("Here is your log.", path, "text/comma-separated-values");
}
When pressed (in the simulator). I get this stack after selecting the dummy e-mail contact to send to:
java.lang.NullPointerException
at com.codename1.impl.javase.JavaSEPort.scale(JavaSEPort.java:3483)
at com.codename1.ui.Image.scale(Image.java:963)
at com.codename1.ui.Image.scaledImpl(Image.java:933)
at com.codename1.ui.Image.scaled(Image.java:898)
at com.codename1.impl.javase.JavaSEPort$60.save(JavaSEPort.java:6693)
at com.codename1.share.ShareForm.<init>(ShareForm.java:75)
at com.codename1.share.EmailShare$1$2$1.actionPerformed(EmailShare.java:102)
at com.codename1.ui.util.EventDispatcher.fireActionSync(EventDispatcher.java:455)
at com.codename1.ui.util.EventDispatcher.fireActionEvent(EventDispatcher.java:358)
at com.codename1.ui.List.fireActionEvent(List.java:1532)
at com.codename1.ui.List.pointerReleasedImpl(List.java:2011)
at com.codename1.ui.List.pointerReleased(List.java:2021)
at com.codename1.ui.Form.pointerReleased(Form.java:2560)
at com.codename1.ui.Component.pointerReleased(Component.java:3108)
at com.codename1.ui.Display.handleEvent(Display.java:2017)
at com.codename1.ui.Display.edtLoopImpl(Display.java:1065)
at com.codename1.ui.Display.mainEDTLoop(Display.java:994)
at com.codename1.ui.RunnableWrapper.run(RunnableWrapper.java:120)
at com.codename1.impl.CodenameOneThread.run(CodenameOneThread.java:176)
The EmailShare class expects a path to an image file not an arbitrary file as its second argument so loading that fails.
The Message class is better suited for that indeed. You can also use the cloud send option which won't launch the native email app. E.g. the Log class includes that ability directly thru the Log.sendLog API.
It looks like the Messages class is better suited for this task, and should allow attachments, etc.

IOUtils.copy throws IOException for files larger than ~1 mb

I'm using Apache Commons IO's IOUtils.copy to make copies of existing files, on Google Cloud Storage.
Here is the problem: if the files goes beyond around 1 mb, IOUtils.copy will throw an exception. Files below 1 mb works perfectly fine.
Code snippet
AppEngineFile newFile = null;
GSFileOptionsBuilder optionsBuilder = new GSFileOptionsBuilder().setBucket("bucket-name")
.setKey(newFilename).setMimeType("image/jpeg");
try {
//currentFile is an instance of a AppEngineFile
FileReadChannel readChannel = fileService.openReadChannel(currentFile, true);
newFile = fileService.createNewGSFile(optionsBuilder.build());
FileWriteChannel writeChannel = fileService.openWriteChannel(newFile, true);
InputStream inputStream = Channels.newInputStream(readChannel);
OutputStream outputStream = Channels.newOutputStream(writeChannel);
IOUtils.copy(inputStream, outputStream);
outputStream.flush();
outputStream.close();
inputStream.close();
writeChannel.closeFinally();
} catch (IOException e) {
log.warning(e.toString());
}
I also attempted a more primitive method: but it doesn't work too
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
while (len != -1) {
outputStream.write(buffer, 0, len);
len = inputStream.read(buffer);
}
My assumption now: there is some kind of restriction on the App Engine platform.
Note: I've also tried IOUtiles.copyLarge (which is meant for files larger than 2 GB), but it doesn't work too.
Try using a smaller than 1mb buffer. Latest appengine version reduced buffer size.

Resources