All Products
Search
Document Center

Object Storage Service:Upload an object using a presigned URL (C# SDK V2)

Last Updated:Mar 20, 2026

OSS bucket objects are private by default — only the bucket owner can upload them. Generate a presigned URL to grant temporary upload access to other users without sharing your credentials. The URL is valid for a specified duration and can be reused until it expires. If multiple uploads are performed using the same URL, the object may be overwritten. After expiration, generate a new presigned URL.

Prerequisites

Before you begin, ensure that you have:

  • An OSS bucket in the cn-hangzhou region (or the region where your bucket is located)

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

  • The OSS C# SDK V2 (AlibabaCloud.OSS.V2) installed

  • OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET set as environment variables

No special permissions are required to generate a presigned URL. The oss:PutObject permission is only needed to authorize the actual upload operation.

How it works

Presigned URL uploads use two separate parties:

  1. The object owner (server side) generates a presigned URL using the C# SDK V2 and shares it.

  2. The uploader (client side) uses the URL to upload via a standard HTTP PUT request — no OSS credentials required.

If you include request headers or custom metadata when generating the URL, the uploader must send the same headers in the PUT request. Mismatched headers cause a signature mismatch error.

The following diagram shows the end-to-end flow:

image

Generate a presigned URL (C# SDK V2)

The following example generates a presigned URL for a PUT request and immediately uses it to upload a string.

Important

If you include request headers when generating the presigned URL, send the same headers in the PUT request. Otherwise, OSS rejects the request with a signature mismatch error.

using System.Text;
using OSS = AlibabaCloud.OSS.V2;  // Alias for the Alibaba Cloud OSS SDK.

var region = "cn-hangzhou";        // Region where the bucket is located.
var endpoint = null as string;     // Optional. Override the default endpoint if needed.
var bucket = "your bucket name";   // The bucket name.
var key = "your object key";       // The object key (name in OSS after upload).

// Load SDK defaults. Credentials are read from OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET.
var cfg = OSS.Configuration.LoadDefault();
cfg.CredentialsProvider = new OSS.Credentials.EnvironmentVariableCredentialsProvider();
cfg.Region = region;
if (endpoint != null)
{
    cfg.Endpoint = endpoint;
}

// Create an OSS client.
using var client = new OSS.Client(cfg);

// Generate a presigned URL for a PUT request.
var result = client.Presign(new OSS.Models.PutObjectRequest()
{
    Bucket = bucket,
    Key = key,
});

// Upload content using the presigned URL.
const string content = "hello oss!";
using var hc = new HttpClient();
var httpResult = await hc.PutAsync(result.Url, new ByteArrayContent(Encoding.UTF8.GetBytes(content)));

Console.WriteLine("PutObject done");
Console.WriteLine($"StatusCode: {httpResult.StatusCode}");
The sample code uses the China (Hangzhou) region (cn-hangzhou) with the public endpoint. To access OSS from another Alibaba Cloud service in the same region, use an internal endpoint. For the full list of regions and endpoints, see OSS regions and endpoints.

Share result.Url with the uploader. The URL is a standard HTTPS endpoint — the uploader needs no OSS SDK or credentials.

Upload using the presigned URL

All examples below use the presigned URL generated in the previous section. Replace <signedUrl> with the actual URL.

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;

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

        // Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
        String pathName = "C:\\Users\\demo.txt";

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

            System.out.println("Status code returned for the upload:"+response.getStatusLine().getStatusCode());
            if(response.getStatusLine().getStatusCode() == 200){
                System.out.println("Upload successful using network library.");
            }
            System.out.println(response.toString());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
             if (response != null) {
                 response.close();
             }
             if (httpClient != null) {
                 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 new HTTP client.
	client := &http.Client{}

	// 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.
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("Failed to send request: %w", err)
	}
	defer resp.Body.Close()

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

	fmt.Printf("Status code returned for the upload: %d\n", resp.StatusCode)
	if resp.StatusCode == 200 {
		fmt.Println("Upload successful using network library.")
	}
	fmt.Println(string(body))

	return nil
}

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

	// Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
	filePath := "C:\\Users\\demo.txt"

	err := uploadFile(signedUrl, filePath)
	if err != nil {
		fmt.Println("An error occurred:", err)
	}
}

python

import requests

def upload_file(signed_url, file_path):
    try:
        # Open the file.
        with open(file_path, 'rb') as file:
            # Send a PUT request to upload the file.
            response = requests.put(signed_url, data=file)
     
        print(f"Status code returned for the upload: {response.status_code}")
        if response.status_code == 200:
            print("Upload successful using network library.")
        print(response.text)
 
    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    # Replace <signedUrl> with the authorized URL.
    signed_url = "<signedUrl>"
    
    # Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
    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 {
        // Create a read stream.
        const fileStream = fs.createReadStream(filePath);
        
        // Send a PUT request to upload the file.
        const response = await axios.put(signedUrl, fileStream, {
            headers: {
                'Content-Type': 'application/octet-stream' // Adjust the Content-Type as needed.
            }
        });

        console.log(`Status code returned for the upload: ${response.status}`);
        if (response.status === 200) {
            console.log('Upload successful using network library.');
        }
        console.log(response.data);
    } catch (error) {
        console.error(`An error occurred: ${error.message}`);
    }
}

// Main function.
(async () => {
    // Replace <signedUrl> with the authorized URL.
    const signedUrl = '<signedUrl>';
    
    // Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
    const filePath = 'C:\\Users\\demo.txt';

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

browser.js

Important

If you encounter a 403 signature mismatch error when you use Browser.js to upload a file, it is usually because the browser automatically adds the Content-Type request header, and this header was not included when the presigned URL was generated. This mismatch causes a signature verification failure. To resolve this issue, you must specify the Content-Type request header when you generate the presigned URL.

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

    <!-- Select a file -->
    <input type="file" id="fileInput" />
    <button id="uploadButton">Upload File</button>

    <script>
        // Replace this with the presigned 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('Error during upload:', error);
                alert('Upload failed: ' + error.message);
            }
        });

        /**
         * Upload the file to OSS.
         * @param {File} file - The file to upload.
         * @param {string} presignedUrl - The presigned URL.
         */
        const upload = async (file, presignedUrl) => {
            const response = await fetch(presignedUrl, {
                method: 'PUT',
                body: file,  // Upload the entire file directly.
            });

            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. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
var filePath = "C:\\Users\\demo.txt";
// Replace <signedUrl> with the authorized URL.
var presignedUrl = "<signedUrl>";

// Create an HTTP client and open the local file stream.
using var httpClient = new HttpClient(); 
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var content = new StreamContent(fileStream);
            
// Create a PUT request.
var request = new HttpRequestMessage(HttpMethod.Put, presignedUrl);
request.Content = content;

// Send the request.
var response = await httpClient.SendAsync(request);

// Process the response.
if (response.IsSuccessStatusCode)
{
    Console.WriteLine($"Upload successful! Status code: {response.StatusCode}");
    Console.WriteLine("Response headers:");
    foreach (var header in response.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
}
else
{
    string responseContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Upload failed! Status code: {response.StatusCode}");
    Console.WriteLine("Response content: " + 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) {
        // Set the URL.
        curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());

        // Set the request method to PUT.
        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;
            return;
        }

        // Get the file size.
        fseek(file, 0, SEEK_END);
        long fileSize = ftell(file);
        fseek(file, 0, SEEK_SET);

        // Set the file size.
        curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);

        // Set the input file handle.
        curl_easy_setopt(curl, CURLOPT_READDATA, file);

        // Execute the request.
        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 returned for the upload: " << httpCode << std::endl;

            if (httpCode == 200) {
                std::cout << "Upload successful using network library." << std::endl;
            }
        }

        // Close the file.
        fclose(file);

        // Clean up.
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
}

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

    // Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
    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 code returned for the upload: " + responseCode);

                if (responseCode == 200) {
                    Log.d(TAG, "Upload successful using network library.");
                }

                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 authorized URL.
        String signedUrl = "<signedUrl>";
        // Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
        String filePath = "C:\\Users\\demo.txt";
        activity.uploadFile(signedUrl, filePath);
    }
}

Scenarios

Upload with request headers and custom metadata

When you need to set the storage class, content type, or custom metadata on the uploaded object, include these in the presigned URL. The uploader must then send the same headers in the PUT request — they are part of the signature.

