Created
June 7, 2011 16:43
-
-
Save taf2/1012632 to your computer and use it in GitHub Desktop.
MultiPart Form POST - Compatible with Uploading Files to S3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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