сборка сервера фронта в бинарник
This commit is contained in:
19
build.sh
Executable file
19
build.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Building frontend ==="
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo "=== Copying dist to server/ ==="
|
||||||
|
rm -rf server/dist
|
||||||
|
cp -r dist server/dist
|
||||||
|
|
||||||
|
echo "=== Building Go binary ==="
|
||||||
|
cd server
|
||||||
|
go mod init protoc-frontend-server 2>/dev/null || true
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o ../protoc-frontend.exe .
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Done! ==="
|
||||||
|
echo "Binary: protoc-frontend.exe"
|
||||||
|
echo "Usage: protoc-frontend.exe [-port 1111] [-api http://localhost:8000]"
|
||||||
BIN
protoc-frontend.exe
Executable file
BIN
protoc-frontend.exe
Executable file
Binary file not shown.
3
server/go.mod
Normal file
3
server/go.mod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module protoc-frontend-server
|
||||||
|
|
||||||
|
go 1.25.5
|
||||||
89
server/main.go
Normal file
89
server/main.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed dist/*
|
||||||
|
var distFS embed.FS
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
port := flag.Int("port", 1112, "port to listen on")
|
||||||
|
apiTarget := flag.String("api", "http://localhost:8000", "backend API URL to proxy /api requests")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Proxy for /api -> backend
|
||||||
|
backendURL, err := url.Parse(*apiTarget)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid api url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(backendURL)
|
||||||
|
originalDirector := proxy.Director
|
||||||
|
proxy.Director = func(r *http.Request) {
|
||||||
|
originalDirector(r)
|
||||||
|
// Rewrite /api/... -> /...
|
||||||
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
|
||||||
|
if r.URL.Path == "" {
|
||||||
|
r.URL.Path = "/"
|
||||||
|
}
|
||||||
|
r.Host = backendURL.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static files from embedded dist/
|
||||||
|
distSub, err := fs.Sub(distFS, "dist")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to open embedded dist: %v", err)
|
||||||
|
}
|
||||||
|
fileServer := http.FileServer(http.FS(distSub))
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
// API proxy
|
||||||
|
mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
// SPA: serve static files, fallback to index.html
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Try to serve the file directly
|
||||||
|
path := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
|
if path == "" {
|
||||||
|
path = "index.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file exists in embedded FS
|
||||||
|
if f, err := distSub.Open(path); err == nil {
|
||||||
|
f.Close()
|
||||||
|
fileServer.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to index.html for SPA routing
|
||||||
|
indexFile, err := fs.ReadFile(distSub, "index.html")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "index.html not found", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.Write(indexFile)
|
||||||
|
})
|
||||||
|
|
||||||
|
addr := fmt.Sprintf(":%d", *port)
|
||||||
|
log.Printf("Serving on http://localhost%s", addr)
|
||||||
|
log.Printf("API proxy -> %s", *apiTarget)
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user