Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

The HTTP request cancellation event is not fired in the Firebase HTTP Function #1585

Open
wiesjan opened this issue Jul 12, 2024 · 4 comments

Comments

@wiesjan
Copy link

wiesjan commented Jul 12, 2024

Related issues

[REQUIRED] Version info

node:

v20.13.1

firebase-functions:

5.0.1

firebase-tools:

13.9.0

firebase-admin:
12.1.0

express

4.19.2

[REQUIRED] Test case

  • frontend.html
<html>
<head></head>
<body>
<button id="sendExpressApp">send to express app</button>
<button id="sendToFn">send to function</button>

<button id="abort">abort</button>

<script type="text/javascript">
  const callApi = (url) => {
    controller = new AbortController();
    const signal = controller.signal;
    fetch(url, { signal })
      .then((response) => {
        console.log("Request complete", response);
      })
      .catch((err) => {
        console.error(`Request error: ${ err.message }`);
      });
  }

  let controller;
  const expressUrl = "http://127.0.0.1:3001";
  const fnUrl = "http://127.0.0.1:5001/dev-guestacount/europe-west1/firebaseFunction";

  const callExpressBtn = document.getElementById("sendExpressApp");
  const callFunctionBtn = document.getElementById("sendToFn");
  const abortBtn = document.getElementById("abort");

  callExpressBtn.addEventListener("click", () => callApi(expressUrl));
  callFunctionBtn.addEventListener("click", () => callApi(fnUrl));
  abortBtn.addEventListener("click", () => {
    if (controller) {
      controller.abort();
      console.log("Aborted");
    }
  });
</script>
</body>
</html>
  • firebase-function.ts
import express, { Request, Response } from 'express';
import cors from 'cors';
import { https, region } from 'firebase-functions/v1';

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const app = express();
app.use(cors());

app.use('/*', async (req: Request, res: Response) => {
  console.log('start processing...');

  // Detecting close event
  req.on('close', function() {
    const { destroyed } = req;
    console.log('Client connection close....!', { destroyed });
  });

  await delay(5000);

  if (!req.destroyed) {
    console.log('sending response');
    res.send('Hello World!');
  }
});

export const firebaseFunction = region('europe-west1').runWith({ invoker: 'public' })
  .https.onRequest((request: https.Request, response: Response) => app(request, response));

  • express-server.js
const express = require('express');
const cors = require('cors');

const app = express();
const port = 3001;
app.use(cors());

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

app.get('/', async (expressRequest, expressResponse) => {
  console.log('start processing...');

  // Detecting close event
  expressRequest.on('close', function () {
    const { destroyed } = expressRequest;
    console.log('Client connection close....!', { destroyed });
  });

  await delay(5000);

  if (!expressRequest.destroyed) {
    console.log('sending response');
    expressResponse.send('Hello World!')
  }
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${ port }`)
})

[REQUIRED] Steps to reproduce

Run the Firebase function defined in the firebase-function.ts file locally in the emulator, via firebase emulators:start --only functions, and the native Express application, via node express-server.js.
Open the frontend.html file in your browser. When the firebase-function function is loaded by the emulator, click the send to function button in your browser and then (after about a second) the abort button.
Notice in the network tab of the browser's developer tools that the function call was canceled by the browser, but no information about it appeared in the emulator console.
Then click the send to express app button in your browser and, as before, after about a second, click abort. Similarly to the previous case, in the network dev tools tab of the browser you will see that the call has been canceled, but also in the console where the express application is running (express-server.js) you can see a log proving that the event informing about the request cancellation has been received by the application.

[REQUIRED] Expected behavior

The expected behavior is for the req.on('close') event to be triggered when the client (browser) cancels the HTTP request also for the requests received by Firebase Functions, just as it is for the native Express.js application.

[REQUIRED] Actual behavior

Currently, for calls received by the Firebase Function, the req.on('close') event fires only when the call completes, and nothing happens when the client cancels the call.

Were you able to successfully deploy your functions?

There are no problems with deploy

@google-oss-bot
Copy link
Collaborator

I found a few problems with this issue:

  • I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
  • This issue does not seem to follow the issue template. Make sure you provide all the required information.

@exaby73
Copy link
Contributor

exaby73 commented Aug 1, 2024

Hey @wiesjan. Does the same occur on deployed functions as well?

@exaby73 exaby73 added Needs: Author Feedback Issues awaiting author feedback and removed needs-triage labels Aug 1, 2024
@wiesjan
Copy link
Author

wiesjan commented Aug 2, 2024

@exaby73 yes, we observe the same behavior when the function is deployed. The req.on('close') event is fired only after the call is completed, i.e. after 5 seconds in the example provided.

@google-oss-bot google-oss-bot added Needs: Attention and removed Needs: Author Feedback Issues awaiting author feedback labels Aug 2, 2024
@kevsjh
Copy link

kevsjh commented Oct 28, 2024

any update on this? this is useful for llm apps on early user termination

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

4 participants