Fine Uploader S3 / jQuery / PHP - 上传失败=原因:XHR返回响应代码0

时间:2015-04-21 17:35:30

标签: fine-uploader

我一直在努力将S3上传器应用到我的应用程序中并且越来越接近但没有雪茄。简而言之,这是我的设置:

  • 运行:5.2.0 S3 with jQuery
  • 服务器:ArchLinux上的PHP 5.6.6
  • 最大文件大小25MB(在s3.php和s3demo-cors.php中定义)
  • 启用调试:它只是弹出带有XHR错误代码0消息的Javascript警报(似乎无法了解调试后或在apache日志中发生的事情)
  • 出于安全考虑,我特别希望仅为我的存储区端点使用HTTPS。
  • 使用Google Chrome版本42.0.2311.90 m和IE 11.0.9600
  • 进行测试
  • 使用Chrome它只是失败了,IE失败了但它至少显示了上传文件的进度表,它达到了65%然后失败。

我在这里关注了博客文章:http://blog.fineuploader.com/2013/08/16/fine-uploader-s3-upload-directly-to-amazon-s3-from-your-browser/多次(希望我不会遗漏某些内容)。

  • 问题:我很好奇的一件事是JSON策略和签名。我真的没有看到s3demo-cors.php示例中的代码创建了json格式的详细信息,也许我错过了那个元素?

我知道我的IAM权限是有效的,因为其他PHP测试允许成功的各种PutObject和list命令。

我已经验证了我的CORS配置设置如下进行测试:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <ExposeHeader>ETag</ExposeHeader>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>

我有一些我正在使用的文件:

s3.php =我的测试页面带有fineuploader实例

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- jQuery
====================================================================== -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>

<!-- Fine Uploader Gallery CSS file
====================================================================== -->
<link href="fine-uploader-gallery.css" rel="stylesheet">

<!-- Fine Uploader S3 jQuery JS file
====================================================================== -->
<script src="s3.jquery.fine-uploader.js"></script>

<!-- Fine Uploader Customized Gallery template
====================================================================== -->
<script type="text/template" id="qq-template-s3">
    <div class="qq-uploader-selector qq-uploader qq-gallery" qq-drop-area-text="Drop files here">
        <div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
            <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
        </div>
        <div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
            <span class="qq-upload-drop-area-text-selector"></span>
        </div>
        <div class="qq-upload-button-selector qq-upload-button">
            <div>Upload a file</div>
        </div>
        <span class="qq-drop-processing-selector qq-drop-processing">
            <span>Processing dropped files...</span>
            <span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
        </span>
        <ul class="qq-upload-list-selector qq-upload-list" role="region" aria-live="polite" aria-relevant="additions removals">
            <li>
                <span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
                <div class="qq-progress-bar-container-selector qq-progress-bar-container">
                    <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
                </div>
                <span class="qq-upload-spinner-selector qq-upload-spinner"></span>
                <div class="qq-thumbnail-wrapper">
                    <a class="preview-link" target="_blank">
                        <img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale>
                    </a>
                </div>
                <button class="qq-upload-cancel-selector qq-upload-cancel">X</button>
                <button class="qq-upload-retry-selector qq-upload-retry">
                    <span class="qq-btn qq-retry-icon" aria-label="Retry"></span>
                    Retry
                </button>

                <div class="qq-file-info">
                    <div class="qq-file-name">
                        <span class="qq-upload-file-selector qq-upload-file"></span>
                        <span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
                    </div>
                    <input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
                    <span class="qq-upload-size-selector qq-upload-size"></span>
                    <button class="qq-btn qq-upload-delete-selector qq-upload-delete">
                        <span class="qq-btn qq-delete-icon" aria-label="Delete"></span>
                    </button>
                    <button class="qq-btn qq-upload-pause-selector qq-upload-pause">
                        <span class="qq-btn qq-pause-icon" aria-label="Pause"></span>
                    </button>
                    <button class="qq-btn qq-upload-continue-selector qq-upload-continue">
                        <span class="qq-btn qq-continue-icon" aria-label="Continue"></span>
                    </button>
                </div>
            </li>
        </ul>

        <dialog class="qq-alert-dialog-selector">
            <div class="qq-dialog-message-selector"></div>
            <div class="qq-dialog-buttons">
                <button class="qq-cancel-button-selector">Close</button>
            </div>
        </dialog>

        <dialog class="qq-confirm-dialog-selector">
            <div class="qq-dialog-message-selector"></div>
            <div class="qq-dialog-buttons">
                <button class="qq-cancel-button-selector">No</button>
                <button class="qq-ok-button-selector">Yes</button>
            </div>
        </dialog>

        <dialog class="qq-prompt-dialog-selector">
            <div class="qq-dialog-message-selector"></div>
            <input type="text">
            <div class="qq-dialog-buttons">
                <button class="qq-cancel-button-selector">Cancel</button>
                <button class="qq-ok-button-selector">Ok</button>
            </div>
        </dialog>
    </div>
</script>

<style>
    #fine-uploader-s3 .preview-link {
        display: block;
        height: 100%;
        width: 100%;
    }
</style>

<title>Fine Uploader S3 Demo</title>
</head>
<body>
<!-- Fine Uploader DOM Element
====================================================================== -->
<div id="fine-uploader-s3"></div>

<!-- Your code to create an instance of Fine Uploader and bind to the     DOM/template
====================================================================== -->
<script>
$('#fine-uploader-s3').fineUploaderS3({
    debug: true,
    template: 'qq-template-s3',
    request: {
        endpoint: "https://s3.amazonaws.com/<HIDDEN>",
        accessKey: "<HIDDEN>"
    },
    signature: {
        endpoint: "s3demo-cors.php"
    },
    uploadSuccess: {
        endpoint: "s3demo-cors.php?success",
        params: {
            isBrowserPreviewCapable: qq.supportedFeatures.imagePreviews
        }
    },
    iframeSupport: {
        localBlankPagePath: "success.php"
    },
    cors: {
        expected: true
    },
    chunking: {
        enabled: true
    },
    resume: {
        enabled: true
    },
    retry: {
        enableAuto: true // defaults to false
    },
    deleteFile: {
        enabled: true,
        method: "POST",
        endpoint: "s3demo-cors.php"
    },
    validation: {
        itemLimit: 100,
        sizeLimit: 25000000
    },
    thumbnails: {
        placeholders: {
            notAvailablePath: "not_available-generic.png",
            waitingPath: "waiting-generic.png"
        }
    },
    callbacks: {
        onComplete: function(id, name, response) {
            var previewLink = qq(this.getItemByFileId(id)).getByClass('preview-link')[0];

            if (response.success) {
                previewLink.setAttribute("href", response.tempLink)
            }
        },
        onError: function(id, name, errorReason, xhrOrXdr) {
            alert(qq.format("Error on file number {} - {}.  Reason: {}", id, name, errorReason));
        }
    }
});
</script>
</body>
</html>

我的PHP服务器端代码来自您的示例s3demo-cors.php (抱歉代码格式在这里粘贴时有点乱码)

    <?php
/**
* PHP Server-Side Example for Fine Uploader S3.
* Maintained by Widen Enterprises.
*
* Note: This is the exact server-side code used by the S3 example
* on fineuploader.com.
*
* This example:
*  - handles both CORS and non-CORS environments
*  - handles delete file requests for both DELETE and POST methods
*  - Performs basic inspections on the policy documents and REST headers before signing them
*  - Ensures again the file size does not exceed the max (after file is in S3)
*  - signs policy documents (simple uploads) and REST requests
*    (chunked/multipart uploads)
*  - returns a thumbnailUrl in the response for older browsers so thumbnails can be displayed next to the file
*
* Requirements:
*  - PHP 5.3 or newer
*  - Amazon PHP SDK (only if utilizing the AWS SDK for deleting files or otherwise examining them)
*
* If you need to install the AWS SDK, see http://docs.aws.amazon.com/aws-sdk-php-2/guide/latest/installation.html.
*/
// You can remove these two lines if you are not using Fine Uploader's
// delete file feature
//require 'aws-autoloader.php';
require 'vendor/autoload.php';
use Aws\S3\S3Client;
// These assume you have the associated AWS keys stored in
// the associated system environment variables
$clientPrivateKey = '<HIDDEN>';
// These two keys are only needed if the delete file feature is enabled
// or if you are, for example, confirming the file size in a successEndpoint
// handler via S3's SDK, as we are doing in this example.
$serverPublicKey = $_SERVER['PARAM1'];
$serverPrivateKey = $_SERVER['PARAM2'];
// The following variables are used when validating the policy document
// sent by the uploader: 
$expectedBucketName = "<HIDDEN>";
// $expectedMaxSize is the value you set the sizeLimit property of the 
// validation option. We assume it is `null` here. If you are performing
// validation, then change this to match the integer value you specified
// otherwise your policy document will be invalid.
// http://docs.fineuploader.com/branch/develop/api/options.html#validation-    option
$expectedMaxSize = 25000000;
$method = getRequestMethod();
// This first conditional will only ever evaluate to true in a
// CORS environment
if ($method == 'OPTIONS') {
handlePreflight();
}
// This second conditional will only ever evaluate to true if
// the delete file feature is enabled
else if ($method == "DELETE") {
handleCorsRequest(); // only needed in a CORS environment
deleteObject();
}
// This is all you really need if not using the delete file feature
// and not working in a CORS environment
else if ($method == 'POST') {
handleCorsRequest();
// Assumes the successEndpoint has a parameter of "success" associated with it,
// to allow the server to differentiate between a successEndpoint request
// and other POST requests (all requests are sent to the same endpoint in this example).
// This condition is not needed if you don't require a callback on upload success.
if (isset($_REQUEST["success"])) {
    verifyFileInS3(shouldIncludeThumbnail());
}
else {
    signRequest();
}
}
// This will retrieve the "intended" request method.  Normally, this is the
// actual method of the request.  Sometimes, though, the intended request method
// must be hidden in the parameters of the request.  For example, when attempting to
// send a DELETE request in a cross-origin environment in IE9 or older, it is not
// possible to send a DELETE request.  So, we send a POST with the intended method,
// DELETE, in a "_method" parameter.
function getRequestMethod() {
global $HTTP_RAW_POST_DATA;
// This should only evaluate to true if the Content-Type is undefined
// or unrecognized, such as when XDomainRequest has been used to
// send the request.
if(isset($HTTP_RAW_POST_DATA)) {
    parse_str($HTTP_RAW_POST_DATA, $_POST);
}
if (isset($_POST['_method'])) {
    return $_POST['_method'];
}
return $_SERVER['REQUEST_METHOD'];
}
// Only needed in cross-origin setups
function handleCorsRequest() {
// If you are relying on CORS, you will need to adjust the allowed domain here.
header('Access-Control-Allow-Origin: *');
}
// Only needed in cross-origin setups
function handlePreflight() {
handleCorsRequest();
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
}
function getS3Client() {
global $serverPublicKey, $serverPrivateKey;
return S3Client::factory(array(
    'key' => $serverPublicKey,
    'secret' => $serverPrivateKey
));
}
// Only needed if the delete file feature is enabled
function deleteObject() {
getS3Client()->deleteObject(array(
    'Bucket' => $_POST['bucket'],
    'Key' => $_POST['key']
));
}
function signRequest() {
header('Content-Type: application/json');
$responseBody = file_get_contents('php://input');
$contentAsObject = json_decode($responseBody, true);
$jsonContent = json_encode($contentAsObject);
if (!empty($contentAsObject["headers"])) {
    signRestRequest($contentAsObject["headers"]);
}
else {
    signPolicy($jsonContent);
}
}
function signRestRequest($headersStr) {
if (isValidRestRequest($headersStr)) {
    $response = array('signature' => sign($headersStr));
    echo json_encode($response);
}
else {
    echo json_encode(array("invalid" => true));
}
}
function isValidRestRequest($headersStr) {
global $expectedBucketName;
$pattern = "/\/$expectedBucketName\/.+$/";
preg_match($pattern, $headersStr, $matches);
return count($matches) > 0;
}
function signPolicy($policyStr) {
$policyObj = json_decode($policyStr, true);
if (isPolicyValid($policyObj)) {
    $encodedPolicy = base64_encode($policyStr);
    $response = array('policy' => $encodedPolicy, 'signature' =>             sign($encodedPolicy));
    echo json_encode($response);
}
else {
    echo json_encode(array("invalid" => true));
}
}
function isPolicyValid($policy) {
global $expectedMaxSize, $expectedBucketName;
$conditions = $policy["conditions"];
$bucket = null;
$parsedMaxSize = null;
for ($i = 0; $i < count($conditions); ++$i) {
    $condition = $conditions[$i];
    if (isset($condition["bucket"])) {
        $bucket = $condition["bucket"];
    }
    else if (isset($condition[0]) && $condition[0] == "content-length-range") {
        $parsedMaxSize = $condition[2];
    }
}
return $bucket == $expectedBucketName && $parsedMaxSize == (string)$expectedMaxSize;
}
function sign($stringToSign) {
global $clientPrivateKey;
return base64_encode(hash_hmac(
    'sha1',
    $stringToSign,
    $clientPrivateKey,
    true
));
}
// This is not needed if you don't require a callback on upload success.
function verifyFileInS3($includeThumbnail) {
global $expectedMaxSize;
$bucket = $_POST["bucket"];
$key = $_POST["key"];
// If utilizing CORS, we return a 200 response with the error message in the body
// to ensure Fine Uploader can parse the error message in IE9 and IE8,
// since XDomainRequest is used on those browsers for CORS requests.      XDomainRequest
// does not allow access to the response body for non-success responses.
if (isset($expectedMaxSize) && getObjectSize($bucket, $key) > $expectedMaxSize) {
    // You can safely uncomment this next line if you are not depending on CORS
    header("HTTP/1.0 500 Internal Server Error");
    deleteObject();
    echo json_encode(array("error" => "File is too big!", "preventRetry" => true));
}
else {
    $link = getTempLink($bucket, $key);
    $response = array("tempLink" => $link);
    if ($includeThumbnail) {
        $response["thumbnailUrl"] = $link;
    }
    echo json_encode($response);
}
}
// Provide a time-bombed public link to the file.
function getTempLink($bucket, $key) {
$client = getS3Client();
$url = "{$bucket}/{$key}";
$request = $client->get($url);
return $client->createPresignedUrl($request, '+15 minutes');
}
function getObjectSize($bucket, $key) {
$objInfo = getS3Client()->headObject(array(
    'Bucket' => $bucket,
    'Key' => $key
));
return $objInfo['ContentLength'];
}
// Return true if it's likely that the associate file is natively
// viewable in a browser.  For simplicity, just uses the file extension
// to make this determination, along with an array of extensions that one
// would expect all supported browsers are able to render natively.
function isFileViewableImage($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$viewableExtensions = array("jpeg", "jpg", "gif", "png");
return in_array($ext, $viewableExtensions);
}
// Returns true if we should attempt to include a link
// to a thumbnail in the uploadSuccess response.  In it's simplest form
// (which is our goal here - keep it simple) we only include a link to
// a viewable image and only if the browser is not capable of generating a         client-side preview.
function shouldIncludeThumbnail() {
$filename = $_POST["name"];
$isPreviewCapable = $_POST["isBrowserPreviewCapable"] == "true";
$isFileViewableImage = isFileViewableImage($filename);
return !$isPreviewCapable && $isFileViewableImage;
}
?>

2 个答案:

答案 0 :(得分:0)

根据您的后续评论解释具体问题:

  

因此,我发现以下错误:XMLHttpRequest无法加载s3.amazonaws.com/dev-pre-content。 No&#39; Access-Control-Allow-Origin&#39;标头出现在请求的资源上。起源&#39; 192.168.1.215&#39 ;;因此不允许访问。

问题在于您的存储桶的CORS配置。您需要确保具有与要上传到的特定存储桶相关联的相应CORS规则

答案 1 :(得分:-1)

这正是你所说的,我认为应该实施政策签名。当您将文件直接发送到S3服务器时,您必须首先通知política分配,因此您的javascript将传递将在您的php文件上的凭据,以便在您发送所需的文件之后。 这是política签名的一个工作示例

$AWSbucket = 'yout burcket';
$AWSkey = 'your key';
$AWSsecret = 'your secret';
$acl = 'public-read';

// Get the file extension
$file = $_POST['name'];
$extension = substr($_POST['name'], strrpos($_POST['name'], '.')+1);

// Prepare the filename
$fileName = 'c' . sha1(uniqid(mt_rand(), true));
$key = 'arquivos/'.$fileName . '.' . $extension;

// Set the expiration time of the policy
$policyExpiration = gmdate('Y-m-d\TH:i:s\Z', strtotime('+24 hour'));

// Set the policy
$policy = str_replace("\n", "", '
{"expiration": "' . $policyExpiration . '",
 "conditions": [
   {"acl": "' . $acl . '"},
   {"bucket": "' . $AWSbucket . '"},
   {"success_action_status": "201"},
   ["starts-with", "$key", "' . $key . '"],
 ]
}');

// 1 - Encode the policy using UTF-8.
// 2 - Encode those UTF-8 bytes using Base64.
// 3 - Sign the policy with your Secret Access Key using HMAC SHA-1.
// 4 - Encode the SHA-1 signature using Base64.

// Prepare the signature
$b64 = base64_encode(utf8_encode($policy));
$signature = base64_encode(hash_hmac('sha1', $b64, $AWSsecret, true));

// Return the post information
echo json_encode(array(
    'key' => $key,
    'acl' => $acl,
    'MinPartSize' => 25 * 1024 * 1024,
    'policy' => $b64,
    'signature' => $signature,
    'AWSAccessKeyId' => $AWSkey,
    'success_action_status' => 201,
    'bucket' => $AWSbucket
));