Upload a file using a signed URL (Harmony SDK)

更新时间:
复制 MD 格式

OSS buckets are private by default. To let another party upload files to your bucket without sharing your credentials, generate a signed URL using the OSS Harmony SDK. The signed URL embeds a time-limited signature that grants PUT access to a specific object.

Usage notes

  • The signed URL can be used multiple times before it expires. If multiple upload operations are performed using the same URL, the file may be overwritten.

  • Signature V4 URLs are valid for up to 7 days.

  • Once a URL expires, it cannot be reused. Generate a new signed URL.

  • To generate a signed URL for a PUT request, the credential owner must have the oss:PutObject permission. The recipient of the URL does not need any OSS permissions to perform the upload.

  • If you specify request headers when generating the signed URL, the PUT request must include the exact same headers. Mismatched headers cause a signature mismatch error.

  • For supported regions and endpoints, see Access OSS through bucket domain names.

  • For more information about Signature V4, see Signature V4 (Recommended).

How it works

The file owner uses the Harmony SDK to generate a signed URL for a PUT request. The URL encodes the bucket, object key, expiration time, and signature — computed locally from the access credentials, without a network call to OSS. The recipient uses the URL to upload a file directly to OSS using any HTTP client.

Signed URL upload flow

Prerequisites

Before you begin, ensure that you have:

  • An OSS bucket

  • STS temporary access credentials (accessKeyId, accessKeySecret, securityToken) for a RAM user with the oss:PutObject permission. See Grant custom access policies to RAM users.

  • The Harmony SDK (@aliyun/oss) installed

Upload a file using a signed URL

Step 1: Generate a signed URL

Use the Harmony SDK to generate a signed URL for a PUT request.

Note

The SDK computes the signature locally using your access credentials. No network request is made to OSS when generating the URL.

import Client, { EHttpMethod } from '@aliyun/oss';

// Create an OSS client instance.
const client = new Client({
  // Replace with the AccessKeyId of your STS temporary access credential.
  accessKeyId: 'yourAccessKeyId',
  // Replace with the AccessKeySecret of your STS temporary access credential.
  accessKeySecret: 'yourAccessKeySecret',
  // Replace with the SecurityToken of your STS temporary access credential.
  securityToken: 'yourSecurityToken',
  // Specify the region where the bucket is located. Example: oss-cn-hangzhou.
  region: 'oss-cn-hangzhou',
});

// Specify the bucket name and object key.
const bucket = 'yourBucketName';
const key = 'yourObjectName';

const generateSignedUrl = async () => {
  // If no signature version is explicitly set, the version from Client.options.signVersion is used.
  const url = await client.signatureUrl({
    method: EHttpMethod.PUT,
    bucket,
    key,
    expires: 60 * 60, // Expiration time in seconds (60 minutes).
  });

  console.log(url);
};

generateSignedUrl();

The generated URL looks like:

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******************************************************

Step 2: Upload a file using the signed URL

Send a PUT request to the signed URL from any HTTP client or language. Replace <signedUrl> with the URL generated in Step 1.

All examples below use a standard HTTP PUT request — no OSS SDK or credentials are required on the upload side.

curl

curl -X PUT -T /path/to/local/file "<signedUrl>"

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;

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

        // Replace <signedUrl> with the signed URL.
        URL signedUrl = new URL("<signedUrl>");

        // Specify the full path of the local file.
        String pathName = "C:\\Users\\demo.txt";

        try {
            HttpPut put = new HttpPut(signedUrl.toString());
            HttpEntity entity = new FileEntity(new File(pathName));
            put.setEntity(entity);
            httpClient = HttpClients.createDefault();
            response = httpClient.execute(put);

            System.out.println("Status code: " + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                System.out.println("Upload successful.");
            }
        } 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 {
    // Open the file.
    file, err := os.Open(filePath)
    if err != nil {
        return fmt.Errorf("unable to open file: %w", err)
    }
    defer file.Close()

    // Create a PUT request.
    req, err := http.NewRequest("PUT", signedUrl, file)
    if err != nil {
        return fmt.Errorf("failed to create request: %w", err)
    }

    // Send the request.
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return fmt.Errorf("failed to send request: %w", err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return fmt.Errorf("failed to read response: %w", err)
    }

    fmt.Printf("Status code: %d\n", resp.StatusCode)
    if resp.StatusCode == 200 {
        fmt.Println("Upload successful.")
    }
    fmt.Println(string(body))

    return nil
}

