Introduzione
Copiare file da una macchina a un'altra è un’operazione piuttosto semplice quando si ha accesso fisico ad entrambe le macchine. Qualora tu stia utilizzando un Cloud Server, e non avessi quindi fisicamente accesso alla macchina sulla quale vuoi inviare una copia dei tuoi file, la maniera più sicura per effettuare questo tipo di trasferimento è sicuramente una copia mediante il protocollo SSH.
Secure Copy Protocol (SCP) è un protocollo che sfrutta questo tipo di connessione, garantendo integrità e sicurezza nell’invio dei file da una sorgente al destinatario. Oltre a poter effettuare questo tipo di copia mediante semplice shell, molti linguaggi di programmazione forniscono moduli e librerie per includere questa operazione all’interno dei propri progetti.
In questo tutorial vedrai come effettuare l’invio e la ricezione di file e cartelle in Node.JS, indipendentemente dal sistema operativo della macchina sorgente e destinataria.
Per procedere dovrai aver già installato sul tuo sistema il framework Node.js. Qualora non l'avessi ancora fatto e utilizzassi un server Linux Ubuntu, puoi seguire la nostra guida su Come installare Node.js su Ubuntu 18.04.
Installazione moduli Node.js
Il trasferimento via SCP in Node.JS è possibile grazie al modulo “node-scp”installabile mediante il Packet Manager di Node. Apri quindi il terminale e procedi alla sua installazione mediante il comando:
npm i node-scp
Se per collegarti in SSH al dispositivo destinatario hai bisogno di indicare anche la chiave privata, avrai bisogno di installare un ulteriore modulo che ti servirà per far inviare anche la tua chiave “.pem” dallo script. In tal caso, procedi con l’installazione del modulo “fs” tramite:
npm i fs
Definizione server remoto
All’interno del tuo progetto inizia con il definire tutti i parametri che utilizzerai per collegarti al destinatario come se ti collegassi via SSH.
const scp = require('node-scp')
//var fs = require('fs');
var remote_server = {
host: 'your host', //remote host ip
port: 22, //port used for scp
username: 'username', //username to authenticate
password: 'password', //password to authenticate
// forceIPv4: boolean, //Connection allow only via resolved IPv4 address (true/false)
// forceIPv6: boolean, //Connection allow only via resolved IPv6 address (true/false)
// privateKey: fs.readFileSync('./key.pem'),
// passphrase: 'your key passphrase',
}
La variabili scp ed fs rappresentano in Node.JS una istanziazione delle librerie appena installate. Mediante queste variabili sarà infatti possibile accedere ai metodi messi a disposizione dalle librerie, rispettivamente per inviare/ricevere file e per fare accesso ai file salvati sul client.
L’oggetto “remote_server” rappresenta invece l’host destinatario da dover interrogare e, nello specifico:
- host: indirizzo IP dell’host destinatario;
- port: numero della porta sulla quale collegarsi (di default per la connessione ssh/scp viene usata la porta 22);
- username: username dell’utente per effettuare il login sull’host destinatario;
- password: password dell’utente per effettuare il login sull’host destinatario;
- forceIPv4 (OPZIONALE): nel caso in cui fosse necessario indicare la connessione mediante solo IPv4 impostare tale parametro a “true”;
- forceIPv6 (OPZIONALE): nel caso in cui fosse necessario indicare la connessione mediante solo IPv6 impostare tale parametro a “true”;
- privateKey (OPZIONALE): nel caso in cui fosse necessario indicare la chiave per la connessione al destinatario, decommenta questa riga e passa come argomento della funzione “fs.readFileSync” il percorso del file “key.pem” presente nell’host sorgente;
- passphrase (OPZIONALE): nel caso in cui fosse necessario indicare la passphrase per il login all’host destinatario, decommenta questa riga e indica la passphrase come stringa di tale attributo.
Promise vs Async-Await
Prima di procedere con il mostrare le diverse tipologie di invio e ricezione di file e cartelle, è fondamentale capire la differenza tra i concetti di “Promise” e di “Async-Await”.
Inviare files con Promise
Mediante il seguente snippet di codice, utilizzando il meccanismo delle Promise, potrai inviare il file (il cui percorso è definito nella variabile “local_file_path”) sull’host destinatario salvandolo nel percorso indicato nella variabile “destination_file_path”.
var local_file_path = './test.txt';
var destination_file_path = '/workspace/test.txt';
send_file_using_promise(local_file_path, destination_file_path);
function send_file_using_promise(file_path, destination_path){
scp(remote_server).then(client => {
client.uploadFile(file_path, destination_path)
.then(response => {
client.close()
})
.catch(error => {})
}).catch(e => console.log(e))
}
La funzione “send_file_using_promise” tenterà una connessione al server precedentemente definito (remote_server) e in caso di riuscita effettuerà l’upload del file sul percorso prestabilito.
Al termine dell’invio chiuderà la connessione e rilascerà il comando a Node.JS per l’esecuzione di eventuali ulteriori funzioni previste.
Inviare files con Async/Await
Analogamente a quanto descritto prima, anche questa porzione di codice effettuerà l’upload del file specificato nella variabille local_file_path sull’host “remote_server” nel percorso indicato nella variabile “detination_file_path”.
var local_file_path = './test.txt';
var detination_file_path = '/workspace/test.txt'; send__fileusing_async_await(local_file_path, detination_file_path); async function send_file_using_async_await(file_path, destination_path){
try {
const c = await scp(remote_server)
await c.uploadFile(file_path, destination_path)
c.close()
} catch (e) {
console.log(e)
}
}
In questo caso però l’operazione avviene in maniera Asincrona, la cui logica viene comunque temporizzata mediante gli “await” posti all’interno della funzione “send_file_using_async_await”.
Scaricare file con Promise
Per scaricare invece il file con Promise utilizziamo il seguente codice:
var local_file_path = './test.txt';
var detination_file_path = '/workspace/test.txt';
download_file_using_promise(detination_file_path, local_file_path);
function download_file_using_promise(file_path, destination_path){
scp(remote_server).then(client => {
client.downloadFile(file_path, destination_path)
.then(response => {
client.close()
})
.catch(error => {})
}).catch(e => console.log(e))
}
Il protocollo SCP è bidirezionale: così come è possibile effettuare l’upload di un file su un destinatario è possibile effettuare il download dello stesso da una macchina sul proprio client. Le macchine quindi si scambiano i ruoli di sorgente e destinatario e questo cambio trova rappresentazione nella sostituzione del metodo “client.uploadFile” con “client.downloadFile”.
La logica del flusso resta invariata con la differenza che il file “text.txt” non viene più inviato dalla tua macchina verso quello che prima era l’host destinatario bensì viene acquisito sulla tua macchina da quest’ultimo. Da notare come i parametri passati alla funzione risultino invertiti rispetto alla funzione precedente.
Scaricare file con Async/Await
In maniera similare avviene il download del file appena mostrato mediante async-await:
var local_file_path = './test.txt';
var detination_file_path = '/workspace/test.txt';
download_file_using_async_await(detination_file_path, local_file_path);
async function download_file_using_async_await(file_path, destination_path){
try {
const client = await scp(remote_server)
await client.downloadFile(file_path, destination_path)
client.close()
} catch(e) {
console.log(e)
}
}
L’invocazione della funzione “download_file_using_async_await” tenterà la connessione al client, aspetterà il rilascio del comando dato dall’avvenuta connessione allo stesso ed effettuerà il download del file nel percorso indicato nel secondo parametro passato alla funzione. Una volta scaricato il file verrà salvato nel percorso indicato nella variabile “local_file_path”.
Caricare cartelle
Analogamente ai file, é possibile utilizzare scp per trasferire intere cartelle da un macchina all'altra.
Nei seguenti esempi ti mostriamo una implementazione di esempio per il caricamento di cartelle, utilizzando sia Promise sia Async/Await:
Promise
var local_folder_path = './local/dir';
var detination_folder_path = '/server/path';
send_folder_using_promise(local_folder_path, detination_folder_path);
function send_folder_using_promise(folder_path, destination_path){
scp(remote_server).then(client => {
client.uploadDir(folder_path, destination_path)
.then(response => {
client.close()
})
.catch(error => {})
}).catch(e => console.log(e))
}
Async/Await
var local_folder_path = './local/dir';
var detination_folder_path = '/server/path';
send_folder_using_async_await(local_folder_path, detination_folder_path);
async function send_folder_using_async_await(folder_path, destination_path)
{
try {
const client = await scp(remote_server)
await client.uploadDir(folder_path, destination_path)
client.close()
} catch (e) {
console.log(e)
}
}
Verificare l'esistenza di un percorso
Vediamo infine come verificare se sulla nostra macchina remota esista o meno un determinato percorso.
Questa funzionalità potrebbe tornarti molto utile qualora avessi bisogno di controllare l'effettivo caricamento di file o di cartelle. Potresti quindi usarla come una verifica finale del tuo script, provando a ritentare il caricamento se ci dovesse essere stato qualche errore durante il trasferimento.
Promise
var path_to_be_checked = '/server/path';
check_if_path_extists_using_promise(path_to_be_checked);
function check_if_path_extists_using_promise(path_to_check){
scp(remote_server).then(client => {
client.exists(path_to_check)
.then(response => {
client.close()
})
.catch(error => {})
}).catch(e => console.log(e))
}
Async/Await
var path_to_be_checked = '/server/path';
check_if_path_extists_using_async_await(path_to_be_checked);
async function check_if_path_extists_using_async_await(path_to_check){
try {
const client = await scp(remote)
await client.exists(path_to_check)
client.close()
} catch (e) {
console.log(e)
}
}