Fasthttp: Reverse Proxy?

Created on 10 Mar 2016  路  19Comments  路  Source: valyala/fasthttp

The golang httputil package has a ReverseProxy that will serve from an http.Request.

Is there any comporable revrese proxy for fasthttp that will serve from a fasthttp.Request?

enhancement question

Most helpful comment

Currently there is no reverse proxy, but it is on the radar - see 'proxy handler' line there.

Currently reverse proxy may be implemented quite easily using HostClient:

import (
    "github.com/valyala/fasthttp"
)

var proxyClient = &fasthttp.HostClient{
    Addr: "upstream.host:port",
    // set other options here if required - most notably timeouts.
}

func ReverseProxyHandler(ctx *fasthttp.RequestCtx) {
    req := &ctx.Request
    resp := &ctx.Response
    prepareRequest(req)
    if err := proxyClient.Do(req, resp); err != nil {
        ctx.Logger().Printf("error when proxying the request: %s", err)
    }
    postprocessResponse(resp)
}

func prepareRequest(req *fasthttp.Request) {
    // do not proxy "Connection" header.
    req.Header.Del("Connection")
    // strip other unneeded headers.

    // alter other request params before sending them to upstream host
}

func postprocessResponse(resp *fasthttp.Response) {
    // do not proxy "Connection" header
    resp.Header.Del("Connection")

    // strip other unneeded headers

    // alter other response data if needed
}

func main() {
    if err := fasthttp.ListenAndServe(":8080", reverseProxyHandler); err != nil {
        log.Fatalf("error in fasthttp server: %s", err)
    }
}

All 19 comments

Currently there is no reverse proxy, but it is on the radar - see 'proxy handler' line there.

Currently reverse proxy may be implemented quite easily using HostClient:

import (
    "github.com/valyala/fasthttp"
)

var proxyClient = &fasthttp.HostClient{
    Addr: "upstream.host:port",
    // set other options here if required - most notably timeouts.
}

func ReverseProxyHandler(ctx *fasthttp.RequestCtx) {
    req := &ctx.Request
    resp := &ctx.Response
    prepareRequest(req)
    if err := proxyClient.Do(req, resp); err != nil {
        ctx.Logger().Printf("error when proxying the request: %s", err)
    }
    postprocessResponse(resp)
}

func prepareRequest(req *fasthttp.Request) {
    // do not proxy "Connection" header.
    req.Header.Del("Connection")
    // strip other unneeded headers.

    // alter other request params before sending them to upstream host
}

func postprocessResponse(resp *fasthttp.Response) {
    // do not proxy "Connection" header
    resp.Header.Del("Connection")

    // strip other unneeded headers

    // alter other response data if needed
}

func main() {
    if err := fasthttp.ListenAndServe(":8080", reverseProxyHandler); err != nil {
        log.Fatalf("error in fasthttp server: %s", err)
    }
}

Thanks for the example. To reverse proxy to a Unix Domain Socket, could I simply pass HostClient a Dialer function that makes use of net.DialUnix?

To reverse proxy to a Unix Domain Socket, could I simply pass HostClient a Dialer function that makes use of net.DialUnix?

Yes

There's a problem, I'm afraid, HostClient.Do follows redirects, and there's no way to turn that off.

@pdcawley , Do* methods in any client type (Client, HostClient and PipelineClient currently) shouldn't follow redirects. Only Get* and Post methods follow redirects, since their API lacks ability to return url to redirect.

Could you provide a short code snippet demonstrating that HostClient.Do follows redirects?

Nope. I misread the code.

@valyala First of all, thanks for sharing a great software!
I would like to implement a cache server using fasthttp, so I need a reverse proxy support.
I would like a hook point when all response headers from the upstream (origin) server are read so that I can determine the content should or should not be cached.

When I cache a content, I would like to write a response chunk to the client as well as the write the chunk to a buffer on the server at the same time. And once when I find the content is too large to be cached, I would like to switch to the pass though mode and just write a response chunk to the client without writing the chunk to a buffer on the server.

What structure is the best for such a need? A hook when a HostClient read all response headers, or splitting HostClient.Do into sending a request, reading response headers and reading the response body?

@hnakamur , your requirements for reverse proxy sound good - they may be used as a basis for reverse proxy implementation in fasthttp.

I would like a hook point when all response headers from the upstream (origin) server are read so that I can determine the content should or should not be cached

Currently fasthttp doesn't support response headers' inspection before reading response body. But this may be implemented by reading response headers with ResponseHeader.Read and inspecting their contents before reading or discarding response body.

I would like to write a response chunk to the client as well as the write the chunk to a buffer on the server at the same time

