How to glue different applications inside a docker container or implement a reverse proxy in Go

Multiple web applications in the docker container may be implemented in different languages or frameworks. They all may need to be served from the container on the same external port so that it looks like a single application from outside the container.

You may have a third-party application to run in the container, which you cannot change, and you have to run it as is. At the same time, you would like to have your custom endpoints alongside that application.

For example, the main application runs from the root / and uses all required URLs below the root. You may want to have one URL, for example, /health, which you want to serve yourself.

The solution for this configuration is the reverse proxy, which routes requests inside the container to different ports, and even redirects the requests to external URLs.

Enter the reverse proxy

In the article, we will “glue” three web applications implemented in Python, Node and Go. All three will be deployed in the same container, and each application will be invoked depending on the URL.

The default application is in Python. This application serves from the root (/) and all other URLs not handed by other applications. This application runs on port 9000 inside the container. This port is not exposed outside the container.

The application in Node runs on port 9100 and receives the requests with the /node prefix. This port also is not exposed outside the container.

The application in Go runs on port 8000 and receives requests with the /go prefix. This port is exposed as the container port.

Additionally, one more special prefix, /google, will redirect to Google Search.

Recap:

  • /node* goes to the Node application
  • /go* goes to the Go application
  • /google goes to “https://google.com“.
  • /* – everything else goes to the default Python application

The application in Go also implements the reverse proxy mechanism, the essence of the example.

Note:

The code in this article is for the demonstration purpose only. The configuration may be hard coded. In the actual production deployment, there should be a better abstraction on configuring Dockerfile and the applications.

The code of the applications is also the bare minimum. Each endpoint will print some text and the received URL.

Python application

<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>Request</span>
<span>from</span> <span>fastapi.responses</span> <span>import</span> <span>PlainTextResponse</span>
<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>
<span>@</span><span>app</span><span>.</span><span>get</span><span>(</span><span>"/{path:path}"</span><span>,</span> <span>response_class</span><span>=</span><span>PlainTextResponse</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span><span>request</span><span>:</span> <span>Request</span><span>,</span> <span>path</span><span>:</span> <span>str</span><span>):</span>
<span>return</span> <span>f</span><span>"I'm Python!</span><span>\r\n</span><span>[</span><span>{</span><span>path</span><span>}</span><span>]</span><span>\r\n</span><span>"</span>
<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>Request</span>
<span>from</span> <span>fastapi.responses</span> <span>import</span> <span>PlainTextResponse</span>

<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>

<span>@</span><span>app</span><span>.</span><span>get</span><span>(</span><span>"/{path:path}"</span><span>,</span> <span>response_class</span><span>=</span><span>PlainTextResponse</span><span>)</span>
<span>async</span> <span>def</span> <span>root</span><span>(</span><span>request</span><span>:</span> <span>Request</span><span>,</span> <span>path</span><span>:</span> <span>str</span><span>):</span>
    <span>return</span> <span>f</span><span>"I'm Python!</span><span>\r\n</span><span>[</span><span>{</span><span>path</span><span>}</span><span>]</span><span>\r\n</span><span>"</span>
from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse app = FastAPI() @app.get("/{path:path}", response_class=PlainTextResponse) async def root(request: Request, path: str): return f"I'm Python!\r\n[{path}]\r\n"

Enter fullscreen mode Exit fullscreen mode

Node application

<span>const</span> <span>{</span> <span>createServer</span> <span>}</span> <span>=</span> <span>require</span><span>(</span><span>"</span><span>http</span><span>"</span><span>);</span>
<span>createServer</span><span>((</span><span>req</span><span>,</span> <span>res</span><span>)</span> <span>=></span> <span>{</span>
<span>res</span><span>.</span><span>setHeader</span><span>(</span><span>"</span><span>content-type</span><span>"</span><span>,</span> <span>"</span><span>text/plain</span><span>"</span><span>);</span>
<span>res</span><span>.</span><span>end</span><span>(</span><span>`I'm Node!\r\n[</span><span>${</span><span>req</span><span>.</span><span>url</span><span>}</span><span>]\r\n`</span><span>);</span>
<span>}).</span><span>listen</span><span>(</span><span>process</span><span>.</span><span>env</span><span>.</span><span>PORT</span> <span>||</span> <span>9000</span><span>);</span>
<span>const</span> <span>{</span> <span>createServer</span> <span>}</span> <span>=</span> <span>require</span><span>(</span><span>"</span><span>http</span><span>"</span><span>);</span>

<span>createServer</span><span>((</span><span>req</span><span>,</span> <span>res</span><span>)</span> <span>=></span> <span>{</span>
    <span>res</span><span>.</span><span>setHeader</span><span>(</span><span>"</span><span>content-type</span><span>"</span><span>,</span> <span>"</span><span>text/plain</span><span>"</span><span>);</span>
    <span>res</span><span>.</span><span>end</span><span>(</span><span>`I'm Node!\r\n[</span><span>${</span><span>req</span><span>.</span><span>url</span><span>}</span><span>]\r\n`</span><span>);</span>
<span>}).</span><span>listen</span><span>(</span><span>process</span><span>.</span><span>env</span><span>.</span><span>PORT</span> <span>||</span> <span>9000</span><span>);</span>
const { createServer } = require("http"); createServer((req, res) => { res.setHeader("content-type", "text/plain"); res.end(`I'm Node!\r\n[${req.url}]\r\n`); }).listen(process.env.PORT || 9000);

Enter fullscreen mode Exit fullscreen mode

Go application with a built-in proxy

This is the meat and potatoes of the article.

It uses the “ReverseProxy” object and the “NewSingleHostReverseProxy” utility from the standard Go library. The application has no third-party external dependencies.

The code below starts the HTTP listener on port 8000, which will be exposed outside the container, and redirects the traffic to other ports according to the logic above.

<span>package</span> <span>main</span>
<span>import</span> <span>(</span>
<span>"fmt"</span>
<span>"log"</span>
<span>"net/http"</span>
<span>"net/http/httputil"</span>
<span>"net/url"</span>
<span>"os"</span>
<span>"strings"</span>
<span>)</span>
<span>func</span> <span>main</span><span>()</span> <span>{</span>
<span>defaultURL</span><span>,</span> <span>err</span> <span>:=</span> <span>url</span><span>.</span><span>Parse</span><span>(</span><span>"http://localhost:9000"</span><span>)</span>
<span>if</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span>
<span>log</span><span>.</span><span>Fatal</span><span>(</span><span>fmt</span><span>.</span><span>Errorf</span><span>(</span><span>"error parsing default URL: %v"</span><span>,</span> <span>err</span><span>))</span>
<span>}</span>
<span>nodeURL</span><span>,</span> <span>err</span> <span>:=</span> <span>url</span><span>.</span><span>Parse</span><span>(</span><span>"http://localhost:9100"</span><span>)</span>
<span>if</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span>
<span>log</span><span>.</span><span>Fatal</span><span>(</span><span>fmt</span><span>.</span><span>Errorf</span><span>(</span><span>"error parsing node URL: %v"</span><span>,</span> <span>err</span><span>))</span>
<span>}</span>
<span>googleURL</span><span>,</span> <span>err</span> <span>:=</span> <span>url</span><span>.</span><span>Parse</span><span>(</span><span>"https://google.com"</span><span>)</span>
<span>if</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span>
<span>log</span><span>.</span><span>Fatal</span><span>(</span><span>fmt</span><span>.</span><span>Errorf</span><span>(</span><span>"error parsing google's URL: %v"</span><span>,</span> <span>err</span><span>))</span>
<span>}</span>
<span>http</span><span>.</span><span>HandleFunc</span><span>(</span><span>"/"</span><span>,</span> <span>func</span><span>(</span><span>w</span> <span>http</span><span>.</span><span>ResponseWriter</span><span>,</span> <span>r</span> <span>*</span><span>http</span><span>.</span><span>Request</span><span>)</span> <span>{</span>
<span>if</span> <span>q</span><span>,</span> <span>found</span> <span>:=</span> <span>strings</span><span>.</span><span>CutPrefix</span><span>(</span><span>r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>,</span> <span>"/google"</span><span>);</span> <span>found</span> <span>{</span>
<span>proxy</span> <span>:=</span> <span>&</span><span>httputil</span><span>.</span><span>ReverseProxy</span><span>{</span>
<span>Rewrite</span><span>:</span> <span>func</span><span>(</span><span>r</span> <span>*</span><span>httputil</span><span>.</span><span>ProxyRequest</span><span>)</span> <span>{</span>
<span>r</span><span>.</span><span>SetURL</span><span>(</span><span>googleURL</span><span>)</span>
<span>r</span><span>.</span><span>Out</span><span>.</span><span>URL</span><span>.</span><span>Path</span> <span>=</span> <span>"/"</span> <span>+</span> <span>q</span>
<span>},</span>
<span>}</span>
<span>proxy</span><span>.</span><span>ServeHTTP</span><span>(</span><span>w</span><span>,</span> <span>r</span><span>)</span>
<span>return</span>
<span>}</span>
<span>if</span> <span>strings</span><span>.</span><span>HasPrefix</span><span>(</span><span>r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>,</span> <span>"/go"</span><span>)</span> <span>{</span>
<span>w</span><span>.</span><span>Write</span><span>([]</span><span>byte</span><span>(</span><span>fmt</span><span>.</span><span>Sprintf</span><span>(</span><span>"I'm Go!</span><span>\r\n</span><span>[%v]</span><span>\n</span><span>"</span><span>,</span> <span>r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>)))</span>
<span>return</span>
<span>}</span>
<span>if</span> <span>strings</span><span>.</span><span>HasPrefix</span><span>(</span><span>r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>,</span> <span>"/node"</span><span>)</span> <span>{</span>
<span>httputil</span><span>.</span><span>NewSingleHostReverseProxy</span><span>(</span><span>nodeURL</span><span>)</span><span>.</span><span>ServeHTTP</span><span>(</span><span>w</span><span>,</span> <span>r</span><span>)</span>
<span>return</span>
<span>}</span>
<span>httputil</span><span>.</span><span>NewSingleHostReverseProxy</span><span>(</span><span>defaultURL</span><span>)</span><span>.</span><span>ServeHTTP</span><span>(</span><span>w</span><span>,</span> <span>r</span><span>)</span>
<span>})</span>
<span>port</span> <span>:=</span> <span>os</span><span>.</span><span>Getenv</span><span>(</span><span>"PORT"</span><span>)</span>
<span>if</span> <span>port</span> <span>==</span> <span>""</span> <span>{</span>
<span>port</span> <span>=</span> <span>"8000"</span>
<span>}</span>
<span>if</span> <span>err</span> <span>:=</span> <span>http</span><span>.</span><span>ListenAndServe</span><span>(</span><span>":"</span><span>+</span><span>port</span><span>,</span> <span>nil</span><span>);</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span>
<span>log</span><span>.</span><span>Fatal</span><span>(</span><span>err</span><span>)</span>
<span>}</span>
<span>}</span>
<span>package</span> <span>main</span>

<span>import</span> <span>(</span>
    <span>"fmt"</span>
    <span>"log"</span>
    <span>"net/http"</span>
    <span>"net/http/httputil"</span>
    <span>"net/url"</span>
    <span>"os"</span>
    <span>"strings"</span>
<span>)</span>

<span>func</span> <span>main</span><span>()</span> <span>{</span>
    <span>defaultURL</span><span>,</span> <span>err</span> <span>:=</span> <span>url</span><span>.</span><span>Parse</span><span>(</span><span>"http://localhost:9000"</span><span>)</span>
    <span>if</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span>
        <span>log</span><span>.</span><span>Fatal</span><span>(</span><span>fmt</span><span>.</span><span>Errorf</span><span>(</span><span>"error parsing default URL: %v"</span><span>,</span> <span>err</span><span>))</span>
    <span>}</span>

    <span>nodeURL</span><span>,</span> <span>err</span> <span>:=</span> <span>url</span><span>.</span><span>Parse</span><span>(</span><span>"http://localhost:9100"</span><span>)</span>
    <span>if</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span>
        <span>log</span><span>.</span><span>Fatal</span><span>(</span><span>fmt</span><span>.</span><span>Errorf</span><span>(</span><span>"error parsing node URL: %v"</span><span>,</span> <span>err</span><span>))</span>
    <span>}</span>

    <span>googleURL</span><span>,</span> <span>err</span> <span>:=</span> <span>url</span><span>.</span><span>Parse</span><span>(</span><span>"https://google.com"</span><span>)</span>
    <span>if</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span>
        <span>log</span><span>.</span><span>Fatal</span><span>(</span><span>fmt</span><span>.</span><span>Errorf</span><span>(</span><span>"error parsing google's URL: %v"</span><span>,</span> <span>err</span><span>))</span>
    <span>}</span>

    <span>http</span><span>.</span><span>HandleFunc</span><span>(</span><span>"/"</span><span>,</span> <span>func</span><span>(</span><span>w</span> <span>http</span><span>.</span><span>ResponseWriter</span><span>,</span> <span>r</span> <span>*</span><span>http</span><span>.</span><span>Request</span><span>)</span> <span>{</span>
        <span>if</span> <span>q</span><span>,</span> <span>found</span> <span>:=</span> <span>strings</span><span>.</span><span>CutPrefix</span><span>(</span><span>r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>,</span> <span>"/google"</span><span>);</span> <span>found</span> <span>{</span>
            <span>proxy</span> <span>:=</span> <span>&</span><span>httputil</span><span>.</span><span>ReverseProxy</span><span>{</span>
                <span>Rewrite</span><span>:</span> <span>func</span><span>(</span><span>r</span> <span>*</span><span>httputil</span><span>.</span><span>ProxyRequest</span><span>)</span> <span>{</span>
                    <span>r</span><span>.</span><span>SetURL</span><span>(</span><span>googleURL</span><span>)</span>
                    <span>r</span><span>.</span><span>Out</span><span>.</span><span>URL</span><span>.</span><span>Path</span> <span>=</span> <span>"/"</span> <span>+</span> <span>q</span>
                <span>},</span>
            <span>}</span>
            <span>proxy</span><span>.</span><span>ServeHTTP</span><span>(</span><span>w</span><span>,</span> <span>r</span><span>)</span>
            <span>return</span>
        <span>}</span>
        <span>if</span> <span>strings</span><span>.</span><span>HasPrefix</span><span>(</span><span>r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>,</span> <span>"/go"</span><span>)</span> <span>{</span>
            <span>w</span><span>.</span><span>Write</span><span>([]</span><span>byte</span><span>(</span><span>fmt</span><span>.</span><span>Sprintf</span><span>(</span><span>"I'm Go!</span><span>\r\n</span><span>[%v]</span><span>\n</span><span>"</span><span>,</span> <span>r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>)))</span>
            <span>return</span>
        <span>}</span>
        <span>if</span> <span>strings</span><span>.</span><span>HasPrefix</span><span>(</span><span>r</span><span>.</span><span>URL</span><span>.</span><span>Path</span><span>,</span> <span>"/node"</span><span>)</span> <span>{</span>
            <span>httputil</span><span>.</span><span>NewSingleHostReverseProxy</span><span>(</span><span>nodeURL</span><span>)</span><span>.</span><span>ServeHTTP</span><span>(</span><span>w</span><span>,</span> <span>r</span><span>)</span>
            <span>return</span>
        <span>}</span>
        <span>httputil</span><span>.</span><span>NewSingleHostReverseProxy</span><span>(</span><span>defaultURL</span><span>)</span><span>.</span><span>ServeHTTP</span><span>(</span><span>w</span><span>,</span> <span>r</span><span>)</span>
    <span>})</span>
    <span>port</span> <span>:=</span> <span>os</span><span>.</span><span>Getenv</span><span>(</span><span>"PORT"</span><span>)</span>
    <span>if</span> <span>port</span> <span>==</span> <span>""</span> <span>{</span>
        <span>port</span> <span>=</span> <span>"8000"</span>
    <span>}</span>
    <span>if</span> <span>err</span> <span>:=</span> <span>http</span><span>.</span><span>ListenAndServe</span><span>(</span><span>":"</span><span>+</span><span>port</span><span>,</span> <span>nil</span><span>);</span> <span>err</span> <span>!=</span> <span>nil</span> <span>{</span>
        <span>log</span><span>.</span><span>Fatal</span><span>(</span><span>err</span><span>)</span>
    <span>}</span>
<span>}</span>
package main import ( "fmt" "log" "net/http" "net/http/httputil" "net/url" "os" "strings" ) func main() { defaultURL, err := url.Parse("http://localhost:9000") if err != nil { log.Fatal(fmt.Errorf("error parsing default URL: %v", err)) } nodeURL, err := url.Parse("http://localhost:9100") if err != nil { log.Fatal(fmt.Errorf("error parsing node URL: %v", err)) } googleURL, err := url.Parse("https://google.com") if err != nil { log.Fatal(fmt.Errorf("error parsing google's URL: %v", err)) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if q, found := strings.CutPrefix(r.URL.Path, "/google"); found { proxy := &httputil.ReverseProxy{ Rewrite: func(r *httputil.ProxyRequest) { r.SetURL(googleURL) r.Out.URL.Path = "/" + q }, } proxy.ServeHTTP(w, r) return } if strings.HasPrefix(r.URL.Path, "/go") { w.Write([]byte(fmt.Sprintf("I'm Go!\r\n[%v]\n", r.URL.Path))) return } if strings.HasPrefix(r.URL.Path, "/node") { httputil.NewSingleHostReverseProxy(nodeURL).ServeHTTP(w, r) return } httputil.NewSingleHostReverseProxy(defaultURL).ServeHTTP(w, r) }) port := os.Getenv("PORT") if port == "" { port = "8000" } if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatal(err) } }

Enter fullscreen mode Exit fullscreen mode

Container startup script

This script starts all three applications on different ports and provides the required environment variables. This script is the container entry point.

<span>#!/usr/bin/env bash</span>
<span>(</span><span>source</span> .venv/bin/activate <span>&&</span> uvicorn main:app <span>--port</span> 9000<span>)</span> &
<span>(</span><span>PORT</span><span>=</span>9100 ./node ./main.js<span>)</span> &
<span>(</span><span>PORT</span><span>=</span>8000 ./proxy<span>)</span> &
<span>wait exit</span> <span>$?</span>
<span>#!/usr/bin/env bash</span>

<span>(</span><span>source</span> .venv/bin/activate <span>&&</span> uvicorn main:app <span>--port</span> 9000<span>)</span> &
<span>(</span><span>PORT</span><span>=</span>9100 ./node ./main.js<span>)</span> &
<span>(</span><span>PORT</span><span>=</span>8000 ./proxy<span>)</span> &

<span>wait exit</span> <span>$?</span>
#!/usr/bin/env bash (source .venv/bin/activate && uvicorn main:app --port 9000) & (PORT=9100 ./node ./main.js) & (PORT=8000 ./proxy) & wait exit $?

Enter fullscreen mode Exit fullscreen mode

Dockerfile

Dockerfile is multistaged because it needs to collect artefacts from the different applications in one container.

<span>ARG</span><span> GO_VERSION=1.20</span>
<span>FROM</span><span> </span><span>golang:${GO_VERSION}-alpine</span><span> </span><span>AS</span><span> </span><span>build-proxy</span>
<span>WORKDIR</span><span> /app</span>
<span>COPY</span><span> main.go .</span>
<span>RUN </span>go build <span>-o</span> proxy main.go
<span># ---</span>
<span>FROM</span><span> </span><span>python:3-slim</span><span> </span><span>AS</span><span> </span><span>build-python</span>
<span>WORKDIR</span><span> /app</span>
<span>COPY</span><span> main.py .</span>
<span>RUN </span>python <span>-m</span> venv .venv
<span>RUN </span><span>.</span> .venv/bin/activate <span>&&</span> pip <span>install </span>uvicorn fastapi
<span># ---</span>
<span>FROM</span><span> </span><span>node:20-bullseye-slim</span><span> </span><span>AS</span><span> </span><span>build-node</span>
<span>WORKDIR</span><span> /app</span>
<span>COPY</span><span> main.js .</span>
<span>RUN </span><span>cp</span> <span>`</span>which node<span>`</span> .
<span># ---</span>
<span>FROM</span><span> python:3-slim</span>
<span>WORKDIR</span><span> /app</span>
<span>COPY</span><span> --from=build-python /app .</span>
<span>COPY</span><span> --from=build-node /app .</span>
<span>COPY</span><span> --from=build-proxy /app/proxy .</span>
<span>COPY</span><span> ./run.sh .</span>
<span>CMD</span><span> ["./run.sh"]</span>
<span>ARG</span><span> GO_VERSION=1.20</span>

<span>FROM</span><span> </span><span>golang:${GO_VERSION}-alpine</span><span> </span><span>AS</span><span> </span><span>build-proxy</span>
<span>WORKDIR</span><span> /app</span>

<span>COPY</span><span> main.go .</span>
<span>RUN </span>go build <span>-o</span> proxy main.go

<span># ---</span>
<span>FROM</span><span> </span><span>python:3-slim</span><span> </span><span>AS</span><span> </span><span>build-python</span>

<span>WORKDIR</span><span> /app</span>

<span>COPY</span><span> main.py .</span>
<span>RUN </span>python <span>-m</span> venv .venv
<span>RUN </span><span>.</span> .venv/bin/activate <span>&&</span> pip <span>install </span>uvicorn fastapi

<span># ---</span>
<span>FROM</span><span> </span><span>node:20-bullseye-slim</span><span> </span><span>AS</span><span> </span><span>build-node</span>

<span>WORKDIR</span><span> /app</span>

<span>COPY</span><span> main.js .</span>
<span>RUN </span><span>cp</span> <span>`</span>which node<span>`</span> .

<span># ---</span>
<span>FROM</span><span> python:3-slim</span>
<span>WORKDIR</span><span> /app</span>

<span>COPY</span><span> --from=build-python /app .</span>
<span>COPY</span><span> --from=build-node /app .</span>
<span>COPY</span><span> --from=build-proxy /app/proxy .</span>
<span>COPY</span><span> ./run.sh .</span>

<span>CMD</span><span> ["./run.sh"]</span>
ARG GO_VERSION=1.20 FROM golang:${GO_VERSION}-alpine AS build-proxy WORKDIR /app COPY main.go . RUN go build -o proxy main.go # --- FROM python:3-slim AS build-python WORKDIR /app COPY main.py . RUN python -m venv .venv RUN . .venv/bin/activate && pip install uvicorn fastapi # --- FROM node:20-bullseye-slim AS build-node WORKDIR /app COPY main.js . RUN cp `which node` . # --- FROM python:3-slim WORKDIR /app COPY --from=build-python /app . COPY --from=build-node /app . COPY --from=build-proxy /app/proxy . COPY ./run.sh . CMD ["./run.sh"]

Enter fullscreen mode Exit fullscreen mode

Build the docker image

The image’s name is “zoo”.

docker build <span>-t</span> zoo <span>.</span>
docker build <span>-t</span> zoo <span>.</span>
docker build -t zoo .

Enter fullscreen mode Exit fullscreen mode

Running the container

This command runs the container in interactive mode and with --rm to be deleted when it stops.

docker run <span>-p</span> 8000:8000 <span>--rm</span> <span>-it</span> zoo
docker run <span>-p</span> 8000:8000 <span>--rm</span> <span>-it</span> zoo
docker run -p 8000:8000 --rm -it zoo

Enter fullscreen mode Exit fullscreen mode

Testing

The container is up the running. It listens on port 8000, so we can test it.

Hitting the Python application:

curl http://localhost:8000
curl http://localhost:8000
curl http://localhost:8000

Enter fullscreen mode Exit fullscreen mode

I'm Python!
[]
I'm Python!
[]
I'm Python! []

Enter fullscreen mode Exit fullscreen mode

curl http://localhost:8000/a/b/c
curl http://localhost:8000/a/b/c
curl http://localhost:8000/a/b/c

Enter fullscreen mode Exit fullscreen mode

I'm Python!
[a/b/c]
I'm Python!
[a/b/c]
I'm Python! [a/b/c]

Enter fullscreen mode Exit fullscreen mode

Hitting the Node application:

curl http://localhost:8000/node
curl http://localhost:8000/node
curl http://localhost:8000/node

Enter fullscreen mode Exit fullscreen mode

I'm Node!
[/node]
I'm Node!
[/node]
I'm Node! [/node]

Enter fullscreen mode Exit fullscreen mode

curl http://localhost:8000/node/a/b/c
curl http://localhost:8000/node/a/b/c
curl http://localhost:8000/node/a/b/c

Enter fullscreen mode Exit fullscreen mode

I'm Node!
[/node/a/b/c]
I'm Node!
[/node/a/b/c]
I'm Node! [/node/a/b/c]

Enter fullscreen mode Exit fullscreen mode

Hitting the Go application:

curl http://localhost:8000/go
curl http://localhost:8000/go
curl http://localhost:8000/go

Enter fullscreen mode Exit fullscreen mode

I'm Go!
[/go]
I'm Go!
[/go]
I'm Go! [/go]

Enter fullscreen mode Exit fullscreen mode

curl http://localhost:8000/go/a/b/c
curl http://localhost:8000/go/a/b/c
curl http://localhost:8000/go/a/b/c

Enter fullscreen mode Exit fullscreen mode

I'm Go!
[/go/a/b/c]
I'm Go!
[/go/a/b/c]
I'm Go! [/go/a/b/c]

Enter fullscreen mode Exit fullscreen mode

Finally, you can put the http://localhost:8000/google/?q=abc URL into the browser, which will forward you to Google Search.

This is it! We have implemented the configurable reverse proxy in Go.

It glues together three applications written in different languages, and they run inside one container as a single deployable unit.

Makefile

For convenience, a Makefile that can build the image and run the container:

make
make
make

Enter fullscreen mode Exit fullscreen mode

Run tests above:

make <span>test</span>
make <span>test</span>
make test

Enter fullscreen mode Exit fullscreen mode

Performance

Extra data transfer may be necessary when the traffic goes through the proxy. We say “maybe” because, in reality, the proxy can propagate actual data copying between sockets to the kernel. The approach is called Zero-Copy networking. It eliminates the overhead because all network listeners run on the same machine and are controlled by the same kernel.

The Go standard library on Linux does precisely that.

Extra copying is needed when the proxy redirects to the external URL, but this is not the intended use case for this application.

Links

The sources are available at https://github.com/begoon/go-reverse-proxy.

原文链接:How to glue different applications inside a docker container or implement a reverse proxy in Go

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
Hard-working is actually a cool thing.
努力学习其实是一件很酷的事
评论 抢沙发

请登录后发表评论

    暂无评论内容