All Products
Search
Document Center

Object Storage Service:Unggah objek menggunakan URL yang ditandatangani sebelumnya (Java SDK V1)

Last Updated:Mar 21, 2026

Bucket OSS bersifat privat secara default—hanya Pemilik bucket yang dapat mengunggah objek. URL yang ditandatangani sebelumnya memungkinkan Anda mendelegasikan akses unggah kepada pihak ketiga tanpa membagikan kredensial Anda. Hasilkan URL tersebut dengan metode PUT dan waktu kedaluwarsa, lalu bagikan ke pihak ketiga. Pihak ketiga tersebut dapat menggunakannya untuk mengunggah objek langsung ke OSS hingga URL tersebut kedaluwarsa.

Perilaku utama:

  • URL tersebut dapat digunakan beberapa kali sebelum kedaluwarsa. Setiap unggahan akan menimpa objek sebelumnya.

  • Setelah URL kedaluwarsa, unggahan akan gagal. Hasilkan URL baru jika diperlukan.

  • Periode validitas maksimum untuk URL yang ditandatangani sebelumnya menggunakan Signature Version 4 (V4) adalah 7 hari. Jika Anda menghasilkan URL tersebut menggunakan kredensial temporary dari Security Token Service (STS), maka URL tersebut akan kedaluwarsa saat token STS kedaluwarsa—meskipun Anda menetapkan waktu kedaluwarsa yang lebih lama dalam kode Anda.

Prasyarat

Sebelum memulai, pastikan Anda telah memiliki:

Tidak diperlukan izin untuk menghasilkan URL yang ditandatangani sebelumnya itu sendiri. Namun, unggahan oleh pihak ketiga akan gagal kecuali akun yang menghasilkan URL tersebut memiliki izin oss:PutObject.

Cara kerja

Mengunggah objek dengan URL yang ditandatangani sebelumnya melibatkan dua pihak:

  1. Pemilik objek menghasilkan URL yang ditandatangani sebelumnya untuk permintaan PUT menggunakan Java SDK dan membagikannya kepada pengunggah.

  2. Pengunggah mengirim permintaan HTTP PUT ke URL tersebut—tanpa memerlukan kredensial OSS.

Diagram berikut menunjukkan alurnya.

image

Catatan penggunaan

  • Semua contoh menggunakan titik akhir publik untuk wilayah China (Hangzhou) (https://oss-cn-hangzhou.aliyuncs.com). Jika Anda mengakses OSS dari layanan Alibaba Cloud lainnya di wilayah yang sama, gunakan titik akhir internal sebagai gantinya. Untuk daftar wilayah dan titik akhir yang didukung, lihat Regions and endpoints.

  • Semua contoh membuat instans OSSClient menggunakan titik akhir OSS. Untuk membuat instans menggunakan domain kustom atau STS, lihat Configuration examples for common scenarios.

  • Semua contoh menggunakan URL yang ditandatangani sebelumnya versi V4. Untuk informasi latar belakang, lihat Signature Version 4 (recommended).

Unggah objek menggunakan URL yang ditandatangani sebelumnya

Langkah 1: Hasilkan URL yang ditandatangani sebelumnya (pemilik objek)

Gunakan Java SDK untuk menghasilkan URL yang ditandatangani sebelumnya untuk permintaan HTTP PUT.

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import java.net.URL;
import java.util.*;
import java.util.Date;

public class GetSignUrl {
    public static void main(String[] args) throws Throwable {
        // Titik akhir untuk wilayah China (Hangzhou). Ganti dengan titik akhir aktual Anda.
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // Baca kredensial akses dari variabel lingkungan.
        EnvironmentVariableCredentialsProvider credentialsProvider =
            CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // Ganti dengan nama bucket Anda, misalnya examplebucket.
        String bucketName = "examplebucket";
        // Ganti dengan jalur lengkap objek, misalnya exampleobject.txt.
        // Jangan sertakan nama bucket dalam jalur objek.
        String objectName = "exampleobject.txt";
        // Ganti dengan wilayah tempat bucket Anda berada, misalnya cn-hangzhou.
        String region = "cn-hangzhou";

        // Bangun instans OSSClient dengan Signature Version 4.
        // Panggil shutdown() saat klien tidak lagi diperlukan untuk melepaskan sumber daya.
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        URL signedUrl = null;
        try {
            // Tetapkan waktu kedaluwarsa. Contoh ini menetapkannya menjadi 1 jam dari sekarang.
            Date expiration = new Date(new Date().getTime() + 3600 * 1000L);

            // Bangun permintaan URL yang ditandatangani sebelumnya untuk HTTP PUT.
            GeneratePresignedUrlRequest request =
                new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
            request.setExpiration(expiration);

            // Hasilkan URL yang ditandatangani sebelumnya dan cetak.
            signedUrl = ossClient.generatePresignedUrl(request);
            System.out.println("Presigned URL for PUT: " + signedUrl);

        } catch (OSSException oe) {
            System.out.println("Kesalahan OSS — permintaan mencapai OSS tetapi ditolak.");
            System.out.println("Pesan kesalahan: " + oe.getErrorMessage());
            System.out.println("Kode kesalahan:    " + oe.getErrorCode());
            System.out.println("ID Permintaan:     " + oe.getRequestId());
            System.out.println("Host ID:           " + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Kesalahan klien — tidak dapat berkomunikasi dengan OSS.");
            System.out.println("Pesan kesalahan: " + ce.getMessage());
        }
    }
}

Langkah 2: Unggah file menggunakan URL yang ditandatangani sebelumnya (pengunggah)

Bagikan URL yang ditandatangani sebelumnya kepada pengunggah. Semua contoh di bawah mengirim permintaan HTTP PUT ke URL tersebut—tanpa memerlukan kredensial OSS.

curl

curl -X PUT -T /path/to/local/file \
  "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"

Java

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.io.*;
import java.net.URL;
import java.util.*;

public class SignUrlUpload {
    public static void main(String[] args) throws Throwable {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;

        URL signedUrl = new URL("<signedUrl>");
        String pathName = "C:\\Users\\demo.txt";

        // Header-header ini harus cocok dengan yang ditandatangani pada Langkah 1.
        Map<String, String> headers = new HashMap<String, String>();
        headers.put("x-oss-callback",
            "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9");
        headers.put("x-oss-callback-var",
            "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==");

        try {
            HttpPut put = new HttpPut(signedUrl.toString());
            HttpEntity entity = new FileEntity(new File(pathName));
            put.setEntity(entity);

            for (Map.Entry header : headers.entrySet()) {
                put.addHeader(header.getKey().toString(), header.getValue().toString());
            }

            httpClient = HttpClients.createDefault();
            response = httpClient.execute(put);

            System.out.println("status HTTP: " + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                System.out.println("Unggahan berhasil.");
            }
            System.out.println(response.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            response.close();
            httpClient.close();
        }
    }
}

Go

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func uploadFile(signedUrl, filePath string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return fmt.Errorf("tidak dapat membuka file: %w", err)
    }
    defer file.Close()

    client := &http.Client{}
    req, err := http.NewRequest("PUT", signedUrl, file)
    if err != nil {
        return fmt.Errorf("gagal membuat permintaan: %w", err)
    }

    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("gagal mengirim permintaan: %w", err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return fmt.Errorf("gagal membaca respons: %w", err)
    }

    fmt.Printf("Status HTTP: %d\n", resp.StatusCode)
    if resp.StatusCode == 200 {
        fmt.Println("Unggahan berhasil.")
    }
    fmt.Println(string(body))

    return nil
}

func main() {
    // Ganti <signedUrl> dengan URL yang ditandatangani sebelumnya dari Langkah 1.
    signedUrl := "<signedUrl>"

    // Ganti dengan jalur lengkap ke file lokal yang akan diunggah.
    filePath := "C:\\Users\\demo.txt"

    if err := uploadFile(signedUrl, filePath); err != nil {
        fmt.Println("Terjadi kesalahan:", err)
    }
}

Python

import requests

def upload_file(signed_url, file_path):
    try:
        with open(file_path, 'rb') as file:
            response = requests.put(signed_url, data=file)

        print(f"Status HTTP: {response.status_code}")
        if response.status_code == 200:
            print("Unggahan berhasil.")
        print(response.text)

    except Exception as e:
        print(f"Terjadi kesalahan: {e}")

if __name__ == "__main__":
    # Ganti <signedUrl> dengan URL yang ditandatangani sebelumnya dari Langkah 1.
    signed_url = "<signedUrl>"

    # Ganti dengan jalur lengkap ke file lokal yang akan diunggah.
    file_path = "C:\\Users\\demo.txt"

    upload_file(signed_url, file_path)

Node.js

const fs = require('fs');
const axios = require('axios');

async function uploadFile(signedUrl, filePath) {
    try {
        const fileStream = fs.createReadStream(filePath);

        const response = await axios.put(signedUrl, fileStream, {
            headers: {
                'Content-Type': 'application/octet-stream'
            }
        });

        console.log(`Status HTTP: ${response.status}`);
        if (response.status === 200) {
            console.log('Unggahan berhasil.');
        }
        console.log(response.data);
    } catch (error) {
        console.error(`Terjadi kesalahan: ${error.message}`);
    }
}

(async () => {
    // Ganti <signedUrl> dengan URL yang ditandatangani sebelumnya dari Langkah 1.
    const signedUrl = '<signedUrl>';

    // Ganti dengan jalur lengkap ke file lokal yang akan diunggah.
    const filePath = 'C:\\Users\\demo.txt';

    await uploadFile(signedUrl, filePath);
})();

Browser.js

Penting

Jika Anda mendapatkan kesalahan signature mismatch 403 saat mengunggah dari browser, biasanya karena browser secara otomatis menambahkan header permintaan Content-Type yang tidak disertakan saat Anda menghasilkan URL yang ditandatangani sebelumnya. Untuk memperbaikinya, tentukan Content-Type saat menghasilkan URL (lihat Unggah dengan header permintaan dan metadata pengguna).

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contoh unggah file</title>
</head>
<body>
    <h1>Contoh unggah file</h1>

    <input type="file" id="fileInput" />
    <button id="uploadButton">Unggah file</button>

    <script>
        // Ganti dengan URL yang ditandatangani sebelumnya dari Langkah 1.
        const signedUrl = "<signedUrl>";

        document.getElementById('uploadButton').addEventListener('click', async () => {
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];

            if (!file) {
                alert('Silakan pilih file untuk diunggah.');
                return;
            }

            try {
                await upload(file, signedUrl);
                alert('File berhasil diunggah!');
            } catch (error) {
                console.error('Kesalahan saat unggah:', error);
                alert('Unggahan gagal: ' + error.message);
            }
        });

        const upload = async (file, presignedUrl) => {
            const response = await fetch(presignedUrl, {
                method: 'PUT',
                body: file,
            });

            if (!response.ok) {
                throw new Error(`Unggahan gagal, status: ${response.status}`);
            }

            console.log('File berhasil diunggah.');
        };
    </script>
</body>
</html>

C#

using System.Net.Http.Headers;

// Ganti dengan jalur lengkap ke file lokal yang akan diunggah.
var filePath = "C:\\Users\\demo.txt";
// Ganti <signedUrl> dengan URL yang ditandatangani sebelumnya dari Langkah 1.
var presignedUrl = "<signedUrl>";

using var httpClient = new HttpClient();
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var content = new StreamContent(fileStream);

var request = new HttpRequestMessage(HttpMethod.Put, presignedUrl);
request.Content = content;

var response = await httpClient.SendAsync(request);

if (response.IsSuccessStatusCode)
{
    Console.WriteLine($"Unggahan berhasil. Status: {response.StatusCode}");
    Console.WriteLine("Header respons:");
    foreach (var header in response.Headers)
    {
        Console.WriteLine($"  {header.Key}: {string.Join(", ", header.Value)}");
    }
}
else
{
    string responseContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Unggahan gagal. Status: {response.StatusCode}");
    Console.WriteLine("Respons: " + responseContent);
}

C++

#include <iostream>
#include <fstream>
#include <curl/curl.h>

void uploadFile(const std::string& signedUrl, const std::string& filePath) {
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());
        curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);

        FILE *file = fopen(filePath.c_str(), "rb");
        if (!file) {
            std::cerr << "Tidak dapat membuka file: " << filePath << std::endl;
            return;
        }

        fseek(file, 0, SEEK_END);
        long fileSize = ftell(file);
        fseek(file, 0, SEEK_SET);

        curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);
        curl_easy_setopt(curl, CURLOPT_READDATA, file);

        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            std::cerr << "curl_easy_perform() gagal: " << curl_easy_strerror(res) << std::endl;
        } else {
            long httpCode = 0;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
            std::cout << "Status HTTP: " << httpCode << std::endl;
            if (httpCode == 200) {
                std::cout << "Unggahan berhasil." << std::endl;
            }
        }

        fclose(file);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
}

