Practice of Direct Transfer for Web End

Last updated: 2024-09-29 12:04:47

Feature Overview

This document explains how to upload files directly to a Cloud Object Storage (COS) bucket from a web page without relying on an SDK, using simple code.
Note
The content of this document is based on the XML version of the API.

Preparations

1. Log in to the COS console and create a bucket to obtain the Bucket (bucket name) and Region (region name). For more information, please refer to the Creating a Bucket document.
2. Navigate to the bucket details page and click the Security Management tab. Scroll down to find the Cross-Origin Resource Sharing (CORS) Settings configuration, and click Add Rule. Configure as shown in the example below. For more information, please refer to the Setting up Cross-Origin Access document.


3. Log in to the Access Management Console to obtain your project's SecretId and SecretKey.

Solution Description

Implementation Process

1. Select a file in the frontend, and the frontend sends the file extension to the server.
2. The server generates a random COS file path with a timestamp based on the file extension and calculates the corresponding signature. It then returns the URL and signature information to the front-end.
3. The front-end uses PUT or POST requests to directly upload files to COS.

Solution strengths

Permissions security: The scope of security permissions can be effectively limited with the server-side signatures, which can only be used to upload a specified file path.
Path security: The random COS file path is determined by the server, which can effectively avoid the problem of existing files being overwritten and security risks.

Practical Steps

Configuring the Server to Implement Signatures

Note:
Add a layer of authority check on your website itself when the server is officially deployed.
Refer to the document Request Signature for how to calculate the signature.
Refer to Nodejs Example for the server-side calculation signature code using Nodejs.

Web Upload Example

The following code is an example of PUT Object interface (recommended) and POST Object interface, the operation guide is as follows:

Use AJAX PUT to upload

AJAX Upload requires the browser to support basic HTML5 features. The current solution uses PUT Object documentation. The operation guide is as follows:
1. Prepare the bucket configuration according to the steps of Prerequisites.
2. Create a test.html file and copy the code below into the test.html file.
3. Deploy the back-end signature service and modify the signature service address in test.html.
4. Place test.html under the Web server, access the page through a browser, and test the file upload feature.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Ajax Put Upload (Server-side signature calculation)</title>
<style>
h1,
h2 {
font-weight: normal;
}

#msg {
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Ajax Put Upload (Server-side signature calculation)</h1>

<input id="fileSelector" type="file" />
<input id="submitBtn" type="submit" />

<div id="msg"></div>

<script>
(function () {
// url encode format for encoding more characters
const camSafeUrlEncode = function (str) {
return encodeURIComponent(str)
.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A');
};

// Calculate the signature.
const getAuthorization = function (opt, callback) {
// Replace with your server address to get the PUT upload signature, demo: https://github.com/tencentyun/cos-demo/blob/main/server/upload-sign/nodejs/app.js
const url = http://127.0.0.1:3000/put-sign?ext=${opt.ext};
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
let credentials;
try {
const result = JSON.parse(e.target.responseText);
credentials = result;
} catch (e) {
callback('Error in getting signature');
}
if (credentials) {
// Print to confirm if the credentials are correct
// console.log(credentials);
callback(null, {
securityToken: credentials.sessionToken,
authorization: credentials.authorization,
cosKey: credentials.cosKey,
cosHost: credentials.cosHost,
});
} else {
console.error(xhr.responseText);
callback('Error in getting signature');
}
};
xhr.onerror = function (e) {
callback('Error in getting signature');
};
xhr.send();
};

// Uploading Files
const uploadFile = function (file, callback) {
const fileName = file.name;
// Get the file extension
let ext = '';
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > -1) {
// Get the file extension here. The server generates the final upload path.
ext = fileName.substring(lastDotIndex + 1);
}
getAuthorization({ ext }, function (err, info) {
if (err) {
alert(err);
return;
}
const auth = info.authorization;
const securityToken = info.securityToken;
const Key = info.cosKey;
const protocol =
location.protocol === 'https:' ? 'https:' : 'http:';
const prefix = protocol + '//' + info.cosHost;
const url =
prefix + '/' + camSafeUrlEncode(Key).replace(/%2F/g, '/');
const xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('Authorization', auth);
securityToken &&
xhr.setRequestHeader('x-cos-security-token', securityToken);
xhr.upload.onprogress = function (e) {
console.log(
'Upload progress ' +
Math.round((e.loaded / e.total) * 10000) / 100 +
'%'
);
};
xhr.onload = function () {
if (/^2\d\d$/.test('' + xhr.status)) {
const ETag = xhr.getResponseHeader('etag');
callback(null, { url: url, ETag: ETag });
} else {
callback('file' + Key + 'Upload failed, status code: ' + xhr.status);
}
};
xhr.onerror = function () {
callback(
'File ' + Key + ' Upload failed. Please check if the CORS cross-domain rules are configured properly'
);
};
xhr.send(file);
});
};

