initial commit
This commit is contained in:
136
cmd/link.go
Normal file
136
cmd/link.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type TunnelRequest struct {
|
||||
ID string `json:"id"`
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Body []byte `json:"body"`
|
||||
}
|
||||
|
||||
type TunnelResponse struct {
|
||||
ID string `json:"id"`
|
||||
StatusCode int `json:"status_code"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Body []byte `json:"body"`
|
||||
}
|
||||
|
||||
var serverAddr string
|
||||
|
||||
var linkCmd = &cobra.Command{
|
||||
Use: "link <vhost_location> <local_port>",
|
||||
Short: "Create a tunnel link",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
vhostLoc := args[0]
|
||||
localPort := args[1]
|
||||
|
||||
fmt.Printf("Creating tunnel: %s -> localhost:%s\n", vhostLoc, localPort)
|
||||
|
||||
if err := startTunnel(vhostLoc, localPort); err != nil {
|
||||
log.Fatal("Failed to start tunnel:", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
linkCmd.Flags().StringVarP(&serverAddr, "server", "s", "localhost:8080", "Conduit server address")
|
||||
}
|
||||
|
||||
func startTunnel(vhost, localPort string) error {
|
||||
// Connect to WebSocket
|
||||
wsURL := fmt.Sprintf("ws://%s/_conduit/tunnel?vhost=%s", serverAddr, vhost)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Printf("Tunnel active! %s.example.com -> localhost:%s\n", vhost, localPort)
|
||||
|
||||
// Handle incoming requests
|
||||
for {
|
||||
var req TunnelRequest
|
||||
if err := conn.ReadJSON(&req); err != nil {
|
||||
log.Printf("Error reading request: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
go handleTunnelRequest(conn, &req, localPort)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleTunnelRequest(conn *websocket.Conn, req *TunnelRequest, localPort string) {
|
||||
// Make request to local service
|
||||
localURL := fmt.Sprintf("http://localhost:%s%s", localPort, req.URL)
|
||||
|
||||
httpReq, err := http.NewRequest(req.Method, localURL, bytes.NewReader(req.Body))
|
||||
if err != nil {
|
||||
sendErrorResponse(conn, req.ID, 500, "Failed to create request")
|
||||
return
|
||||
}
|
||||
|
||||
// Set headers
|
||||
for k, v := range req.Headers {
|
||||
httpReq.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// Make the request
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
sendErrorResponse(conn, req.ID, 502, "Failed to reach local service")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
sendErrorResponse(conn, req.ID, 500, "Failed to read response")
|
||||
return
|
||||
}
|
||||
|
||||
// Convert response headers
|
||||
headers := make(map[string]string)
|
||||
for k, v := range resp.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Send response back
|
||||
tunnelResp := &TunnelResponse{
|
||||
ID: req.ID,
|
||||
StatusCode: resp.StatusCode,
|
||||
Headers: headers,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
if err := conn.WriteJSON(tunnelResp); err != nil {
|
||||
log.Printf("Error sending response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func sendErrorResponse(conn *websocket.Conn, reqID string, statusCode int, message string) {
|
||||
resp := &TunnelResponse{
|
||||
ID: reqID,
|
||||
StatusCode: statusCode,
|
||||
Headers: map[string]string{"Content-Type": "text/plain"},
|
||||
Body: []byte(message),
|
||||
}
|
||||
conn.WriteJSON(resp)
|
||||
}
|
||||
26
cmd/root.go
Normal file
26
cmd/root.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "conduit",
|
||||
Short: "A tunneling service similar to ngrok",
|
||||
Long: `Conduit allows you to expose local services through secure tunnels`,
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
rootCmd.AddCommand(linkCmd)
|
||||
}
|
||||
30
cmd/serve.go
Normal file
30
cmd/serve.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"reichard.io/conduit/server"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var port string
|
||||
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start the conduit server",
|
||||
Long: `Start the conduit server to handle incoming tunnel requests`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Starting Conduit server on port %s...\n", port)
|
||||
|
||||
srv := server.NewServer()
|
||||
if err := srv.Start(":" + port); err != nil {
|
||||
log.Fatal("Failed to start server:", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
serveCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to run the server on")
|
||||
}
|
||||
Reference in New Issue
Block a user