【最不佳实践】文件上传并不简单

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
简介: Serverless架构带来的除了一种新的架构,一种新的编程范式,还有就是一种思路上的转变,尤其是开发过程上的一些思路变化。有人说要把Serverless架构看成是一种天然的分布式架构,需要用分布式架构的思路去开发Serverless应用,诚然,这种说法是正确的,但是在一些情况下,Serverless还是有着一些特有的“特性”,所以在Serverless架构下,还是要有一些开发的“观念转变”。

Serverless架构带来的除了一种新的架构,一种新的编程范式,还有就是一种思路上的转变,尤其是开发过程上的一些思路变化。有人说要把Serverless架构看成是一种天然的分布式架构,需要用分布式架构的思路去开发Serverless应用,诚然,这种说法是正确的,但是在一些情况下,Serverless还是有着一些特有的“特性”,所以在Serverless架构下,还是要有一些开发的“观念转变”。

传统文件上传

传统Web框架中,我们上传文件是非常简单和便捷的,例如Python的Flask框架:

f = request.files['file']
f.save('my_file_path')

Serverless架构下文件上传

但是在Serverless架构下,却不能直接上传文件,因为:

  • 一般情况下,一些云平台的API网关触发器会讲二进制文件转换成字符串;不便直接获取和存储;
  • 一般情况下,API网关与FaaS平台之间传递的数据包有大小限制,很多平台被限制在6M;
  • FaaS平台大都是无状态的,即使存储到当前实例中,也会随着实例释放而导致文件丢失;

所以,传统框架中常用的上传方案是不太适合在Serverless架构中直接使用的,在Serverless架构上传文件的方法通常有两种:

  • 一种是BASE64后上传,持久化到对象存储或者是NAS中,这种做法可能会触及到API网关与FaaS平台之间传递的数据包有大小限制,所以一般使用这种上传方法的通常是上传头像头像等小文件的业务场景;
  • 第二种上传方法是通过对象存储等平台来上传,因为客户端直接通过密钥等信息来将文件直传到对象存储是有一定风险的,所以通常情况是客户端发起上传请求,函数计算根据请求内容进行预签名操作,并将预签名地址返回给客户端,客户端再使用指定的方法进行上传,上传完成之后,可以通过对象存储触发器等来对上传结果进行更新等:

image

以阿里云函数计算为例,针对上述两种常见的上传方法通过Python Web框架Bottle来实现:

在函数计算中,先初始化对象存储相关的对象等:

AccessKey = {
    "id": '',
    "secret": ''
}
OSSConf = {
    'endPoint': 'oss-cn-hangzhou.aliyuncs.com',
    'bucketName': 'bucketName',
    'objectSignUrlTimeOut': 60
}

# 获取获取/上传文件到OSS的临时地址
auth = oss2.Auth(AccessKey['id'], AccessKey['secret'])
bucket = oss2.Bucket(auth, OSSConf['endPoint'], OSSConf['bucketName'])
# 对象存储操作
getUrl = lambda object, method: bucket.sign_url(method, object, OSSConf['objectSignUrlTimeOut'])
getSignUrl = lambda object: getUrl(object, "GET")
putSignUrl = lambda object: getUrl(object, "PUT")

# 获取随机字符串
randomStr = lambda len: "".join(random.sample('abcdefghijklqrstuvwxyz123456789ABCDEFGZSA' * 100, len))

简单上传:适合小文件

第一种上传方法,通过Base64上传之后,持久化到对象存储,适合小文件上传,流程比较简单:

# 文件上传
# URI: /file/upload
# Method: POST
@bottle.route('/file/upload', "POST")
def postFileUpload():
    try:
        pictureBase64 = bottle.request.GET.get('picture', '').split("base64,")[1]
        object = randomStr(100)
        with open('/tmp/%s' % object, 'wb') as f:
            f.write(base64.b64decode(pictureBase64))
        bucket.put_object_from_file(object, '/tmp/%s' % object)
        return response({
            "status": 'ok',
        })
    except Exception as e:
        print("Error: ", e)
        return response(ERROR['SystemError'], 'SystemError')

科学上传:更安全稳定上传方法

第二种上传方法,获取预签名的对象存储地址,再在客户端发起上传请求,直传到对象存储,相对来说会更科学更通用,但是流程相对复杂:

