zl程序教程

您现在的位置是:首页 >  后端

当前栏目

JAVA模拟PostObject表单上传OSS,实现签名直传

JAVA模拟上传 实现 表单 签名 oss
2023-09-14 08:57:17 时间
先感慨一下:不亲自去趟一趟自己的坑,永远无法理解用户的痛!!

使用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请求过程,和相关注意事项。

 OSS解析POST请求的大致流程如下图(仅供参考,与真实实现有差异):
ce31679379050932b9ff25b26374ea45fa4db84b
  请求的处理流程可以简单的理解为三个步骤:
      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、表格存储、日志存储与分析、归档存储及混合云存储等,充分满足用户数据存储和迁移上云需求,连续三年跻身全球云存储魔力象限四强。