This sounds good in theory, but the caching in such a system may be abused by slow clients consuming big response bodies at 1kbps speed. Slow clients also may consume upstream resources (connection, file handlers, buffers, etc.), which may be used as DoS-attack vector to upstream servers. It would be better just reading upstream response chunk up to the given size limit as fast as possible, caching it and then feeding slow clients from cache.

What structure is the best for such a need? A hook when a HostClient read all response headers, or splitting HostClient.Do into sending a request, reading response headers and reading the response body?

I don't know :( Probably it would be better to start with new client type (ProxyClient) and then integrating changes into HostClient if possible.

ask a question, i copy your code above.

import (
    "github.com/valyala/fasthttp"
)

var proxyClient = &fasthttp.HostClient{
    Addr: "upstream.host:port",
    // set other options here if required - most notably timeouts.
}

func ReverseProxyHandler(ctx *fasthttp.RequestCtx) {
    req := &ctx.Request
    resp := &ctx.Response
    prepareRequest(req)
    if err := proxyClient.Do(req, resp); err != nil {
        ctx.Logger().Printf("error when proxying the request: %s", err)
    }
    postprocessResponse(resp)
}

func prepareRequest(req *fasthttp.Request) {
    // do not proxy "Connection" header.
    req.Header.Del("Connection")
    // strip other unneeded headers.

    // alter other request params before sending them to upstream host
}

func postprocessResponse(resp *fasthttp.Response) {
    // do not proxy "Connection" header
    resp.Header.Del("Connection")

    // strip other unneeded headers

    // alter other response data if needed
}

func main() {
    if err := fasthttp.ListenAndServe(":8080", reverseProxyHandler); err != nil {
        log.Fatalf("error in fasthttp server: %s", err)
    }
}

but i throw an error, maybe i'm unfamiliar with fasthttp.

error when proxying the request: the server closed connection before returning the first response byte. Make sure the server returns 'Connection: close' response header before closing the connection

i'm a newbie to golang and fasthttp ,another question , how to change the http from proxy server?

i know why .
i add

req.SetHost(upstream.host)

in prepareRequest function,
then it's ok.

another question , why delete connection header in postprocessResponse function. @valyala

@zhuqiuyang Connection, Authenticate and Authorization are "single hop" headers - see http://www.w3.org/Protocols/rfc2616/rfc2616.txt:

14.10 Connection
    The Connection general-header field allows the sender to specify
    options that are desired for that particular connection and MUST NOT
    be communicated by proxies over further connections.

@katzien thx for your answer. i have to read it carefully.

@valyala thx for your answer. I think it may be better to add an example of Reverse Proxy in the source code.

how do i stop sending to client if i detected some status response from the backend? e.g. dropping the client connection or not sending to client.

Currently there is no reverse proxy, but it is on the radar - see 'proxy handler' line there.

Currently reverse proxy may be implemented quite easily using HostClient:

import (
    "github.com/valyala/fasthttp"
)

var proxyClient = &fasthttp.HostClient{
    Addr: "upstream.host:port",
    // set other options here if required - most notably timeouts.
}

func ReverseProxyHandler(ctx *fasthttp.RequestCtx) {
    req := &ctx.Request
    resp := &ctx.Response
    prepareRequest(req)
    if err := proxyClient.Do(req, resp); err != nil {
        ctx.Logger().Printf("error when proxying the request: %s", err)
    }
    postprocessResponse(resp)
}

func prepareRequest(req *fasthttp.Request) {
    // do not proxy "Connection" header.
    req.Header.Del("Connection")
    // strip other unneeded headers.

    // alter other request params before sending them to upstream host
}

func postprocessResponse(resp *fasthttp.Response) {
    // do not proxy "Connection" header
    resp.Header.Del("Connection")

    // strip other unneeded headers

    // alter other response data if needed
}

func main() {
    if err := fasthttp.ListenAndServe(":8080", reverseProxyHandler); err != nil {
        log.Fatalf("error in fasthttp server: %s", err)
    }
}

how can i control when to send the request to client? e.g. i would like to send the status and header and body information based through cache and not proxy backend.

If you want to respond based on the response of the backend you can read the response from resp and decide based on that. You can for example reset the response if you don't want to send back what the backend responded with.

In your example this line sends the request

if err := proxyClient.Do(req, resp); err != nil {

So you control when to send it by calling or not calling this line I guess.

ok thx. i asked because i was too used to the net/http. now i'm rewriting it based on fasthttp concept.

i know why .
i add

req.SetHost(upstream.host)

in prepareRequest function,
then it's ok.

You saved me! thanks a lot!

Was this page helpful?
0 / 5 - 0 ratings