Files
qgo-server/main.go
Smile Rex fd7655aded
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m50s
fix1
2026-03-04 01:14:20 +03:00

161 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"context"
"log"
"net/http"
"strings"
"sync"
"github.com/caddyserver/certmagic"
"github.com/quic-go/quic-go/http3"
"github.com/quic-go/webtransport-go"
)
type Room struct {
conns map[*webtransport.Session]bool
mu sync.Mutex
}
type Server struct {
rooms map[string]*Room
mu sync.Mutex
}
func NewServer() *Server {
return &Server{rooms: make(map[string]*Room)}
}
func (s *Server) getRoom(name string) *Room {
s.mu.Lock()
defer s.mu.Unlock()
if room, ok := s.rooms[name]; ok {
return room
}
room := &Room{conns: make(map[*webtransport.Session]bool)}
s.rooms[name] = room
return room
}
func (s *Server) handleSession(roomName string, sess *webtransport.Session) {
room := s.getRoom(roomName)
room.mu.Lock()
room.conns[sess] = true
room.mu.Unlock()
log.Printf("User joined room: %s", roomName)
defer func() {
room.mu.Lock()
delete(room.conns, sess)
room.mu.Unlock()
log.Printf("User left room: %s", roomName)
}()
for {
stream, err := sess.AcceptStream(context.Background())
if err != nil {
return
}
go s.handleStream(room, sess, stream)
}
}
func (s *Server) handleStream(room *Room, sender *webtransport.Session, stream *webtransport.Stream) {
buf := make([]byte, 4096)
for {
n, err := stream.Read(buf)
if err != nil {
return
}
data := append([]byte(nil), buf[:n]...)
room.mu.Lock()
for conn := range room.conns {
if conn == sender {
continue
}
go func(c *webtransport.Session, d []byte) {
out, err := c.OpenStream()
if err != nil {
return
}
defer out.Close()
out.Write(d)
}(conn, data)
}
room.mu.Unlock()
}
}
func main() {
domain := "qgo-server.quizer.space"
email := "serverussnap@outlook.com"
// 1. Настройка CertMagic
certmagic.DefaultACME.Email = email
certmagic.DefaultACME.Agreed = true
cfg := certmagic.NewDefault()
// Важно: ManageSync получит сертификат ДО запуска серверов
err := cfg.ManageSync(context.Background(), []string{domain})
if err != nil {
log.Fatal("CertMagic error:", err)
}
tlsConf := cfg.TLSConfig()
// NextProtos критически важен для WebTransport
tlsConf.NextProtos = []string{"h3", "http/1.1"}
server := NewServer()
mux := http.NewServeMux()
// 2. WebTransport сервер
wt := &webtransport.Server{
H3: &http3.Server{
Addr: ":443",
TLSConfig: tlsConf,
Handler: mux,
},
CheckOrigin: func(r *http.Request) bool { return true }, // Разрешаем подключения отовсюду
}
mux.HandleFunc("/room/", func(w http.ResponseWriter, r *http.Request) {
// Сообщаем браузеру, что сервер поддерживает HTTP/3 на порту 443
w.Header().Set("Alt-Svc", `h3=":443"; ma=86400`)
roomName := strings.TrimPrefix(r.URL.Path, "/room/")
if roomName == "" {
http.Error(w, "Room name required", 400)
return
}
sess, err := wt.Upgrade(w, r)
if err != nil {
log.Printf("Upgrade error (check if you use https): %v", err)
return
}
go server.handleSession(roomName, sess)
})
// 3. Запуск UDP (HTTP/3 + WebTransport)
go func() {
log.Printf("Starting UDP (WebTransport) on https://%s/room/", domain)
if err := wt.ListenAndServe(); err != nil {
log.Fatalf("UDP Server Error: %v", err)
}
}()
// 4. Запуск TCP (HTTPS + ACME Challenge)
// Это нужно, чтобы браузер сначала зашел по TCP и узнал про Alt-Svc
log.Printf("Starting TCP (HTTPS) on https://%s", domain)
httpServer := &http.Server{
Addr: ":443",
TLSConfig: tlsConf,
Handler: mux,
}
// Используем ListenAndServeTLS с пустыми путями, так как сертификаты в tlsConf
log.Fatal(httpServer.ListenAndServeTLS("", ""))
}