int main() {
    // Ganti <signedUrl> dengan URL yang ditandatangani sebelumnya dari Langkah 1.
    std::string signedUrl = "<signedUrl>";

    // Ganti dengan jalur lengkap ke file lokal yang akan diunggah.
    std::string filePath = "C:\\Users\\demo.txt";

    uploadFile(signedUrl, filePath);

    return 0;
}

Android

package com.example.signurlupload;

import android.os.AsyncTask;
import android.util.Log;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class SignUrlUploadActivity {

    private static final String TAG = "SignUrlUploadActivity";

    public void uploadFile(String signedUrl, String filePath) {
        new UploadTask().execute(signedUrl, filePath);
    }

    private class UploadTask extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {
            String signedUrl = params[0];
            String filePath = params[1];

            HttpURLConnection connection = null;
            DataOutputStream dos = null;
            FileInputStream fis = null;

            try {
                URL url = new URL(signedUrl);
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("PUT");
                connection.setDoOutput(true);
                connection.setRequestProperty("Content-Type", "application/octet-stream");

                fis = new FileInputStream(filePath);
                dos = new DataOutputStream(connection.getOutputStream());

                byte[] buffer = new byte[1024];
                int length;
                while ((length = fis.read(buffer)) != -1) {
                    dos.write(buffer, 0, length);
                }

                dos.flush();
                dos.close();
                fis.close();

                int responseCode = connection.getResponseCode();
                Log.d(TAG, "Status HTTP: " + responseCode);
                if (responseCode == 200) {
                    Log.d(TAG, "Unggahan berhasil.");
                }

                return "Unggahan selesai. Status: " + responseCode;

            } catch (IOException e) {
                e.printStackTrace();
                return "Unggahan gagal: " + e.getMessage();
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }

        @Override
        protected void onPostExecute(String result) {
            Log.d(TAG, result);
        }
    }

    public static void main(String[] args) {
        SignUrlUploadActivity activity = new SignUrlUploadActivity();
        // Ganti <signedUrl> dengan URL yang ditandatangani sebelumnya dari Langkah 1.
        String signedUrl = "<signedUrl>";
        // Ganti dengan jalur lengkap ke file lokal yang akan diunggah.
        String filePath = "C:\\Users\\demo.txt";
        activity.uploadFile(signedUrl, filePath);
    }
}

Skenario lainnya

Gunakan URL yang ditandatangani sebelumnya untuk mengunggah dengan header permintaan dan metadata pengguna

Sertakan header permintaan (seperti kelas penyimpanan dan Content-Type) serta metadata pengguna dalam URL yang ditandatangani sebelumnya. Pengunggah harus mengirim header yang sama saat mengunggah—OSS memvalidasinya terhadap parameter yang ditandatangani dan mengembalikan kesalahan tanda tangan jika berbeda.

Langkah 1: Hasilkan URL yang ditandatangani sebelumnya

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.StorageClass;

import java.net.URL;
import java.util.*;
import java.util.Date;

public class GetSignUrl {
    public static void main(String[] args) throws Throwable {
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        EnvironmentVariableCredentialsProvider credentialsProvider =
            CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        String bucketName = "examplebucket";
        String objectName = "exampleobject.txt";
        String region = "cn-hangzhou";

        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        // Definisikan header permintaan yang akan ditandatangani.
        Map<String, String> headers = new HashMap<String, String>();
        headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
        headers.put(OSSHeaders.CONTENT_TYPE, "text/plain; charset=utf8");

        // Definisikan metadata pengguna yang akan ditandatangani.
        // SDK secara otomatis menambahkan awalan x-oss-meta-.
        Map<String, String> userMetadata = new HashMap<String, String>();
        userMetadata.put("key1", "value1");
        userMetadata.put("key2", "value2");

        URL signedUrl = null;
        try {
            Date expiration = new Date(new Date().getTime() + 3600 * 1000L);

            GeneratePresignedUrlRequest request =
                new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
            request.setExpiration(expiration);
            request.setHeaders(headers);
            request.setUserMetadata(userMetadata);

            signedUrl = ossClient.generatePresignedUrl(request);
            System.out.println("Presigned URL for PUT: " + signedUrl);

        } catch (OSSException oe) {
            System.out.println("Kesalahan OSS: " + oe.getErrorMessage());
            System.out.println("Kode kesalahan: " + oe.getErrorCode());
            System.out.println("ID Permintaan: " + oe.getRequestId());
        } catch (ClientException ce) {
            System.out.println("Kesalahan klien: " + ce.getMessage());
        }
    }
}