// Listen for form submissions
document.getElementById('submitBtn').onclick = function (e) {
const file = document.getElementById('fileSelector').files[0];
if (!file) {
document.getElementById('msg').innerText = 'No file selected for upload';
return;
}
file &&
uploadFile(file, function (err, data) {
console.log(err || data);
document.getElementById('msg').innerText = err
? err
: 'Upload successfully, ETag=' + data.ETag;
});
};
})();
</script>
</body>
</html>
The execution effect is as shown below:



Use AJAX POST to upload

AJAX Upload requires the browser to support basic HTML5 features. The current solution uses Post Object interface. Operation guide:
1. Obtain the bucket information by taking the steps in Prerequisites.
2. Create a test.html file and copy the code below into the test.html file.
3. Deploy the signature service at the backend and modify the signature service address in test.html.
4. Place test.html on the Web server. Then, browser the page to test the file upload feature.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Ajax Post Upload (Server-side signature calculation)</title>
<style>
h1,
h2 {
font-weight: normal;
}

#msg {
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Post Object Upload (Server-side signature calculation)</h1>

<input id="fileSelector" type="file" />
<input id="submitBtn" type="submit" />

<div id="msg"></div>

<script>
(function () {
let prefix = '';
let Key = '';

// url encode format for encoding more characters
const camSafeUrlEncode = function (str) {
return encodeURIComponent(str)
.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A');
};

// Get permission policies
const getAuthorization = function (opt, callback) {
// Replace with your server address to get the post upload signature, demo: https://github.com/tencentyun/cos-demo/blob/main/server/upload-sign/nodejs/app.js
const url = http://127.0.0.1:3000/post-policy?ext=${opt.ext};
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
let credentials;
try {
const result = JSON.parse(e.target.responseText);
credentials = result;
} catch (e) {
callback('Error in getting signature');
}
if (credentials) {
// Print to confirm if the credentials are correct
// console.log(credentials);
callback(null, {
securityToken: credentials.sessionToken,
cosKey: credentials.cosKey,
cosHost: credentials.cosHost,
policy: credentials.policy,
qAk: credentials.qAk,
qKeyTime: credentials.qKeyTime,
qSignAlgorithm: credentials.qSignAlgorithm,
qSignature: credentials.qSignature,
});
} else {
console.error(xhr.responseText);
callback('Error in getting signature');
}
};
xhr.send();
};

// Uploading Files
const uploadFile = function (file, callback) {
const fileName = file.name;
// Get the file extension
let ext = '';
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > -1) {
// Get the file extension here. The server generates the final upload path.
ext = fileName.substring(lastDotIndex + 1);
}
getAuthorization({ ext }, function (err, credentials) {
if (err) {
alert(err);
return;
}
const protocol =
location.protocol === 'https:' ? 'https:' : 'http:';
prefix = protocol + '//' + credentials.cosHost;
Key = credentials.cosKey;
const fd = new FormData();

// Put an empty.html in the current directory so that the API can jump back after uploading
fd.append('key', Key);

// Use policy signature protection formats
credentials.securityToken &&
fd.append('x-cos-security-token', credentials.securityToken);
fd.append('q-sign-algorithm', credentials.qSignAlgorithm);
fd.append('q-ak', credentials.qAk);
fd.append('q-key-time', credentials.qKeyTime);
fd.append('q-signature', credentials.qSignature);
fd.append('policy', credentials.policy);

// File content, place the file field at the end of the form to avoid long file content affecting the signature judgment and authentication.
fd.append('file', file);

// xhr
const url = prefix;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.upload.onprogress = function (e) {
console.log(
'Upload progress ' +
Math.round((e.loaded / e.total) * 10000) / 100 +
'%'
);
};
xhr.onload = function () {
if (Math.floor(xhr.status / 100) === 2) {
const ETag = xhr.getResponseHeader('etag');
callback(null, {
url:
prefix + '/' + camSafeUrlEncode(Key).replace(/%2F/g, '/'),
ETag: ETag,
});
} else {
callback('file' + Key + 'Upload failed, status code: ' + xhr.status);
}
};
xhr.onerror = function () {
callback(
'File ' + Key + ' Upload failed. Please check if the CORS cross-domain rules are configured properly'
);
};
xhr.send(fd);
});
};

