JAVA模拟PostObject表单上传OSS,实现签名直传
使用JAVA实现PostObject这个需求,其实来自之前support同学的一段描述,说是有用户需求,但是官方没有任何demo的代码参考,用户自己根据官方文档介绍实现却是各种很难调查的问题。这个背景就不细说了。后来因为项目需要,就照着官网也去实现了一把,各中酸泪尽享其中,总之,我们还是有很多需要改进的地方的。为了用户,自勉,共勉!!
Post Object使用HTML表单上传文件到指定bucket,作为Put的替代品,使得基于浏览器上传文件到bucket成为可能。关于OSS官方文档对PostObject这个API的介绍文档参见这里(建议先阅读这个PostObject的介绍,下面的内容均与此有关,不然可能看不懂哦)。
官网中首先给出了HTTP的请求语法,即HTTP请求头和通过multipart/form-data编码的处于消息实体中用来传递参数的表单域形式;接下来通过表单域的表格形式逐个介绍了“file”和“key”这样“必选”的表单域,“OSSAccessKeyId”、“policy”、“Signature”这种因为其中一个的出现而变成必须出现的表单域,还有REST请求头、x-oss-meta-*用户meta及其他可选的表单域;然后介绍了一些表单域的特殊使用方式和注意事项;最后花了大篇幅介绍Post Policy和Signature的功能、用法。 文档大致清晰,只是因为概念较多,不容易理解,并且在实现中对容易出错的地方没有进行强调,导致问题调查的难度增加。根据亲身经历分析,主要的问题可能两个方面。
对multipart/form-data这种MIME类型的编码方式不熟悉。 对OSS系统解析PostObject请求的实现规则不了解。 接下来,针对上面两个方面进行详细的讲解。
multipart/form-data的介绍详见RFC 2388,以下几点简单提一下:
1. “multipart/form-data”包含一系列的域,每个域都有一个类型为“form-data”的content-disposition首部,并且,这个首部包含参数“name”,用来描述该表单域内容的描述信息。所以,每个域都会有如文档中给出的示例形式:
Content-Disposition: form-data; name="your_key"
注意:":"和";"后面都有一个空格。
2. 表单中有需求上传用户文件,可能会需要文件名称或者其他文件属性,需要包含在content-dispoisition首部中,如参数"filename",而且对于表单域中的任何MIME类型,都有一个optional的Content-Type属性,用来标识文件内容类型。所以文档中给出了“file”表单域的示例如下:
Content-Disposition: form-data; name="file"; filename="MyFilename.jpg" Content-Type: image/jpeg
注意:“filename”前的";"后仍然有一个空格;Content-Type之后的“:”同样有一个空格。
3. 使用boundary来分隔数据,为了和主体内容区分,尽量使用复杂的boundary。如文档中给出的HTTP首部中的内容:
Content-Type: multipart/form-data; boundary=9431149156168
4. 每个表单域的结构都是固定的。规定每个表单域都以"--"boundary+开头,然后回车(/r/n);然后是该表单域的描述信息(见描述1),接着/r/n。如果传送的内容是一个文件的话,那么还会包含文件名信息,回车(/r/n)之后接上文件内容的类型(见描述2)。然后,紧接着再一个回车(/r/n),开始真正的具体内容,最后以/r/n结束。
5. 在最后的表单域结束,以"--"+boundary+"--"结尾,表示请求体结束。
6. 补充一点,HTTP请求header与主体信息之间(header和第一个表单域的交界处),也需要有一个/r/n用来区分,即多出一个空行,如文档中:
Content-Type: multipart/form-data; boundary=9431149156168 --9431149156168 Content-Disposition: form-data; name="key"
以上,大致是对照RFC 2388的标准描述了OSS官方文档中给出的请求语法以及相关的分析。下面会讲解一小部分OSS系统解析PostObject请求过程,和相关注意事项。
请求的处理流程可以简单的理解为三个步骤:
1. 从HTTP请求header中解析boundary,用来区分域的分界;
2. 解析各个域的内容,直到遇到‘file’这个表单域;
3. 解析‘file’表单域。
所以,文档中会强调,需要将‘file’这个表单域放置在“最后一个域”中,不然,处于file之后的表单域不保证一定能生效哦!如果是把‘key’这个必须的表单域放置在‘file’的后面,亲测,肯定是InvalidArgument。
简单介绍一些图上的某些流程:
1) check POLICY、OSSACCESSKEYID、SIGNATURE existence: 因为POLICY、OSSACCESSKEYID、SIGNATURE这三个域,其中一个出现,其他两个都会成为必选域,所以这里会做该类检查。
2) Authorization: 根据POLICY、OSSACCESSKEYID、SIGNATURE等信息对验证该Post请求的合法性。
3) Policy rule check: 检查请求各个表单域中的设置是否符合policy的配置。
4) check length Legality: 因为Post请求的body总长度有限制,对可选的域的长度会进行检查。
5) ParseFile中的ParseContentType: 解析file域中的ContentType字段,该字段不是必须的。
最后,这里附上JAVA实现的OSS PostObject上传的代码(Maven工程),供各位OSS用户和研究人员参考使用:
import javax.activation.MimetypesFileTypeMap; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; * Created by yushuting on 16/4/17. public class OssPostObject { private String postFileName = "your_file"; // 确保运行代码的路径中有该文件 private String ossEndpoint = "your_endpoint"; // 如: http://oss-cn-shanghai.aliyuncs.com private String ossAccessId = "your_accessid"; // 你的访问AK信息 private String ossAccessKey = "your_accesskey"; // 你的访问AK信息 private String objectName = "your_object_name"; // 你上传文件之后的object名称 private String bucket = "your_bucket"; // 你之前创建的bucket,确保这个bucket已经创建 private void PostObject() throws Exception { String filepath=postFileName; String urlStr = ossEndpoint.replace("http://", "http://"+bucket+"."); // 提交表单的URL为bucket域名 LinkedHashMap String, String textMap = new LinkedHashMap String, String // key String objectName = this.objectName; textMap.put("key", objectName); // Content-Disposition textMap.put("Content-Disposition", "attachment;filename="+filepath); // OSSAccessKeyId textMap.put("OSSAccessKeyId", ossAccessId); // policy String policy = "{\"expiration\": \"2120-01-01T12:00:00.000Z\",\"conditions\": [[\"content-length-range\", 0, 104857600]]}"; String encodePolicy = java.util.Base64.getEncoder().encodeToString(policy.getBytes()); textMap.put("policy", encodePolicy); // Signature String signaturecom = com.aliyun.oss.common.auth.ServiceSignature.create().computeSignature(ossAccessKey, encodePolicy); textMap.put("Signature", signaturecom); Map String, String fileMap = new HashMap String, String fileMap.put("file", filepath); String ret = formUpload(urlStr, textMap, fileMap); System.out.println("[" + bucket + "] post_object:" + objectName); System.out.println("post reponse:" + ret); private static String formUpload(String urlStr, Map String, String textMap, Map String, String fileMap) throws Exception { String res = ""; HttpURLConnection conn = null; String BOUNDARY = "9431149156168"; try { URL url = new URL(urlStr); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(30000); conn.setDoOutput(true); conn.setDoInput(true); conn.setRequestMethod("POST"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); OutputStream out = new DataOutputStream(conn.getOutputStream()); // text if (textMap != null) { StringBuffer strBuf = new StringBuffer(); Iterator iter = textMap.entrySet().iterator(); int i = 0; while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; if (i == 0) { strBuf.append("--").append(BOUNDARY).append( "\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n"); strBuf.append(inputValue); } else { strBuf.append("\r\n").append("--").append(BOUNDARY).append( "\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n"); strBuf.append(inputValue); i++; out.write(strBuf.toString().getBytes()); // file if (fileMap != null) { Iterator iter = fileMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; File file = new File(inputValue); String filename = file.getName(); String contentType = new MimetypesFileTypeMap().getContentType(file); if (contentType == null || contentType.equals("")) { contentType = "application/octet-stream"; StringBuffer strBuf = new StringBuffer(); strBuf.append("\r\n").append("--").append(BOUNDARY).append( "\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n"); strBuf.append("Content-Type: " + contentType + "\r\n\r\n"); out.write(strBuf.toString().getBytes()); DataInputStream in = new DataInputStream(new FileInputStream(file)); int bytes = 0; byte[] bufferOut = new byte[1024]; while ((bytes = in.read(bufferOut)) != -1) { out.write(bufferOut, 0, bytes); in.close(); StringBuffer strBuf = new StringBuffer(); out.write(strBuf.toString().getBytes()); byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes(); out.write(endData); out.flush(); out.close(); // 读取返回数据 StringBuffer strBuf = new StringBuffer(); BufferedReader reader = new BufferedReader(new InputStreamReader( conn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { strBuf.append(line).append("\n"); res = strBuf.toString(); reader.close(); reader = null; } catch (Exception e) { System.err.println("发送POST请求出错: " + urlStr); throw e; } finally { if (conn != null) { conn.disconnect(); conn = null; return res; public static void main(String[] args) throws Exception { OssPostObject ossPostObject = new OssPostObject(); ossPostObject.PostObject();
注意在pom.xml加上:
dependency groupId com.aliyun.oss /groupId artifactId aliyun-sdk-oss /artifactId version 2.2.1 /version /dependency
------------------------------------------------------分隔符-----------------------------------------------------------
诚聘英才
阿里云函数服务是一个全新的,支持事件驱动编程模式的计算服务。 他帮助用户聚焦自身业务逻辑,以Serverless的方式构建应用,快速的实现低成本,可扩展,高可用的系统,而无需考虑服务器等底层基础设施的管理。 用户能够快速的创建原型,同样的架构能随业务规模平滑伸缩。让计算变得更高效,更经济,更弹性,更可靠。无论小型创业公司,还是大型企业,都受益其中。
我们的团队正在迅速扩张,求贤若渴。我们想寻找这样的队友:
基本功扎实。既能阅读论文追踪业界趋势,又能快速编码解决实际问题。 严谨的,系统化的思维能力。既能整体考虑业务机会,系统架构,运维成本等诸多因素,又能掌控设计/开发/测试/发布的完整流程,预判并控制风险。 好奇心和使命感驱动。乐于探索未知领域,不仅是梦想家,也是践行者。 坚韧、乐观、自信。能在压力和困难中看到机会,让工作充满乐趣!如果您对云计算充满热情,想要构建一个有影响力计算平台和生态体系,请加入我们,和我们一起实现梦想!
详见:http://www.atatech.org/articles/53851
将你的简历发送到shuting.yst@alibaba-inc.com,标题 应聘阿里云-姓名
如果你有自己的git地址或者个人博客,将会大大加分哦,一起在邮件中发给我吧~~~
Java数组模拟队列 队列的作用就像电影院前的人们站成的排一样:第一个进入附属的人将最先到达队头买票。最后排队的人最后才能买到票。
Java 模拟二级文件系统 终 本系列将记述使用 Java 实现一个简单的二级文件系统的过程。架构 构建项目的时候,避免代码的最重要的技巧在于区分哪些功能是哪些部分应该实现的,从语义和逻辑上考察这个问题,搞清楚了之后代码就不会变成一团乱。 之前对于文件、用户和磁盘的操作全都在文件系统中实现了。而我们要写的交互界面,将起到操作系统的作用。换句话说它要处理用户的认证、命令解析、打印必要的提示信息、询问命令执行依赖的参数,并最终根据已有的信息调用文件系统或其他代码产生影响。
Java 模拟二级文件系统 下 本系列将记述使用 Java 实现一个简单的二级文件系统的过程。架构为了避免单个代码文件过于冗长,我决定将文件系统和交互界面分开。其中文件系统使用单例模式,交互界面则是以主程序的方式,与命令行交互并调用文件系统完成用户意图的操作。
阿里云存储服务 193982 阿里云存储基于飞天盘古2.0分布式存储系统,产品包括对象存储OSS、块存储Block Storage、共享文件存储NAS、表格存储、日志存储与分析、归档存储及混合云存储等,充分满足用户数据存储和迁移上云需求,连续三年跻身全球云存储魔力象限四强。
相关文章
- java.net.DatagramSocket
- java卸载 安装错误_Java卸载后无法重新安装 提示已安装过[通俗易懂]
- java输出值取后两位小数,Java输出结果保留两位小数
- java启动器_JAVA基础:Java 启动器如何查找类
- JAVA代码—最简单的九九乘法表
- java代码大全及详解_Java练级攻略[通俗易懂]
- java parrallel for,Java 8 parallel forEach进度指示
- 【测开技能】Java语言系列(十)if判断
- java的栈内存和堆内存_Java本地方法栈
- java匿名对象_匿名对象概念和使用
- java 调用win32 api 学习总结
- java中字符串分割特殊字符处理_java字符串按照特定字符分割
- 终于有人把 Python、AI、Java 一起讲明白了!
- java Swing实现的正则表达式测试工具详解编程语言
- 用字符串模拟两个大数相加——java实现详解编程语言
- JavaScript与Java的区别
- Linux安装Java环境必备指南(linux装java)
- 处理实现Redis Java中有效期管理的技巧(redisjava过期)
- 失效Redis Java实现自动过期失效(redisjava过期)
- 策略利用Redis Java构建过期策略(redisjava过期)
- 如何使用Java备份Oracle数据库?(java备份oracle)
- 基于Linux操作系统上实现 Java 编程(linux r java)
- 基于获取JAVA路径,包括CLASSPATH外的路径的方法详解
- 深入解析java中的静态代理与动态代理