denoland / deploy_feedback

For reporting issues with Deno Deploy

Home Page:https://deno.com/deploy

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Recursive requests and `loop detected`

mashaal opened this issue · comments

At the moment, recursive requests to a Deno Deploy instance are blocked. This prevents interesting use cases like allowing an SSR framework to call an internal route, and render the correct content.

Ideally, the same code would work in browser as well as Deno Deploy. Right now, it only works in browser.

Mock code:

const Content = () => {
  const { data, error } = useSWR('test', fetcher);
  return <h1>{data.title}</h1>;
};

const fetcher = () =>
  fetch(
    `https://my_deno_deploy_url/api/test`,
  ).then((data) => data.json());

This works fine when used locally with the Deno CLI, as well as in the browser (during client side navigation).

But when requesting the route directly from Deno Deploy, I get this error:

508: Loop Detected (LOOP_DETECTED)\n\nRecursive requests to the same deployment cannot be processed.

It depends on your router, but I would think trying to call the handler directly would be more performant than going through a HTTP request. You already have the mapping of routes and their handler functions. You should be able to just call it directly by passing new Request.

Just encoountered the same issue, pretty hard to debug since it only happens on Deno Deploy and not locally. Calling the handler directly seems like a fine workaround but not the ideal solution...

@mashaal, I'm seeing this behaviour as well, and only on Deno Deploy as well. When you hit this route, https://chicago-fresh.deno.dev/joke-of-the-day, boom.

Here's the relevant source code.

import { HandlerContext, Handlers, PageProps } from "$fresh/server.ts";
import Layout from "../components/Layout.tsx";

type Joke = string;

export const handler: Handlers<Joke> = {
  async GET(req: Request, ctx: HandlerContext<Joke>) {
    const jokeUrl = new URL("/api/joke", req.url);
    console.log(jokeUrl);
    const response = await fetch(jokeUrl);
    const joke = await response.text();

    return ctx.render(joke);
  },
};

export default function ProjectPage(props: PageProps<Joke>) {
  return (
    <Layout title="Movies">
      <h1>Joke of the Day</h1>
      <p>{props.data}</p>
    </Layout>
  );
}

https://github.com/nickytonline/chicago-js-dec-2022-fresh-demo/blob/main/routes/joke-of-the-day.tsx

I'm also experiencing this limitation in Deno Deploy (no issue running my Fresh project locally).

I would prefer to keep my user-facing and API routes in the same project. My goal is to separate my frontend and backend concerns while also being able to reuse shared functionality. Handlers being unable to call routes from the same project would require me to break out the frontend and API routes into separate projects. That doesn't feel good.

Are there different/better ways to detect loops in Deploy?

You can call your API route handler functions directly from the route handlers

Ran into this same issue server rendering react-query calls with Ultra.

In case this helps anyone, this pattern is hacky but works:

export async function fetcher() {
  if (IS_SERVER) {
    const server_fetcher = (await import('./server.ts')).default
    return server_fetcher()
  }
  const client_fetcher = return (await import('./client.ts')).default
  return client_fetcher()
}

Where the default export at ./server.ts is the underlying server-side function (which would also be called and returned by your Hono route handler, or whatever), and where the default export at ./client.ts is a client-side fetch call to the api route.

I'm also experiencing this issue. In my case, I'm proxying from Deno Deploy and then the proxied site (also hosted on Deno Deploy ) issues a redirect which is then followed. When I run the proxy locally it works just fine.

Also worth noting that, when I hit this, it doesn't show up in any of the logs.

For anybody else running into this while implementing a proxy, the easiest (and arguably most correct) fix is to set redirect to manual on the call to fetch(). That way, the proxy passes the 302 back to the browser and the browser re-enters the redirect itself. While it does result in an extra request to the proxy, it does make the proxy act more like it isn't there which is generally what you want.