// Listen for form submissions
document.getElementById('submitBtn').onclick = function (e) {
const file = document.getElementById('fileSelector').files[0];
if (!file) {
document.getElementById('msg').innerText = 'No file selected for upload';
return;
}
file &&
uploadFile(file, function (err, data) {
console.log(err || data);
document.getElementById('msg').innerText = err
? err
: 'Upload successfully, ETag=' + data.ETag + 'url=' + data.url;
});
};
})();
</script>
</body>
</html>
The execution effect is as shown below:



Use Form to upload

Form Upload supports uploads from older browsers (for example, IE8). The current solution uses Post Object interface. Operation guide:
1. Obtain the bucket information by taking the steps in Prerequisites.
2. Create a test.html file and copy the code below into the test.html file.
3. Deploy the signature service at the backend and modify the signature service address in test.html.
4. In the directory where test.html is stored, create an empty empty.html file to be redirected back when the upload is successful.
5. Place test.html and empty.html on the Web server. Then, browse the page to test the file upload feature.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Simple Form Upload (Compatible with IE8) (Server-side Signature Calculation)</title>
<style>
h1,
h2 {
font-weight: normal;
}
#msg {
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Simple Form Upload (Compatible with IE8) (Server-side Signature Calculation)</h1>
<div>Minimum compatibility with IE6 for uploading, and onprogress is not supported</div>.

<form
id="form"
target="submitTarget"
action=""
method="post"
enctype="multipart/form-data"
accept="*/*"
>
<input id="name" name="name" type="hidden" value="" />
<input name="success_action_status" type="hidden" value="200" />
<input
id="success_action_redirect"
name="success_action_redirect"
type="hidden"
value=""
/>
<input id="key" name="key" type="hidden" value="" />
<input id="policy" name="policy" type="hidden" value="" />
<input
id="q-sign-algorithm"
name="q-sign-algorithm"
type="hidden"
value=""
/>
<input id="q-ak" name="q-ak" type="hidden" value="" />
<input id="q-key-time" name="q-key-time" type="hidden" value="" />
<input id="q-signature" name="q-signature" type="hidden" value="" />
<input name="Content-Type" type="hidden" value="" />
<input
id="x-cos-security-token"
name="x-cos-security-token"
type="hidden"
value=""
/>

<!-- Place the file field at the end of the form to avoid long file content affecting signature judgment and authentication -->
<input id="fileSelector" name="file" type="file" />
<input id="submitBtn" type="button" value="Submit" />
</form>
<iframe
id="submitTarget"
name="submitTarget"
style="display: none"
frameborder="0"
></iframe>

<div id="msg"></div>