Step 1: Generate the presigned URL (C# SDK V2)

using System.Text;
using OSS = AlibabaCloud.OSS.V2;
using System.Globalization;
using System.Net.Http.Headers;

var region = "cn-hangzhou";
var endpoint = null as string;
var bucket = "your bucket name";
var key = "your object key";

var cfg = OSS.Configuration.LoadDefault();
cfg.CredentialsProvider = new OSS.Credentials.EnvironmentVariableCredentialsProvider();
cfg.Region = region;
if (endpoint != null)
{
    cfg.Endpoint = endpoint;
}

using var client = new OSS.Client(cfg);

// Generate a presigned URL with storage class, content type, and custom metadata.
var result = client.Presign(new OSS.Models.PutObjectRequest()
    {
        Bucket = bucket,
        Key = key,
        StorageClass = "Standard",
        ContentType = "text/plain; charset=utf8",
        Metadata = new Dictionary<string, string>() {
            { "key1", "value1" },
            { "key2", "value2" }
        }
    }
);

// Build the PUT request, adding all signed headers from the presigned URL result.
using var hc = new HttpClient();
var content1 = "hello oss";
var requestMessage = new HttpRequestMessage(HttpMethod.Put, new Uri(result.Url));
requestMessage.Content = new ByteArrayContent(Encoding.UTF8.GetBytes(content1));

// Add the HTTP headers included in the presigned URL.
foreach (var item in result.SignedHeaders!)
{
    switch (item.Key.ToLower())
    {
        case "content-disposition":
            requestMessage.Content.Headers.ContentDisposition = ContentDispositionHeaderValue.Parse(item.Value);
            break;
        case "content-encoding":
            requestMessage.Content.Headers.ContentEncoding.Add(item.Value);
            break;
        case "content-language":
            requestMessage.Content.Headers.ContentLanguage.Add(item.Value);
            break;
        case "content-type":
            requestMessage.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(item.Value);
            break;
        case "content-md5":
            requestMessage.Content.Headers.ContentMD5 = Convert.FromBase64String(item.Value);
            break;
        case "content-length":
            requestMessage.Content.Headers.ContentLength = Convert.ToInt64(item.Value);
            break;
        case "expires":
            if (DateTime.TryParse(
                    item.Value,
                    CultureInfo.InvariantCulture,
                    DateTimeStyles.None,
                    out var expires
                ))
                requestMessage.Content.Headers.Expires = expires;
            break;
        default:
            requestMessage.Headers.Add(item.Key, item.Value);
            break;
    }
}

var httpResult = await hc.SendAsync(requestMessage);

Console.WriteLine("PutObject done");
Console.WriteLine($"StatusCode: {httpResult.StatusCode}");

Step 2: Upload using the presigned URL

Each example below sends the same headers that were signed into the URL. Replace <signedUrl> with the actual URL.

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
package com.aliyun.sample;

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;

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

        // Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
        String pathName = "C:\\Users\\demo.txt";

        // Set the request headers. The request header information must be the same as the information used when the URL was generated.
        Map<String, String> headers = new HashMap<String, String>();
        //Specify the storage class of the object.
        headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
        //Specify the ContentType.
        headers.put(OSSHeaders.CONTENT_TYPE, "text/plain;charset=utf8");

        // Set the custom user metadata. The custom user metadata must be the same as the information used when the URL was generated.
        Map<String, String> userMetadata = new HashMap<String, String>();
        userMetadata.put("key1","value1");
        userMetadata.put("key2","value2");

        try {
            HttpPut put = new HttpPut(signedUrl.toString());
            System.out.println(put);
            HttpEntity entity = new FileEntity(new File(pathName));
            put.setEntity(entity);
            // If you set header parameters, such as user metadata and storage class, when you generate the presigned URL, you must also send these parameters to the server when you use the presigned URL to upload the file. A signature error is reported if the parameters sent to the server are inconsistent with the signed parameters.
            for(Map.Entry<String, String> header: headers.entrySet()){
                put.addHeader(header.getKey(), header.getValue());
            }
            for(Map.Entry<String, String> meta: userMetadata.entrySet()){
                // If you use userMeta, the SDK internally adds the 'x-oss-meta-' prefix. When you use other methods to generate a presigned URL for an upload, you must also add the 'x-oss-meta-' prefix to the userMeta.
                put.addHeader("x-oss-meta-"+meta.getKey(), meta.getValue());
            }

            httpClient = HttpClients.createDefault();

            response = httpClient.execute(put);

            System.out.println("Status code returned for the upload:"+response.getStatusLine().getStatusCode());
            if(response.getStatusLine().getStatusCode() == 200){
                System.out.println("Upload successful using network library.");
            }
            System.out.println(response.toString());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (response != null) {
                response.close();
            }
            if (httpClient != null) {
                httpClient.close();
            }
        }
    }
}
Go
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 {
	// Open the file.
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer file.Close()

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

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

	// Set the request headers.
	for key, value := range headers {
		req.Header.Set(key, value)
	}

	// Set the custom user metadata.
	for key, value := range metadata {
		req.Header.Set(fmt.Sprintf("x-oss-meta-%s", key), value)
	}

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

	// Process the response.
	fmt.Printf("Status code returned for the upload: %d\n", resp.StatusCode)
	if resp.StatusCode == 200 {
		fmt.Println("Upload successful using network library.")
	} else {
		fmt.Println("Upload failed.")
	}
	body, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(body))

	return nil
}

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

	// Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
	filePath := "C:\\Users\\demo.txt"

	// Set the request headers. The request header information must be the same as the information used when the URL was generated.
	headers := map[string]string{
		"Content-Type": "text/plain;charset=utf8",
		"x-oss-storage-class": "Standard",
	}

	// Set the custom user metadata. The custom user metadata must be the same as the information used when the URL was generated.
	metadata := map[string]string{
		"key1": "value1",
		"key2": "value2",
	}

	err := uploadFile(signedUrl, filePath, headers, metadata)
	if err != nil {
		fmt.Printf("An error occurred: %v\n", err)
	}
}
Python
import requests
from requests.auth import HTTPBasicAuth
import os

