1

I just started to use Golang and I want to remake my already working NodeJS/TypeScript app in Go.

One endpoint of my API simply adds server-side generated authorization headers and sends a request to a remote API. Basically filling those headers for me by calling my API instead of the remote API.

This is what I am currently writing

func Endpoint(ctx *fiber.Ctx) error {
    url := "https://api.twitch.tv" + ctx.OriginalURL()

    req, _ := http.NewRequest(http.MethodGet, url, nil)

    req.Header.Set("Authorization", "Bearer ---------")
    req.Header.Set("Client-Id", "---------")

    client := &http.Client{}
    res, err := client.Do(req)

    // temporary error handling
    if err != nil {
        log.Fatalln(err)
    }

    body, err := ioutil.ReadAll(res.Body)

    // temporary error handling
    if err != nil {
        log.Fatalln(err)
    }

    var forwardedBody interface{}

    json.Unmarshal(body, &forwardedBody)

    return ctx.Status(fiber.StatusOK).JSON(forwardedBody)
}

I'd like to know if I am on the right steps, because making a request, parsing the JSON response with ioutil then unmarshall it to send it back seems kind of overboard for the simplicity of what I am trying to achieve ?

Edit: Thank you for the help, this is what I will be going for

func Endpoint(ctx *fiber.Ctx) error {
    url := "https://api.twitch.tv" + ctx.OriginalURL()

    req, _ := http.NewRequest(http.MethodGet, url, nil)

    req.Header.Set("Authorization", "Bearer ---------")
    req.Header.Set("Client-ID", "---------")

    client := &http.Client{}
    res, err := client.Do(req)

    if err != nil {
        return ctx.SendStatus(fiber.StatusBadRequest)
    }

    ctx.Set("Content-Type", "application/json; charset=utf-8")

    return ctx.Status(res.StatusCode).SendStream(res.Body)
}
erik258
  • 14,701
  • 2
  • 25
  • 31
nanoK
  • 51
  • 5
  • 3
    The field `res.Body` is an io.Reader. Fiber has [a method for writing an io.Reader to the response](https://pkg.go.dev/github.com/gofiber/fiber/v2#Ctx.SendStream). –  Feb 05 '22 at 02:07
  • I found `ctx.SendStream` and looks like what you are referring to, thanks – nanoK Feb 05 '22 at 02:20

2 Answers2

2

You can use httputil.ReverseProxy. Which takes a base URL and forwards requests to the base URL, concatenating the path.

ReverseProxy is an HTTP Handler that takes an incoming request and sends it to another server, proxying the response back to the client.

http.Handle("/", &httputil.ReverseProxy{
    Director: func(r *http.Request) {
        r.URL.Scheme = "https"
        r.URL.Host = "go.dev"
        r.Host = r.URL.Host
        r.Header.Set("X-Foo", "Bar")
    },
})

If you are not serving this from the root path / you can use StripPrefix.

http.HandleFunc("/foo/", http.StripPrefix("/foo/", proxy)

There is also a helper function NewSingleHostReverseProxy, which possibly removes the need to configure the proxy struct yourself. But I think it will be better to set the Host header along with your custom header.

The Fool
  • 16,715
  • 5
  • 52
  • 86
  • 1
    Interesting, Fiber also [has a proxy middleware](https://docs.gofiber.io/api/middleware/proxy) and it has been working wonderfully, thank you! – nanoK Feb 07 '22 at 07:19
1

You don't need to attempt to parse the data as JSON. This will be problematic if any of your endpoints don't return JSON, anyway, so just inject the body directly into the response:

body, err := ioutil.ReadAll(res.Body)

// temporary error handling
if err != nil {
    log.Fatalln(err)
}

// Inject the body from the inner response into the actual response so it can be returned
ctx.Response().SetBody(body)

return cx.Status(fiber.StatusOK)

Woody1193
  • 7,252
  • 5
  • 40
  • 90