public
Last active

MultiPart Form POST - Compatible with Uploading Files to S3

  • Download Gist
media_uploader.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
Ti.include("/lib/strftime.js");
// see: http://blog.odonnell.nu/posts/streaming-uploads-s3-python-and-poster/
 
function MediaLoader(user) {
this.user = user;
}
MediaLoader.prototype = {
// upload a file with some postData fields
//
// multipartUpload(host, port, "your_file_name", fileObject, [['field1','value'], ['field2','value']], function(p) { });
//
multipartUpload: function(host, port, fileName, file, postData, callback) {
var CHUNK_SIZE = 8192;
 
Ti.API.info(fileName);
Ti.API.info(postData);
 
var multipartUpload = this.multipartUpload.bind(this);
var sock = Ti.Network.Socket.createTCP({
host: host,
port: port,
connected: function(e) {
Ti.API.info("connected building request to POST");
var fileSize = file.size;
Ti.API.info("FileSize: " + fileSize);
var boundary = "----------GI3Ef1cH2ae0KM7cH2Ef1cH2cH2GI3";
var bry = "--" + boundary + "\r\n";
var bodyBuffer = Ti.createBuffer();
var bufferWriter = function(buffer, str) {
var tmp = Ti.createBuffer({value: str});
buffer.append(tmp);
tmp.release();
}
var trailingBytes = Ti.createBuffer({value:"\r\n" + bry + 'Content-Disposition: form-data; name="Upload"' + "\r\n\r\n" + "Submit Query\r\n--" + boundary + '--' + "\n"});
// build the body buffer excluding the file so we can calculate the correct Content-Length
// build the body in chunks each chunk wrapped in a boundary, bry
var subHeader = function(key,value) {
bufferWriter(bodyBuffer, bry);
bufferWriter(bodyBuffer, 'Content-Disposition: form-data; name="' + key + '"' + "\r\n\r\n");
bufferWriter(bodyBuffer, value);
bufferWriter(bodyBuffer, "\r\n");
}
var writeDirect = function(str) {
var tmp = Ti.createBuffer({value: str});
sock.write(tmp);
tmp.release();
}
var writeKeyValue = function(key, value) {
writeDirect(bry);
writeDirect('Content-Disposition: form-data; name="' + key + '"' + "\r\n\r\n");
writeDirect(value);
writeDirect("\r\n");
}
postData.forEach(function(keypair) { subHeader(keypair[0], keypair[1]); });
// start to read the file and chunk it to the socket
// Content-Disposition: form-data; name="file"; filename="test1.mov"
bufferWriter(bodyBuffer, bry);
bufferWriter(bodyBuffer, 'Content-Disposition: form-data; name="file"; filename="' + fileName + '"' + "\r\n");
bufferWriter(bodyBuffer, "Content-Type: application/octet-stream\r\n\r\n");
 
var contentLength = bodyBuffer.length + fileSize + trailingBytes.length;
Ti.API.info("ContentLength: " + contentLength);
var bytesToUpload = contentLength;
callback({state: 'connected', bytesToUpload: bytesToUpload});
 
var headerBuffer = Ti.createBuffer();
// build the request header using the calculated Content-Length
bufferWriter(headerBuffer, "POST / HTTP/1.1\r\n");
bufferWriter(headerBuffer, "Host: " + host + "\r\n");
bufferWriter(headerBuffer, "Connection: close\r\n");
bufferWriter(headerBuffer, "User-Agent: Ti.PostUploader\r\n");
bufferWriter(headerBuffer, "Accept-Types: text/xml\r\n");
bufferWriter(headerBuffer, "Content-Type: multipart/form-data; boundary=" + boundary + "\r\n");
bufferWriter(headerBuffer, "Content-Length: " + contentLength + "\r\n");
bufferWriter(headerBuffer, "\r\n");
var bytesSent = sock.write(headerBuffer);
callback({state: 'progress', bytesSent: bytesSent, bytesToUpload: bytesToUpload});
// send the first chunk of the request body
bytesSent += sock.write(bodyBuffer);
callback({state: 'progress', bytesSent: bytesSent, bytesToUpload: bytesToUpload});
 
contentLength -= bodyBuffer.length;
Ti.API.info("sent initial body: " + contentLength);
 
if (contentLength < fileSize + trailingBytes.length) {
Ti.API.error("uhh. less contentLength remaining but more to write: " + contentLength + " < " + (fileSize + trailingBytes.length));
sock.close();
}
 
headerBuffer.release();
bodyBuffer.release();
 
// open the file as a stream
var bytes_read = 0;
var length = 0;
 
if (typeof(file.open) == 'function') { // so we can test
var stream = file.open(Ti.Filesystem.MODE_READ, true);
} else {
var stream = Ti.Stream.createStream({ mode: Ti.Stream.MODE_READ, binary: true, source: file });
}
 
Ti.API.info(stream);
var read_buffer = Ti.createBuffer({length: CHUNK_SIZE, byteOrder: Ti.Codec.BIG_ENDIAN, type: Ti.Codec.TYPE_BYTE});
// start streaming the bytes in CHUNK_SIZE blocks
var bytes_written = 0;
var total_written = 0;
while ((length = stream.read(read_buffer)) > 0) {
read_buffer.length = length; // ensure size
bytes_written = sock.write(read_buffer);
if (length != bytes_written) {
Ti.API.error("Possible network error? expected to write: " + length + " but actually wrote: " + bytes_written);
}
read_buffer.clear(); // clear the buffer before the next read
bytes_read += length;
total_written += bytes_written;
bytesSent += bytes_written;
callback({state: 'progress', bytesSent: bytesSent, bytesToUpload: bytesToUpload});
}
contentLength -= total_written;
Ti.API.info("read: " + bytes_read + " bytes: " + contentLength + ", with: " + trailingBytes.length + " bytes left");
if (contentLength != trailingBytes.length) {
Ti.API.error("uhh. less contentLength remaining but more to write: " + contentLength + " < " + trailingBytes.length);
}
//writeDirect("hello");
bytesSent += sock.write(trailingBytes);
callback({state: 'progress', bytesSent: bytesSent, bytesToUpload: bytesToUpload});
trailingBytes.release();
stream.close(); // done with the file stream
read_buffer.clear(); // clear the buffer before the next read
 
Ti.API.info("read response");
// start reading the response
var bytesRead = 0;
var xmlData = "";
while ((bytesRead = sock.read(read_buffer)) > -1) {
xmlData += Ti.Codec.decodeString({source: read_buffer, length: bytesRead });
read_buffer.clear(); // clear the buffer before the next read
}
sock.close();
read_buffer.release();
try {
Ti.API.info("parse XML:\n" + xmlData.split("\r\n\r\n")[1].replace(/^\s+|\s+$/g,""));
var result = Ti.XML.parseString(xmlData.split("\r\n\r\n")[1].replace(/^\s+|\s+$/g,""));
var locations = result.getElementsByTagName("Location");
if (locations.length > 0) {
var resourceLocation = locations.item(0).firstChild.nodeValue;
} else {
// probably an error
var messages = result.getElementsByTagName("Message");
if (messages.length > 0) { throw messages.item(0).firstChild.nodeValue; }
else { throw ("unknown error"); }
}
callback({state: 'complete', bytesSent: bytesSent, bytesToUpload: bytesToUpload, resource: resourceLocation, postData: postData });
} catch(e) {
callback({state: 'error', message: e.message});
}
},
error: function(e) {
Ti.API.error(e);
callback({state: 'error', message: e});
}
});
sock.connect();
}
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.