首页 > 解决方案 > 异步编程 - 如果文件不存在则创建文件然后读取它

问题描述

我正在用 Node.js 编写一个与 AD FS 对话的身份验证微服务,但在正确的时间读取文件时遇到问题。我对 Node.js 和异步编程还是很陌生。我正在尝试使用 Passport 模块,它需要 AD FS 服务器的证书。过去,我只是手动下载证书,并将其放在程序可以读取的位置,但我想摆脱这种情况,并在第一次按需创建它。我可以在玩具程序中毫无问题地创建证书,但是当我尝试将其合并到更大的程序中时,我遇到了文件尚不可用的异步/竞争条件问题。我本质上是在尝试做:

//pseudo code
if (file does not exist) {
    create_file();
    return read_file();
}
else {
    return read_file();
}

问题出在 getAdfsCertificate 函数中。我不知道如何等待写入完成。我想我需要做

writeCertificate(x509CertificateStr, adfsCertificateFilename)
    .then(() => return fs.readFileSync(adfsServerCertFilename));

但我不确定语法,我认为我正在将 Promise 与 async/await 混合和匹配。

我的代码片段如下:

这是请求证书的地方

passport.use(new Strategy(
    {
        jwt: {
            algorithm: 'RS256'
        },
        cert: getAdfsCertificate(process.env.ADFS_SERVER, adfsServerCertFilename),
        path: '',
        realm: process.env.REALM,
        homeRealm: '',
        identityProviderUrl: process.env.IDENTITY_PROVIDER_URL,
        thumbprint: process.env.ADFS_THUMBPRINT
    },
    function (profile, done) {
        // console.log("Auth with", profile);
        if (!profile.upn) {
            return done(new Error("No upn found"), null);
        }
        done(null, profile);
    }
));

这些是获取证书并在必要时创建的相关功能:

async function getAdfsCertificate(adfsServer, adfsCertificateFilename) {
    let fileExists = await fs.promises.access(adfsCertificateFilename).then(() => true).catch(() => false);

    if (!fileExists) {
        console.log('certificate file does not exist');
        
        console.log('fetching FederationMetadata from AD FS Server');
        var xmlResponse = await getFederationMetadataXml(adfsServer);
        
        console.log('extracting x509 certificate from xml');
        var x509CertificateStr = await parseX509FromFederationMetaData(xmlResponse);
        
        console.log('saving x509 certificate to file');
        await writeCertificate(x509CertificateStr, adfsCertificateFilename);
    }
    
    console.log('retrieving certificate from file');
    return fs.readFileSync(adfsServerCertFilename);
}

async function getFederationMetadataXml(adfsServer) {
    try {
        const httpsAgent = new https.Agent({ rejectUnauthorized: false });
        let response = await axios.get('https://' + adfsServer + '/FederationMetadata/2007-06/FederationMetadata.xml', { httpsAgent });
        return response.data;
    } catch (error) {
        console.log('Error getting AD FS FederationMetadData ', error);
    }
}


async function parseX509FromFederationMetaData(metadataXml) {
    try {
        var xmlDoc = libxml.parseXmlString(metadataXml);
        var x509cert = xmlDoc.get('/a:EntityDescriptor/b:Signature/b:KeyInfo/b:X509Data/b:X509Certificate',
            {
                a: 'urn:oasis:names:tc:SAML:2.0:metadata',
                b: 'http://www.w3.org/2000/09/xmldsig#'
            });

        return x509cert.text();
    } catch (error) {
        console.log('Error parsing x509 from FederationMetadata', error);
    }
}

async function writeCertificate(str, filename) {
    try {
        // wrap long lines of certificate to 64 character lines
        const line_length = 64;
        const line_count = Math.ceil(str.length / line_length)

        let writestream = fs.createWriteStream(filename);

        let offset = 0;

        writestream.write('-----BEGIN CERTIFICATE-----\n');

        for (let i = 0; i < line_count; ++i) {
            writestream.write(str.substr(offset, line_length));
            writestream.write('\n');
            offset += line_length;
        }
        
        writestream.write('-----END CERTIFICATE-----\n');

        writestream.end();
    } catch (error) {
        console.log('Error while writing certificate file', error);
    }
}

标签: node.jsasync-await

解决方案


我不确定回答您自己问题的协议,尤其是当它包含来自评论的反馈时。我想发布一个“答案”,所以我可以用完全格式化的代码来说明。

我摆脱了对 fs.promises.access 的异步调用,并将其替换为 fs.existsSync 函数。我还放入了一个 else 块以防止 fs.readSync 在那里被调用。最后,我使用 writeCertificate 承诺中的“.then”来执行 fs.readSync。

async function getAdfsCertificate(adfsServer, adfsCertificateFilename) {
    if (! fs.existsSync(adfsCertificateFilename)) {
        // first time app is run, certificate does not exist
        // download and format appropriately
        console.log('certificate file does not exist');
        
        console.log('fetching FederationMetadata from AD FS Server');
        var xmlResponse = await getFederationMetadataXml(adfsServer);
        
        console.log('Extracting x509 certificate from xml');
        var x509CertificateStr = await parseX509FromFederationMetaData(xmlResponse);
        
        console.log('saving x509 certificate to file');
        writeCertificate(x509CertificateStr, adfsCertificateFilename)
            .then(() => { return fs.readFileSync(adfsServerCertFilename);});
    } else {
        // certificate exists, read it
        console.log('retrieving certificate from file');
        return fs.readFileSync(adfsServerCertFilename);
    }
}

修改 writeCertificate 以返回一个承诺。我认为将其声明为“异步”就足够了。

function writeCertificate(str, filename) {
    return new Promise(function(resolve, reject) {
        // wrap long lines of certificate to 64 character lines
        const line_length = 64;
        const line_count = Math.ceil(str.length / line_length)

        let f = fs.createWriteStream(filename);

        let offset = 0;

        f.write('-----BEGIN CERTIFICATE-----\n');

        for (let i = 0; i < line_count; ++i) {
            f.write(str.substr(offset, line_length));
            f.write('\n');
            offset += line_length;
        }
        
        f.write('-----END CERTIFICATE-----\n');
        f.end();

        f.on('finish', resolve);
        f.on('error', reject);
    });
}

推荐阅读