How to simulate uploading a post-object form to OSS in Java

** Alibaba Cloud Object Storage Service (OSS) ** stores objects in resources called "buckets". Post Object uses an HTML form to upload the file to the specified bucket.

Preface

Alibaba Cloud Object Storage Service (OSS) is an easy-to-use service that allows you to store, back up, and archive large amounts of data in the cloud. OSS acts as an encrypted central repository, giving you secure access to files around the world. In addition, guaranteed up to 99.9% availability makes it ideal for global teams and international project management.

Alibaba Cloud Object Storage Service (OSS) securely stores objects in resources called "buckets". OSS gives you full access to the buckets and allows you to view the logs and objects in each bucket. You can read, write, delete, and save an unlimited number of objects in your bucket. The high performance of OSS supports multiple reads / writes at the same time. The data transfer to the bucket is over SSL and is encrypted.

Post Object uses an HTML form instead of Put to upload the file to the specified bucket. This allows you to upload files to your bucket via your browser. The desire to implement Post Object in Java comes from brief explanations from various support personnel. According to them, users who need this feature will face various difficult problems when trying to implement the feature according to the official documentation. This happens because there is no official code reference.

Procedure flow

Let's take a look at the steps the user should take.

--The official website first provides HTTP request syntax. The format of the HTTP request header and the multipart / form-data-encoded form field in the message body to satisfy the parameters. --Next, we will introduce each "required" form field such as "file" and "key" in the form field table. The user requests any one of the form fields such as "OSSAccessKeyId", "policy", and "Signature". You also need a REST request header, x-oss -meta- * user meta, and other optional form fields. --Posting this introduces some special uses and caveats for some form fields. It also shares a stack of information about Post Policy and Signature features and how to use them. ――The documentation is clear to some extent, but there are many concepts that are difficult to understand, and it is difficult to investigate the problem. In addition, there are no highlights about error-prone implementation, which presents two major challenges for users: Users can run into two major challenges, and two more aspects are involved.

--Unfamiliar with MIME type encodings like multipart / form-data. --Unfamiliar with OSS implementation rules for parsing Post Object requests.

Next, I will explain the two aspects mentioned above.

For a detailed introduction to multipart / form-data, see RFC 2388. There are a few things to note here. Let's discuss them one by one.

  1. At the first point, the "multipart / form-data" request contains a series of fields. Each field has a "form-data" type content-disposition header. This header also contains the parameter "name" for describing the contents of the form fields. Therefore, all fields have a format similar to the example shown in the documentation.

Content-Disposition: form-data; name=“your_key"

Note: Both ":" and ";" follow the space.

  1. The second point to note is that if you need to upload a user file to a form, you may need the filename and other file attributes in the Content-disposition header. In addition, there is an optional Content-Type attribute to identify the content type of the file for any MIME type value in the form field. Therefore, the documentation lists examples of "file" form fields as follows:
Content-Disposition: form-data; name="file"; filename="MyFilename.jpg "
Content-Type: image/jpeg

Note: The ";" before the "filename" still has a trailing space. Similarly, there is a trailing space in the ":" after the "Content-Type".

  1. The third point is to separate the data with boundaries. Let's use a complex border to distinguish it from the main content. This can be achieved in a way similar to the content of HTTP headers, as described in the documentation.

Content-Type: multipart/form-data; boundary=9431149156168

  1. The fourth point to note is that the structure of each form field is fixed. By design, each form field begins with a "-" boundary +, followed by a carriage return (/ r / n). This is followed by the form field description (see point 1), then / r / n. If the content you want to transfer is a file, the filename information also includes the carriage return (/ r / n) followed by the file content type (see point 2). In addition, there is another carriage return (/ r / n) to start the actual content, which must end with / r / n.

  2. Also note that the last form field ends with "-" + boundary + "-".

  3. In addition, the / r / n mark is needed to distinguish the HTTP request header from the body information (at the junction of the header and the first form field). This is basically an extra blank line like a document, which looks like this:

Content-Type: multipart/form-data; boundary=9431149156168

--9431149156168
Content-Disposition: form-data; name="key"

So far, we have provided a general description of the request syntax provided in the official OSS documentation and a related analysis by comparison with the RFC 2388 standard.

We'll delve into some of the caveats associated with some of the processing that the OSS system does to parse POST object requests.

The general procedure for OSS to parse a POST request is shown in the figure below for reference.

image.png

The request processing flow is summarized in three core steps.

  1. Parse HTTP request header boundaries to distinguish field boundaries.
  2. Parse the contents of various fields until the flow reaches the'file'form field.
  3. Parse the file'form field.