func main() {
    // Replace <signedUrl> with the signed URL.
    signedUrl := "<signedUrl>"

    // Specify the full path of the local file.
    filePath := "C:\\Users\\demo.txt"

    if err := uploadFile(signedUrl, filePath); err != nil {
        fmt.Println("Error:", 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 code: {response.status_code}")
        if response.status_code == 200:
            print("Upload successful.")
        print(response.text)

    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    # Replace <signedUrl> with the signed URL.
    signed_url = "<signedUrl>"

    # Specify the full path of the local file.
    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 code: ${response.status}`);
        if (response.status === 200) {
            console.log('Upload successful.');
        }
        console.log(response.data);
    } catch (error) {
        console.error(`An error occurred: ${error.message}`);
    }
}

(async () => {
    // Replace <signedUrl> with the signed URL.
    const signedUrl = '<signedUrl>';

    // Specify the full path of the local file.
    const filePath = 'C:\\Users\\demo.txt';

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

Browser (JavaScript)

Important

Browsers automatically add a Content-Type request header. If Content-Type was not specified when generating the signed URL, the header causes a signature mismatch (HTTP 403). To prevent this, specify Content-Type when generating the signed URL (see Upload a file with request headers).

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Upload Example</title>
</head>
<body>
    <input type="file" id="fileInput" />
    <button id="uploadButton">Upload File</button>

    <script>
        // Replace with the signed URL generated in Step 1.
        const signedUrl = "<signedUrl>";

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

            if (!file) {
                alert('Please select a file to upload.');
                return;
            }

            try {
                await upload(file, signedUrl);
                alert('File uploaded successfully!');
            } catch (error) {
                console.error('Upload error:', error);
                alert('Upload failed: ' + error.message);
            }
        });

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

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

            console.log('File uploaded successfully.');
        };
    </script>
</body>
</html>

C#

using System.Net.Http.Headers;

// Specify the full path of the local file.
var filePath = "C:\\Users\\demo.txt";
// Replace <signedUrl> with the signed URL.
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($"Upload successful. Status code: {response.StatusCode}");
}
else
{
    string responseContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Upload failed. Status code: {response.StatusCode}");
    Console.WriteLine("Response: " + responseContent);
}

C++

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

void uploadFile(const std::string& signedUrl, const std::string& filePath) {
    CURL* curl = curl_easy_init();
    if (!curl) return;

    curl_global_init(CURL_GLOBAL_DEFAULT);

    // Set the URL and request method.
    curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());
    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);

    // Open the file.
    FILE* file = fopen(filePath.c_str(), "rb");
    if (!file) {
        std::cerr << "Unable to open file: " << filePath << std::endl;
        curl_easy_cleanup(curl);
        return;
    }

    // Get the file size.
    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);

    CURLcode res = curl_easy_perform(curl);

    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    } else {
        long httpCode = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
        std::cout << "Status code: " << httpCode << std::endl;
        if (httpCode == 200) {
            std::cout << "Upload successful." << std::endl;
        }
    }

    fclose(file);
    curl_easy_cleanup(curl);
    curl_global_cleanup();
}

int main() {
    // Replace <signedUrl> with the signed URL.
    std::string signedUrl = "<signedUrl>";

    // Specify the full path of the local file.
    std::string filePath = "C:\\Users\\demo.txt";

    uploadFile(signedUrl, filePath);
    return 0;
}

Android (Java)

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 code: " + responseCode);
                if (responseCode == 200) {
                    Log.d(TAG, "Upload successful.");
                }

                return "Upload complete. Status code: " + responseCode;

            } catch (IOException e) {
                e.printStackTrace();
                return "Upload failed: " + 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();
        // Replace <signedUrl> with the signed URL.
        String signedUrl = "<signedUrl>";
        // Specify the full path of the local file.
        String filePath = "C:\\Users\\demo.txt";
        activity.uploadFile(signedUrl, filePath);
    }
}

Upload a file with request headers

If your upload requires specific HTTP headers (such as Content-Type or x-oss-storage-class), include them when generating the signed URL. The PUT request must send the exact same headers — any mismatch results in a signature mismatch error.

Step 1: Generate a signed URL with headers

import Client, { EHeaderKey, EHttpMethod } from '@aliyun/oss';

const client = new Client({
  accessKeyId: 'yourAccessKeyId',
  accessKeySecret: 'yourAccessKeySecret',
  securityToken: 'yourSecurityToken',
  region: 'oss-cn-hangzhou',
});

const bucket = 'yourBucketName';
const key = 'yourObjectName';

const generateSignedUrl = async () => {
  const url = await client.signatureUrl({
    method: EHttpMethod.PUT,
    bucket,
    key,
    expires: 60 * 60,
    headers: {
      [EHeaderKey.CONTENT_TYPE]: 'text/plain;charset=utf-8', // Content-Type to include in the signature.
    },
  });

  console.log(url);
};

generateSignedUrl();

Step 2: Upload using the signed URL, passing the same headers

curl

curl -X PUT \
     -H "Content-Type: text/plain;charset=utf-8" \
     -T "C:\\Users\\demo.txt" \
     "<signedUrl>"

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;

        // Replace <signedUrl> with the signed URL.
        URL signedUrl = new URL("<signedUrl>");

        // Specify the full path of the local file.
        String pathName = "C:\\Users\\demo.txt";

        // Set request headers — must match the headers used to generate the signed URL.
        Map<String, String> headers = new HashMap<>();
        // headers.put("Content-Type", "text/plain;charset=utf-8");
        // headers.put("x-oss-storage-class", "Standard");

        // Set user-defined metadata — must match the metadata used to generate the signed URL.
        Map<String, String> userMetadata = new HashMap<>();
        // userMetadata.put("key1", "value1");

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

            for (Map.Entry<String, String> header : headers.entrySet()) {
                put.addHeader(header.getKey(), header.getValue());
            }
            // The SDK adds the x-oss-meta- prefix internally. When using other methods,
            // add the prefix manually.
            for (Map.Entry<String, String> meta : userMetadata.entrySet()) {
                put.addHeader("x-oss-meta-" + meta.getKey(), meta.getValue());
            }

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

            System.out.println("Status code: " + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                System.out.println("Upload successful.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            response.close();
            httpClient.close();
        }
    }
}

Python

import requests

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

    # Add the x-oss-meta- prefix to each metadata entry.
    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 code: {response.status_code}")
            if response.status_code == 200:
                print("Upload successful.")
            else:
                print("Upload failed.")
            print(response.text)
    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    # Replace <signedUrl> with the signed URL.
    signed_url = "<signedUrl>"

    # Specify the full path of the local file.
    file_path = "C:\\Users\\demo.txt"

    # Set request headers — must match the headers used to generate the signed URL.
    headers = {
        "Content-Type": "text/plain; charset=utf8",
        "x-oss-storage-class": "Standard",
    }

    # Set user-defined metadata — must match the metadata used to generate the signed URL.
    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 {
        // Add the x-oss-meta- prefix to each metadata entry.
        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 code: ${response.status}`);
        if (response.status === 200) {
            console.log('Upload successful.');
        } else {
            console.log('Upload failed.');
        }
        console.log(response.data);
    } catch (error) {
        console.error(`An error occurred: ${error.message}`);
    }
}

