Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

🤔 Converts net/http request handlers to fiber request? #299

Closed
alvarowolfx opened this issue Apr 25, 2020 · 11 comments
Closed

🤔 Converts net/http request handlers to fiber request? #299

alvarowolfx opened this issue Apr 25, 2020 · 11 comments
Assignees

Comments

@alvarowolfx
Copy link

alvarowolfx commented Apr 25, 2020

Question description

My use case is to user Fiber inside Google Cloud Functions or other places that enforce us to use the go http.Handler interface.

The code snippet below is an example using functions-framework-go. I know that I can probably go ahead and use App Engine or Cloud Run, but would be interesting to support also Cloud Functions.

Sorry if this is a silly question, I'm not that used to Go and I was trying to add more recipes to the repository and encourage people to use Fiber on all Google Cloud Go environments.

Code snippet

import (
	"net/http"
	"os"

	"github.com/GoogleCloudPlatform/functions-framework-go/funcframework"
	"github.com/gofiber/fiber"
)

var app *fiber.App

func init() {	
	app = fiber.New()

	app.Get("/", func(c *fiber.Ctx) {
		c.Send("Hello World 🚀")
	})
}

func MyFunction(w http.ResponseWriter, r *http.Request) {	
	app.ServeHTTP(w, r)	// What to do here to feed app.Fiber with req and response ???
}

func main() {
	funcframework.RegisterHTTPFunction("/", MyFunction)
	port := "8080"
	if envPort := os.Getenv("PORT"); envPort != "" {
		port = envPort
	}

	if err := funcframework.Start(port); err != nil {
		log.Fatalf("app.Start: %v\n", err)
	}
}
@welcome
Copy link

welcome bot commented Apr 25, 2020

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

@Shareed2k
Copy link
Contributor

Shareed2k commented Apr 25, 2020

you can try something like this

import (
	"github.com/gofiber/fiber"
	"github.com/valyala/fasthttp/fasthttpadaptor"
	"net/http"
)

...

g.Get("/", WrapHandler(pprof.Index))

func WrapHandler(f func(http.ResponseWriter, *http.Request)) func(ctx *fiber.Ctx) {
	return func(ctx *fiber.Ctx) {
		fasthttpadaptor.NewFastHTTPHandler(http.HandlerFunc(f))(ctx.Fasthttp)
	}
}

@Fenny
Copy link
Member

Fenny commented Apr 25, 2020

@Shareed2k is right, the fasthttpadaptor converts net/http request handlers to fasthttp request handlers. But keep in mind that this function may be used for easy switching from net/http to fasthttp, it has the following drawbacks comparing to using manually written fasthttp request handler:

  • A lot of useful functionality provided by fasthttp is missing
    from net/http handler.
  • net/http -> fasthttp handler conversion has some overhead,
    so the returned handler will be always slower than manually written
    fasthttp handler.

So it is advisable using this function only for development but then convert the net/http handlers to fiber when going into production.

@Fenny Fenny changed the title There is a way to feed Fiber routing with a standard Go http.Handler interface ? 🤔 🤔 Converts net/http request handlers to fiber request? Apr 25, 2020
@alvarowolfx
Copy link
Author

alvarowolfx commented Apr 26, 2020

Hey, thanks for the support. What I wanted to do was the opposite of that. I was able to do that by using an HTTP client calling the internal Fiber app.

It's super cumbersome but solves the problem on the Cloud Functions case that strictly requires us to use http.HandlerFunc. For Cloud Run and App Engine that will not be required. I'll try to wrap up some basic examples for each Serverless platform on Google Cloud and open a PR on the recipes repository if you guys think that's valuable.

package functions

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"

	"github.com/GoogleCloudPlatform/functions-framework-go/funcframework"
	"github.com/gofiber/fiber"
	"github.com/valyala/fasthttp/fasthttputil"
)

var app *fiber.App

func init() {
	app = fiber.New()
	group := app.Group("/api")

	group.Get("/hello", func(c *fiber.Ctx) {
		c.Send("Hello World 🚀")
	})

	group.Get("/ola", func(c *fiber.Ctx) {
		c.Send("Olá Mundo 🚀")
	})
}

func MyCloudFunction(w http.ResponseWriter, r *http.Request) {
	ln := fasthttputil.NewInmemoryListener()
	defer ln.Close()

	// Copy request
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	url := fmt.Sprintf("%s://%s%s", "http", "0.0.0.0", r.RequestURI)
	proxyReq, err := http.NewRequest(r.Method, url, bytes.NewReader(body))
	proxyReq.Header = r.Header

	// Create http client
	client := http.Client{
		Transport: &http.Transport{
			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
				return ln.Dial()
			},
		},
	}

	// Serve request to internal HTTP client
	go func() {
		err := app.Serve(ln)
		if err != nil {
			log.Fatalf("server err : %v", err)
			panic(err)
		}
	}()

	// Call internal Fiber API
	response, err := client.Do(proxyReq)
	if err != nil {
		fmt.Fprintf(w, "err : %v", err)
		return
	}

	// Copy response and headers
	for k, values := range response.Header {
		for _, v := range values {
			w.Header().Set(k, v)
		}
	}
	w.WriteHeader(response.StatusCode)

	io.Copy(w, response.Body)
	response.Body.Close()
}

func main() {
	funcframework.RegisterHTTPFunction("/", MyCloudFunction)

	port := "8080"
	if envPort := os.Getenv("PORT"); envPort != "" {
		port = envPort
	}

	if err := funcframework.Start(port); err != nil {
		log.Fatalf("app.Start: %v\n", err)
	}
}

@gocs
Copy link

gocs commented Apr 26, 2020

you can try something like this

import (
	"github.com/gofiber/fiber"
	"github.com/valyala/fasthttp/fasthttpadaptor"
	"net/http"
)

...

g.Get("/", WrapHandler(pprof.Index))

func WrapHandler(f func(http.ResponseWriter, *http.Request)) func(ctx *fiber.Ctx) {
	return func(ctx *fiber.Ctx) {
		fasthttpadaptor.NewFastHTTPHandler(http.HandlerFunc(f))(ctx.Fasthttp)
	}
}

Is there any way to prevent that pprof dependency?

@Shareed2k
Copy link
Contributor

pprof is just example

@alvarowolfx
Copy link
Author

Just submitted the PR adding the Google Cloud examples gofiber/recipes#16

@Fenny
Copy link
Member

Fenny commented Apr 29, 2020

@alvarowolfx, I'm happy you got this working. Too bad Google Cloud forces you to use a http.HandlerFunc instead of a fixed port.

I will work on a simple adapter middleware for fiber when I have some spare time left. Thanks for sharing your problem and solution, I will mention this issue in an upcoming TODO issue.

PS: @alvarowolfx are you able to listen on a second port? If so you could proxy the request to the fiber port.

@Fenny Fenny closed this as completed Apr 29, 2020
@alvarowolfx
Copy link
Author

I'll take a look on starting it on a separated port and proxying the request, but I think the current workaround that I sent do almost that.

But at least this is only required on Cloud functions, maybe on the recipes repo we encourage people to use app engine or cloud run for now, as this is not needed on those environments.

@dorneanu
Copy link

Hi,

is it "safe"/production-ready to use adaptor?

Cheers,
Victor

@ReneWerner87
Copy link
Member

yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants