精细上传到 S3 存储桶收到 405 方法不允许错误


Fine Uploader to S3 bucket getting 405 Method Not Allowed error

我一直在用头撞墙,完全被难住了。我正在尝试使用 FineUploader 将文件直接上传到我的 Amazon S3 存储桶。我基本上从 fineuploader.com 网页(将文件直接上传到 Amazon S3(和服务器端 PHP 中复制了代码。

当我尝试上传文件时,我看到发送到签名终端节点的帖子似乎成功工作,但是当它尝试上传到 S3 时,我收到 405"方法不允许"错误。

.HTML

<!DOCTYPE html>
<html >
<head >
    <meta charset = "utf-8" >
    <link href = "http://fineuploader.com/source/fineuploader-3.9.1.min.css" rel = "stylesheet" >
</head >
<body >
<div id = "fine-uploader" ></div >
<script src = "http://code.jquery.com/jquery-latest.js" ></script >
<script src = "js/uploader.js" ></script >
<script >
    $(document).ready(function () {
        $("#fine-uploader").fineUploaderS3({
            debug: true,
            request: {
                endpoint: 'upload.roughdrag.com',
                accessKey: 'AKIAJL37USSCV......'
            },
            signature: {
                endpoint: 'handlers/uploadHandler.php'
            },
            uploadSuccess: {
                endpoint: 'index.php'
            },
            iframeSupport: {
                localBlankPagePath: 'blank.html'
            },
            retry: {
                enableAuto: true // defaults to false
            },
            paste: {
                targetElement: $(document),
                promptForName: true
            },
            deleteFile: {
                enabled: true,
                endpoint: 'handlers/uploadHandler.php'
            }
        });
    });
</script >
</body >
</html >

PHP 签名端点 - 上传处理程序.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)
 *
 * 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('../../includes/functions.php');
use Aws'S3'S3Client;
// These assume you have the associated AWS keys stored in
// the associated system environment variables
$clientPrivateKey = '{removed}';
// 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 = '{removed}';
$serverPrivateKey = '{removed}';
// The following variables are used when validating the policy document
// sent by the uploader:
$expectedBucketName = "upload.roughdrag.com";
// $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 = 5000000;
$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();
    } 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 ($_POST['_method'] != null) {
        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: http://www.roughdrag.com');
}
// 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);
    $headersStr = $contentAsObject["headers"];
    if ($headersStr) {
        signRestRequest($headersStr);
    } 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()
{
    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 (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" => "Your file is too big!"));
    } else {
        echo json_encode(array("tempLink" => getTempLink($bucket, $key)));
    }
}
// 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'];
}
?>

Amazon S3 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>
</CORSConfiguration>

IAM 组策略

{
  "Version":"2012-10-17",
  "Statement":[{
     "Effect":"Allow",
     "Action":"s3:PutObject",
     "Resource":"arn:aws:s3:::upload.roughdrag.com/*"
   }]
}

上传者.js是从 http://fineuploader.com/source/all.fineuploader-3.9.1.min.js 捕获

控制台响应

[FineUploader 3.9.0-3] Grabbed 1 dropped files.
[FineUploader 3.9.0-3] Received 1 files or inputs.
[FineUploader 3.9.0-3] Submitting S3 signature request for 0
[FineUploader 3.9.0-3] Sending POST request for 0
POST http://www.roughdrag.com/handlers/uploadHandler.php  200 OK 195ms  
[FineUploader 3.9.0-3] Sending upload request for 0
POST http://upload.roughdrag.com/  405 Method Not Allowed 559ms 
"NetworkError: 405 Method Not Allowed - http://upload.roughdrag.com/"
[FineUploader 3.9.0-3] Received response status 405 with body: <html>
<head><title>405 Method Not Allowed</title></head>
<body>
<h1>405 Method Not Allowed</h1>
<ul>
<li>Code: MethodNotAllowed</li>
<li>Message: The specified method is not allowed against this resource.</li>
<li>ResourceType: OBJECT</li>
<li>Method: POST</li>
<li>RequestId: 3493FE605B461EAF</li>
<li>HostId: HDXmtSpHufy6LDIH1Nsp0oYkLDvTC3XKFRRIadw66gmaMsF53Z3WYsCWooOoRcw2</li>
</ul>
<hr/>
</body>
</html>
[FineUploader 3.9.0-3] Waiting 5 seconds before retrying breakout.jpg...
[FineUploader 3.9.0-3] Detected valid cancel, retry, or delete click event on file 'breakout.jpg', ID: 0.
[FineUploader 3.9.0-3] Cancelling 0

该软件看起来很棒,但我就是无法超越这个。任何帮助,不胜感激。

我猜这是将自定义域名映射到 S3 存储桶时创建的 DNS 问题。 解决 upload.roughdrag.com 后,您似乎已将此 CNAME 映射到"upload.roughdrag.com.s3-website-us-east-1.amazonaws.com"。 尝试将该别名记录映射到"upload.roughdrag.com.s3.amazaonaws.com"。

更新 1:

如果在此更改后您仍然看到问题,我会在 AWS S3 论坛中发布。 希望员工能回答。 您的存储桶/别名记录可能存在问题,我无法从我的角度看到。 看起来 upload.roughdrag.com.s3.amazonaws.com 的 POST 请求已经成功,但是向 upload.roughdrag.com 发送 POST 请求时出现问题。 我与邮递员核实了这一点。

更新 2:

随着您最新的别名记录更改,看起来 S3 正在接受 POST 请求。

我遇到了同样的问题。通过将区域添加到以下函数中来修复它:

function getS3Client() {
    global $serverPublicKey, $serverPrivateKey;
    return S3Client::factory(array(
        'key' => $serverPublicKey,
        'secret' => $serverPrivateKey,
        'region' => 'ap-southeast-2'  //Revomve from non Sydney bucket
    ));
}

通过单击属性找到存储桶的区域。然后是静态网站托管,仅从 ap-xxxxx-x 的端点获取详细信息