Skip to content

Instantly share code, notes, and snippets.

@taf2
Created June 7, 2011 16:43
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save taf2/1012632 to your computer and use it in GitHub Desktop.
Save taf2/1012632 to your computer and use it in GitHub Desktop.
MultiPart Form POST - Compatible with Uploading Files to S3
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();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment