XMLHttpRequest错误

3
我有一个谷歌应用引擎应用程序,在所有内容都在一个主机上运行时,它可以很好地在生产环境中工作,并且当Web应用程序在单独的主机上运行时,大部分情况下也可以正常工作。所有与服务器的查询(GET,POST,PUT,DELETE)均按预期进行。这表明我已经在整个系统中正确配置了所有CORS(我几周前就解决了这个问题)。
唯一无法使其正常工作的部分是文件上传。我正在使用django、djangoappengine、django-cors-headers和filetransfers,所有这些的净结果是,当从远程服务器运行时,我无法上传文件,但其他所有东西都能正常工作。在Chrome的JavaScript控制台中,我看到以下错误:
XMLHttpRequest cannot load http://localhost:8080/_ah/upload/ahl...<truncated>.
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:9000' is therefore not allowed
access. The response had HTTP status code 405.

这显然是一个CORS错误,所以我大致知道需要发生什么。但我无法弄清楚如何对我的配置进行必要的更改以克服这个问题。
这是我的总体设置:
- dev_appserver.py在8080端口上提供API服务 - grunt serve在9000端口上提供客户端应用程序 - CORS设置:
- 开发环境:CORS_ORIGIN_ALLOW_ALL = True - 生产环境:CORS_ORIGIN_WHITELIST = [ '(app.domain.com for my app)' ]
在生产环境中,我认为解决方法是配置我的bucket的CORS,但我不确定。然而,在部署之前,我不知道如何为本地开发服务器配置它,以便测试数据的整体流程。
以下是最终失败的JavaScript(该应用程序使用AngularJS):
var form = angular.element('#media-form');
var data = new FormData(form);

// have the API return a URL using prepare_upload in
// filetransfers module to upload to:
var uploadActionUrl = https://api.domain.com/upload_url/';
$http.get(uploadActionUrl)
  .then(function(response) {
    // I get here with no problem
    $http.post(response.data.action, formData)
      .then(function(response) {
        console.log('got:', response);
      }, function(error) {
        console.log('upload error:', error); // <- this is where I end up
      });
  }, function(error) {
    console.log('get upload URL error:', error);
  });

同样的代码在同一主机上运行时可以正常工作(因此API本身功能正常),并且(重要的是)除了上传我的文件之外,所有HTTP方法都适用于所有端点,因此CORS本身已正确设置与App Engine进行交互。只有文件上传部分不起作用。

我想也许修复包括使用JSON而不是FormData来组装我的上传表单,但我以前从未找到过这样的方法。

--- 更新以添加 ---

澄清一点,导致此错误的端点不是直接在我的应用程序内部,而是在由一个单独的Google服务处理的URL上。给我URL的代码是:

from google.appengine.ext.blobstore import create_upload_url

def prepare_upload(request, url, **kwargs):
    return create_upload_url(
        url,
        gs_bucket_name = settings.GOOGLE_CLOUD_STORAGE_BUCKET
    ), {}

我得到的URL的格式为/_ah/upload/<一次性密钥>,该URL上发生的所有事情(似乎)都不在我的控制范围之内,包括添加标头。

当我说“在同一主机上运行时这个有效”时,我应该澄清一下。由于之前的应用程序没有使用AngularJS,因此帖子和响应处理的细节有些不同;我可能在这里也做了其他一些微小的错误,但在解决CORS问题之前,我无法排除任何问题。 - seawolf
url的处理程序是否发送CORS标头?以下是使其正常工作所需执行的示例:https://github.com/GoogleCloudPlatform/appengine-python-blobstore-cors-upload/blob/master/main.py - Josh J
为什么不直接将文件以formData的形式POST到端点,而是先GET再POST呢?像这样:var input = $('selector'); var data = new FormData(); data.append('name', input.name); data.append('type', input.type); data.append(input.name, input.files[0]); $.ajax({ url: 'url', type: 'POST', processData: false, contentType: false, // important for jqXHR data: data}.done() - dliu
端点URL是由数据存储即时创建的,只能使用一次,因此我必须从服务器检索URL(GET),然后将文件上传到该端点(POST)。 - seawolf
@seawolf 你有解决这个问题的方法吗?我也遇到了同样的问题,我的POST请求到/_ah/upload 在预检测的OPTIONS请求期间失败。 - redhotvengeance
1
我从未找到一个好的解决方案。相反,我在AppEngine项目上创建了一个表单,其中包含一些JavaScript钩子来发出重要事件的信号(已加载、文件已选择、上传完成等),然后我将该表单作为IFrame嵌入客户端应用程序并监听这些事件。虽然不太美观,但它能完成工作。 - seawolf
2个回答

1
一种方法是在您的app.yaml中为特定的url设置http头。例如:
handlers:
- url: /_ah/upload....
  ...
  http_headers:
    Access-Control-Allow-Origin: http://localhost:9000

3
这个特定的设置只能在静态文件处理程序上实现(参见静态文件处理程序)。几天前当我发现这个功能时,我很有希望,但据我所知,这并没有在这种情况下提供帮助。 - seawolf

1
你的http处理程序必须拥有OPTIONS方法,以便向浏览器发送CORS标头。
例如:Chrome在发送PUT请求之前,总是会向同一URL发送一个OPTIONS请求,以检查CORS标头。如果浏览器无法获取OPTIONS请求的响应,则CORS将失败。
请查看此App Engine webapp2示例。
class BaseRestHandler(webapp2.RequestHandler):
    def options(self, *args, **kwargs):
        self.response.headers['Access-Control-Allow-Origin'] = '*'
        self.response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept'
        self.response.headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE'

https://en.wikipedia.org/wiki/Cross-origin_resource_sharing


1
这已经完成了,并且在所有其他端点上都可以工作。但是,单个端点位于我的代码库之外(由App Engine环境中的外部服务处理),它没有提供这些标头。 - seawolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接