Therefore, the documentation emphasizes placing the'file'form field in the "last field". Otherwise, the form fields after "file" may have no effect. Placing the required form field "key" after "file" ensures that the result is an InvalidArgument.

Next, I will briefly explain the work flow as shown in the figure.

  1. Check the existence of POLICY, OSSACCESSKEYID, and SIGNATURE. This check is mandatory. If one of the three fields POLICY, OSSACCESSKEYID, and SIGNATURE appears, the other two fields are required.
  2. Existence of authority. Check the validity of the Post request based on the information of POLICY, OSSACCESSKEYID, and SIGNATURE.
  3. Policy rule check Check if the settings in the various form fields of the request comply with the policy settings.
  4. Check the legality of the length.

This is intended to check the length of the options field due to the limited overall length of the Post request body. 5) Parse the ParseContentType with ParseFile. Parses the ContentType field in the File field. This field is not required.

This concludes with Java code (Maven project) that implements Post Object upload in OSS for reference and use by those who are familiar with 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";  
Make sure that the file exists at the path indicated in the run code. 
    private String ossEndpoint = "your_endpoint";  
For example: http://oss-cn-shanghai.aliyuncs.com
    private String ossAccessId = "your_accessid";  This is your access AK
    private String ossAccessKey = "your_accesskey";  This is your access AK
    private String objectName = "your_object_name";  This is the object name after you upload the file
    private String bucket = "your_bucket";  Make sure that the bucket you created previously has been created. 

    private void PostObject() throws Exception {

        String filepath=postFileName;
        String urlStr = ossEndpoint.replace("http://", "http://"+bucket+".");  This is the URL for the submitted form is the bucket domain name

        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();

            // Read the returned data
            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("Error in sending a POST request:  " + 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();
    }

}

Please note that you need to add the following to pom.xml:

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>2.2.1</version>
</dependency>

Conclusion

Post Object allows you to upload files to your bucket based on your browser. Post Object message body encoding uses multipart / form-data. In Post Object operations, the program transfers parameters as form fields in the message body. The Post Object uses AccessKeySecret to calculate the signature of the policy. The POST form field is an option for uploading public read / write buckets, but we recommend that you use this field to limit POST requests.

Recommended Posts

How to simulate uploading a post-object form to OSS in Java
How to display a web page in Java
How to create a Java environment in just 3 seconds
How to create a data URI (base64) in Java
How to convert A to a and a to A using AND and OR in Java
How to convert a file to a byte array in Java
How to make a Java container
[Java] How to create a folder
How to name variables in Java
How to develop and register a Sota app in Java
How to make a Java array
How to concatenate strings in java
How to implement a job that uses Java API in JobScheduler
How to create a new Gradle + Java + Jar project in Intellij 2016.03
How to implement date calculation in Java
How to implement Kalman filter in Java
Multilingual Locale in Java How to use Locale
How to insert a video in Rails
How to do base conversion in Java
How to make a Discord bot (Java)
How to implement coding conventions in Java
How to embed Janus Graph in Java
How to print a Java Word document
How to publish a library in jCenter
How to test a private method in Java and partially mock that method
[Personal memo] How to interact with a random number generator in Java
How to pass a proxy when throwing REST over SSL in Java
How to get the absolute path of a directory running in Java
[Java] [For beginners] How to insert elements directly in a 2D array
Two ways to start a thread in Java + @
Code to escape a JSON string in Java
Try to create a bulletin board in Java
How to get Class from Element in Java
I wanted to make (a == 1 && a == 2 && a == 3) true in Java
How to run a djUnit task in Ant
How to add a classpath in Spring Boot
How to create a theme in Liferay 7 / DXP
How to implement a like feature in Rails
How to easily create a pull-down in Rails
[Java] How to substitute Model Mapper in Jackson
How to solve an Expression Problem in Java
How to make a follow function in Rails
How to write Java String # getBytes in Kotlin?
How to automatically generate a constructor in Eclipse
How to ZIP a JAVA CSV file and manage it in a Byte array
How to store data simultaneously in a model associated with a nested form (Rails 6.0.0)
How to clear all data in a particular table
How to call functions in bulk with Java reflection
[Java] How to omit the private constructor in Lombok
How to implement a like feature in Ajax in Rails
How to jump from Eclipse Java to a SQL file
How to input / output IBM mainframe files in Java?
java: How to write a generic type list [Note]
[Java] How to play rock-paper-scissors (equivalent to paiza rank A)
How to create a Spring Boot project in IntelliJ
[Java] How to get a request by HTTP communication
I tried to make a login function in Java
How to display a browser preview in VS Code
[How to insert a video in haml with Rails]
How to write a date comparison search in Rails
How to store Rakuten API data in a table