def upload_file(signed_url, file_path, headers=None, metadata=None):
    """
    Use a presigned URL to upload a file to OSS.

    :param signed_url: The presigned URL.
    :param file_path: The full path of the file to upload.
    :param headers: Optional. Custom HTTP headers.
    :param metadata: Optional. Custom metadata.
    :return: None
    """
    if not headers:
        headers = {}
    if not metadata:
        metadata = {}

    # Update headers and add the metadata prefix.
    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 returned for the upload: {response.status_code}")
            if response.status_code == 200:
                print("Upload successful using network library.")
            else:
                print("Upload failed.")
            print(response.text)
    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    # Replace <signedUrl> with the authorized URL.
    signed_url = "<signedUrl>"
   
    # Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the directory where the script is located by default.
    file_path = "C:\\Users\\demo.txt"

    # Set the request headers. The request header information must be the same as the information used when the URL was generated.
    headers = {
         "Content-Type": "text/plain;charset=utf8",
         "x-oss-storage-class": "Standard"
    }

    # Set the custom user metadata. The custom user metadata must be the same as the information used when the URL was generated.
    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 {
        // Update headers and add the metadata prefix.
        for (const [key, value] of Object.entries(metadata)) {
            headers[`x-oss-meta-${key}`] = value;
        }

        // Read the file stream.
        const fileStream = fs.createReadStream(filePath);

        // Send a PUT request.
        const response = await axios.put(signedUrl, fileStream, {
            headers: headers
        });

        console.log(`Status code returned for the upload: ${response.status}`);
        if (response.status === 200) {
            console.log("Upload successful using network library.");
        } else {
            console.log("Upload failed.");
        }
        console.log(response.data);
    } catch (error) {
        console.error(`An error occurred: ${error.message}`);
    }
}

// Main function.
(async () => {
    // Replace <signedUrl> with the authorized URL.
    const signedUrl = "<signedUrl>";

    // Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the directory where the script is located by default.
    const filePath = "C:\\Users\\demo.txt";

    // Set the request headers. The request header information must be the same as the information used when the URL was generated.
    const headers = {
         "Content-Type": "text/plain;charset=utf8",
         "x-oss-storage-class": "Standard"
    };

    // Set the custom user metadata. The custom user metadata must be the same as the information used when the URL was generated.
    const metadata = {
         "key1": "value1",
         "key2": "value2"
    };

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

If you encounter a 403 signature mismatch error when you use Browser.js to upload a file, it is usually because the browser automatically adds the Content-Type request header, and this header was not included when the presigned URL was generated. This mismatch causes a signature verification failure. To resolve this issue, you must specify the Content-Type request header when you generate the presigned URL.

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

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

    <script>
        // Replace this with the actual presigned URL.
        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('Error during upload:', error);
                    alert('Upload failed: ' + error.message);
                }
            } else {
                alert('Please select a file to upload.');
            }
        });

        const upload = async (file, presignedUrl) => {
            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(`Upload failed, status: ${response.status}`);
            }

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

// Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
var filePath = "C:\\Users\\demo.txt";
// Replace <signedUrl> with the authorized URL.
var presignedUrl = "<signedUrl>";

// Create an HTTP client and open the local file stream.
using var httpClient = new HttpClient(); 
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var content = new StreamContent(fileStream);
            
// Create a PUT request.
var request = new HttpRequestMessage(HttpMethod.Put, presignedUrl);
request.Content = content;

// If you set header parameters, such as user metadata and storage class, when you generate the presigned URL, you must also send these parameters to the server when you use the presigned URL to upload the file. A signature error is reported if the parameters sent to the server are inconsistent with the signed parameters.
// Set the request headers. The request header information must be the same as the information used when the URL was generated.
request.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain") { CharSet = "utf8" };  // Specify the ContentType.       
// Set the custom user metadata. The custom user metadata must be the same as the information used when the URL was generated.
request.Content.Headers.Add("x-oss-meta-key1", "value1");
request.Content.Headers.Add("x-oss-meta-key2", "value2");
// Specify the storage class of the object.
request.Content.Headers.Add("x-oss-storage-class", "Standard");

// Print the request headers.
Console.WriteLine("Request headers:");
foreach (var header in request.Content.Headers)
{
    Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
}

// Send the request.
var response = await httpClient.SendAsync(request);

