148 lines
3.7 KiB
Go
148 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"embed"
|
|
"encoding/base64"
|
|
"flag"
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
//go:embed web/*
|
|
var embeddedWeb embed.FS
|
|
|
|
var version = "dev"
|
|
|
|
func main() {
|
|
noOpen := flag.Bool("no-open", false, "do not open the browser automatically")
|
|
publicMode := flag.Bool("public", false, "run the public web service")
|
|
listenAddress := flag.String("listen", "0.0.0.0:8080", "public web service listen address")
|
|
publicOrigin := flag.String("origin", "https://lrc.flechazo.xin", "allowed public browser origin")
|
|
downloadDirectory := flag.String("downloads", "", "directory containing optional local-app downloads")
|
|
healthcheckURL := flag.String("healthcheck", "", "check a running service and exit")
|
|
showVersion := flag.Bool("version", false, "print version and exit")
|
|
flag.Parse()
|
|
if *showVersion {
|
|
fmt.Println(version)
|
|
return
|
|
}
|
|
if *healthcheckURL != "" {
|
|
client := &http.Client{Timeout: 3 * time.Second}
|
|
response, err := client.Get(*healthcheckURL)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
_ = response.Body.Close()
|
|
if response.StatusCode != http.StatusOK {
|
|
log.Fatalf("healthcheck returned HTTP %d", response.StatusCode)
|
|
}
|
|
return
|
|
}
|
|
if *publicMode {
|
|
runPublic(*listenAddress, *publicOrigin, *downloadDirectory)
|
|
return
|
|
}
|
|
runLocal(*noOpen)
|
|
}
|
|
|
|
func runLocal(noOpen bool) {
|
|
|
|
listener, err := net.Listen("tcp4", "127.0.0.1:0")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
token, err := randomToken()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
webFS, err := fs.Sub(embeddedWeb, "web")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
baseURL := "http://" + listener.Addr().String()
|
|
app := newApp(baseURL, token, webFS, newQQClient())
|
|
server := &http.Server{
|
|
Handler: app.routes(),
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
MaxHeaderBytes: 16 * 1024,
|
|
}
|
|
|
|
launchURL := baseURL + "/start?t=" + token
|
|
fmt.Printf("LRC Local %s\n仅监听本机:%s\n关闭此窗口即可停止。\n", version, baseURL)
|
|
if !noOpen {
|
|
go func() {
|
|
time.Sleep(150 * time.Millisecond)
|
|
if err := openBrowser(launchURL); err != nil {
|
|
log.Printf("无法自动打开浏览器,请手动访问:%s", launchURL)
|
|
}
|
|
}()
|
|
} else {
|
|
fmt.Printf("请访问:%s\n", launchURL)
|
|
}
|
|
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func runPublic(listenAddress, origin, downloadDirectory string) {
|
|
listener, err := net.Listen("tcp4", listenAddress)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
webFS, err := fs.Sub(embeddedWeb, "web")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
app, err := newPublicApp(origin, downloadDirectory, webFS, newQQClient())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
server := &http.Server{
|
|
Handler: app.routes(),
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 5 * time.Minute,
|
|
IdleTimeout: 60 * time.Second,
|
|
MaxHeaderBytes: 16 * 1024,
|
|
}
|
|
log.Printf("LRC Local %s public service listening on %s", version, listener.Addr())
|
|
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func randomToken() (string, error) {
|
|
buf := make([]byte, 32)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
return "", err
|
|
}
|
|
return base64.RawURLEncoding.EncodeToString(buf), nil
|
|
}
|
|
|
|
func openBrowser(url string) error {
|
|
var command *exec.Cmd
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
command = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
|
|
case "darwin":
|
|
command = exec.Command("open", url)
|
|
default:
|
|
command = exec.Command("xdg-open", url)
|
|
}
|
|
command.Stdout = os.Stdout
|
|
command.Stderr = os.Stderr
|
|
return command.Start()
|
|
}
|