All Products
Search
Document Center

Object Storage Service:Use a presigned URL to upload an object (Python SDK V1)

Last Updated:Mar 19, 2026

Generate a presigned URL with OSS SDK for Python to let anyone upload an object to your bucket — no credentials required on their end.

By default, objects in an Object Storage Service (OSS) bucket have a private access control list (ACL): only the object owner can access them. A presigned URL embeds a time-limited signature that grants PUT access to a specific object path. Share the URL with a user and they can upload directly to OSS without needing an access key.

Prerequisites

Before you begin, ensure that you have:

  • An OSS bucket in the target region

  • The oss:PutObject permission on the bucket (required to allow uploads via the presigned URL)

  • OSS SDK for Python V1 installed (pip install oss2)

  • OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET set as environment variables

How it works

The upload flow involves two parties:

  1. The object owner (who has oss:PutObject permission) generates a presigned URL using OSS SDK for Python.

  2. The uploader (who has no OSS credentials) sends an HTTP PUT request to that URL to upload the object.

image

Key behaviors:

  • The presigned URL is valid for a configurable period (up to 7 days with the V4 signature algorithm).

  • The uploader can use the URL multiple times during the validity period.

  • After the URL expires, generate a new one.

  • If the server receives and verifies the request before the URL expires, the upload completes even if the URL expires mid-transfer.

Generate a presigned URL

The following code generates a presigned URL for a PUT upload using the V4 signature algorithm.

# -*- coding: utf-8 -*-
import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

# Load credentials from environment variables.
# Set OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET before running this code.
auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())

# Specify the endpoint for the bucket's region.
# Example: https://oss-cn-hangzhou.aliyuncs.com for China (Hangzhou).
endpoint = "https://oss-cn-hangzhou.aliyuncs.com"

# Specify the region. Required when using the V4 signature algorithm.
region = "cn-hangzhou"

bucket = oss2.Bucket(auth, endpoint, "yourBucketName", region=region)

# Specify the full object path. Do not include the bucket name.
object_name = 'exampledir/exampleobject.txt'

# Generate the presigned URL.
# Set slash_safe=True so that forward slashes (/) in the object path
# are not treated as escape characters — the URL is then directly usable.
url = bucket.sign_url('PUT', object_name, 60, slash_safe=True)
print('Presigned URL:', url)

sign_url() parameters

ParameterTypeDefaultDescription
methodstrHTTP method. Use 'PUT' for upload.
keystrFull object path (excluding the bucket name). Example: exampledir/exampleobject.txt.
expiresintValidity period in seconds. Maximum: 604800 (7 days) with the V4 signature algorithm.
slash_safeboolFalseSet to True to prevent forward slashes in the object path from being URL-encoded. If False, the generated URL cannot be used directly.
headersdictNoneRequest headers to bind to the URL (for example, Content-Type or x-oss-storage-class). The uploader must include these headers when using the URL.

Returns: str — the presigned URL, including signature query parameters such as x-oss-signature, x-oss-date, and x-oss-credential.

The examples in this topic use the public endpoint for the China (Hangzhou) region. To access OSS from other Alibaba Cloud services in the same region, use the internal endpoint. For a full list of endpoints, see Regions and endpoints.

Upload using the presigned URL

Send an HTTP PUT request to the presigned URL to upload an object. All examples below use the URL generated in the previous step.

Important

No extra authentication headers are needed — the signature is embedded in the URL. Do not modify the URL after generating it.

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;

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

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

        // Full path of the local file to upload.
        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 succeeded.");
            }
        } 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("unable to open file: %w", err)
    }
    defer file.Close()

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

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

    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("Status code: %d\n", resp.StatusCode)
    if resp.StatusCode == 200 {
        fmt.Println("Upload succeeded.")
    }
    fmt.Println(string(body))
    return nil
}

func main() {
    // Replace <signedUrl> with the presigned URL.
    signedUrl := "<signedUrl>"
    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 succeeded.")
        print(response.text)
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    # Replace <signedUrl> with the presigned URL.
    signed_url = "<signedUrl>"
    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 succeeded.");
        }
    } catch (error) {
        console.error(`Error: ${error.message}`);
    }
}

