derhuerst / gemini

Gemini protocol server & client for Node.js.
ISC License
49 stars 8 forks source link

add ability to specify a connect timeout #7

Closed binyamin closed 3 years ago

binyamin commented 3 years ago

Context: If I fetch a URL which does not exist, the program seems to hang for around 2 minutes before giving me an error. I want to set it to something shorter.

derhuerst commented 3 years ago

which program hangs? the client?

please post a script that i can use to reproduce the problem.

binyamin commented 3 years ago
const request = require('@derhuerst/gemini/client.js');

const defaultOpts = {
    followRedirects: true,
    verifyAlpnId: () => true,
    tlsOpt: {
        rejectUnauthorized: false
    }
};

function gemfetch(url) {
    return new Promise((resolve, reject) => {
        request(url, defaultOpts, (err, res) => {
            if(err) {
                reject(err);
            }

            res.once("readable", () => {
                const doc = String(res.read());
                resolve(doc);
            })
        })
    })
}

gemfetch("gemini://binyam.in")
.then(res => {
    console.log(res)
}).catch(err => {
    throw err;
})
derhuerst commented 3 years ago

By listening on readable and using read(), you're switching res to non-flowing mode, which in itself is not a problem (for small responses). But read() does not wait until there's data. When you call it, returns whatever is currently buffered in res (which is more or less what the client has received since the last read() call). If there's no response body immediately (either because the server didn't immediately respond with it, or because of network delays), then your first read() call will return null.

There are multiple ways to fully read a readable stream, but since you just want to concatenate all data into a string, this is the simplest way:

let doc = ''
res.on('data', chunk => {
  doc += chunk
})
res.once('end', () => {
  resolve(doc)
})

Also, reject() does not stop the calling function from continuing to run, it is just a regular function. Therefore, your code will try to read from res even if err contains an error. If you return after calling reject(err), you will see the original ETIMEDOUT error.

If you just want to send Gemini requests in fetch style (like your code demonstrates above), use gemini-fetch, it is a thin layer on top of this library.

derhuerst commented 3 years ago

I've just re-read your original question: Is this Issue about a server that doesn't even accept the request? As in, the TLS connection attempt times out?

derhuerst commented 3 years ago

As of @derhuerst/gemini@1.2.0, you can now pass connectTimeout to request():

const request = require('@derhuerst/gemini/client.js');

function gemfetch(url) {
    return new Promise((resolve, reject) => {
        request(url, {
            connectTimeout: 10 * 1000, // 10s
        }, (err, res) => {
            if(err) {
                reject(err);
                return;
            }

            let doc = ''
            res.on('data', (chunk) => {
                doc += chunk;
            })
            res.once('end', () => {
                resolve(doc);
            })
        })
    })
}

gemfetch("gemini://binyam.in")
.then(res => {
    console.log(res)
})
.catch(err => {
    console.error(err);
    process.exit(1)
})
binyamin commented 3 years ago

@derhuerst Thank you. As I mentioned, it's a browser for gemini. How would you recommend I connect to the servers? Does it make sense to use gemini-fetch for each URI?

derhuerst commented 3 years ago

Is the browser supposed to handle large files? If yes, use a stream-based approach. Otherwise I think something like gemini-fetch will be a more convenient API.

binyamin commented 3 years ago

I hadn't thought about file size. I'll probably use gemini-fetch. Thank you again.

derhuerst commented 3 years ago

yeah, a stream based API is only relevant if a) you want to download/display files larger than the memory you want to use up, or b) if you want to start parsing/processing/rendering the file before it has finished downloading.