// Process the response.
if (response.IsSuccessStatusCode)
{
    Console.WriteLine($"Upload successful! Status code: {response.StatusCode}");
    Console.WriteLine("Response headers:");
    foreach (var header in response.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
}
else
{
    string responseContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Upload failed! Status code: {response.StatusCode}");
    Console.WriteLine("Response content: " + responseContent);
}
C++
#include <iostream>
#include <fstream>
#include <curl/curl.h>
#include <map>
#include <string>

// Callback function to handle the HTTP response.
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) {
        // Set the URL.
        curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());

        // Set the request method to PUT.
        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;
            return;
        }

        // Set the file size.
        fseek(file, 0, SEEK_END);
        long fileSize = ftell(file);
        rewind(file);

        // Set the file read callback.
        curl_easy_setopt(curl, CURLOPT_READDATA, file);
        curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);

        // Set the request headers.
        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);

        // Set the response handling callback.
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);

        // Execute the request.
        res = curl_easy_perform(curl);

        // Check the response.
        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 returned for the upload: " << responseCode << std::endl;
            if (responseCode == 200) {
                std::cout << "Upload successful using network library." << std::endl;
            } else {
                std::cout << "Upload failed." << std::endl;
            }
            std::cout << readBuffer << std::endl;
        }

        // Clean up.
        fclose(file);
        curl_slist_free_all(chunk);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
}

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

    // Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
    std::string filePath = "C:\\Users\\demo.txt";

    // Set the request headers. The request header information must be the same as the information used when the URL was generated.
    std::map<std::string, std::string> headers = {
         {"Content-Type", "text/plain;charset=utf8"},
         {"x-oss-storage-class", "Standard"}
    };

    // Set the custom user metadata. The custom user metadata must be the same as the information used when the URL was generated.
    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);

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

        // Specify the full path of the local file. If you do not specify a local path, the file is uploaded from the project path of the sample program by default.
        String pathName = "/storage/emulated/0/demo.txt";

        // Set the request headers. The request header information must be the same as the information used 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");

        // Set the custom user metadata. The custom user metadata must be the same as the information used when the URL was generated.
        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);

                // Set the request headers.
                for (Entry<String, String> header : headers.entrySet()) {
                    connection.setRequestProperty(header.getKey(), header.getValue());
                }

                // Set the custom user metadata.
                for (Entry<String, String> meta : userMetadata.entrySet()) {
                    connection.setRequestProperty("x-oss-meta-" + meta.getKey(), meta.getValue());
                }

                // Read the file.
                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();

                // Get the response.
                int responseCode = connection.getResponseCode();
                Log.d(TAG, "Status code returned for the upload: " + responseCode);
                if (responseCode == 200) {
                    Log.d(TAG, "Upload successful using network library.");
                } else {
                    Log.d(TAG, "Upload failed.");
                }

                InputStream is = connection.getInputStream();
                byte[] responseBuffer = new byte[1024];
                StringBuilder responseStringBuilder = new StringBuilder();
                while ((count = is.read(responseBuffer)) != -1) {
                    responseStringBuilder.append(new String(responseBuffer, 0, count));
                }
                Log.d(TAG, responseStringBuilder.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, "Upload successful.", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(SignUrlUploadActivity.this, "Upload failed.", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Multipart upload with presigned URLs

For large objects, use multipart upload: split the file into parts, generate a separate presigned URL for each part, and upload them in sequence. The C# SDK V2 handles part tracking and completion.

The example below uses a part size of 512 KB (512*1024 bytes). Adjust based on your file size and network conditions.

using OSS = AlibabaCloud.OSS.V2;

var region = "cn-hangzhou";
var endpoint = null as string;
var bucket = "you bucket name";
var key = "your object key";
var partSize = 512*1024;   // Part size: 512 KB.
var filePath = "filePath"; // Full path of the file to upload.

var cfg = OSS.Configuration.LoadDefault();
cfg.CredentialsProvider = new OSS.Credentials.EnvironmentVariableCredentialsProvider();
cfg.Region = region;
if (endpoint != null)
{
    cfg.Endpoint = endpoint;
}

using var client = new OSS.Client(cfg);

// Step 1: Initiate the multipart upload to get an upload ID.
var initResult = await client.InitiateMultipartUploadAsync(new()
{
    Bucket = bucket,
    Key = key
});

using var hc = new HttpClient();
using var file = File.OpenRead(filePath);
long fileSize = file.Length;
long partNumber = 1;

// Step 2: Generate a presigned URL for each part and upload it.
for (long offset = 0; offset < fileSize; offset += partSize)
{
    var size = Math.Min(partSize, fileSize - offset);

    var presignResult = client.Presign(new OSS.Models.UploadPartRequest
    {
        Bucket = bucket,
        Key = key,
        PartNumber = partNumber,
        UploadId = initResult.UploadId,
        Body = new OSS.IO.BoundedStream(file, offset, size)
    });

    var httpResult = await hc.PutAsync(
        presignResult.Url,
        new StreamContent(new OSS.IO.BoundedStream(file, offset, size))
    );

    if (!httpResult.IsSuccessStatusCode)
    {
        throw new Exception("upload part fail");
    }
    partNumber++;
}

// Step 3: List all uploaded parts.
var uploadParts = new List<OSS.Models.UploadPart>();
var paginator = client.ListPartsPaginator(new ()
{
    Bucket = bucket,
    Key = key,
    UploadId = initResult.UploadId,
});

await foreach (var page in paginator.IterPageAsync())
{
    foreach (var part in page.Parts ?? [])
    {
        uploadParts.Add(new() { PartNumber = part.PartNumber, ETag = part.ETag });
    }
}

// Step 4: Sort parts and complete the multipart upload.
uploadParts.Sort((left, right) => { return (left.PartNumber > right.PartNumber) ? 1 : -1; });

var cmResult = await client.CompleteMultipartUploadAsync(new()
{
    Bucket = bucket,
    Key = key,
    UploadId = initResult.UploadId,
    CompleteMultipartUpload = new()
    {
        Parts = uploadParts
    }
});

Console.WriteLine("MultipartUpload done");
Console.WriteLine($"StatusCode: {cmResult.StatusCode}");
Console.WriteLine($"RequestId: {cmResult.RequestId}");
Console.WriteLine("Response Headers:");
cmResult.Headers.ToList().ForEach(x => Console.WriteLine(x.Key + " : " + x.Value));

Troubleshooting

SignatureNotMatch errors

If the upload fails with a signature mismatch error, check the following:

  • Mismatched headers: Any request header included when generating the presigned URL must be sent in the PUT request with the exact same value. Missing or modified headers cause signature verification to fail.

  • Browser adds Content-Type automatically: When using Browser.js, specify Content-Type when generating the presigned URL so the browser-added header is covered by the signature.

  • Expired URL: Generate a new presigned URL if the existing one has expired.

FAQ

Does the upload fail if the presigned URL expires during the upload?

No. The signature is verified when the upload request is initiated. If the URL is valid at that point, the upload proceeds to completion even if the URL expires mid-transfer.

If you use a temporary security credential (STS token), the effective expiration is the shorter of the token's validity period and the URL's specified expiration time.

Do I need to include request headers and metadata if I didn't set them when generating the URL?

No. Request headers and custom metadata are optional. If you didn't include them when generating the presigned URL, remove the related header-setting code from the upload request.

What's next

For the complete sample code, see PresignPutObject.cs.

For related permissions, see Authorize a RAM user to access multiple directories in a bucket.