(async () => {
    // Replace <signedUrl> with the presigned URL.
    const signedUrl = '<signedUrl>';
    const filePath = 'C:\\Users\\demo.txt';
    await uploadFile(signedUrl, filePath);
})();

Browser.js

Important

Browsers automatically include a Content-Type request header, which causes a 403 SignatureNotMatch error if Content-Type was not specified when generating the presigned URL. To prevent this, always include Content-Type in the headers parameter when calling sign_url() for browser-based uploads. See Upload with request headers and user metadata for an example.

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

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

        document.getElementById('uploadButton').addEventListener('click', async () => {
            const file = document.getElementById('fileInput').files[0];
            if (!file) {
                alert('Select a file first.');
                return;
            }
            try {
                const response = await fetch(signedUrl, {
                    method: 'PUT',
                    body: file,
                });
                if (!response.ok) {
                    throw new Error(`Upload failed, status: ${response.status}`);
                }
                alert('Upload succeeded.');
            } catch (error) {
                console.error('Upload error:', error);
                alert('Upload failed: ' + error.message);
            }
        });
    </script>
</body>
</html>

C#

using System.Net.Http.Headers;

var filePath = "C:\\Users\\demo.txt";
// Replace <signedUrl> with the presigned 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 succeeded. 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_global_init(CURL_GLOBAL_DEFAULT);
    CURL* curl = curl_easy_init();
    if (!curl) {
        curl_global_cleanup();
        return;
    }

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

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

    curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());
    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(curl, CURLOPT_READDATA, file);
    curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);

    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        std::cerr << "curl error: " << 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 succeeded." << std::endl;
    }

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

int main() {
    // Replace <signedUrl> with the presigned URL.
    std::string signedUrl = "<signedUrl>";
    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 = "SignUrlUpload";

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

                try (FileInputStream fis = new FileInputStream(filePath);
                     DataOutputStream 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();
                }

                int responseCode = connection.getResponseCode();
                Log.d(TAG, "Status code: " + responseCode);
                return responseCode == 200 ? "Upload succeeded." : "Upload failed. 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 presigned URL.
        String signedUrl = "<signedUrl>";
        String filePath = "C:\\Users\\demo.txt";
        activity.uploadFile(signedUrl, filePath);
    }
}

Upload with request headers and user metadata

When you need to control the storage class or attach metadata to the object, include those headers when generating the presigned URL. The uploader must then send the same headers — omitting them causes a SignatureNotMatch error.

Step 1: Generate a presigned URL with headers

# -*- coding: utf-8 -*-
import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())
endpoint = "https://oss-cn-hangzhou.aliyuncs.com"
region = "cn-hangzhou"

bucket = oss2.Bucket(auth, endpoint, "yourBucketName", region=region)
object_name = 'exampledir/exampleobject.txt'

# Define the headers to bind to the URL.
headers = {
    'Content-Type': 'text/plain; charset=utf8',
    'x-oss-storage-class': 'Standard',
    # User metadata: prefix each key with x-oss-meta-
    'x-oss-meta-key1': 'value1',
    'x-oss-meta-key2': 'value2',
}

url = bucket.sign_url('PUT', object_name, 60, slash_safe=True, headers=headers)
print('Presigned URL:', url)
Important

The uploader must send exactly these headers when using the URL. Any mismatch causes a SignatureNotMatch error.

Step 2: Upload using the presigned URL with headers

The following examples show how to include the required headers. Replace <signedUrl> with the URL from Step 1.

Required headers for this example:

HeaderValue
Content-Typetext/plain;charset=utf8
x-oss-storage-classStandard
x-oss-meta-key1value1
x-oss-meta-key2value2

curl

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

