Skip to content
This repository was archived by the owner on Aug 3, 2024. It is now read-only.

Commit 3ded040

Browse files
authored
Add support for multiple tunnels simultaneously (#8)
1 parent 1a3bebd commit 3ded040

File tree

17 files changed

+336
-120
lines changed

17 files changed

+336
-120
lines changed

.github/workflows/playwright.yml

+32-32
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,51 @@
11
name: Playwright Tests
22
on:
33
push:
4-
branches: [ main, master ]
4+
branches: [main, master]
55
pull_request:
6-
branches: [ main, master ]
6+
branches: [main, master]
77
jobs:
88
test:
99
timeout-minutes: 60
1010
runs-on: ubuntu-latest
1111
steps:
12-
- name: Install pnpm
13-
run: npm i -g pnpm
14-
- uses: actions/checkout@v3
15-
- uses: actions/setup-node@v3
16-
with:
17-
node-version: 16
18-
cache: 'pnpm'
19-
- name: Install dependencies
20-
run: pnpm install
21-
- name: Install Playwright Browsers
22-
run: pnpm dlx playwright install --with-deps
23-
- name: Add hosts to /etc/hosts
24-
run: |
12+
- name: Install pnpm
13+
run: npm i -g pnpm
14+
- uses: actions/checkout@v3
15+
- uses: actions/setup-node@v3
16+
with:
17+
node-version: 16
18+
cache: "pnpm"
19+
- name: Install dependencies
20+
run: pnpm install
21+
- name: Install Playwright Browsers
22+
run: pnpm dlx playwright install --with-deps
23+
- name: Add hosts to /etc/hosts
24+
run: |
2525
sudo echo "127.0.0.1 test.localhost" | sudo tee -a /etc/hosts
26-
- name: Build client and server
27-
run: |
26+
- name: Build client and server
27+
run: |
2828
go build -ldflags="-s -w" -o beaver ./cmd/beaver_client
2929
go build -ldflags="-s -w" -o beaver_server ./cmd/beaver_server
3030
go build -ldflags="-s -w" -o test_server ./e2e/server.go
31-
- name: Start test servers
32-
run: |
31+
- name: Start test servers
32+
run: |
3333
./test_server &
3434
sleep 5
3535
./beaver_server --config docs/beaver_server.yaml &
3636
sleep 5
37-
- name: Start test client
38-
run: |
39-
./beaver --config docs/beaver_client.yaml --port 9999 --subdomain test &
37+
- name: Start test client
38+
run: |
39+
./beaver --config docs/beaver_client.yaml http 9999 --subdomain test &
4040
sleep 3
41-
- name: Run Playwright tests
42-
run: pnpm dlx playwright test
43-
- name: Kill test servers
44-
run: |
41+
- name: Run Playwright tests
42+
run: pnpm dlx playwright test
43+
- name: Kill test servers
44+
run: |
4545
make kill-test-servers
46-
- uses: actions/upload-artifact@v3
47-
if: always()
48-
with:
49-
name: playwright-report
50-
path: playwright-report/
51-
retention-days: 30
46+
- uses: actions/upload-artifact@v3
47+
if: always()
48+
with:
49+
name: playwright-report
50+
path: playwright-report/
51+
retention-days: 30

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ node_modules
2020
/playwright/.cache/
2121

2222
dist/
23+
!beaver_server/

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ start-test-servers:
1717
@go run cmd/beaver_server/main.go --config docs/beaver_server.yaml &
1818
@sleep 5
1919
@echo "Starting beaver client"
20-
@go run cmd/beaver_client/main.go --config docs/beaver_client.yaml --port 9999 --subdomain test &
20+
@go run cmd/beaver_client/main.go --config docs/beaver_client.yaml http 9999 --subdomain test &
2121
@sleep 5
2222

2323
kill-test-servers:

README.md

+40-4
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,48 @@
1010
Download the binary from [releases page](https://github.com/amalshaji/beaver/releases).
1111

1212
```bash
13-
beaver - tunnel local ports to public URLs:
13+
➜ beaver --help
14+
Tunnel local ports to public URLs
1415

1516
Usage:
16-
--config string Config file path (default "/Users/amalshaji/.beaver/beaver_client.yaml")
17+
beaver [flags]
18+
beaver [command]
19+
20+
Available Commands:
21+
completion Generate the autocompletion script for the specified shell
22+
help Help about any command
23+
http Tunnel local http servers
24+
start Start tunnels defined in the config file
25+
26+
Flags:
27+
--config string Path to the client config file (default "/Users/amalshaji/.beaver/beaver_client.yaml")
28+
-h, --help help for beaver
29+
30+
Use "beaver [command] --help" for more information about a command.
31+
32+
---
33+
34+
➜ beaver http --help
35+
Tunnel local http servers
36+
37+
Usage:
38+
beaver http [PORT] [flags]
39+
40+
Flags:
41+
-h, --help help for http
1742
--subdomain string Subdomain to tunnel http requests (default "<random_subdomain>")
18-
--port int Local http server port (required)
43+
44+
---
45+
46+
➜ beaver start --help
47+
Start tunnels defined in the config file
48+
49+
Usage:
50+
beaver start [--all] or [tunnel1 tunnel2] [flags]
51+
52+
Flags:
53+
--all Start all tunnels listed in the config
54+
-h, --help help for start
1955
```
2056

2157
#### Example
@@ -26,7 +62,7 @@ Usage:
2662
2023/02/05 19:46:07 Tunnel running on https://sccrej.tunnel.example.com
2763
```
2864

29-
Now, `https://sccrej.tunnel.example.com http://localhost:8000`
65+
Now, `https://sccrej.tunnel.example.com -> http://localhost:8000`
3066

3167
#### Config
3268

client/client.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,9 @@ func NewClient(config *Config) (c *Client) {
3333

3434
// Start the Proxy
3535
func (c *Client) Start(ctx context.Context) {
36-
for _, target := range c.Config.Targets {
37-
pool := NewPool(c, target)
38-
c.pools[target] = pool
39-
go pool.Start(ctx)
40-
}
36+
pool := NewPool(c, c.Config.Target)
37+
c.pools[c.Config.id] = pool
38+
go pool.Start(ctx)
4139
}
4240

4341
// Shutdown the Proxy

client/config.go

+35-21
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,76 @@
11
package client
22

33
import (
4-
"fmt"
54
"os"
65
"strings"
76

87
gonanoid "github.com/matoous/go-nanoid/v2"
98
uuid "github.com/nu7hatch/gouuid"
9+
1010
"gopkg.in/yaml.v3"
1111
)
1212

13-
// Config configures an Proxy
13+
type TunnelConfig struct {
14+
Name string
15+
Subdomain string
16+
Port int
17+
}
18+
19+
type ProxyTunnels struct {
20+
Tunnels []TunnelConfig
21+
}
22+
1423
type Config struct {
1524
id string
1625
subdomain string
1726
port int
1827
showWsReadErrors bool
1928

20-
Targets []string
29+
Target string
2130
PoolIdleSize int
2231
PoolMaxSize int
2332
SecretKey string
2433
}
2534

26-
// NewConfig creates a new ProxyConfig
27-
func NewConfig() (config *Config) {
28-
config = new(Config)
35+
// ProxyConfig configures an ProxyConfig
2936

37+
func (config *Config) setDefaults() {
3038
id, err := uuid.NewV4()
3139
if err != nil {
3240
panic(err)
3341
}
3442
config.id = id.String()
3543

36-
config.Targets = []string{"wss://t.amal.sh"}
37-
config.PoolIdleSize = 1
38-
config.PoolMaxSize = 100
44+
if config.Target == "" {
45+
config.Target = "wss://x.amal.sh"
46+
}
47+
if config.PoolIdleSize == 0 {
48+
config.PoolIdleSize = 1
49+
}
50+
if config.PoolMaxSize == 0 {
51+
config.PoolMaxSize = 100
52+
}
3953

40-
return
4154
}
4255

4356
// LoadConfiguration loads configuration from a YAML file
44-
func LoadConfiguration(path, subdomain string, port int, showWsReadErrors bool) (config *Config, err error) {
45-
config = NewConfig()
57+
func LoadConfiguration(configFile string, subdomain string, port int, showWsReadErrors bool) (Config, error) {
58+
var config Config
4659

47-
bytes, err := os.ReadFile(path)
60+
bytes, err := os.ReadFile(configFile)
4861
if err != nil {
49-
return
62+
return Config{}, err
5063
}
5164

52-
err = yaml.Unmarshal(bytes, config)
65+
err = yaml.Unmarshal(bytes, &config)
5366
if err != nil {
54-
return
67+
return Config{}, err
5568
}
56-
for i, v := range config.Targets {
57-
if !strings.HasSuffix(v, "/register") {
58-
config.Targets[i] = fmt.Sprintf("%s/register", v)
59-
}
69+
70+
config.setDefaults()
71+
72+
if !strings.HasSuffix(config.Target, "/register") {
73+
config.Target = config.Target + "/register"
6074
}
6175

6276
if subdomain == "" {
@@ -69,5 +83,5 @@ func LoadConfiguration(path, subdomain string, port int, showWsReadErrors bool)
6983
config.port = port
7084
config.showWsReadErrors = showWsReadErrors
7185

72-
return
86+
return config, nil
7387
}

client/connection.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func NewConnection(pool *Pool) *Connection {
4444
// Connect to the IsolatorServer using a HTTP websocket
4545
func (connection *Connection) Connect(ctx context.Context) (err error) {
4646
if connection.IsInitialConnection() {
47-
log.Println("Creating tunnel connection")
47+
log.Printf("Creating tunnel connection for :%d", connection.pool.client.Config.port)
4848
}
4949

5050
var res *http.Response
@@ -92,11 +92,12 @@ func (connection *Connection) Connect(ctx context.Context) (err error) {
9292
}
9393

9494
if connection.IsInitialConnection() {
95-
log.Printf("Tunnel running on %s://%s.%s%s",
95+
log.Printf("Tunnel connected %s://%s.%s%s -> http://localhost:%d",
9696
httpScheme,
9797
connection.pool.client.Config.subdomain,
9898
URL.Hostname(),
9999
httpPort,
100+
connection.pool.client.Config.port,
100101
)
101102
}
102103

@@ -182,7 +183,11 @@ func (connection *Connection) serve(ctx context.Context) {
182183
urlPath = urlPath + "?" + req.URL.RawQuery
183184
}
184185

185-
log.Printf("[%s] %d %s", req.Method, resp.StatusCode, urlPath)
186+
log.Printf("[%s] %d %s",
187+
req.Method,
188+
resp.StatusCode,
189+
urlPath,
190+
)
186191

187192
// Serialize response
188193
jsonResponse, err := json.Marshal(beaver.SerializeHTTPResponse(resp))

client/pool.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (pool *Pool) connector(ctx context.Context) {
7171
// Try to reach ideal pool size
7272
for i := 0; i < toCreate; i++ {
7373
conn := NewConnection(pool)
74-
pool.connections = append(pool.connections, conn)
74+
pool.add(conn)
7575

7676
go func() {
7777
err := conn.Connect(ctx)

cmd/beaver_client/http.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"github.com/amalshaji/beaver/client"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var (
12+
port int
13+
subdomain string
14+
httpCmd = &cobra.Command{
15+
Use: "http [PORT]",
16+
Short: "Tunnel local http servers",
17+
Args: func(cmd *cobra.Command, args []string) error {
18+
if len(args) == 0 {
19+
return fmt.Errorf("local server port is required")
20+
}
21+
if len(args) > 1 {
22+
return fmt.Errorf("only one port number is allowed")
23+
}
24+
25+
var err error
26+
port, err = strconv.Atoi(args[0])
27+
if err != nil {
28+
return fmt.Errorf("port must be a number")
29+
}
30+
31+
return nil
32+
},
33+
Run: func(cmd *cobra.Command, args []string) {
34+
var tunnels = make([]client.TunnelConfig, 0)
35+
tunnels = append(tunnels, client.TunnelConfig{Port: port, Subdomain: subdomain})
36+
startTunnels(tunnels)
37+
},
38+
}
39+
)
40+
41+
func init() {
42+
httpCmd.Flags().StringVar(&subdomain, "subdomain", "", "Subdomain to tunnel http requests (default \"<random_subdomain>\")")
43+
}

0 commit comments

Comments
 (0)