proxy / main.go
sarveshpatel's picture
Create main.go
4e5b86f verified
package main
import (
"encoding/json"
"io"
"log"
"net/http"
"sync"
"sync/atomic"
"time"
"github.com/gorilla/websocket"
)
type Request struct {
ID uint64 `json:"id"`
Method string `json:"method"`
Path string `json:"path"`
Headers map[string][]string `json:"headers"`
Body []byte `json:"body"`
}
type Response struct {
ID uint64 `json:"id"`
Status int `json:"status"`
Headers map[string][]string `json:"headers"`
Body []byte `json:"body"`
}
type Tunnel struct {
conn *websocket.Conn
mu sync.Mutex
pending map[uint64]chan Response
pendMu sync.RWMutex
counter uint64
lastPing time.Time
}
var (
tunnel *Tunnel
tunnelMu sync.RWMutex
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
ReadBufferSize: 65536,
WriteBufferSize: 65536,
}
authToken = "your-secret-token-change-me"
)
func main() {
http.HandleFunc("/", handler)
log.Println("Server starting on :7860")
log.Fatal(http.ListenAndServe(":7860", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/_tunnel":
handleTunnel(w, r)
case "/_health":
handleHealth(w)
default:
handleProxy(w, r)
}
}
func handleHealth(w http.ResponseWriter) {
tunnelMu.RLock()
connected := tunnel != nil
tunnelMu.RUnlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"status": "ok",
"connected": connected,
"time": time.Now().Unix(),
})
}
func handleTunnel(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Tunnel-Token") != authToken {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("upgrade failed: %v", err)
return
}
t := &Tunnel{
conn: conn,
pending: make(map[uint64]chan Response),
lastPing: time.Now(),
}
tunnelMu.Lock()
if tunnel != nil {
tunnel.conn.Close()
}
tunnel = t
tunnelMu.Unlock()
log.Println("client connected")
t.readLoop()
tunnelMu.Lock()
if tunnel == t {
tunnel = nil
}
tunnelMu.Unlock()
log.Println("client disconnected")
}
func (t *Tunnel) readLoop() {
defer t.conn.Close()
for {
_, data, err := t.conn.ReadMessage()
if err != nil {
return
}
var resp Response
if json.Unmarshal(data, &resp) != nil {
continue
}
t.pendMu.RLock()
ch, ok := t.pending[resp.ID]
t.pendMu.RUnlock()
if ok {
select {
case ch <- resp:
default:
}
}
}
}
func (t *Tunnel) send(req Request) (Response, error) {
ch := make(chan Response, 1)
t.pendMu.Lock()
t.pending[req.ID] = ch
t.pendMu.Unlock()
defer func() {
t.pendMu.Lock()
delete(t.pending, req.ID)
t.pendMu.Unlock()
}()
data, _ := json.Marshal(req)
t.mu.Lock()
err := t.conn.WriteMessage(websocket.TextMessage, data)
t.mu.Unlock()
if err != nil {
return Response{}, err
}
select {
case resp := <-ch:
return resp, nil
case <-time.After(60 * time.Second):
return Response{Status: 504}, nil
}
}
func handleProxy(w http.ResponseWriter, r *http.Request) {
tunnelMu.RLock()
t := tunnel
tunnelMu.RUnlock()
if t == nil {
http.Error(w, "tunnel not connected", http.StatusBadGateway)
return
}
body, _ := io.ReadAll(r.Body)
req := Request{
ID: atomic.AddUint64(&t.counter, 1),
Method: r.Method,
Path: r.URL.RequestURI(),
Headers: r.Header,
Body: body,
}
resp, err := t.send(req)
if err != nil {
http.Error(w, "tunnel error", http.StatusBadGateway)
return
}
for k, vals := range resp.Headers {
for _, v := range vals {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.Status)
w.Write(resp.Body)
}