For a pet project of mine some http file uploading is involved. It's easy to write a jsp page doing a multipart http post request with a selected file and fields. Doing it in JavaFX doesn't seem to work out of the box. That is, not as a multipart http request. And if it does, please be gentle when reminding me. :-) I thought this might be useful to other people too. At the bottom of the page I'm including a link to the source code. Consider the following jsp page : <%@page contentType="text/html" pageEncoding="windows-1252"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/> <title>Upload Document</title> </head> <body> <h1>Upload a document in Alexandria</h1> <form method='POST' enctype='multipart/form-data' action='wsrs/document'> <table border="0" width="60%"> <tr> <td>File</td> <td><input type="file" name="upfile"/></td> </tr> <tr> <td>File Name</td> <td><input type="text" name="fileName"/></td> </tr> <tr> <td>Title</td> <td><input type="text" name="title"/></td> </tr> <tr> <td>Notes</td> <td><input type="text" name="notes"/></td> </tr> </table> <input type="submit" value="Upload Document"/> </form> </body> </html> This page will allow the user to enter a title, notes and select a file from the local filesystem. Pressing the submit button uploads the file, together with the two fields to the original server. To achieve the same in JavaFX I've subclassed HttpRequest in a way the same operation is done as follows : def fileToUpload:File; ... code to select a local file ... def uploadRequest = FileUpload.HTTPRequest { location: http://localhost:8080/..... // sequence of files to upload files: [ fileToUpload ] // sequence of fields to upload fields: [ Pair { name: "title" value: "file title" } Pair { name: "notes" value: "file notes" } ] } uploadRequest.start(); Simply pass the a sequence of File objects (in case you want to upload more than one file) and a sequence of name/value Pair objects for the fields. Execute the start() function, just like you would do for a regular http get request. The source code for the FileUpload.fx script : import javafx.io.http.HttpRequest; import java.io.File; import java.io.OutputStream; 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 * * @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 HTTPRequest 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 on the wire - use a native array of byte // in order to use Java's IO classes var buffer = IOUtils.createByteBuffer(10240); var input = new BufferedInputStream( new FileInputStream( file ) ); var eof = false; try { while ( true ) { var read = input.read(buffer); if ( read > 0 ) { output.write(buffer, 0, read); } else { break; } } } finally { 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(); } } } I'm not that experienced with low-level HTTP, but it seems to work just fine. For those who wonder why I created the IOUtils Java class, this is to create a native byte array for doing the I/O. The Java I/O classes won't accept Byte sequences. Neither can they be cast to native byte arrays. I haven't found the way yet to do this in JavaFX. As such this class is rather straightfoward : public class IOUtils { public static final byte[] createByteBuffer( int length ) { return new byte[length]; } } There is no doubt this code can be optimized or corrected in some way. For my purposes it does the job. For the sake of being complete I'm including a basic upload dialog with which the user can select a file and upload it to the a hardcoded url. import java.io.File; import javax.swing.JFileChooser; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.layout.HBox; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.VBox; import javafx.scene.control.Label; import javafx.scene.control.TextBox; import javafx.scene.layout.LayoutInfo; import javafx.scene.control.Button; import javafx.data.Pair; import FileUpload.*; import java.lang.Exception; /** * @author jgoyvaer */ def WIDTH = 400; def HEIGHT = 160; def UPLOAD_URL = "http://localhost:8080/....."; var fileToUpload:File; var fileNameToUpload:String; var progressUpload:Float; var bytesToUpload:Float; var uploadRequest:HttpRequest; function start() { progressUpload = 0; bytesToUpload = 0; uploadRequest = HttpRequest { location: UPLOAD_URL files: [ fileToUpload ] fields: [ Pair { name: "title" value: "file title" } Pair { name: "notes" value: "file notes" } ] onToWrite: function( size:Long ) { bytesToUpload = size; } onWritten: function( size:Long ) { progressUpload = 1.0 * size / bytesToUpload; } onDone: stop onException: function( ex:Exception ) { println(ex.getMessage()); stop(); } } uploadRequest.start(); } function stop() { progressUpload = 0; bytesToUpload = 0; if ( uploadRequest != null ) { uploadRequest.stop(); uploadRequest = null; } } Stage { title: "Uploading files over HTTP" scene: Scene { width: WIDTH height: HEIGHT content: HBox { spacing: 5 content: [ ProgressIndicator { progress: bind progressUpload layoutInfo: LayoutInfo { width: 100 height: 150 } scaleX: 4 scaleY: 4 } VBox { spacing: 5 content: [ Label { text:"File to upload" } HBox { spacing: 2 content: [ TextBox { text: bind fileNameToUpload with inverse layoutInfo: LayoutInfo { width: WIDTH - 150; } } Button { text:"..." action: function() { def fc = JFileChooser{}; if ( fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION ) { fileToUpload = fc.getSelectedFile(); fileNameToUpload = fileToUpload.getAbsolutePath(); } } } ] } Button { text: bind if ( uploadRequest != null ) "Cancel" else "Upload"; disable: bind fileToUpload == null; action: function() { if ( uploadRequest != null ) { stop(); } else { start(); } } } ] } ] } } } The dialog script is using Swing's JFileChooser. As a result I guess this code will only run in a desktop profile application. As a final note I would advise to use JFXtras' MigLayout to shorten the coding a bit. It would add an additional dependency with the desktop profile though. Please let me know if this code was any useful to you. :-) |