valyala / fasthttp

Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http
MIT License
21.94k stars 1.76k forks source link

What is the best practice for forwarding a Request? #1506

Closed davidleitw closed 1 year ago

davidleitw commented 1 year ago

Recently, I have been trying to use fasthttp to write microservices based on dapr. The following is a simple example for practice. First, the client initiates a request to the Verify service, and then the Verify service calls the Addition service after verifying that the JSON is correct. After Addition completes processing, it returns to the Verify service and then to the client.

PS: Since the request needs to be forwarded to the dapr sidecar, the request will always be forwarded to localhost:3500

package function

import (
    "encoding/json"
    "log"
    "net/http"

    "github.com/valyala/fasthttp"
)

const (
    sidecarAddress       = "localhost:3500"
    enableSidecarAddress = true
)

var proxyClient = &fasthttp.HostClient{
    Addr: sidecarAddress,
}

type verifyApiRequestBody struct {
    N1 int `json:"n1"`
    N2 int `json:"n2"`
}

func proxyHandler(ctx *fasthttp.RequestCtx, appId string) {
    log.Println("proxy handler: send request to addition service")
    req, res := fasthttp.AcquireRequest(), fasthttp.AcquireResponse()
    defer fasthttp.ReleaseRequest(req)
    defer fasthttp.ReleaseResponse(res)

    req.Header.SetMethod(fasthttp.MethodPost)
    req.Header.Set("dapr-app-id", appId)
    req.Header.SetContentType("application/json")
    if enableSidecarAddress {
        log.Println("enable sidecar address with req.SetRequestURI")
        req.SetRequestURI("http://localhost:3500")
    }
    // req.SetRequestURI(sidecarAddress)
    req.SetBody(ctx.PostBody())

    if err := proxyClient.Do(req, res); err != nil {
        log.Println("proxy handler, get error: ", err.Error())
    }

    log.Printf("proxy handler got result, status code = %d, body = %s", res.StatusCode(), string(res.Body()))
    ctx.SetBody(res.Body())
    ctx.SetStatusCode(res.StatusCode())
}

func Handler(ctx *fasthttp.RequestCtx) {
    reqBody := &verifyApiRequestBody{}

    if err := json.Unmarshal(ctx.PostBody(), &reqBody); err != nil {
        ctx.Error(err.Error(), http.StatusBadRequest)
        return
    }

    if reqBody.N1 < 0 || reqBody.N2 < 0 {
        ctx.Error("n1, n2 must bigger than zero", http.StatusBadRequest)
        return
    }

    log.Printf("Verify event, n1 = %d, n2 = %d, verified.\n", reqBody.N1, reqBody.N2)
    proxyHandler(ctx, "addition")
}

My question is as follows:

My questions are basically the above two points. I want to optimize the speed of forwarding requests as much as possible. Thank you for your answers!

erikdubbelboer commented 1 year ago

To be honest I haven't tried it, but you could try proxyClient.Do(ctx.Request, ctx.Response). In theory that should also work and won't require any copies.

davidleitw commented 1 year ago

@erikdubbelboer After examining the APIs provided by fasthttp in detail, I tried using SetBodyRaw() to avoid copying. The detailed code is as follows. If my understanding of the API is correct, this should also avoid copying.

func proxyHandler(proxyClient *fasthttp.HostClient, ctx *fasthttp.RequestCtx, appId, method string) {
    req, res := fasthttp.AcquireRequest(), fasthttp.AcquireResponse()
    defer fasthttp.ReleaseRequest(req)
    defer fasthttp.ReleaseResponse(res)

    req.Header.SetMethod(method)
    req.Header.Set("dapr-app-id", appId)
    req.Header.SetContentType("application/json")
    req.SetRequestURI("http://localhost:3500")
    req.SetBodyRaw(ctx.PostBody())

    if err := proxyClient.Do(req, res); err != nil {
        ctx.Error(fmt.Sprintf("proxy handler get error: %s\n", err.Error()), http.StatusBadGateway)
        return
    }

    ctx.Response.SetBodyRaw(res.Body())
    ctx.SetStatusCode(res.StatusCode())
}

What I want to ask is whether the SetBodyRaw API can avoid copying []byte as I understand it. I'm afraid my understanding is incorrect, but thank you for taking the time to answer my question. Thank you!

erikdubbelboer commented 1 year ago

Yes that's probably an even better solution 👍