Saturday, June 20, 2009

JavaFX http file upload

I have been fiddling around to find an easy way to upload files with a post http request. This doesn't come out of the box with JavaFX 1.2. With a simple subclass of HttpRequest however this is possible:

Consider the use case of having the user to select one or more files to upload :

// total amount of bytes to upload
var uploadFileSize = 0;

// total amount uploaded bytes
var uploadedBytes = 0;

// a file chooser to select the files to upload
def fc = JFileChooser{};

// the http request object to use for a post
var uploadRequest:HttpRequest;

// the url to post to
def uploadURL = "http://localhost:8080/alexandria/wsrs/document";

// more than one file can be selected
fc.setMultiSelectionEnabled(true);

// the user did select one or more files
if ( fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION ) {

// compute total bytes to upload (approximation)
uploadFileSize = 0;
for ( file in fc.getSelectedFiles() ) {
uploadFileSize += file.length();
}

// kick off the upload
uploadedBytes = 0;
progressDialog.visible = false;

// initiate the upload request to the server
uploadRequest = FileUploadHTTPRequest {
location: uploadURL
files: fc.getSelectedFiles()

onWritten:function(bytes: Long) {
uploadedBytes = bytes;
}

onDone: function() {
println("Upload finished")
}

onResponseCode: function(code:Integer) {
println("onResponseCode - responseCode: {code}")
}

onResponseMessage: function(msg:String) {
println("onResponseMessage - responseMessage: {msg}")
}
}

uploadRequest.start();

}

This is the source code for the upload http request

import javafx.io.http.HttpRequest;
import java.io.File;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import javafx.io.http.HttpHeader;
import javafx.date.DateTime;
import javafx.data.Pair;

/**
* HttpRequest able to upload files to a given url, using Pair objects
* to send fields.
*
* @author jan
*/

def PREFIX = "--";

def NEWLINE = "\r\n";

def BOUNDARY = createBoundary();

def CONTENT_TYPE = "multipart/form-data; boundary={BOUNDARY}";

def NAME = "upfile";

function createBoundary() :String {
def now = DateTime {};
return "{Long.toHexString(now.instant)}";
}


public class FileUploadHTTPRequest extends HttpRequest {

// A sequence of files to upload
public-init var files:File[];

// Fields to upload together with the request
public-init var fields:Pair[];

postinit{

// we always do a post
method = HttpRequest.POST;

// override the onOutput
onOutput = writeFiles;

// add the headers we need
insert HttpHeader {
name:HttpHeader.ACCEPT
value: "*/*"
} into headers;
insert HttpHeader {
name:HttpHeader.CONTENT_TYPE
value: CONTENT_TYPE
} into headers;
insert HttpHeader {
name:HttpHeader.CONNECTION
value: "keep-alive"
} into headers;
insert HttpHeader {
name:HttpHeader.CACHE_CONTROL
value: "no-cache"
} into headers;
insert HttpHeader {
name:HttpHeader.ACCEPT_CHARSET
value: "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
} into headers;
}

function writeFiles( output:OutputStream ) {
try {
// send the files
if ( files != null and sizeof files > 0 ) {
for ( file in files ) {
// leading characters
output.write(PREFIX.getBytes());
output.write(BOUNDARY.getBytes());
output.write(NEWLINE.getBytes());
output.write("Content-Disposition: form-data; name=\"{NAME}\"; filename=\"{file.getName()}\"".getBytes());
output.write(NEWLINE.getBytes());
output.write("Content-Type: application/octet-stream".getBytes());
output.write(NEWLINE.getBytes());
output.write(NEWLINE.getBytes());
// write the file's content
var input = new BufferedInputStream( new FileInputStream( file ) );
IOUtils.copy(input, output);
input.close();
// end of file
output.write(NEWLINE.getBytes());
output.flush();
}
output.write(NEWLINE.getBytes());
}

// write the fields
if ( fields != null and sizeof fields > 0 ) {
for ( field:Pair in fields ) {
output.write(PREFIX.getBytes());
output.write(BOUNDARY.getBytes());
output.write(NEWLINE.getBytes());
// write content header
output.write("Content-Disposition: form-data; name=\"{field.name}\"".getBytes());
output.write(NEWLINE.getBytes());
output.write(NEWLINE.getBytes());
// write content
output.write("{field.value}".getBytes());
output.write(NEWLINE.getBytes());
output.flush();
}
}
output.write(PREFIX.getBytes());
output.write(BOUNDARY.getBytes());
output.write(PREFIX.getBytes());
output.write(NEWLINE.getBytes());

} finally {
output.flush();
output.close();
}
}
}

Sunday, May 3, 2009

Code samples page

A one-hour presentation derailed into a two and half hour workshop. I really thought I could give it in half an hour with another half an hour for the questions and trials. It derailed even when I thought I've cut out the maximum to avoid a long presentation.

I hope at least some people appreciated - they patiently sat down until I was finished. Thanks guys !

As the sample code for the presentation is way too big to send to the audience I hastily opened a web page to put them on. This way anyone who's experimenting with JavaFX can have some code already to play with.

The page can be visited here

Monday, April 27, 2009

Launch

Tomorrow I'll be introducing JavaFX in my company. I hope I can convince them to give it at least a try. And who knows: actively helping JavaFX to survive in these uncertain times for this quite unique and marvelous language.

The tools are quite like in the old days - not yet mainstream.

I'm launching this blog tonight in the hope I won't be teaching a dead language tomorrow. It would really be a shame if JavaFX would go down the drain for the sake of making even more money.