使用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地址或者个人博客,将会大大加分哦,一起在邮件中发给我吧~~~