gofiber / fiber

⚡️ Express inspired web framework written in Go
https://gofiber.io
MIT License
34.04k stars 1.67k forks source link

🤗 [Question]: Calling other endpoints without network #2949

Closed nexovec closed 6 months ago

nexovec commented 7 months ago

Question Description

Hi, Is it possible to call another route from the same fiber server without going through the network?

In the snippet below, I would like to call /api/endpoint from the /ui that returns a nice UI with the result of the api call (It needs to set fiber to being awesome just in case someone forgot to do it), and to call it with all the registered middlewares and the original request headers, without going through the network, because it seems like an unneeded step.

Is this possible?

Code Snippet (optional)

package main

import "github.com/gofiber/fiber/v3"
import "log"

func main() {
  app := fiber.New()

  // An example to describe the question
        app.Get("/ui", func(c *fiber.Ctx) error {
                ret := req.Get("/api/endpoint"), or something // TODO: help needed
        return templates.WelcomeUI(ret).Render(c.Context() ... )
    })
        api := fiber.Group("/api", AdminOnlyMw)
    api.Get("/endpoint", func(c *fiber.Ctx) error {
                technology.fiber.SetAwesome(true)
        return c.SendText("fiber is awesome")
    })

  log.Fatal(app.Listen(":3000"))
}

Checklist:

welcome[bot] commented 7 months ago

Thanks for opening your first issue here! 🎉 Be sure to follow the issue template! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

gaby commented 7 months ago

@nexovec Why do you need to call another handler just to set a boolean? Move that logic to a separate function and call that function from both handlers.

nexovec commented 7 months ago

@gaby I am aiming at having less indirection and less boilerplate code for actually dispatching an HTTP request, so having a function for setting a boolean is making this worse, not better. The routes being called are always depending on the expected middlewares being called too. Needless to say, my use case is more complicated, with about 3–4 levels of this indirection that I just can't do anything about(think frontend calling a backend calling an email dispatcher calling authorization).

I would like to "call the router," not the function, ideally while staying inside the fiber framework and not going through the network. Is the purpose of the question clear?

gaby commented 7 months ago

@nexovec Yes, it's clear

@ReneWerner87 Is this even possible? User is using v3 already.

ReneWerner87 commented 7 months ago

In v2 it was possible with https://docs.gofiber.io/api/ctx#redirecttoroute and I think the function is still there.

nexovec commented 7 months ago

In v2 it was possible with https://docs.gofiber.io/api/ctx#redirecttoroute and I think the function is still there.

My understanding is these redirect methods do a client side redirect (basically responding to the original request with a 302). Am I wrong?

nexovec commented 7 months ago

I am still looking for a solution to this issue. To stimulate discussion and give a better idea, this is how I think this could look like:

package main

import "github.com/gofiber/fiber/v3"
import "log"

func main() {
  app := fiber.New()

  // An example to describe the question
        app.Get("/ui", func(c *fiber.Ctx) error {
                response, err := c.CallRoute("/api/endpoint")
        return templates.WelcomeUI(response).Render(c.Context() ... )
    })
        api := fiber.Group("/api", AdminOnlyMw)
    api.Get("/endpoint", func(c *fiber.Ctx) error {
                technology.fiber.SetAwesome(true)
        return c.SendText("fiber is awesome")
    })

  log.Fatal(app.Listen(":3000"))
}

The method <fiber.Ctx>.CallRoute would call another route on the same server without going through the network at all. It would pass the original requests information to the newly called route. In this case, there should also exist a fiber.CallRoute function that would enable you to call a route without having a fiber context object (by having to specify all the request information as parameters).

balexandre commented 6 months ago

my 2 cents here,

if you create a controller a-like, wouldn't be better to just call it from within your call?

example:

package main

import "github.com/gofiber/fiber/v3"
import "log"

func main() {
  app := fiber.New()

  // An example to describe the question
  app.Get("/ui", func(c *fiber.Ctx) error {
    textToSend := setEndpointText(true)
    return templates.WelcomeUI(textToSend).Render(c.Context() ... )
  })

  api := fiber.Group("/api", AdminOnlyMw)
  api.Get("/endpoint", func(c *fiber.Ctx) error {
    textToSend := setEndpointText(true)
    return c.SendText(textToSend)
  })

  log.Fatal(app.Listen(":3000"))
}

func setEndpointText(txt string) string {
  technology.fiber.SetAwesome(true)
  return "fiber is awesome"
}

in a way you would not spend time in a request by redirecting it, and you end up encapsulating the data you want in both cases into just one internal call

kbiits commented 6 months ago

@nexovec You shouldn't look for the answers in a web framework library.

Things you may need to consider is it the network overhead really blocking you? If not, just go with the network call, especially if it happens on server side I think the latency should be negligible for your users.

What if the network call is really blocking you? You should refactor your code. You can create a pipeline pattern to make the behavior similar to middleware in fiber, and you should abstract your code so you can reuse it inside fiber middlewares and remove duplicates on your codebase.

nexovec commented 6 months ago

@kbiits the primary concern is reliability of the connection, the latency is a really bad problem and you shouldn't need to do a client side redirect unless you specifically need to, but the fact the request might not even arrive is much more troubling.

I just have to comment on how bad of an architectural decision it is to have 2 separate middleware systems, even worse when you make the other one to supplement the first one. Fiber is already doing this for me, it makes me really happy I don't have to deal with this myself.

I like code locality and so I don't have the slightest reason to abstract away from the fiber API either, I like it, I don't have duplicity problems.

In fact, I'd like to rely on fiber to provide faculties to simulate requests and interfaces for testing and introspection, and this falls well within the scope of what a framework should offer, if for nothing else than how closely coupled those are to the framework.

The only thing I'm asking for rn is to be able to "call a route" as you would with a function.

(Not getting confrontational, just hard disagree)

Is this understandable?

kbiits commented 6 months ago

Yes, it's all clear @nexovec. But I don't see any reason for fiber to provide such API. Why does fiber need to provide an API to simulate requests without a network? If it's used for testing, yes, it makes sense, but I don't get it to expose such API to the fiber users. There are many ways for fiber users to test their code without relying on the API you mentioned. Also, your usecase is very niche.

Nevertheless, I think you can still use *fiber.App.Stack() method which returns fiber stack routes to achieve what you want. You need to filter the routes that you want and call the handlers just like you call functions.

image
nexovec commented 6 months ago

Nevertheless, I think you can still use *fiber.App.Stack() method

@kbiits This is exactly what I imagined! Thank you so much. It's a bit clumsy to use this directly, I will likely write a convenience wrapper for this. This is massively helpful.

Also, your usecase is very niche.

This is a bit sad to hear. To me going through network when you don't have to is an extra failure point, I feel a bit of joy anytime I can avoid it.

kbiits commented 6 months ago

@kbiits This is exactly what I imagined! Thank you so much. It's a bit clumsy to use this directly, I will likely write a convenience wrapper for this. This is massively helpful.

Glad to help.

This is a bit sad to hear. To me going through network when you don't have to is an extra failure point, I feel a bit of joy anytime I can avoid it.

Yes, I really understand your point. We engineers of course don't want to add overhead to our system. But, objectively speaking, fiber also need to keep their library to be as simple as it can.

All the best for your projects