hono icon indicating copy to clipboard operation
hono copied to clipboard

Context method to send request to self

Open nicksrandall opened this issue 2 years ago • 10 comments

Sometimes in a view route, I want to call an another route handler to get data. It would be awesome if Hono has this built in.

Runtimes like Cloudflare workers do not allow workers to "fetch" themselves over the network so this would be a nice work around that would allow hono to skip the network entirely and just call itself.

Proposed API

const app = new Hono():

app.get("/api/data", async c => {
  // get some data
  return c.json(data);
});

app.get("/view", async c => {
  const resp = await c.fetch("/api/data", c.req.raw); // follows `Fetch` API
  const data = await resp.json();
  return c.render(<SomeComponent data={data} />);
});

I'd be happy to send a PR if maintainers are open to this change.

Originally posted by @nicksrandall in https://github.com/honojs/hono/discussions/1675

nicksrandall avatar Nov 09 '23 22:11 nicksrandall

Hi, @nicksrandall

I think we can do it with this code:

app.get('/api', (c) => {
  return c.json({ foo: 'bar' })
})

app.get('/', async (c) => {
  const res = await app.request('/api')
  return c.json(await res.json())
})

yusukebe avatar Nov 13 '23 08:11 yusukebe

Thanks for responding @yusukebe

As I understand it, that code does not keep track of the execution context or environment variables so it doesn't work for my use-case.

nicksrandall avatar Nov 13 '23 14:11 nicksrandall

@yusukebe to be more specific, the code you suggest does work for very simple use-cases but it doesn't not work if the API route is using ENV variables or execution context (in a cloudflare worker). That is why I think it makes sense for a new method to live on the "context" because we want to execute the handler with the current context. While I think c.fetch(...) name makes the most sense to me, we could just as easily name the method c.request(...) if you felt like that was more consistent. Thoughts?

If we made that change, your example would look something like this:

app.get('/api', (c) => {
  return c.json({ foo: 'bar' })
})

app.get('/', async (c) => {
  const res = await c.request('/api')
  return c.json(await res.json())
})

Again thanks for your help with this!

nicksrandall avatar Nov 14 '23 16:11 nicksrandall

I'm not run it yet, but I think this would work with ENV:

app.get('/', (c) => {
  app.request('/api', c.req.raw, c.env)
  return c.json(0)
})

yusukebe avatar Nov 14 '23 21:11 yusukebe

@yusukebe Your suggestion does pass provides the env variables but it still doesn't pass the execution context.

nicksrandall avatar Dec 04 '23 15:12 nicksrandall

Hi.

You can also pass the execution context like the following:

app.request('/api', c.req.raw, c.env, c.executionCtx)

yusukebe avatar Dec 05 '23 08:12 yusukebe

That works, however, this error is thrown in the console:

Your worker created multiple branches of a single stream (for instance, by calling response.clone()orrequest.clone()) but did not read the body of both branches. This is wasteful, as it forces the system to buffer the entire stream of data in memory, rather than streaming it through. This may cause your worker to be unexpectedly terminated for going over the memory limit. If you only meant to copy the request or response headers and metadata (e.g. in order to be able to modify them), use the appropriate constructors instead (for instance, new Response(response.body, response), new Request(request), etc).

Zerebokep avatar Feb 06 '24 19:02 Zerebokep

@Zerebokep

That warning is annoying, but you can ignore it.

yusukebe avatar Feb 08 '24 08:02 yusukebe