Langkah 2: Unggah file dengan header yang sesuai

Semua contoh di bawah menetapkan header yang sama seperti yang ditandatangani pada Langkah 1. Menghilangkan atau mengubah header yang ditandatangani mana pun akan menyebabkan kesalahan tanda tangan.

curl -X PUT \
     -H "Content-Type: text/plain;charset=utf8" \
     -H "x-oss-storage-class: Standard" \
     -H "x-oss-meta-key1: value1" \
     -H "x-oss-meta-key2: value2" \
     -T "C:\\Users\\demo.txt" \
     "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.StorageClass;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.io.*;
import java.net.URL;
import java.util.*;

public class SignUrlUpload {
    public static void main(String[] args) throws Throwable {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;

        URL signedUrl = new URL("<signedUrl>");
        String pathName = "C:\\Users\\demo.txt";

        // Header ini harus persis sesuai dengan yang ditandatangani di Langkah 1.
        Map<String, String> headers = new HashMap<String, String>();
        headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
        headers.put(OSSHeaders.CONTENT_TYPE, "text/plain;charset=utf8");

        // Entri metadata ini harus persis sesuai dengan yang ditandatangani di Langkah 1.
        Map<String, String> userMetadata = new HashMap<String, String>();
        userMetadata.put("key1", "value1");
        userMetadata.put("key2", "value2");

        try {
            HttpPut put = new HttpPut(signedUrl.toString());
            HttpEntity entity = new FileEntity(new File(pathName));
            put.setEntity(entity);

            for (Map.Entry header : headers.entrySet()) {
                put.addHeader(header.getKey().toString(), header.getValue().toString());
            }
            for (Map.Entry meta : userMetadata.entrySet()) {
                // Tambahkan awalan x-oss-meta- saat menyetel metadata pengguna langsung melalui HTTP.
                put.addHeader("x-oss-meta-" + meta.getKey().toString(), meta.getValue().toString());
            }

            httpClient = HttpClients.createDefault();
            response = httpClient.execute(put);

            System.out.println("Status HTTP: " + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                System.out.println("Unggahan berhasil.");
            }
            System.out.println(response.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            response.close();
            httpClient.close();
        }
    }
}
package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
)

func uploadFile(signedUrl string, filePath string, headers map[string]string, metadata map[string]string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()

    fileBytes, err := ioutil.ReadAll(file)
    if err != nil {
        return err
    }

    req, err := http.NewRequest("PUT", signedUrl, bytes.NewBuffer(fileBytes))
    if err != nil {
        return err
    }

    // Tetapkan header permintaan — harus sesuai dengan yang ditandatangani di Langkah 1.
    for key, value := range headers {
        req.Header.Set(key, value)
    }
    // Tetapkan metadata pengguna — harus sesuai dengan yang ditandatangani di Langkah 1.
    for key, value := range metadata {
        req.Header.Set(fmt.Sprintf("x-oss-meta-%s", key), value)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    fmt.Printf("Status HTTP: %d\n", resp.StatusCode)
    if resp.StatusCode == 200 {
        fmt.Println("Unggahan berhasil.")
    } else {
        fmt.Println("Unggahan gagal.")
    }
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))

    return nil
}

func main() {
    signedUrl := "<signedUrl>"
    filePath := "C:\\Users\\demo.txt"

    headers := map[string]string{
        "Content-Type":        "text/plain;charset=utf8",
        "x-oss-storage-class": "Standard",
    }

    metadata := map[string]string{
        "key1": "value1",
        "key2": "value2",
    }

    if err := uploadFile(signedUrl, filePath, headers, metadata); err != nil {
        fmt.Printf("Terjadi kesalahan: %v\n", err)
    }
}
import requests

def upload_file(signed_url, file_path, headers=None, metadata=None):
    if not headers:
        headers = {}
    if not metadata:
        metadata = {}

    # Gabungkan metadata pengguna ke dalam header dengan awalan x-oss-meta-.
    for key, value in metadata.items():
        headers[f'x-oss-meta-{key}'] = value

    try:
        with open(file_path, 'rb') as file:
            response = requests.put(signed_url, data=file, headers=headers)
            print(f"Status HTTP: {response.status_code}")
            if response.status_code == 200:
                print("Unggahan berhasil.")
            else:
                print("Unggahan gagal.")
            print(response.text)
    except Exception as e:
        print(f"Terjadi kesalahan: {e}")

if __name__ == "__main__":
    signed_url = "<signedUrl>"
    file_path = "C:\\Users\\demo.txt"

    # Harus sesuai dengan yang ditandatangani di Langkah 1.
    headers = {
        "Content-Type": "text/plain;charset=utf8",
        "x-oss-storage-class": "Standard"
    }
    metadata = {
        "key1": "value1",
        "key2": "value2"
    }

    upload_file(signed_url, file_path, headers, metadata)
Node.js
const fs = require('fs');
const axios = require('axios');

async function uploadFile(signedUrl, filePath, headers = {}, metadata = {}) {
    try {
        // Gabungkan metadata pengguna ke dalam header dengan awalan x-oss-meta-.
        for (const [key, value] of Object.entries(metadata)) {
            headers[`x-oss-meta-${key}`] = value;
        }

        const fileStream = fs.createReadStream(filePath);

        const response = await axios.put(signedUrl, fileStream, { headers });

        console.log(`Status HTTP: ${response.status}`);
        if (response.status === 200) {
            console.log('Unggahan berhasil.');
        } else {
            console.log('Unggahan gagal.');
        }
        console.log(response.data);
    } catch (error) {
        console.error(`Terjadi kesalahan: ${error.message}`);
    }
}

(async () => {
    const signedUrl = '<signedUrl>';
    const filePath = 'C:\\Users\\demo.txt';

    // Harus sesuai dengan yang ditandatangani di Langkah 1.
    const headers = {
        'Content-Type': 'text/plain;charset=utf8',
        'x-oss-storage-class': 'Standard'
    };
    const metadata = {
        'key1': 'value1',
        'key2': 'value2'
    };

    await uploadFile(signedUrl, filePath, headers, metadata);
})();
Browser.js
Penting

Jika Anda mendapatkan kesalahan signature mismatch 403 saat mengunggah dari browser, biasanya karena browser secara otomatis menambahkan header permintaan Content-Type yang tidak disertakan saat Anda menghasilkan URL yang ditandatangani sebelumnya. Untuk memperbaikinya, tentukan Content-Type saat menghasilkan URL (seperti yang ditunjukkan pada Langkah 1 di atas).

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contoh unggah file (Java SDK)</title>
</head>
<body>
    <h1>Contoh unggah file (Java SDK)</h1>

    <input type="file" id="fileInput" />
    <button id="uploadButton">Unggah file</button>

    <script>
        // Ganti dengan URL yang ditandatangani sebelumnya dari Langkah 1.
        const signedUrl = "<signedUrl>";

        document.getElementById('uploadButton').addEventListener('click', async () => {
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];

            if (file) {
                try {
                    await upload(file, signedUrl);
                } catch (error) {
                    console.error('Kesalahan saat unggah:', error);
                    alert('Unggahan gagal: ' + error.message);
                }
            } else {
                alert('Silakan pilih file untuk diunggah.');
            }
        });

        const upload = async (file, presignedUrl) => {
            // Header ini harus sesuai dengan yang ditandatangani di Langkah 1.
            const headers = {
                'Content-Type': 'text/plain;charset=utf8',
                'x-oss-storage-class': 'Standard',
                'x-oss-meta-key1': 'value1',
                'x-oss-meta-key2': 'value2'
            };

            const response = await fetch(presignedUrl, {
                method: 'PUT',
                headers: headers,
                body: file
            });

            if (!response.ok) {
                throw new Error(`Unggahan gagal, status: ${response.status}`);
            }

            alert('File berhasil diunggah.');
            console.log('File berhasil diunggah.');
        };
    </script>
</body>
</html>
C#
using System.Net.Http.Headers;

var filePath = "C:\\Users\\demo.txt";
var presignedUrl = "<signedUrl>";

using var httpClient = new HttpClient();
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var content = new StreamContent(fileStream);

var request = new HttpRequestMessage(HttpMethod.Put, presignedUrl);
request.Content = content;

// Header ini harus sesuai dengan yang ditandatangani di Langkah 1.
request.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain") { CharSet = "utf8" };
request.Content.Headers.Add("x-oss-meta-key1", "value1");
request.Content.Headers.Add("x-oss-meta-key2", "value2");
request.Content.Headers.Add("x-oss-storage-class", "Standard");

Console.WriteLine("Header permintaan:");
foreach (var header in request.Content.Headers)
{
    Console.WriteLine($"  {header.Key}: {string.Join(", ", header.Value)}");
}

var response = await httpClient.SendAsync(request);

if (response.IsSuccessStatusCode)
{
    Console.WriteLine($"Unggahan berhasil. Status: {response.StatusCode}");
}
else
{
    string responseContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Unggahan gagal. Status: {response.StatusCode}");
    Console.WriteLine("Respons: " + responseContent);
}
C++
#include <iostream>
#include <fstream>
#include <curl/curl.h>
#include <map>
#include <string>

size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
    size_t totalSize = size * nmemb;
    output->append((char*)contents, totalSize);
    return totalSize;
}

void uploadFile(const std::string& signedUrl, const std::string& filePath,
                const std::map<std::string, std::string>& headers,
                const std::map<std::string, std::string>& metadata) {
    CURL* curl;
    CURLcode res;
    std::string readBuffer;

    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());
        curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);

        FILE* file = fopen(filePath.c_str(), "rb");
        if (!file) {
            std::cerr << "Tidak dapat membuka file: " << filePath << std::endl;
            return;
        }

        fseek(file, 0, SEEK_END);
        long fileSize = ftell(file);
        rewind(file);

        curl_easy_setopt(curl, CURLOPT_READDATA, file);
        curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);

        // Bangun daftar header — harus sesuai dengan yang ditandatangani di Langkah 1.
        struct curl_slist* chunk = nullptr;
        for (const auto& header : headers) {
            std::string headerStr = header.first + ": " + header.second;
            chunk = curl_slist_append(chunk, headerStr.c_str());
        }
        for (const auto& meta : metadata) {
            std::string metaStr = "x-oss-meta-" + meta.first + ": " + meta.second;
            chunk = curl_slist_append(chunk, metaStr.c_str());
        }
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);

        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);

        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            std::cerr << "curl_easy_perform() gagal: " << curl_easy_strerror(res) << std::endl;
        } else {
            long responseCode;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
            std::cout << "Status HTTP: " << responseCode << std::endl;
            if (responseCode == 200) {
                std::cout << "Unggahan berhasil." << std::endl;
            } else {
                std::cout << "Unggahan gagal." << std::endl;
            }
            std::cout << readBuffer << std::endl;
        }

        fclose(file);
        curl_slist_free_all(chunk);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
}

