1

我有一个在 Flash 或 Flash Builder 4.5 上编译的标准 AIR 脚本,旨在将文件上传到 php 服务器端代码。在完成请求之前,代码首先将文件写入用户桌面。

import flash.net.*;
import flash.filesystem.*

//create the file
var file:File = File.desktopDirectory;
file = file.resolvePath("test.txt");
var fs:FileStream = new FileStream();
fs.open(file, FileMode.WRITE);
//write "test" to txt file
fs.writeUTFBytes("test");
fs.close();

//start request
var req:URLRequest = new URLRequest();
req.method = "POST";
req.url = //php file
var vars:URLVariables = new URLVariables();
vars.a = 1;
vars.b = 2;
vars.c = 3
req.data = vars;
file.upload(req, "txt_file");

现在,当我在 localhost (XAMPP) 上我自己的 Apache 测试服务器上上传到一个 php 文件时,文件上传通过没有任何错误。但是,当我尝试上传到我的实际服务器时,我收到一个 I/O 错误:

错误 #2044:未处理的 IOErrorEvent:。文本=错误 #2038:文件 I/O 错误。

我检查了我的 max_file_size 设置为 2MB,所以这不是问题。网址也是正确的。查看其他来源(例如http://www.judahfrangipane.com/blog/2007/01/01/error-2044-unhandled-ioerrorevent-texterror-2038-file-io-error/),有人认为问题出在Apache ModSecurity,通过将目录的 .htaccess 设置为:

SecFilterEngine Off
SecFilterScanPOST Off

这不起作用。事实上,它使我设计用于从同一服务器获取数据的其余代码过时了。我也知道这不是跨域问题。我也尝试将 php 代码设置为返回 HTTP 200。仍然无法正常工作。有些人认为这些错误有点随机,并且无论错误如何,实际文件上传都是发生的。所以问题可以通过捕获错误并忽略它来解决(http://dev.nuclearrooster.com/2008/04/05/uploading-with-a-filereference-from-flex-3-on-os-x /)。这对我来说不是一个选项,因为我需要跟踪上传进度。

有趣的是,这似乎是一个 Mac OSX 问题,因为我在 Windows 编译器上运行的确切脚本没有错误,尽管唯一的规定是它们是用旧版本的 AIR 制作的。所以我不确定这里是否是这种情况。

我已经用头撞墙3天了。请帮我......

更新

刚刚发现我的文件上传到的服务器正在对文件上传请求返回 HTTP 301 响应,这再次仅发生在 AIR 上的 OSX 上,而不是 Windows 或通过 Firefox 提交的表单数据上。

4

1 回答 1

1

好的。这绝对似乎是 Mac OSX 的问题。出于某种原因,OSX 中 File.upload() 方法创建的 HTTP 标头会导致外部服务器崩溃,并出现 301 Move Permanently。这不会在 Windows 中发生。当然,就 AIR 编译器而言,除了 HTTP 2xx 响应之外的任何响应都会导致 I/O 错误。因此,为了解决文件上传问题,我们必须手动创建负责上传本身的 HTTP 标头。我建议在解决此问题之前不要使用File.upload()(或FileReference.upload()就此而言)。

为此,我们上传的文件必须转换为 ByteArray。因此我只会使用 File/FileReference 来捕获文件。对于实际上传,文件必须转换为ByteArray.

在我上面的例子中,它非常简单:

import flash.net.*
import flash.utils.ByteArray;

var xml:XML = new XML(<items><item>one</item><item>two</item></items>);
var data:ByteArray = new ByteArray();
data.writeUTF(xml.toXMLString());

现在就创建手动 HTTP 标头而言,归功于 Jonathan Marston ( http://marstonstudio.com/2007/10/19/how-to-take-a-snapshot-of-a-flash-movie-and-automatically -upload-the-jpg-to-a-server-in-three-easy-steps/)和他漂亮的UploadPostHelper课程:

package {

    import flash.events.*;
    import flash.net.*;
    import flash.utils.ByteArray;
    import flash.utils.Endian;

    /**
     * Take a fileName, byteArray, and parameters object as input and return ByteArray post data suitable for a UrlRequest as output
     *
     * @see http://marstonstudio.com/?p=36
     * @see http://www.w3.org/TR/html4/interact/forms.html
     * @see http://www.jooce.com/blog/?p=143
     * @see http://www.jooce.com/blog/wp%2Dcontent/uploads/2007/06/uploadFile.txt
     * @see http://blog.je2050.de/2006/05/01/save-bytearray-to-file-with-php/
     *
     * @author Jonathan Marston
     * @version 2007.08.19
     *
     * This work is licensed under a Creative Commons Attribution NonCommercial ShareAlike 3.0 License.
     * @see http://creativecommons.org/licenses/by-nc-sa/3.0/
     *
     */
    public class UploadPostHelper {

        /**
         * Boundary used to break up different parts of the http POST body
         */
        private static var _boundary:String = "";

        /**
         * Get the boundary for the post.
         * Must be passed as part of the contentType of the UrlRequest
         */
        public static function getBoundary():String {

            if(_boundary.length == 0) {
                for (var i:int = 0; i < 0x20; i++ ) {
                    _boundary += String.fromCharCode( int( 97 + Math.random() * 25 ) );
                }
            }

            return _boundary;
        }

        /**
         * Create post data to send in a UrlRequest
         */
        public static function getPostData(fileName:String, byteArray:ByteArray, parameters:Object = null):ByteArray {

            var i: int;
            var bytes:String;

            var postData:ByteArray = new ByteArray();
            postData.endian = Endian.BIG_ENDIAN;

            //add Filename to parameters
            if(parameters == null) {
                parameters = new Object();
            }
            parameters.Filename = fileName;

            //add parameters to postData
            for(var name:String in parameters) {
                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(parameters[name]);
                postData = LINEBREAK(postData);
            }

            //add Filedata to postData
            postData = BOUNDARY(postData);
            postData = LINEBREAK(postData);
            bytes = 'Content-Disposition: form-data; name="Filedata"; filename="';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData.writeUTFBytes(fileName);
            postData = QUOTATIONMARK(postData);
            postData = LINEBREAK(postData);
            bytes = 'Content-Type: application/octet-stream';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData = LINEBREAK(postData);
            postData = LINEBREAK(postData);
            postData.writeBytes(byteArray, 0, byteArray.length);
            postData = LINEBREAK(postData);

            //add upload filed to postData
            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);

            //closing boundary
            postData = BOUNDARY(postData);
            postData = DOUBLEDASH(postData);

            return postData;
        }

        /**
         * Add a boundary to the PostData with leading doubledash
         */
        private static function BOUNDARY(p:ByteArray):ByteArray {
            var l:int = UploadPostHelper.getBoundary().length;

            p = DOUBLEDASH(p);
            for (var i:int = 0; i < l; i++ ) {
                p.writeByte( _boundary.charCodeAt( i ) );
            }
            return p;
        }

        /**
         * Add one linebreak
         */
        private static function LINEBREAK(p:ByteArray):ByteArray {
            p.writeShort(0x0d0a);
            return p;
        }

        /**
         * Add quotation mark
         */
        private static function QUOTATIONMARK(p:ByteArray):ByteArray {
            p.writeByte(0x22);
            return p;
        }

        /**
         * Add Double Dash
         */
        private static function DOUBLEDASH(p:ByteArray):ByteArray {
            p.writeShort(0x2d2d);
            return p;
        }

    }
}

现在,我们利用类来创建我们的 HTTP 标头,使用 good old URLRequest

var req:URLRequest = new URLRequest();
req.url = //server-side url (.php)
req.contentType = "multipart/form-data; boundary=" + UploadPostHelper.getBoundary();
req.method = URLRequestMethod.POST;
//Be sure to place the actual file name with its extension as the file name. Set the second argument as the ByteArray created earlier. Any other parameters (name-value pairs) can be added via an optional third parameter (see class above)
req.data = UploadPostHelper.getPostData("test.xml", data);
req.requestHeaders.push(new URLRequestHeader("Cache-Control", "no-cache"));

现在不要使用File.upload()来完成请求,只需使用URLLoader

var loader:URLLoader = new URLLoader();
//We have to load the data as binary as well
loader.dataFormat = URLLoaderDataFormat.BINARY;
loader.addEventListener(Event.COMPLETE, complete);
loader.addEventListener(IOErrorEvent.IO_ERROR, ioerror);
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, secerror);
loader.load(req);

//Event handlers
function complete(e:Event):void {
    var ba:ByteArray = e.target.data;
    //Returns the XML data
    trace(ba.readUTF()); 
}
function ioerror(e:IOErrorEvent):void {}
function secerror(e:SecurityErrorEvent):void {}

一切都应该很好地工作。就服务器端而言,文件名由上面的类定义为Filedata. 用于捕获文件的简单 PHP 脚本如下所示:

<?php
echo file_get_contents($_FILES['Filedata']['tmp_name']);
var_dump($_FILES['Filedata']);
/*
array(1) {
    ["Filedata"]=>array(5) {
        ["name"]=>string(8) "test.xml"
        ["type"]=>string(24) "application/octet-stream"
        ["tmp_name"]=>string(38) "root/tmp/php2jk8yk"
        ["error"]=>int(0)
        ["size"]=>int(58)
    }
}
*/
?>
于 2013-08-12T12:59:59.670 回答