Search code examples
error-handlingwebserverform-datadeno

How to handle a user abort by Deno.serve


The Deno website has an example at: https://deno.land/[email protected]/runtime/http_server_apis#inspecting-the-incoming-request

I have shortened it to the following code:

const abortController = new AbortController();
Deno.addSignalListener('SIGINT', () => {
    abortController.abort();
});

Deno.serve(
    {
        port:3000,
        signal: abortController.signal
    },
    async (req) => {
        if (req.body) {
            try {
                console.log('Start');
                const body = await req.formData();
                for (const key of body.keys()) {
                    console.log(key);
                }
                console.log('Never reached, when user abort.');
            } catch {
                // Will not be triggered on abort.
                console.log('Error on await req.formData()');
            }
        }

        return new Response("Hello, World!");
    }
);

There is the note below it: Be aware that the req.text() call can fail if the user hangs up the connection before the body is fully received. Make sure to handle this case.

However, I have not found how to catch such a case. Can Deno detect that the connection has been terminated?

By the way, I added the "AbortHandler" for the test. If await req.formData() passes successfully, the server can be terminated with Ctrl + C in the terminal (Linux Mint). If the user cancels the "upload", after the Ctrl + C command is still running a process until I close the terminal.

I think that is why Deno writes on the website, you have to catch this case. How to do this?


Solution

  • From Deno version 1.37 onwards, if a client closes the connection while the body is being consumed (using methods like .json, .arrayBuffer, .text, etc.), the promise will now appropriately reject.


    OLD ANSWER

    You can test that case with the following snippet.

    const body = new ReadableStream({
            start(controller) {
                    controller.enqueue(new Uint8Array([97]));
            }
    });
    
    const abort = new AbortController();
    setTimeout(() => abort.abort(), 1500);
    
    const res = await fetch('http://localhost:3000', { 
       body, 
       method: 'POST', 
       signal: abort.signal 
    });
    
    console.log(res.status);
    console.log(await res.text());
    

    Unfortunately Deno does not detect connection drops, see: https://github.com/denoland/deno/issues/16246

    Deno.serve(
        {
            port:3000,
        },
        async (req) => {
            console.log(req.body);
            if (req.body) {
                try {
                    for await(const chunk of req.body) { 
                        console.log(chunk);
                    }
                    console.log('Never reached, when user abort.');
                } catch {
                    // Will not be triggered on abort.
                    console.log('never reached, bug in Deno');
                }
            }
    
            return new Response("Hello, World!");
        }
    );
    

    You can see that the first chunk is logged, but there's no error at all when the connection is dropped.

    ReadableStream { locked: false }
    Uint8Array(1) [ 97 ]
    

    You can see more about this here: Deno connection close event