int main() {
    std::string signedUrl = "<signedUrl>";
    std::string filePath = "C:\\Users\\demo.txt";

    // Harus sesuai dengan yang ditandatangani di Langkah 1.
    std::map<std::string, std::string> headers = {
        {"Content-Type", "text/plain;charset=utf8"},
        {"x-oss-storage-class", "Standard"}
    };
    std::map<std::string, std::string> metadata = {
        {"key1", "value1"},
        {"key2", "value2"}
    };

    uploadFile(signedUrl, filePath, headers, metadata);

    return 0;
}
Android
import android.os.AsyncTask;
import android.util.Log;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class SignUrlUploadActivity extends AppCompatActivity {

    private static final String TAG = "SignUrlUploadActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String signedUrl = "<signedUrl>";
        String pathName = "/storage/emulated/0/demo.txt";

        // Harus sesuai dengan yang ditandatangani di Langkah 1.
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "text/plain;charset=utf8");
        headers.put("x-oss-storage-class", "Standard");

        Map<String, String> userMetadata = new HashMap<>();
        userMetadata.put("key1", "value1");
        userMetadata.put("key2", "value2");

        new UploadTask().execute(signedUrl, pathName, headers, userMetadata);
    }

    private class UploadTask extends AsyncTask<Object, Void, Integer> {
        @Override
        protected Integer doInBackground(Object... params) {
            String signedUrl = (String) params[0];
            String pathName = (String) params[1];
            Map<String, String> headers = (Map<String, String>) params[2];
            Map<String, String> userMetadata = (Map<String, String>) params[3];

            try {
                URL url = new URL(signedUrl);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("PUT");
                connection.setDoOutput(true);
                connection.setUseCaches(false);

                for (Entry<String, String> header : headers.entrySet()) {
                    connection.setRequestProperty(header.getKey(), header.getValue());
                }
                for (Entry<String, String> meta : userMetadata.entrySet()) {
                    connection.setRequestProperty("x-oss-meta-" + meta.getKey(), meta.getValue());
                }

                File file = new File(pathName);
                FileInputStream fileInputStream = new FileInputStream(file);
                DataOutputStream dos = new DataOutputStream(connection.getOutputStream());

                byte[] buffer = new byte[1024];
                int count;
                while ((count = fileInputStream.read(buffer)) != -1) {
                    dos.write(buffer, 0, count);
                }

                fileInputStream.close();
                dos.flush();
                dos.close();

                int responseCode = connection.getResponseCode();
                Log.d(TAG, "Status HTTP: " + responseCode);
                if (responseCode == 200) {
                    Log.d(TAG, "Unggahan berhasil.");
                } else {
                    Log.d(TAG, "Unggahan gagal.");
                }

                InputStream is = connection.getInputStream();
                byte[] responseBuffer = new byte[1024];
                StringBuilder sb = new StringBuilder();
                while ((count = is.read(responseBuffer)) != -1) {
                    sb.append(new String(responseBuffer, 0, count));
                }
                Log.d(TAG, sb.toString());

                return responseCode;
            } catch (IOException e) {
                e.printStackTrace();
                return -1;
            }
        }

        @Override
        protected void onPostExecute(Integer result) {
            super.onPostExecute(result);
            if (result == 200) {
                Toast.makeText(SignUrlUploadActivity.this, "Unggahan berhasil.", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(SignUrlUploadActivity.this, "Unggahan gagal.", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Gunakan URL yang ditandatangani sebelumnya untuk unggah multi-bagian

Untuk file besar, hasilkan URL yang ditandatangani sebelumnya terpisah untuk setiap bagian. Setiap URL bagian mencakup parameter kueri partNumber dan uploadId. Setelah semua bagian diunggah, gabungkan menggunakan completeMultipartUpload.

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.common.utils.CRC64;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.model.*;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.Date;

public class MultipartUrl {
    public static void main(String[] args) throws Throwable {
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        EnvironmentVariableCredentialsProvider credentialsProvider =
            CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        String bucketName = "examplebucket";
        String objectName = "exampleobject.txt";
        // Ganti dengan jalur lengkap ke file lokal yang akan diunggah.
        String pathName = "D:\\localpath\\examplefile.txt";
        // Waktu kedaluwarsa untuk URL yang ditandatangani sebelumnya setiap bagian, dalam milidetik.
        long expireTime = 3600 * 1000L;
        String region = "cn-hangzhou";

        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        // Inisialisasi unggah multi-bagian.
        InitiateMultipartUploadRequest initRequest =
            new InitiateMultipartUploadRequest(bucketName, objectName);
        ObjectMetadata metadata = new ObjectMetadata();
        // Tetapkan Content-Type berdasarkan ekstensi file. Default ke application/octet-stream.
        if (metadata.getContentType() == null) {
            metadata.setContentType(
                Mimetypes.getInstance().getMimetype(new File(pathName), objectName));
        }
        initRequest.setObjectMetadata(metadata);

        InitiateMultipartUploadResult upResult = ossClient.initiateMultipartUpload(initRequest);
        // ID unggahan mengidentifikasi sesi unggah multi-bagian ini.
        // Gunakan untuk membatalkan, menanyakan, atau menyelesaikan unggahan.
        String uploadId = upResult.getUploadId();

        List<PartETag> partETags = new ArrayList<PartETag>();
        long partSize = 1 * 100 * 1024L;   // 100 KB per bagian

        File sampleFile = new File(pathName);
        long fileLength = sampleFile.length();
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }

        Map<String, String> headers = new HashMap<String, String>();
        // Hapus komentar untuk menetapkan Content-Type untuk setiap bagian:
        // headers.put(OSSHeaders.CONTENT_TYPE, "text/plain");

        // Hasilkan URL yang ditandatangani sebelumnya untuk setiap bagian dan unggah secara berurutan.
        for (int i = 0; i < partCount; i++) {
            long startPos = i * partSize;
            long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;

            String signUrl = getSignUrl(ossClient, bucketName, objectName,
                HttpMethod.PUT, expireTime, i + 1, uploadId, headers);

            putObjectWithHttp(signUrl, pathName, startPos, curPartSize, headers);
        }

        // Daftar bagian yang diunggah (diperlukan jika penggabungan dilakukan dari proses berbeda).
        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId);
        PartListing partListing = ossClient.listParts(listPartsRequest);

        for (PartSummary part : partListing.getParts()) {
            partETags.add(new PartETag(part.getPartNumber(), part.getETag()));
        }

        // Gabungkan bagian untuk menyelesaikan unggahan.
        CompleteMultipartUploadRequest completeRequest =
            new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
        CompleteMultipartUploadResult completeResult =
            ossClient.completeMultipartUpload(completeRequest);
        System.out.println("Unggah multi-bagian selesai.");

        // Verifikasi integritas file menggunakan CRC-64.
        CRC64 crc = new CRC64();
        InputStream inStream = new FileInputStream(pathName);
        byte[] bytes = new byte[1024];
        int cnt;
        while ((cnt = inStream.read(bytes)) != -1) {
            crc.update(bytes, 0, cnt);
        }

        if (crc.getValue() == completeResult.getServerCRC()) {
            System.out.println("Integritas file terverifikasi.");
        } else {
            System.out.println("Pemeriksaan integritas file gagal. Tangani pengecualian ini.");
        }
    }

    public static void putObjectWithHttp(String signedUrl, String pathName,
                                          long startPos, long partSize,
                                          Map<String, String> headers) throws IOException {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        try {
            HttpPut put = new HttpPut(signedUrl);

            FileInputStream inStream = new FileInputStream(pathName);
            inStream.skip(startPos);
            InputStreamEntity entity = new InputStreamEntity(inStream, partSize);
            BufferedHttpEntity byteArrayEntity = new BufferedHttpEntity(entity);
            put.setEntity(byteArrayEntity);

            for (Map.Entry header : headers.entrySet()) {
                put.addHeader(header.getKey().toString(), header.getValue().toString());
            }

            // Coba ulang hingga 3 kali pada kegagalan sementara.
            httpClient = HttpClients.custom()
                .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
                .build();

            response = httpClient.execute(put);

            System.out.println("Status unggah bagian: " + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                System.out.println("Bagian berhasil diunggah.");
            }
            System.out.println(response.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (response != null) response.close();
            if (httpClient != null) httpClient.close();
        }
    }

    public static String getSignUrl(OSS ossClient, String bucketName, String objectName,
                                     HttpMethod method, long expireTime, int partNum,
                                     String uploadId, Map<String, String> headers) {
        Date expiration = new Date(new Date().getTime() + expireTime);

        GeneratePresignedUrlRequest request =
            new GeneratePresignedUrlRequest(bucketName, objectName, method);
        request.setExpiration(expiration);
        request.setHeaders(headers);
        // Sisipkan nomor bagian dan ID unggahan ke URL sebagai parameter kueri.
        request.addQueryParameter("partNumber", String.valueOf(partNum));
        request.addQueryParameter("uploadId", uploadId);

        URL signedUrl = ossClient.generatePresignedUrl(request);
        System.out.println("URL yang ditandatangani sebelumnya untuk bagian: " + signedUrl);
        return signedUrl.toString();
    }
}

Gunakan URL yang ditandatangani sebelumnya dengan callback unggahan

Untuk memicu callback sisi server setelah unggahan berhasil, sertakan parameter callback dalam URL yang ditandatangani sebelumnya. Isi callback dan variabelnya dikodekan Base64 dan diteruskan sebagai header permintaan.

Langkah 1: Hasilkan URL yang ditandatangani sebelumnya dengan parameter callback

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;

import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;

public class OssPresignExample {
    public static void main(String[] args) throws Throwable {
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        EnvironmentVariableCredentialsProvider credentialsProvider =
            CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        String bucketName = "examplebucket";
        String objectName = "exampleobject.txt";
        String region = "cn-hangzhou";

        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        URL signedUrl = null;
        try {
            // Bangun isi callback. Ganti URL dan variabel dengan nilai Anda.
            String callbackUrl = "http://www.example.com/callback";
            String callbackBody = "{\"callbackUrl\":\"" + callbackUrl
                + "\",\"callbackBody\":\"bucket=${bucket}&object=${object}"
                + "&my_var_1=${x:var1}&my_var_2=${x:var2}\"}";
            String callbackBase64 = Base64.getEncoder().encodeToString(callbackBody.getBytes());

            String callbackVarJson = "{\"x:var1\":\"value1\",\"x:var2\":\"value2\"}";
            String callbackVarBase64 = Base64.getEncoder().encodeToString(callbackVarJson.getBytes());

            // Tambahkan header callback ke permintaan yang ditandatangani.
            Map<String, String> headers = new HashMap<String, String>();
            headers.put(OSSHeaders.OSS_HEADER_CALLBACK, callbackBase64);
            headers.put(OSSHeaders.OSS_HEADER_CALLBACK_VAR, callbackVarBase64);

            Date expiration = new Date(new Date().getTime() + 3600 * 1000);

            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            String expirationStr = dateFormat.format(expiration);

            GeneratePresignedUrlRequest request =
                new GeneratePresignedUrlRequest(bucketName, objectName);
            request.setMethod(HttpMethod.PUT);
            request.setExpiration(expiration);
            request.setHeaders(headers);

            System.out.println("callback:     " + callbackBase64);
            System.out.println("callback-var: " + callbackVarBase64);

            URL url = ossClient.generatePresignedUrl(request);
            System.out.println("method:     PUT");
            System.out.println("expiration: " + expirationStr);
            System.out.println("url:        " + url);

        } catch (OSSException oe) {
            System.out.println("Kesalahan OSS: " + oe.getErrorMessage());
            System.out.println("Kode kesalahan: " + oe.getErrorCode());
            System.out.println("ID Permintaan: " + oe.getRequestId());
        } catch (ClientException ce) {
            System.out.println("Kesalahan klien: " + ce.getMessage());
        }
    }
}

Langkah 2: Unggah file dengan header callback

Semua contoh di bawah meneruskan header x-oss-callback dan x-oss-callback-var, yang harus sesuai dengan nilai yang ditandatangani di Langkah 1.

curl
curl -X PUT \
     -H "x-oss-callback: eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9" \
     -H "x-oss-callback-var: eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==" \
     -T "C:\\Users\\demo.txt" \
     "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"
Python
import requests

def upload_file(signed_url, file_path, headers=None):
    if not headers:
        headers = {}

    try:
        with open(file_path, 'rb') as file:
            response = requests.put(signed_url, data=file, headers=headers)
            print(f"Status HTTP: {response.status_code}")
            if response.status_code == 200:
                print("Unggahan berhasil.")
            else:
                print("Unggahan gagal.")
            print(response.text)
    except Exception as e:
        print(f"Terjadi kesalahan: {e}")

if __name__ == "__main__":
    signed_url = "<signedUrl>"
    file_path = "C:\\Users\\demo.txt"

    headers = {
        "x-oss-callback": "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
        "x-oss-callback-var": "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
    }

    upload_file(signed_url, file_path, headers)
Go
package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
)

func uploadFile(signedUrl string, filePath string, headers map[string]string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()

    fileBytes, err := io.ReadAll(file)
    if err != nil {
        return err
    }

    req, err := http.NewRequest("PUT", signedUrl, bytes.NewBuffer(fileBytes))
    if err != nil {
        return err
    }

    for key, value := range headers {
        req.Header.Add(key, value)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    fmt.Printf("Status HTTP: %d\n", resp.StatusCode)
    if resp.StatusCode == 200 {
        fmt.Println("Unggahan berhasil.")
    } else {
        fmt.Println("Unggahan gagal.")
    }
    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))

    return nil
}

func main() {
    signedUrl := "<signedUrl>"
    filePath := "C:\\Users\\demo.txt"

    headers := map[string]string{
        "x-oss-callback":     "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
        "x-oss-callback-var": "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
    }

    if err := uploadFile(signedUrl, filePath, headers); err != nil {
        fmt.Printf("Terjadi kesalahan: %v\n", err)
    }
}
Java
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.io.*;
import java.net.URL;
import java.util.*;

public class SignUrlUpload {
    public static void main(String[] args) throws Throwable {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;

        URL signedUrl = new URL("<signedUrl>");
        String pathName = "C:\\Users\\demo.txt";

        // Header ini harus sesuai dengan yang ditandatangani di Langkah 1.
        Map<String, String> headers = new HashMap<String, String>();
        headers.put("x-oss-callback",
            "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9");
        headers.put("x-oss-callback-var",
            "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==");

        try {
            HttpPut put = new HttpPut(signedUrl.toString());
            HttpEntity entity = new FileEntity(new File(pathName));
            put.setEntity(entity);

            for (Map.Entry header : headers.entrySet()) {
                put.addHeader(header.getKey().toString(), header.getValue().toString());
            }

            httpClient = HttpClients.createDefault();
            response = httpClient.execute(put);

            System.out.println("Status HTTP: " + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                System.out.println("Unggahan berhasil.");
            }
            System.out.println(response.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            response.close();
            httpClient.close();
        }
    }
}
PHP
<?php

function uploadFile($signedUrl, $filePath, $headers = []) {
    if (!file_exists($filePath)) {
        echo "File tidak ditemukan: $filePath\n";
        return;
    }

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $signedUrl);
    curl_setopt($ch, CURLOPT_PUT, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_INFILE, fopen($filePath, 'rb'));
    curl_setopt($ch, CURLOPT_INFILESIZE, filesize($filePath));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function($key, $value) {
        return "$key: $value";
    }, array_keys($headers), $headers));

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    echo "Status HTTP: $httpCode\n";
    if ($httpCode == 200) {
        echo "Unggahan berhasil.\n";
    } else {
        echo "Unggahan gagal.\n";
    }
    echo $response . "\n";
}

