使用ng-flow上传至GAE Blobstore的文件总是命名为'blob
我正在尝试创建一个页面,用于将图片上传到Google App Engine的blobstore。我使用的是angularjs和ng-flow来实现这个功能。
上传的部分似乎工作得很好,但所有的文件都被存储为'application/octet-stream',并且名字都是'blob'。我该如何让blobstore识别文件名和内容类型呢?
这是我用来上传文件的代码。
在FlowEventsCtrl里面:
$scope.$on('flow::filesSubmitted', function (event, $flow, files) {
$http.get('/files/upload/create').then(function (resp) {
$flow.opts.target = resp.data.url;
$flow.upload();
});
});
在view.html里面:
<div flow-init="{testChunks:false, singleFile:true}"
ng-controller="FlowEventsCtrl">
<div class="panel">
<span flow-btn>Upload File</span>
</div>
<div class="show-files">...</div>
</div>
服务器端的设置如blobstore文档中所述。
谢谢
1 个回答
我解决了我的问题,回头看这个答案似乎很明显。Flow.js和Blobstore上传网址是做不同事情的。我把我的解释留在下面,给那些和我一样犯同样简单错误的人。
Blobstore期待一个包含文件的字段。这个字段里有上传数据的文件名和内容类型。这些数据会作为文件存储在blobstore里。默认情况下,这个字段叫做'file'。
Flow会把数据分成小块上传,并且包含一些字段来记录文件名和其他信息。实际的小块数据是在一个字段中上传的,这个字段的文件名是'blob',内容类型是'application/octet-stream'。服务器需要存储这些小块并把它们重新组合成完整的文件。因为这只是文件的一部分,而不是整个文件,所以它的名字和内容类型都和文件不一样。默认情况下,这个字段也叫做'file'。
所以问题的答案是:文件以'application/octet-stream'的格式存储,名字是'blob',因为我存储的是小块而不是完整的文件。能存储一些东西似乎是因为两个字段使用了相同的默认名称。
因此,解决方案是为Flow请求编写我自己的处理程序:
class ImageUploadHandler(webapp2.RequestHandler):
def post(self):
chunk_number = int(self.request.params.get('flowChunkNumber'))
chunk_size = int(self.request.params.get('flowChunkSize'))
current_chunk_size = int(self.request.params.get('flowCurrentChunkSize'))
total_size = int(self.request.params.get('flowTotalSize'))
total_chunks = int(self.request.params.get('flowTotalChunks'))
identifier = str(self.request.params.get('flowIdentifier'))
filename = str(self.request.params.get('flowFilename'))
data = self.request.params.get('file')
f = ImageFile(filename, identifier, total_chunks, chunk_size, total_size)
f.write_chunk(chunk_number, current_chunk_size, data)
if f.ready_to_build():
info = f.build()
if info:
self.response.headers['Content-Type'] = 'application/json'
self.response.out.write(json.dumps(info.as_dict()))
else:
self.error(500)
else:
self.response.headers['Content-Type'] = 'application/json'
self.response.out.write(json.dumps({
'chunkNumber': chunk_number,
'chunkSize': chunk_size,
'message': 'Chunk ' + str(chunk_number) + ' written'
}))
其中ImageFile是一个写入谷歌云存储的类。
编辑:
下面是ImageFile类。唯一缺少的是FileInfo类,它是一个简单的模型,用来存储生成的URL和文件名。
class ImageFile:
def __init__(self, filename, identifier, total_chunks, chunk_size, total_size):
self.bucket_name = os.environ.get('BUCKET_NAME', app_identity.get_default_gcs_bucket_name())
self.original_filename = filename
self.filename = '/' + self.bucket_name + '/' + self.original_filename
self.identifier = identifier
self.total_chunks = total_chunks
self.chunk_size = chunk_size
self.total_size = total_size
self.stat = None
self.chunks = []
self.load_stat()
self.load_chunks(identifier, total_chunks)
def load_stat(self):
try:
self.stat = gcs.stat(self.filename)
except gcs.NotFoundError:
self.stat = None
def load_chunks(self, identifier, number_of_chunks):
for n in range(1, number_of_chunks + 1):
self.chunks.append(Chunk(self.bucket_name, identifier, n))
def exists(self):
return not not self.stat
def content_type(self):
if self.filename.lower().endswith('.jpg'):
return 'image/jpeg'
elif self.filename.lower().endswith('.jpeg'):
return 'image/jpeg'
elif self.filename.lower().endswith('.png'):
return 'image/png'
elif self.filename.lower().endswith('.git'):
return 'image/gif'
else:
return 'binary/octet-stream'
def ready(self):
return self.exists() and self.stat.st_size == self.total_size
def ready_chunks(self):
for c in self.chunks:
if not c.exists():
return False
return True
def delete_chunks(self):
for c in self.chunks:
c.delete()
def ready_to_build(self):
return not self.ready() and self.ready_chunks()
def write_chunk(self, chunk_number, current_chunk_size, data):
chunk = self.chunks[int(chunk_number) - 1]
chunk.write(current_chunk_size, data)
def build(self):
try:
log.info('File \'' + self.filename + '\': assembling chunks.')
write_retry_params = gcs.RetryParams(backoff_factor=1.1)
gcs_file = gcs.open(self.filename,
'w',
content_type=self.content_type(),
options={'x-goog-meta-identifier': self.identifier},
retry_params=write_retry_params)
for c in self.chunks:
log.info('Writing chunk ' + str(c.chunk_number) + ' of ' + str(self.total_chunks))
c.write_on(gcs_file)
gcs_file.close()
except Exception, e:
log.error('File \'' + self.filename + '\': Error during assembly - ' + e.message)
else:
self.delete_chunks()
key = blobstore.create_gs_key('/gs' + self.filename)
url = images.get_serving_url(key)
info = ImageInfo(name=self.original_filename, url=url)
info.put()
return info
Chunk类:
class Chunk:
def __init__(self, bucket_name, identifier, chunk_number):
self.chunk_number = chunk_number
self.filename = '/' + bucket_name + '/' + identifier + '-chunk-' + str(chunk_number)
self.stat = None
self.load_stat()
def load_stat(self):
try:
self.stat = gcs.stat(self.filename)
except gcs.NotFoundError:
self.stat = None
def exists(self):
return not not self.stat
def write(self, size, data):
write_retry_params = gcs.RetryParams(backoff_factor=1.1)
gcs_file = gcs.open(self.filename, 'w', retry_params=write_retry_params)
for c in data.file:
gcs_file.write(c)
gcs_file.close()
self.load_stat()
def write_on(self, stream):
gcs_file = gcs.open(self.filename)
try:
data = gcs_file.read()
while data:
stream.write(data)
data = gcs_file.read()
except gcs.Error, e:
log.error('Error writing data to chunk: ' + e.message)
finally:
gcs_file.close()
def delete(self):
try:
gcs.delete(self.filename)
self.stat = None
except gcs.NotFoundError:
pass