(async () => {
    // Replace <signedUrl> with the signed URL.
    const signedUrl = '<signedUrl>';

    // Specify the full path of the local file.
    const filePath = 'C:\\Users\\demo.txt';

    // Set request headers — must match the headers used to generate the signed URL.
    const headers = {
        // 'Content-Type': 'text/plain;charset=utf-8',
        // 'x-oss-storage-class': 'Standard',
    };

    // Set user-defined metadata — must match the metadata used to generate the signed URL.
    const metadata = {
        // 'key1': 'value1',
        // 'key2': 'value2',
    };

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

Browser (JavaScript)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Upload Example (Harmony SDK)</title>
</head>
<body>
    <input type="file" id="fileInput" />
    <button id="uploadButton">Upload File</button>

    <script>
        // Replace with the actual signed URL.
        const signedUrl = "<signedUrl>";

        // These must match the headers specified when generating the signed URL.
        const contentType = 'text/plain';
        const storageClass = 'Standard';
        const metadata = {
            'x-oss-meta-key1': 'value1',
            'x-oss-meta-key2': 'value2',
        };

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

            if (!file) {
                alert('Please select a file to upload.');
                return;
            }

            try {
                await upload(file, signedUrl);
                alert('File uploaded successfully!');
            } catch (error) {
                console.error('Upload error:', error);
                alert('Upload failed: ' + error.message);
            }
        });

        const upload = async (file, presignedUrl) => {
            const chunkSize = 1024 * 1024;
            let start = 0;

            while (start < file.size) {
                const end = Math.min(start + chunkSize, file.size);
                const chunk = file.slice(start, end);

                const headers = {
                    'Content-Type': contentType,
                    'x-oss-storage-class': storageClass,
                    ...metadata,
                };

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

                if (!response.ok) {
                    throw new Error(`Upload failed for chunk, status: ${response.status}`);
                }

                console.log('Chunk uploaded successfully.');
                start = end;
            }

            console.log('File uploaded successfully.');
        };
    </script>