$signedUrl = "<signedUrl>";
$filePath = "C:\\Users\\demo.txt";

$headers = [
    "x-oss-callback" => "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
    "x-oss-callback-var" => "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
];

uploadFile($signedUrl, $filePath, $headers);

?>

FAQ

Jika URL yang ditandatangani sebelumnya kedaluwarsa di tengah unggahan, apakah unggahan tersebut gagal?

Tidak. Unggahan berhasil selama masih dalam periode validitas URL. Periode validitas efektif adalah yang lebih pendek antara waktu kedaluwarsa URL yang ditandatangani sebelumnya dan periode validitas token STS (jika URL tersebut dihasilkan menggunakan kredensial temporary STS).

Apakah saya perlu menetapkan header permintaan dan metadata pengguna saat mengunggah jika saya tidak menetapkannya saat menghasilkan URL?

Tidak. Header permintaan dan metadata pengguna bersifat opsional. Jika Anda tidak menyertakannya saat menghasilkan URL, hilangkan kode terkait dari permintaan unggahan Anda.

Langkah selanjutnya

  • Untuk kode contoh lengkap mengenai pembuatan URL yang ditandatangani sebelumnya versi V4, lihat contoh GitHub.

  • Untuk referensi API GeneratePresignedUrlRequest, lihat Javadoc.