Java

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

        // These headers must match those specified when the URL was generated.
        Map<String, String> headers = new HashMap<>();
        headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
        headers.put(OSSHeaders.CONTENT_TYPE, "text/plain;charset=utf8");

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

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

            for (Map.Entry<String, String> header : headers.entrySet()) {
                put.addHeader(header.getKey(), header.getValue());
            }
            // Add the x-oss-meta- prefix to each metadata key.
            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());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            response.close();
            httpClient.close();
        }
    }
}

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
    }

    // These headers must match those specified when the URL was generated.
    for key, value := range headers {
        req.Header.Set(key, value)
    }
    for key, value := range metadata {
        req.Header.Set("x-oss-meta-"+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 succeeded.")
    }
    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.Println("Error:", err)
    }
}

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 key.
    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 succeeded.")
        else:
            print("Upload failed.")
            print(response.text)
    except Exception as e:
        print(f"Error: {e}")

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

    # These headers must match those specified when the URL was generated.
    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 {
        // Add the x-oss-meta- prefix to each metadata key.
        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 succeeded.");
        }
    } catch (error) {
        console.error(`Error: ${error.message}`);
    }
}

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

    // These headers must match those specified when the URL was generated.
    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

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

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

        document.getElementById('uploadButton').addEventListener('click', async () => {
            const file = document.getElementById('fileInput').files[0];
            if (!file) {
                alert('Select a file first.');
                return;
            }
            try {
                // These headers must match those specified when the URL was generated.
                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(signedUrl, {
                    method: 'PUT',
                    headers,
                    body: file,
                });
                if (!response.ok) {
                    throw new Error(`Upload failed, status: ${response.status}`);
                }
                alert('Upload succeeded.');
            } catch (error) {
                console.error('Upload error:', error);
                alert('Upload failed: ' + error.message);
            }
        });
    </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;

// These headers must match those specified when the URL was generated.
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");

var response = await httpClient.SendAsync(request);

if (response.IsSuccessStatusCode)
{
    Console.WriteLine($"Upload succeeded. 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>
#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_global_init(CURL_GLOBAL_DEFAULT);
    CURL* curl = curl_easy_init();
    if (!curl) {
        curl_global_cleanup();
        return;
    }

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

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

    curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());
    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(curl, CURLOPT_READDATA, file);
    curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);

    struct curl_slist* chunk = nullptr;
    // These headers must match those specified when the URL was generated.
    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 error: " << 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 succeeded." << 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";

    // These headers must match those specified when the URL was generated.
    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.*;
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 = "SignUrlUpload";

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

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

        // These headers must match those specified when the URL was generated.
        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);

                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);
                try (FileInputStream fis = new FileInputStream(file);
                     DataOutputStream dos = new DataOutputStream(connection.getOutputStream())) {
                    byte[] buffer = new byte[1024];
                    int count;
                    while ((count = fis.read(buffer)) != -1) {
                        dos.write(buffer, 0, count);
                    }
                    dos.flush();
                }

                int responseCode = connection.getResponseCode();
                Log.d(TAG, "Status code: " + responseCode);
                return responseCode;

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

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

Troubleshooting

Use this checklist if an upload fails:

  • SignatureNotMatch (403): The most common cause. Check that:

    • The request headers sent by the uploader exactly match the headers specified when generating the URL (same names, same values, same casing).

    • The URL has not been modified or re-encoded after generation. Enclose it in quotes when using curl.

    • For browser-based uploads, Content-Type was included in the headers parameter when calling sign_url().

  • URL expired: The validity period has elapsed. Generate a new presigned URL.

  • Region mismatch: Confirm the region parameter matches the region where the bucket is located.

  • Object path encoding: If slash_safe=False (the default), forward slashes in the object path are URL-encoded and the URL cannot be used directly. Set slash_safe=True.

FAQ

If the presigned URL expires during an upload, does the upload fail?

No. If the server receives the request and verifies the signature before expiry, the upload completes regardless of whether the URL expires mid-transfer.

Do I need to include request headers and user metadata if I did not specify them when generating the URL?

No. Request headers and user metadata are optional. If you did not include them when calling sign_url(), omit them when uploading.

What's next