# 获取文件上传地址
# URI: /file/upload/url
# Method: GET
@bottle.route('/file/upload/url', "GET")
def getFileUploadUrl():
    try:
        object = randomStr(100)
        return response({
            "upload": putSignUrl(object),
            "download": 'https://download.xshu.cn/%s' % (object)
        })
    except Exception as e:
        print("Error: ", e)
        return response(ERROR['SystemError'], 'SystemError')

客户端实现/预览

HTML部分:

<div style="width: 70%">
    <div style="text-align: center">
        <h3>Web端上传文件</h3>
    </div>
    <hr>
    <div>
        <p>
            方案1:通过上传到函数计算,进行处理再转存到对象存储,这种方法比较直观,但是问题是FaaS平台与API网关处有数据包大小上限,而且对二进制文件处理并不好。
        </p>
        <input type="file" name="file" id="fileFc"/>
        <input type="button" onclick="UpladFileFC()" value="上传"/>
    </div>
    <hr>
    <div>
        <p>
            方案2:
            直接上传到对象存储,流程是先从函数计算获得临时地址,进行数据存储(例如将文件信息存到redis等),然后再从客户端进行上传到对象存储,上传结束可通过对象存储触发器触发函数,从存储系统(例如已经存储到redis)读取到更对信息,在对图像进行处理。
        </p>
        <input type="file" name="file" id="fileOss"/>
        <input type="button" onclick="UpladFileOSS()" value="上传"/>
    </div>
</div>

通过Base64上传的客户端JavaScript实现:

function UpladFileFC() {
    const oFReader = new FileReader();
    oFReader.readAsDataURL(document.getElementById("fileFc").files[0]);
    oFReader.onload = function (oFREvent) {
        const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new ActiveXObject("Microsoft.XMLHTTP"))
        xmlhttp.onreadystatechange = function () {
            if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                alert(xmlhttp.responseText)
            }
        }
        const url = "https://domain.com/file/upload"
        xmlhttp.open("POST", url, true);
        xmlhttp.setRequestHeader("Content-type", "application/json");
        xmlhttp.send(JSON.stringify({
            picture: oFREvent.target.result
        }));
    }
}

客户端通过预签名地址,直传到对象存储的客户端JavaScript实现:

function doUpload(bodyUrl) {
    const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new ActiveXObject("Microsoft.XMLHTTP"));
    xmlhttp.open("PUT", bodyUrl, true);
    xmlhttp.onload = function () {
        alert(xmlhttp.responseText)
    };
    xmlhttp.send(document.getElementById("fileOss").files[0]);
}

function UpladFileOSS() {
    const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new ActiveXObject("Microsoft.XMLHTTP"))
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            const body = JSON.parse(xmlhttp.responseText)
            if (body['url']) {
                doUpload(body['url'])
            }
        }
    }
    const getUploadUrl = 'https://domain.com/file/upload/url'
    xmlhttp.open("POST", getUploadUrl, true);
    xmlhttp.setRequestHeader("Content-type", "application/json");
    xmlhttp.send();
}

整体效果:

image

目录
相关文章
|
6月前
|
XML 前端开发 Java
SpringMVC实现文件下载实践
SpringMVC实现文件下载实践
67 3
|
8月前
|
网络协议
|
1月前
|
移动开发 前端开发
VForm3的文件上传方式
VForm3的文件上传方式
50 0
|
1月前
|
存储 前端开发 Java
[java后端研发]——文件上传与下载(2种方式)
[java后端研发]——文件上传与下载(2种方式)
128 3
|
编解码 前端开发 API
阿里云视频上传实战
最近在做项目优化,关于阿里云视频上传方面一直存在视频上传过慢问题.由于之前采用的是服务端进行视频上传,主要的逻辑是客户端上传视频到服务端进行视频文件暂存,然后服务端上传视频到阿里云.
阿里云视频上传实战
|
Java 开发者
单文件上传 | 学习笔记
快速学习单文件上传,介绍了单文件上传系统机制, 以及在实际应用过程中如何使用。
74 0
单文件上传 | 学习笔记
|
开发者 Windows Python
文件下载案例 | 学习笔记
快速学习 文件下载案例
97 0
文件下载案例 | 学习笔记
|
数据安全/隐私保护 Windows
|
存储 前端开发 应用服务中间件
关于项目中文件上传
关于项目中文件上传
199 0