从客户端浏览器直传文件到Storage

关于上传文件到Azure Storage没有什么可讲的,不论我们使用哪种平台、语言,上传流程都如下图所示:

从上图我们可以了解到从客户端上传文件到Storage,是需要先将文件上传到应用服务上,然后再通过应用服务器上的后端代码将文件上传到Storage,在这里应用服务器就是起到一个代理的作用。
当然,这是最常规的做法,但是确有一个致命的弱点,那就是我们上传文件到Storage中,是要占用应用服务器的计算、网络、IO等资源的,这对于一些对性能要求很高的应用场景是很难接受的。
那么,我们是否有解决方案?答案是肯定的,我们可以跳过应用服务器从客户端将文件直传到Storage中,那么我们如何来构建这套解决方案呢?这就是我们下面要讲的。
我们先来看下直传文件到Storage的流程图:

这里特别有几点需要注意的地方:
1、 不能使用存储账号和存储密钥来验证身份进行文件上传,因为这会在客户端暴露存储账号和存储密钥等敏感信息。
2、 Storage默认是不能跨域访问的,所以我们需要将应用服务器所在域加入到Storage的跨域规则中。
3、 通过上图,我们可以看到客户端一共发送两次http请求,第一次从应用服务器获取Shared Access Signature(具有写权限),再带着获取到的SAS将文件从客户端上传到Storage。
大致流程和思路已经讲清楚了,那么下面我们就来看看用代码是如何实现的。
Index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<meta charset="utf-8" />
<title>直传文件到Storage</title>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/uploadfile.js"></script>
</head>
<body>
<input type="file" id="file" name="file" /> <div id="output">
<strong>File properties:</strong>
<br />
<p>
Name: <span id="fileName"></span>
</p>
<p>
File size: <span id="fileSize"></span>bytes.
</p>
<p>
File type: <span id="fileType"></span>
</p>
<p>
<input type="button" value="Upload File" id="uploadFile" />
</p>
</div>
</body>
</html>
updatefile.js
(function ($) {
var reader = null,
requestUri = null,
selectedFile = null,
blobName = null;
function sendAjax(url, dataToSend, beforeSendFunction, successFunction) {
$.ajax({
url: url,
type: "PUT",
data: dataToSend,
processData: false,
beforeSend: beforeSendFunction,
tryCount: 0,
success: successFunction,
});
}
function readerOnLoadEnd(evt) {
if (evt.target.readyState === FileReader.DONE) {
var uri = requestUri,
requestData = new Uint8Array(evt.target.result);
sendAjax(uri, requestData,
function (xhr) {
xhr.setRequestHeader("x-ms-blob-type", "BlockBlob");
xhr.setRequestHeader("x-ms-blob-content-type", getContentType(blobName));
}, function (data, status) {
alert("upload success.");
});
}
};
function handleFileSelect(e) {
selectedFile = e.target.files[0];
blobName = selectedFile.name;
var fileSize = selectedFile.size;
$('#output').show();
$('#fileName').text(blobName);
$('#fileSize').text(fileSize);
$('#fileType').text(selectedFile.type);
$.get('{获取Blob SAS的接口地址}', { 'blobName': blobName }, function (data) {
console.log(data);
requestUri = data;
});
}
function startUpload() {
$("#uploadFile").prop('disabled', true);
$("#file").prop('disabled', true);
var slice = selectedFile.slice(0, selectedFile.size);
reader.readAsArrayBuffer(slice);
}
function getContentType(blobName) {
var ext = blobName.substr(blobName.lastIndexOf('.'));
var contentType = 'application/octet-stream';
switch (ext) {
case ".txt":
contentType = "text/plain";
break;
case ".png":
contentType = "image/png";
break;
case ".zip":
contentType = "application/zip";
break;
case ".pptx":
contentType = "application/vnd.ms-powerpoint";
break;
case ".docx":
contentType = "application/msword";
break;
case ".pdf":
contentType = "application/pdf";
break;
case ".jpg":
case ".jpeg":
contentType = "image/jpeg";
break;
case ".html":
contentType = "text/html";
break;
case ".js":
contentType = "application/x-javascript";
break;
case ".css":
contentType = "text/css";
break;
case ".gif":
contentType = "image/gif";
break;
case ".ico":
contentType = "image/x-icon";
break;
case ".mp4":
contentType = "video/mpeg4";
break;
case ".mp3":
contentType = "audio/mp3";
break;
default:
break;
}
return contentType;
}
$(function () {
$('#output').hide();
if (window.File && window.FileReader && window.FileList && window.Blob) {
reader = new FileReader();
reader.onloadend = readerOnLoadEnd;
} else {
alert('The File APIs are not fully supported in this browser.');
$('#file').prop('disabled', true);
return;
}
$('#file').bind('change', handleFileSelect);
//上传文件
$('#uploadFile').bind('click', startUpload);
});
}(jQuery))
后端提供的Shared Access Signature接口代码
private string _StorageConnectionString = ConfigurationManager.AppSettings["StorageConnectionString"];
private string _AccountName = ConfigurationManager.AppSettings["AccountName"];
private string _AccountKey = ConfigurationManager.AppSettings["AccountKey"];
private string _ContainerName = ConfigurationManager.AppSettings["ContainerName"]; [HttpGet]
public string GetRequestStorageUri(string blobName)
{
if (string.IsNullOrEmpty(blobName))
{
throw new ArgumentNullException();
}
if (string.IsNullOrEmpty(_ContainerName)) {
throw new NullReferenceException("container is null or empty");
} CloudBlockBlob blockBlob = GetCloudBlockBlob(_ContainerName, blobName);
string sas = GetBlobSharedAccessSignature(blockBlob);
string requestUri = string.Format("https://{0}.blob.core.chinacloudapi.cn/{1}/{2}{3}", _AccountName, _ContainerName, blobName, sas);
return requestUri;
} private CloudBlockBlob GetCloudBlockBlob(string containerName,string blobName)
{
if (string.IsNullOrEmpty(containerName) || string.IsNullOrEmpty(blobName))
{
throw new ArgumentNullException();
}
if (string.IsNullOrEmpty(_StorageConnectionString))
{
throw new NullReferenceException("storage connection string is null or empty");
}
CloudStorageAccount account = CloudStorageAccount.Parse(_StorageConnectionString);
CloudBlobClient blobClient = account.CreateCloudBlobClient();
ServiceProperties serviceProperties = blobClient.GetServiceProperties();
serviceProperties.Cors.CorsRules.Clear();
serviceProperties.Cors.CorsRules.Add(new CorsRule
{
AllowedOrigins = new List<string> { "{应用服务器所在域}" },
AllowedMethods = CorsHttpMethods.Get| CorsHttpMethods.Put| CorsHttpMethods.Post| CorsHttpMethods.Delete,
AllowedHeaders = new List<string> { "x-ms-*", "content-type", "accept" },
MaxAgeInSeconds = * *
});
blobClient.SetServiceProperties(serviceProperties);
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
container.CreateIfNotExists();
CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName);
return blockBlob;
} private string GetBlobSharedAccessSignature(CloudBlob blob) {
if (blob == null)
{
throw new ArgumentNullException("blob is null");
}
return blob.GetSharedAccessSignature(new SharedAccessBlobPolicy {
Permissions = SharedAccessBlobPermissions.Add| SharedAccessBlobPermissions.Create| SharedAccessBlobPermissions.Delete| SharedAccessBlobPermissions.List| SharedAccessBlobPermissions.Read| SharedAccessBlobPermissions.Write,
SharedAccessStartTime = DateTime.UtcNow,
SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes()
});
}
从客户端浏览器直传文件到Storage的更多相关文章
- 直传文件到Azure Storage的Blob服务中
(此文章同时发表在本人微信公众号“dotNET每日精华文章”,欢迎右边二维码来关注.) 题记:为了庆祝获得微信公众号赞赏功能,忙里抽闲分享一下最近工作的一点心得:如何直接从浏览器中上传文件到Azure ...
- HTML5+flash打造兼容各浏览器的文件上传方案
上一篇文章介绍了HTML5版的文件上传插件,相比flash,采用HTML5的新技术无疑可以提升程序的加载速度.但是在目前的情况看来,HTML5的特性支持度不高,插件的可用性范围确实比较窄.例如,我在插 ...
- asp.net 客户端上传文件全路径获取方法
asp.net 获取客户端上传文件全路径方法: eg:F:\test\1.doc 基于浏览器安全问题,浏览器将屏蔽获取客户端文件全路径的方法,只能获取到文件的文件名,如果需要获取全路径则需要另想其他 ...
- asp.net 浏览器下载文件的四种方式
// 方法一:TransmitFile实现下载 protected void Button1_Click(object sender, EventArgs e) { Response.ContentT ...
- 页面加载异常 清除浏览器静态文件 js css 缓存 js动态加载js css文件,可以配置文件后辍,防止浏览器缓存
js清除浏览器缓存的几种方法 - 兔老霸夏 - 博客园 https://www.cnblogs.com/Mr-Rocker/p/6031096.html js清除浏览器缓存的几种方法 一.CSS和 ...
- Axis 生成客户端client stub文件
[转自] http://blog.csdn.net/qiao000_000/article/details/5568442 开发前,有个同事先给我们不熟悉Web Service的程序员进行了一些培训, ...
- 利用Beef劫持客户端浏览器
利用Beef劫持客户端浏览器 环境: 1.Kali(使用beef生成恶意代码,IP:192.168.114.140) 2.一台web服务器(留言板存在XSS跨站脚本漏洞,IP:192.168.11 ...
- 使用Beef劫持客户端浏览器并进一步使用Beef+msf拿客户端shell
环境: 1.Kali(使用beef生成恶意代码,IP:192.168.114.140) 2.一台web服务器(留言板存在XSS跨站脚本漏洞,IP:192.168.114.204) 3. 客户端(用于访 ...
- 利用form.submit提交表单导出文件到客户端浏览器, 提示下载!
本来是想利用ajax提交json数据到服务端, 让服务端生成一个excel文件并提示客户端浏览器下载的. 但是搞了很久发现ajax方式是无法触发浏览器弹出文件下载的. 网上很多的方案都是说利用form ...
随机推荐
- 获取web页面xpath
1. Open Chrome 2. Right click the element that you want to get xpath 3. select "Inspector" ...
- [整理]WebAPP开发的框架
http://www.zhihu.com/question/27210335 http://ionicframework.com/getting-started/ http://cordova.apa ...
- CSS float浅析
写在开篇: 浮动属性的设计初衷,只是为了实现文本环绕效果! 时刻牢记这一点,才能正确使用浮动. 由于浮动元素脱离文档流,它后面的块级元素会忽略它的存在,占据它原本的位置,但是这个块级元素中的内联元素, ...
- Maven部署dao工程到私服上——(十三)
1.修改settings.xml 需要在客户端即(部署dao工程)的电脑上配置 maven环境,并修改 settings.xml 文件,配置连接私服的用户和密码 . 此用户名和密码用于私服校验,因为私 ...
- JAVA中Collection接口和Map接口的主要实现类
Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些Collection允许相同的元素 ...
- 基于FPGA(DDS)的正弦波发生器
记录背景:昨晚快下班时,与同事rk聊起怎么用FPGA实现正弦波的输出.我第一反应是利用高频的PWM波去滤波,但感觉这样的波形精度肯定很差:后来想起之前由看过怎么用FPGA产生正弦波的技术,但怎么都想不 ...
- 安装Scrapy遇到的坑
安装过程怕是要吐血,架梯子等等结果被setuptools的版本给坑了. 参考网址: http://blog.csdn.net/YHYR_YCY/article/details/78876148 htt ...
- 文字小于12px时,设置line-height不居中问题
设置了文字了小于12px时,会存在设置了line-height的不生效的问题,主要是由于基线的问题,这篇文章解释的很清楚,有兴趣的可以看下https://blog.csdn.net/q12151634 ...
- Java 把异常传递给控制台
最简答而又不用写多少代码就能保护异常信息的方法,就是把它们从main()传递到控制台,对于简单的程序可以像这样: package exceptions; //: exceptions/MainExce ...
- pyqt5-基础
PyQt5是一套来自Digia的Qt5应用框架和Python的粘合剂.支持Python2.x和Python3.x版本. PyQt5以一套Python模块的形式来实现功能.它包含了超过620个类,600 ...