Skip to content

Commit

Permalink
fix: make proxy server work with docker compose and add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Neemias Almeida committed Jan 8, 2024
1 parent 9c7e3de commit 431ae91
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 35 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Note: This tool has nothing to do with hot-deploy for production.
* Support excluding subdirectories
* Allow watching new directories after Air started
* Better building process
* Live reloading on the browser

### Overwrite specify configuration from arguments

Expand Down Expand Up @@ -199,6 +200,19 @@ services:
`air -d` prints all logs.

### Live reloading

To enable live reloading, add the following proxy configuration to `.air.toml`:

```yaml
[proxy]
enabled = true
proxy_url = "http://localhost:8090"
app_url = "http://localhost:8080"
```

In this case we want to access `http://localhost:8090` in the browser which is the proxy server, and it will proxy all the requests with live reloading to the app server running on `http://localhost:8080`. If you are running on docker compose, make sure you use the service name instead of localhost in both URLs and expose the right ports so that you can access the proxy server from your browser, and proxy can reach the app server.

## Installation and Usage for Docker users who don't want to use air image

`Dockerfile`
Expand Down
4 changes: 2 additions & 2 deletions air_example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,5 @@ keep_scroll = true
# Enable live-reloading on the browser. This is useful when developing UI applications.
[proxy]
enabled = true
proxy_port = 8090
app_port = 8080
proxy_url = "http://localhost:8090"
app_url = "http://localhost:8080"
6 changes: 3 additions & 3 deletions runner/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ type cfgScreen struct {
}

type cfgProxy struct {
Enabled bool `toml:"enabled"`
Port int `toml:"proxy_port"`
AppPort int `toml:"app_port"`
Enabled bool `toml:"enabled"`
ProxyURL string `toml:"proxy_url"`
AppURL string `toml:"app_url"`
}

type sliceTransformer struct{}
Expand Down
2 changes: 1 addition & 1 deletion runner/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ func (e *Engine) runBin() error {
cmd, stdout, stderr, _ := e.startCmd(command)
processExit := make(chan struct{})
e.mainDebug("running process pid %v", cmd.Process.Pid)
if e.proxy.config.Enabled {
if e.config.Proxy.Enabled {
e.proxy.Reload()
}

Expand Down
18 changes: 12 additions & 6 deletions runner/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"syscall"
Expand All @@ -27,10 +28,16 @@ type Proxy struct {
}

func NewProxy(cfg *cfgProxy) *Proxy {
if parsed, err := url.Parse(cfg.AppURL); err != nil || parsed.Scheme != "http" {
log.Fatal("bad app_url in the proxy config")
}
if parsed, err := url.Parse(cfg.ProxyURL); err != nil || parsed.Scheme != "http" {
log.Fatal("bad proxy_url in the proxy config")
}
p := &Proxy{
config: cfg,
server: &http.Server{
Addr: fmt.Sprintf("localhost:%d", cfg.Port),
Addr: strings.TrimPrefix(cfg.ProxyURL, "http://"),
},
stream: NewProxyStream(),
}
Expand Down Expand Up @@ -66,7 +73,7 @@ func (p *Proxy) injectLiveReload(origURL string, respBody io.ReadCloser) string
script := `
<script>
const parser = new DOMParser();
const proxyURL = "http://localhost:%d";
const proxyURL = %q;
new EventSource(proxyURL + "/internal/reload").onmessage = () => {
fetch(proxyURL + "%s").then(res => res.text()).then(resStr => {
const newPage = parser.parseFromString(resStr, "text/html");
Expand All @@ -75,15 +82,14 @@ func (p *Proxy) injectLiveReload(origURL string, respBody io.ReadCloser) string
};
</script>
`
parsedScript := fmt.Sprintf(script, p.config.Port, origURL)
parsedScript := fmt.Sprintf(script, p.config.ProxyURL, origURL)

s = s[:body] + parsedScript + s[body:]
return s
}

func (p *Proxy) proxyHandler(w http.ResponseWriter, r *http.Request) {
url := fmt.Sprintf("http://localhost:%d", p.config.AppPort)
req, err := http.NewRequest(r.Method, url, r.Body)
req, err := http.NewRequest(r.Method, p.config.AppURL, r.Body)
if err != nil {
log.Fatalf("proxy could not create request, err: %+v\n", err)
}
Expand All @@ -97,7 +103,7 @@ func (p *Proxy) proxyHandler(w http.ResponseWriter, r *http.Request) {
break
}
if !errors.Is(err, syscall.ECONNREFUSED) {
log.Fatalf("proxy failed to call http://localhost:%d, err: %+v\n", p.config.AppPort, err)
log.Fatalf("proxy failed to call %s, err: %+v\n", p.config.AppURL, err)
}
time.Sleep(100 * time.Millisecond)
}
Expand Down
41 changes: 18 additions & 23 deletions runner/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/http/httptest"
"net/url"
"os"
"strconv"
"sync"
"testing"
)
Expand All @@ -29,49 +28,45 @@ func (r *reloader) RemoveSubscriber(_ int) {
func (r *reloader) Reload() {}
func (r *reloader) Stop() {}

func setupAppServer(t *testing.T) (srv *httptest.Server, port int) {
func setupMockServer(t *testing.T) (srv *httptest.Server, serverURL string) {
srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "thin air")
}))
mockURL, err := url.Parse(srv.URL)
if err != nil {
t.Fatal(err)
}
port, err = strconv.Atoi(mockURL.Port())
if err != nil {
t.Fatal(err)
}
return srv, port
return srv, mockURL.String()
}

func TestNewProxy(t *testing.T) {
_ = os.Unsetenv(airWd)
cfg := &cfgProxy{
Enabled: true,
Port: 1111,
AppPort: 2222,
Enabled: true,
ProxyURL: "http://localhost:1111",
AppURL: "http://localhost:2222",
}
proxy := NewProxy(cfg)
if proxy.config == nil {
t.Fatal("Config should not be nil")
t.Fatal("config should not be nil")
}
if proxy.server == nil {
t.Fatal("watcher should not be nil")
if proxy.server.Addr == "" {
t.Fatal("server address should not be nil")
}
}

func TestProxy_proxyHandler(t *testing.T) {
srv, appPort := setupAppServer(t)
srv, mockURL := setupMockServer(t)
defer srv.Close()

cfg := &cfgProxy{
Enabled: true,
Port: 8090,
AppPort: appPort,
Enabled: true,
ProxyURL: "http://localhost:8090",
AppURL: mockURL,
}
proxy := NewProxy(cfg)

req := httptest.NewRequest("GET", "http://localhost:8090/", nil)
req := httptest.NewRequest("GET", "http://localhost:8090", nil)
rec := httptest.NewRecorder()

proxy.proxyHandler(rec, req)
Expand All @@ -83,19 +78,19 @@ func TestProxy_proxyHandler(t *testing.T) {
}

func TestProxy_reloadHandler(t *testing.T) {
srv, appPort := setupAppServer(t)
srv, mockURL := setupMockServer(t)
defer srv.Close()

reloader := &reloader{subCh: make(chan struct{}), reloadCh: make(chan struct{})}
cfg := &cfgProxy{
Enabled: true,
Port: 8090,
AppPort: appPort,
Enabled: true,
ProxyURL: "http://localhost:8090",
AppURL: mockURL,
}
proxy := &Proxy{
config: cfg,
server: &http.Server{
Addr: fmt.Sprintf("localhost:%d", cfg.Port),
Addr: "localhost:8090",
},
stream: reloader,
}
Expand Down

0 comments on commit 431ae91

Please sign in to comment.