summaryrefslogtreecommitdiff
path: root/webcam/ru/inspirit/net/MultipartURLLoader.as
diff options
context:
space:
mode:
Diffstat (limited to 'webcam/ru/inspirit/net/MultipartURLLoader.as')
-rw-r--r--webcam/ru/inspirit/net/MultipartURLLoader.as582
1 files changed, 582 insertions, 0 deletions
diff --git a/webcam/ru/inspirit/net/MultipartURLLoader.as b/webcam/ru/inspirit/net/MultipartURLLoader.as
new file mode 100644
index 0000000..bfd3db2
--- /dev/null
+++ b/webcam/ru/inspirit/net/MultipartURLLoader.as
@@ -0,0 +1,582 @@
+package ru.inspirit.net
+{
+ import flash.errors.IllegalOperationError;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.HTTPStatusEvent;
+ import flash.events.IOErrorEvent;
+ import flash.events.ProgressEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.net.URLLoader;
+ import flash.net.URLLoaderDataFormat;
+ import flash.net.URLRequest;
+ import flash.net.URLRequestHeader;
+ import flash.net.URLRequestMethod;
+ import flash.utils.ByteArray;
+ import flash.utils.Dictionary;
+ import flash.utils.Endian;
+ import flash.utils.setTimeout;
+ import flash.utils.clearInterval;
+ import ru.inspirit.net.events.MultipartURLLoaderEvent;
+
+ /**
+ * Multipart URL Loader
+ *
+ * Original idea by Marston Development Studio - http://marstonstudio.com/?p=36
+ *
+ * History
+ * 2009.01.15 version 1.0
+ * Initial release
+ *
+ * 2009.01.19 version 1.1
+ * Added options for MIME-types (default is application/octet-stream)
+ *
+ * 2009.01.20 version 1.2
+ * Added clearVariables and clearFiles methods
+ * Small code refactoring
+ * Public methods documentaion
+ *
+ * 2009.02.09 version 1.2.1
+ * Changed 'useWeakReference' to false (thanx to zlatko)
+ * It appears that on some servers setting 'useWeakReference' to true
+ * completely disables this event
+ *
+ * 2009.03.05 version 1.3
+ * Added Async property. Now you can prepare data asynchronous before sending it.
+ * It will prevent flash player from freezing while constructing request data.
+ * You can specify the amount of bytes to write per iteration through BLOCK_SIZE static property.
+ * Added events for asynchronous method.
+ * Added dataFormat property for returned server data.
+ * Removed 'Cache-Control' from headers and added custom requestHeaders array property.
+ * Added getter for the URLLoader class used to send data.
+ *
+ * @author Eugene Zatepyakin
+ * @version 1.3
+ * @link http://blog.inspirit.ru/
+ */
+ public class MultipartURLLoader extends EventDispatcher
+ {
+ public static var BLOCK_SIZE:uint = 64 * 1024;
+
+ private var _loader:URLLoader;
+ private var _boundary:String;
+ private var _variableNames:Array;
+ private var _fileNames:Array;
+ private var _variables:Dictionary;
+ private var _files:Dictionary;
+
+ private var _async:Boolean = false;
+ private var _path:String;
+ private var _data:ByteArray;
+
+ private var _prepared:Boolean = false;
+ private var asyncWriteTimeoutId:Number;
+ private var asyncFilePointer:uint = 0;
+ private var totalFilesSize:uint = 0;
+ private var writtenBytes:uint = 0;
+
+ public var requestHeaders:Array;
+
+ public function MultipartURLLoader()
+ {
+ _fileNames = new Array();
+ _files = new Dictionary();
+ _variableNames = new Array();
+ _variables = new Dictionary();
+ _loader = new URLLoader();
+ requestHeaders = new Array();
+ }
+
+ /**
+ * Start uploading data to specified path
+ *
+ * @param path The server script path
+ * @param async Set to true if you are uploading huge amount of data
+ */
+ public function load(path:String, async:Boolean = false):void
+ {
+ if (path == null || path == '') throw new IllegalOperationError('You cant load without specifing PATH');
+
+ _path = path;
+ _async = async;
+
+ if (_async) {
+ if(!_prepared){
+ constructPostDataAsync();
+ } else {
+ doSend();
+ }
+ } else {
+ _data = constructPostData();
+ doSend();
+ }
+ }
+
+ /**
+ * Start uploading data after async prepare
+ */
+ public function startLoad():void
+ {
+ if ( _path == null || _path == '' || _async == false ) throw new IllegalOperationError('You can use this method only if loading asynchronous.');
+ if ( !_prepared && _async ) throw new IllegalOperationError('You should prepare data before sending when using asynchronous.');
+
+ doSend();
+ }
+
+ /**
+ * Prepare data before sending (only if you use asynchronous)
+ */
+ public function prepareData():void
+ {
+ constructPostDataAsync();
+ }
+
+ /**
+ * Stop loader action
+ */
+ public function close():void
+ {
+ try {
+ _loader.close();
+ } catch( e:Error ) { }
+ }
+
+ /**
+ * Add string variable to loader
+ * If you have already added variable with the same name it will be overwritten
+ *
+ * @param name Variable name
+ * @param value Variable value
+ */
+ public function addVariable(name:String, value:Object = ''):void
+ {
+ if (_variableNames.indexOf(name) == -1) {
+ _variableNames.push(name);
+ }
+ _variables[name] = value;
+ _prepared = false;
+ }
+
+ /**
+ * Add file part to loader
+ * If you have already added file with the same fileName it will be overwritten
+ *
+ * @param fileContent File content encoded to ByteArray
+ * @param fileName Name of the file
+ * @param dataField Name of the field containg file data
+ * @param contentType MIME type of the uploading file
+ */
+ public function addFile(fileContent:ByteArray, fileName:String, dataField:String = 'Filedata', contentType:String = 'application/octet-stream'):void
+ {
+ if (_fileNames.indexOf(fileName) == -1) {
+ _fileNames.push(fileName);
+ _files[fileName] = new FilePart(fileContent, fileName, dataField, contentType);
+ totalFilesSize += fileContent.length;
+ } else {
+ var f:FilePart = _files[fileName] as FilePart;
+ totalFilesSize -= f.fileContent.length;
+ f.fileContent = fileContent;
+ f.fileName = fileName;
+ f.dataField = dataField;
+ f.contentType = contentType;
+ totalFilesSize += fileContent.length;
+ }
+
+ _prepared = false;
+ }
+
+ /**
+ * Remove all variable parts
+ */
+ public function clearVariables():void
+ {
+ _variableNames = new Array();
+ _variables = new Dictionary();
+ _prepared = false;
+ }
+
+ /**
+ * Remove all file parts
+ */
+ public function clearFiles():void
+ {
+ for each(var name:String in _fileNames)
+ {
+ (_files[name] as FilePart).dispose();
+ }
+ _fileNames = new Array();
+ _files = new Dictionary();
+ totalFilesSize = 0;
+ _prepared = false;
+ }
+
+ /**
+ * Dispose all class instance objects
+ */
+ public function dispose(): void
+ {
+ clearInterval(asyncWriteTimeoutId);
+ removeListener();
+ close();
+
+ _loader = null;
+ _boundary = null;
+ _variableNames = null;
+ _variables = null;
+ _fileNames = null;
+ _files = null;
+ requestHeaders = null;
+ _data = null;
+ }
+
+ /**
+ * Generate random boundary
+ * @return Random boundary
+ */
+ public function getBoundary():String
+ {
+ if (_boundary == null) {
+ _boundary = '';
+ for (var i:int = 0; i < 0x20; i++ ) {
+ _boundary += String.fromCharCode( int( 97 + Math.random() * 25 ) );
+ }
+ }
+ return _boundary;
+ }
+
+ public function get ASYNC():Boolean
+ {
+ return _async;
+ }
+
+ public function get PREPARED():Boolean
+ {
+ return _prepared;
+ }
+
+ public function get dataFormat():String
+ {
+ return _loader.dataFormat;
+ }
+
+ public function set dataFormat(format:String):void
+ {
+ if (format != URLLoaderDataFormat.BINARY && format != URLLoaderDataFormat.TEXT && format != URLLoaderDataFormat.VARIABLES) {
+ throw new IllegalOperationError('Illegal URLLoader Data Format');
+ }
+ _loader.dataFormat = format;
+ }
+
+ public function get loader():URLLoader
+ {
+ return _loader;
+ }
+
+ private function doSend():void
+ {
+ var urlRequest:URLRequest = new URLRequest();
+ urlRequest.url = _path;
+ urlRequest.contentType = 'multipart/form-data; boundary=' + getBoundary();
+ urlRequest.method = URLRequestMethod.POST;
+ urlRequest.data = _data;
+
+ if (requestHeaders.length && requestHeaders != null){
+ urlRequest.requestHeaders = requestHeaders.concat();
+ }
+
+ addListener();
+
+ _loader.load(urlRequest);
+ }
+
+ private function constructPostDataAsync():void
+ {
+ clearInterval(asyncWriteTimeoutId);
+
+ _data = new ByteArray();
+ _data.endian = Endian.BIG_ENDIAN;
+
+ _data = constructVariablesPart(_data);
+
+ asyncFilePointer = 0;
+ writtenBytes = 0;
+ _prepared = false;
+ if (_fileNames.length) {
+ nextAsyncLoop();
+ } else {
+ _data = closeDataObject(_data);
+ _prepared = true;
+ dispatchEvent( new MultipartURLLoaderEvent(MultipartURLLoaderEvent.DATA_PREPARE_COMPLETE) );
+ }
+ }
+
+ private function constructPostData():ByteArray
+ {
+ var postData:ByteArray = new ByteArray();
+ postData.endian = Endian.BIG_ENDIAN;
+
+ postData = constructVariablesPart(postData);
+ postData = constructFilesPart(postData);
+
+ postData = closeDataObject(postData);
+
+ return postData;
+ }
+
+ private function closeDataObject(postData:ByteArray):ByteArray
+ {
+ postData = BOUNDARY(postData);
+ postData = DOUBLEDASH(postData);
+ return postData;
+ }
+
+ private function constructVariablesPart(postData:ByteArray):ByteArray
+ {
+ var i:uint;
+ var bytes:String;
+
+ for each(var name:String in _variableNames)
+ {
+ postData = BOUNDARY(postData);
+ postData = LINEBREAK(postData);
+ bytes = 'Content-Disposition: form-data; name="' + name + '"';
+ for ( i = 0; i < bytes.length; i++ ) {
+ postData.writeByte( bytes.charCodeAt(i) );
+ }
+ postData = LINEBREAK(postData);
+ postData = LINEBREAK(postData);
+ postData.writeUTFBytes(_variables[name]);
+ postData = LINEBREAK(postData);
+ }
+
+ return postData;
+ }
+
+ private function constructFilesPart(postData:ByteArray):ByteArray
+ {
+ var i:uint;
+ var bytes:String;
+
+ if(_fileNames.length){
+ for each(var name:String in _fileNames)
+ {
+ postData = getFilePartHeader(postData, _files[name] as FilePart);
+ postData = getFilePartData(postData, _files[name] as FilePart);
+ postData = LINEBREAK(postData);
+ }
+ postData = closeFilePartsData(postData);
+ }
+
+ return postData;
+ }
+
+ private function closeFilePartsData(postData:ByteArray):ByteArray
+ {
+ var i:uint;
+ var bytes:String;
+
+ postData = LINEBREAK(postData);
+ postData = BOUNDARY(postData);
+ postData = LINEBREAK(postData);
+ bytes = 'Content-Disposition: form-data; name="Upload"';
+ for ( i = 0; i < bytes.length; i++ ) {
+ postData.writeByte( bytes.charCodeAt(i) );
+ }
+ postData = LINEBREAK(postData);
+ postData = LINEBREAK(postData);
+ bytes = 'Submit Query';
+ for ( i = 0; i < bytes.length; i++ ) {
+ postData.writeByte( bytes.charCodeAt(i) );
+ }
+ postData = LINEBREAK(postData);
+
+ return postData;
+ }
+
+ private function getFilePartHeader(postData:ByteArray, part:FilePart):ByteArray
+ {
+ var i:uint;
+ var bytes:String;
+
+ postData = BOUNDARY(postData);
+ postData = LINEBREAK(postData);
+ bytes = 'Content-Disposition: form-data; name="Filename"';
+ for ( i = 0; i < bytes.length; i++ ) {
+ postData.writeByte( bytes.charCodeAt(i) );
+ }
+ postData = LINEBREAK(postData);
+ postData = LINEBREAK(postData);
+ postData.writeUTFBytes(part.fileName);
+ postData = LINEBREAK(postData);
+
+ postData = BOUNDARY(postData);
+ postData = LINEBREAK(postData);
+ bytes = 'Content-Disposition: form-data; name="' + part.dataField + '"; filename="';
+ for ( i = 0; i < bytes.length; i++ ) {
+ postData.writeByte( bytes.charCodeAt(i) );
+ }
+ postData.writeUTFBytes(part.fileName);
+ postData = QUOTATIONMARK(postData);
+ postData = LINEBREAK(postData);
+ bytes = 'Content-Type: ' + part.contentType;
+ for ( i = 0; i < bytes.length; i++ ) {
+ postData.writeByte( bytes.charCodeAt(i) );
+ }
+ postData = LINEBREAK(postData);
+ postData = LINEBREAK(postData);
+
+ return postData;
+ }
+
+ private function getFilePartData(postData:ByteArray, part:FilePart):ByteArray
+ {
+ postData.writeBytes(part.fileContent, 0, part.fileContent.length);
+
+ return postData;
+ }
+
+ private function onProgress( event: ProgressEvent ): void
+ {
+ dispatchEvent( event );
+ }
+
+ private function onComplete( event: Event ): void
+ {
+ removeListener();
+ dispatchEvent( event );
+ }
+
+ private function onIOError( event: IOErrorEvent ): void
+ {
+ removeListener();
+ dispatchEvent( event );
+ }
+
+ private function onSecurityError( event: SecurityErrorEvent ): void
+ {
+ removeListener();
+ dispatchEvent( event );
+ }
+
+ private function onHTTPStatus( event: HTTPStatusEvent ): void
+ {
+ dispatchEvent( event );
+ }
+
+ private function addListener(): void
+ {
+ _loader.addEventListener( Event.COMPLETE, onComplete, false, 0, false );
+ _loader.addEventListener( ProgressEvent.PROGRESS, onProgress, false, 0, false );
+ _loader.addEventListener( IOErrorEvent.IO_ERROR, onIOError, false, 0, false );
+ _loader.addEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus, false, 0, false );
+ _loader.addEventListener( SecurityErrorEvent.SECURITY_ERROR, onSecurityError, false, 0, false );
+ }
+
+ private function removeListener(): void
+ {
+ _loader.removeEventListener( Event.COMPLETE, onComplete );
+ _loader.removeEventListener( ProgressEvent.PROGRESS, onProgress );
+ _loader.removeEventListener( IOErrorEvent.IO_ERROR, onIOError );
+ _loader.removeEventListener( HTTPStatusEvent.HTTP_STATUS, onHTTPStatus );
+ _loader.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, onSecurityError );
+ }
+
+ private function BOUNDARY(p:ByteArray):ByteArray
+ {
+ var l:int = getBoundary().length;
+ p = DOUBLEDASH(p);
+ for (var i:int = 0; i < l; i++ ) {
+ p.writeByte( _boundary.charCodeAt( i ) );
+ }
+ return p;
+ }
+
+ private function LINEBREAK(p:ByteArray):ByteArray
+ {
+ p.writeShort(0x0d0a);
+ return p;
+ }
+
+ private function QUOTATIONMARK(p:ByteArray):ByteArray
+ {
+ p.writeByte(0x22);
+ return p;
+ }
+
+ private function DOUBLEDASH(p:ByteArray):ByteArray
+ {
+ p.writeShort(0x2d2d);
+ return p;
+ }
+
+ private function nextAsyncLoop():void
+ {
+ var fp:FilePart;
+
+ if (asyncFilePointer < _fileNames.length) {
+
+ fp = _files[_fileNames[asyncFilePointer]] as FilePart;
+ _data = getFilePartHeader(_data, fp);
+
+ asyncWriteTimeoutId = setTimeout(writeChunkLoop, 10, _data, fp.fileContent, 0);
+
+ asyncFilePointer ++;
+ } else {
+ _data = closeFilePartsData(_data);
+ _data = closeDataObject(_data);
+
+ _prepared = true;
+
+ dispatchEvent( new MultipartURLLoaderEvent(MultipartURLLoaderEvent.DATA_PREPARE_PROGRESS, totalFilesSize, totalFilesSize) );
+ dispatchEvent( new MultipartURLLoaderEvent(MultipartURLLoaderEvent.DATA_PREPARE_COMPLETE) );
+ }
+ }
+
+ private function writeChunkLoop(dest:ByteArray, data:ByteArray, p:uint = 0):void
+ {
+ var len:uint = Math.min(BLOCK_SIZE, data.length - p);
+ dest.writeBytes(data, p, len);
+
+ if (len < BLOCK_SIZE || p + len >= data.length) {
+ // Finished writing file bytearray
+ dest = LINEBREAK(dest);
+ nextAsyncLoop();
+ return;
+ }
+
+ p += len;
+ writtenBytes += len;
+ if ( writtenBytes % BLOCK_SIZE * 2 == 0 ) {
+ dispatchEvent( new MultipartURLLoaderEvent(MultipartURLLoaderEvent.DATA_PREPARE_PROGRESS, writtenBytes, totalFilesSize) );
+ }
+
+ asyncWriteTimeoutId = setTimeout(writeChunkLoop, 10, dest, data, p);
+ }
+
+ }
+}
+
+internal class FilePart
+{
+
+ public var fileContent:flash.utils.ByteArray;
+ public var fileName:String;
+ public var dataField:String;
+ public var contentType:String;
+
+ public function FilePart(fileContent:flash.utils.ByteArray, fileName:String, dataField:String = 'Filedata', contentType:String = 'application/octet-stream')
+ {
+ this.fileContent = fileContent;
+ this.fileName = fileName;
+ this.dataField = dataField;
+ this.contentType = contentType;
+ }
+
+ public function dispose():void
+ {
+ fileContent = null;
+ fileName = null;
+ dataField = null;
+ contentType = null;
+ }
+} \ No newline at end of file