</body>
</html>

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) {
    output->append((char*)contents, size * nmemb);
    return size * nmemb;
}

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 = curl_easy_init();
    if (!curl) return;

    curl_global_init(CURL_GLOBAL_DEFAULT);

    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 << "Failed to open file: " << filePath << std::endl;
        curl_easy_cleanup(curl);
        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);

    // Build the request headers.
    struct curl_slist* chunk = nullptr;
    for (const auto& h : headers) {
        chunk = curl_slist_append(chunk, (h.first + ": " + h.second).c_str());
    }
    for (const auto& m : metadata) {
        chunk = curl_slist_append(chunk, ("x-oss-meta-" + m.first + ": " + m.second).c_str());
    }
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);

    std::string readBuffer;
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);

    CURLcode res = curl_easy_perform(curl);

    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    } else {
        long responseCode;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
        std::cout << "Status code: " << responseCode << std::endl;
        if (responseCode == 200) {
            std::cout << "Upload successful." << std::endl;
        } else {
            std::cout << "Upload failed." << std::endl;
        }
        std::cout << readBuffer << std::endl;
    }

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

int main() {
    // Replace <signedUrl> with the signed URL.
    std::string signedUrl = "<signedUrl>";

    // Specify the full path of the local file.
    std::string filePath = "C:\\Users\\demo.txt";

    // Set request headers — must match the headers used to generate the signed URL.
    std::map<std::string, std::string> headers = {
        // {"Content-Type", "text/plain;charset=utf-8"},
        // {"x-oss-storage-class", "Standard"},
    };

    // Set user-defined metadata — must match the metadata used to generate the signed URL.
    std::map<std::string, std::string> metadata = {
        // {"key1", "value1"},
        // {"key2", "value2"},
    };

    uploadFile(signedUrl, filePath, headers, metadata);
    return 0;
}

Go

package main

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

func uploadFile(signedUrl, filePath string, headers, 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
    }

    // Set request headers — must match the headers used to generate the signed URL.
    for key, value := range headers {
        req.Header.Set(key, value)
    }

    // Set user-defined metadata — must match the metadata used to generate the signed URL.
    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 code: %d\n", resp.StatusCode)
    if resp.StatusCode == 200 {
        fmt.Println("Upload successful.")
    } else {
        fmt.Println("Upload failed.")
    }

    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))

    return nil
}

func main() {
    // Replace <signedUrl> with the signed URL.
    signedUrl := "<signedUrl>"

    // Specify the full path of the local file.
    filePath := "C:\\Users\\demo.txt"

    // Set request headers — must match the headers used to generate the signed URL.
    headers := map[string]string{
        // "Content-Type": "text/plain;charset=utf-8",
        // "x-oss-storage-class": "Standard",
    }

    // Set user-defined metadata — must match the metadata used to generate the signed URL.
    metadata := map[string]string{
        // "key1": "value1",
        // "key2": "value2",
    }

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

Android (Java)

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;

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

        // Replace <signedUrl> with the signed URL.
        String signedUrl = "<signedUrl>";

        // Specify the full path of the local file.
        String pathName = "/storage/emulated/0/demo.txt";

        // Set request headers — must match the headers used to generate the signed URL.
        Map<String, String> headers = new HashMap<>();
        // headers.put("Content-Type", "text/plain;charset=utf-8");
        // headers.put("x-oss-storage-class", "Standard");

        // Set user-defined metadata — must match the metadata used to generate the signed URL.
        Map<String, String> userMetadata = new HashMap<>();
        // userMetadata.put("key1", "value1");

        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 (Map.Entry<String, String> header : headers.entrySet()) {
                    connection.setRequestProperty(header.getKey(), header.getValue());
                }
                for (Map.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 code: " + responseCode);
                if (responseCode == 200) {
                    Log.d(TAG, "Upload successful.");
                }

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

        @Override
        protected void onPostExecute(Integer result) {
            if (result == 200) {
                Toast.makeText(SignUrlUploadActivity.this, "Upload successful.", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(SignUrlUploadActivity.this, "Upload failed.", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Troubleshooting

Signature mismatch (HTTP 403)

If you get a 403 error, the most common cause is a mismatch between the headers used when generating the signed URL and the headers sent in the PUT request.

  • Check that every request header included in the signatureUrl call is also present in the PUT request, with the same values.

  • Browser clients automatically add Content-Type. If this header was not signed into the URL, it causes a 403. Fix this by specifying Content-Type in the headers option when calling signatureUrl.

Expired URL

If the signed URL has expired, it becomes invalid for uploads. Generate a new signed URL.

What's next