<script>
(function () {
const form = document.getElementById('form');
let prefix = '';

// url encode format for encoding more characters
const camSafeUrlEncode = function (str) {
return encodeURIComponent(str)
.replace(/!/g, '%21')
.replace(/'/g, '%27')
.replace(/\(/g, '%28')
.replace(/\)/g, '%29')
.replace(/\*/g, '%2A');
};

// Calculate the signature.
const getAuthorization = function (opt, callback) {
// Replace with your server address to get the post upload signature, demo: https://github.com/tencentyun/cos-demo/blob/main/server/upload-sign/nodejs/app.js
const url = http://127.0.0.1:3000/post-policy?ext=${opt.ext || ''};
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
let credentials;
try {
const result = JSON.parse(e.target.responseText);
credentials = result;
} catch (e) {
callback('Error in getting signature');
}
if (credentials) {
// Print to confirm if the credentials are correct
// console.log(credentials);
callback(null, {
securityToken: credentials.sessionToken,
cosKey: credentials.cosKey,
cosHost: credentials.cosHost,
policy: credentials.policy,
qAk: credentials.qAk,
qKeyTime: credentials.qKeyTime,
qSignAlgorithm: credentials.qSignAlgorithm,
qSignature: credentials.qSignature,
});
} else {
console.error(xhr.responseText);
callback('Error in getting signature');
}
};
xhr.send();
};

// Listen upload completion
let Key;
const submitTarget = document.getElementById('submitTarget');
const showMessage = function (err, data) {
console.log(err || data);
document.getElementById('msg').innerText = err
? err
: 'Upload successfully, ETag=' + data.ETag;
};
submitTarget.onload = function () {
let search;
try {
search = submitTarget.contentWindow.location.search.substr(1);
} catch (e) {
showMessage('file' + Key + 'Upload failed');
}
if (search) {
const items = search.split('&');
let i = 0;
let arr = [];
const data = {};
for (i = 0; i < items.length; i++) {
arr = items[i].split('=');
data[arr[0]] = decodeURIComponent(arr[1] || '');
}
showMessage(null, {
url: prefix + camSafeUrlEncode(Key).replace(/%2F/g, '/'),
ETag: data.etag,
});
} else {
}
};

// Start uploading
document.getElementById('submitBtn').onclick = function (e) {
const filePath = document.getElementById('fileSelector').value;
if (!filePath) {
document.getElementById('msg').innerText = 'No file selected for upload';
return;
}
// Get the file extension
let ext = '';
const lastDotIndex = filePath.lastIndexOf('.');
if (lastDotIndex > -1) {
// Get the file extension here. The server generates the final upload path.
ext = filePath.substring(lastDotIndex + 1);
}
getAuthorization({ ext }, function (err, AuthData) {
if (err) {
alert(err);
return;
}
const protocol =
location.protocol === 'https:' ? 'https:' : 'http:';
prefix = protocol + '//' + AuthData.cosHost;
form.action = prefix;
Key = AuthData.cosKey;
// Put an empty.html in the current directory so that the API can jump back after uploading
document.getElementById('success_action_redirect').value =
location.href.substr(0, location.href.lastIndexOf('/') + 1) +
'empty.html';
document.getElementById('key').value = AuthData.cosKey;
document.getElementById('policy').value = AuthData.policy;
document.getElementById('q-sign-algorithm').value =
AuthData.qSignAlgorithm;
document.getElementById('q-ak').value = AuthData.qAk;
document.getElementById('q-key-time').value = AuthData.qKeyTime;
document.getElementById('q-signature').value = AuthData.qSignature;
document.getElementById('x-cos-security-token').value =
AuthData.securityToken || '';
form.submit();
});
};
})();
</script>
</body>
</html>
The execution effect is as shown below:



Restricting the File Type and the File Size During Upload

File Types Limited by Front End
Refer to the above AJAX PUT upload, add a layer of judgment when selecting files. (Only restricted file extensions are supported)
// Omit other codes
document.getElementById('submitBtn').onclick = function (e) {
const file = document.getElementById('fileSelector').files[0];
if (!file) {
document.getElementById('msg').innerText = 'No file selected for upload';
return;
}
// Get the file extension
const fileName = file.name;
const lastDotIndex = fileName.lastIndexOf('.');
const ext = lastDotIndex > -1 ? fileName.substring(lastDotIndex + 1) : '';

// Please replace with the formats you want to restrict, for example, only jpg and png files
const allowExt = ['jpg', 'png'];
if (!allowExt.includes(ext)) {
alert('Only jpg and png files are supported for upload');
return;
}

file &&
uploadFile(file, function (err, data) {
console.log(err || data);
document.getElementById('msg').innerText = err
? err
: 'Upload successfully, ETag=' + data.ETag + 'url=' + data.url;
});
};
File Size Limited by Front End
Refer to the above AJAX PUT upload, add a layer of judgment when selecting files.
// Omit other codes
document.getElementById('submitBtn').onclick = function (e) {
const file = document.getElementById('fileSelector').files[0];
if (!file) {
document.getElementById('msg').innerText = 'No file selected for upload';
return;
}
const fileSize = file.size;

// Please replace with the object size you want to restrict, up to a maximum of 5GB per object. For example, restrict uploaded files to no more than 5MB.
if (fileSize > 5 * 1024 * 1024) {
alert('The selected file exceeds 5MB, please selected another one');
return;
}

file &&
uploadFile(file, function (err, data) {
console.log(err || data);
document.getElementById('msg').innerText = err
? err
: 'Upload successfully, ETag=' + data.ETag + 'url=' + data.url;
});
};
Server-Side Signature Restriction
Refer to the server-side signature code Nodejs Example for put-sign-limit and post-policy-limit.

Reference

If you need to call more APIs, please see the following JavaScript SDK document: