views:

285

answers:

3

I'm attempting to use Panda with my GWT application. I can upload videos directly to my panda server using

POST MY_PANDA_SERVER/videos/MY_VIDEO_ID/upload

However I would like hide my panda server behind my J2EE (glassfish) server. I would like to achieve this:

  1. Start upload to some servlet on my J2EE server
  2. Authenticate user
  3. POST the file to my panda server while still uploading to servlet

Ideally I would like to never store the file on the J2EE server, but just use it as a proxy to get to the panda server.

A: 

You can use apache commons file upload to receive the file. Then you can use http client to upload the file to your panda server with POST. With apache commons file upload you can process the file in memory so you don't have to store it.

Enrique
but if I have multiple users uploading files around 1 GB in size, my server will run out of memory real quick. I REALLY want to be able to upload it to the panda server while still receiving the file from the user.
KevMo
You can use a custom Servlet so every time you receive an X number of bytes you send those X bytes to panda using http client and then remove those X bytes from the J2EE Server
Enrique
exactly what I want to do.... but how?
KevMo
A: 

Building upon Enrique's answer, I also recommend to use FileUpload and HttpClient. FileUpload can give you a stream of the uploaded file:

// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload();

// Parse the request
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
    FileItemStream item = iter.next();
    String name = item.getFieldName();
    InputStream stream = item.openStream();
    if (item.isFormField()) {
        System.out.println("Form field " + name + " with value "
            + Streams.asString(stream) + " detected.");
    } else {
        System.out.println("File field " + name + " with file name "
            + item.getName() + " detected.");
        // Process the input stream
        ...
    }
}

You could then use HttpClient or HttpComponents to do the POST. You can find an example here.

kgiannakakis
However, the stream is stored in memory and that's what he want to avoid.
BalusC
I've suggested that OP uses the Streaming API of FileUpload (see the link in my answer). I was under the impression that this API doesn't store the response in memory before creating the stream. Am I mistaken?
kgiannakakis
No, it does not. How else would it be able to give the **separate** file items back through the iterator? Knowing which items to give back requires parsing the entire body. Besides, here's a quote of the API: "On the other hand, it is memory and time consuming.". After all, if you check its source code and/or try to reinvent the FileUpload API yourself, you'll see it yourself as well.
BalusC
I haven't looked at the source code. On the other hand: 'The streaming API allows you to trade a little bit of convenience for optimal performance and and a low memory profile' lead me to believe that everything isn't stored at memory first.
kgiannakakis
Wait, you're actually right. It's a "parse on demand" API, parsing happens during `iterator.hasNext()` only. It's however still pointless to parse the stream with FileUpload first and then construct the same stream again with HttpClient.
BalusC
+2  A: 

Commons FileUpload is nice, but not sufficient in your case. It will parse the entire body in memory before providing the file items (and streams). You're not interested in the individual items. You basically just want to stream the request body from the one to other side transparently without altering it or storing it in memory in any way. FileUpload would only parse the request body into some "useable" Java objects and HttpClient would only create the very same request body again based on those Java objects. Those Java objects consumes memory as well.

You don't need a library for this (or it must be Commons IO to replace the for loop with an oneliner using IOUtils#copy()). Just the basic Java NET and IO API's suffices. Here's a kickoff example:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    URLConnection connection = new URL("http://your.url.to.panda").openConnection();
    connection.setDoOutput(true); // POST.
    connection.setRequestProperty("Content-Type", request.getHeader("Content-Type")); // This one is important! You may want to check other request headers and copy it as well.

    // Set streaming mode, else HttpURLConnection will buffer everything.
    int contentLength = request.getContentLength();
    if (contentLength > -1) {
        // Content length is known beforehand, so no buffering will be taken place.
        ((HttpURLConnection) connection).setFixedLengthStreamingMode(contentLength);
     } else {
        // Content length is unknown, so send in 1KB chunks (which will also be the internal buffer size).
        ((HttpURLConnection) connection).setChunkedStreamingMode(1024);
    }

    InputStream input = request.getInputStream();
    OutputStream output = connection.getOutputStream();
    byte[] buffer = new byte[1024]; // Uses only 1KB of memory!

    for (int length = 0; (length = input.read(buffer)) > 0;) {
        output.write(buffer, 0, length);
        output.flush();
    }

    output.close();
    connection.getInputStream(); // Important! It's lazily executed.
}
BalusC
you sir, are my hero.Any idea how to get the response from my Panda server, and send it back to my client?
KevMo
The response body is available by `connection.getInputStream()`. Just write it the same way to `response.getOutputStream()`. You can obtain the necessary response headers by `connection.getHeaderFields()` and pass it through by `response.setHeader(name, value)`.
BalusC
if you upload big files it will kill your java server. That's what I have in my application, every time when i upload file bigger than 200 mb I'm getting java.lang.OutOfMemoryError: Java heap space. How do I fix that?
Maksim
@Maksim: You're right, this was a bug in `HttpURLConnection` which was fixed in Java 1.5 with several new `StreamingMode` methods. I've updated the code example accordingly.
BalusC