Files
clash-meta/hub/route/server.go
wwqgtxx cbcacdbb8c
Some checks failed
Build / build (map[abi:1 debian:loongarch64 goarch:loong64 goos:linux output:loong64-abi1 rpm:loongarch64]) (push) Has been cancelled
Build / build (map[abi:2 debian:loong64 goarch:loong64 goos:linux output:loong64-abi2 rpm:loong64]) (push) Has been cancelled
Build / build (map[debian:amd64 goamd64:v1 goarch:amd64 goos:linux output:amd64-v1 pacman:x86_64 rpm:x86_64 test:test]) (push) Has been cancelled
Build / build (map[debian:amd64 goamd64:v2 goarch:amd64 goos:linux output:amd64-v2 pacman:x86_64 rpm:x86_64]) (push) Has been cancelled
Build / build (map[debian:amd64 goamd64:v3 goarch:amd64 goos:linux output:amd64 pacman:x86_64 rpm:x86_64]) (push) Has been cancelled
Build / build (map[debian:amd64 goamd64:v3 goarch:amd64 goos:linux output:amd64-v3 pacman:x86_64 rpm:x86_64]) (push) Has been cancelled
Build / build (map[debian:arm64 goarch:arm64 goos:linux output:arm64 pacman:aarch64 rpm:aarch64]) (push) Has been cancelled
Build / build (map[debian:armel goarch:arm goarm:6 goos:linux output:armv6 rpm:armv6hl]) (push) Has been cancelled
Build / build (map[debian:armhf goarch:arm goarm:7 goos:linux output:armv7 pacman:armv7hl rpm:armv7hl]) (push) Has been cancelled
Build / build (map[debian:i386 go386:sse2 goarch:386 goos:linux output:386 rpm:i386]) (push) Has been cancelled
Build / build (map[debian:mips64el goarch:mips64le goos:linux output:mips64le rpm:mips64el]) (push) Has been cancelled
Build / build (map[debian:ppc64el goarch:ppc64le goos:linux output:ppc64le rpm:ppc64le]) (push) Has been cancelled
Build / build (map[debian:riscv64 goarch:riscv64 goos:linux output:riscv64 rpm:riscv64]) (push) Has been cancelled
Build / build (map[debian:s390x goarch:s390x goos:linux output:s390x rpm:s390x]) (push) Has been cancelled
Build / build (map[go386:softfloat goarch:386 goos:linux output:386-softfloat]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:darwin goversion:1.20 output:amd64-v1-go120]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:darwin goversion:1.22 output:amd64-v1-go122]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:darwin goversion:1.24 output:amd64-v1-go124]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:darwin output:amd64-compatible]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:darwin output:amd64-v1]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:freebsd output:amd64-compatible]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:freebsd output:amd64-v1]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:linux goversion:1.20 output:amd64-v1-go120 test:test]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:linux goversion:1.23 output:amd64-v1-go123 test:test]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:linux output:amd64-compatible]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:windows goversion:1.20 output:amd64-v1-go120]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:windows goversion:1.21 output:amd64-v1-go121]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:windows goversion:1.22 output:amd64-v1-go122]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:windows goversion:1.23 output:amd64-v1-go123]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:windows goversion:1.24 output:amd64-v1-go124]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:windows output:amd64-compatible]) (push) Has been cancelled
Build / build (map[goamd64:v1 goarch:amd64 goos:windows output:amd64-v1]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:darwin goversion:1.20 output:amd64-v2-go120]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:darwin goversion:1.22 output:amd64-v2-go122]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:darwin goversion:1.24 output:amd64-v2-go124]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:darwin output:amd64-v2]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:freebsd output:amd64-v2]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:linux goversion:1.20 output:amd64-v2-go120]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:linux goversion:1.23 output:amd64-v2-go123]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:windows goversion:1.20 output:amd64-v2-go120]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:windows goversion:1.21 output:amd64-v2-go121]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:windows goversion:1.22 output:amd64-v2-go122]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:windows goversion:1.23 output:amd64-v2-go123]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:windows goversion:1.24 output:amd64-v2-go124]) (push) Has been cancelled
Build / build (map[goamd64:v2 goarch:amd64 goos:windows output:amd64-v2]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:darwin goversion:1.20 output:amd64-v3-go120]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:darwin goversion:1.22 output:amd64-v3-go122]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:darwin goversion:1.24 output:amd64-v3-go124]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:darwin output:amd64-v3]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:darwin output:amd64]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:freebsd output:amd64-v3]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:freebsd output:amd64]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:linux goversion:1.20 output:amd64-v3-go120]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:linux goversion:1.23 output:amd64-v3-go123]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:windows goversion:1.20 output:amd64-v3-go120]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:windows goversion:1.21 output:amd64-v3-go121]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:windows goversion:1.22 output:amd64-v3-go122]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:windows goversion:1.23 output:amd64-v3-go123]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:windows goversion:1.24 output:amd64-v3-go124]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:windows output:amd64-v3]) (push) Has been cancelled
Build / build (map[goamd64:v3 goarch:amd64 goos:windows output:amd64]) (push) Has been cancelled
Build / build (map[goarch:386 goos:android ndk:i686-linux-android34 output:386]) (push) Has been cancelled
Build / build (map[goarch:386 goos:freebsd output:386]) (push) Has been cancelled
Build / build (map[goarch:386 goos:linux goversion:1.20 output:386-go120]) (push) Has been cancelled
Build / build (map[goarch:386 goos:linux goversion:1.23 output:386-go123]) (push) Has been cancelled
Build / build (map[goarch:386 goos:windows goversion:1.20 output:386-go120]) (push) Has been cancelled
Build / build (map[goarch:386 goos:windows goversion:1.21 output:386-go121]) (push) Has been cancelled
Build / build (map[goarch:386 goos:windows goversion:1.22 output:386-go122]) (push) Has been cancelled
Build / build (map[goarch:386 goos:windows goversion:1.23 output:386-go123]) (push) Has been cancelled
Build / build (map[goarch:386 goos:windows goversion:1.24 output:386-go124]) (push) Has been cancelled
Build / build (map[goarch:386 goos:windows output:386]) (push) Has been cancelled
Build / build (map[goarch:amd64 goos:android ndk:x86_64-linux-android34 output:amd64]) (push) Has been cancelled
Build / build (map[goarch:arm goarm:5 goos:linux output:armv5]) (push) Has been cancelled
Build / build (map[goarch:arm goos:android ndk:armv7a-linux-androideabi34 output:armv7]) (push) Has been cancelled
Build / build (map[goarch:arm64 goos:android ndk:aarch64-linux-android34 output:arm64-v8]) (push) Has been cancelled
Build / build (map[goarch:arm64 goos:darwin goversion:1.20 output:arm64-go120]) (push) Has been cancelled
Build / build (map[goarch:arm64 goos:darwin goversion:1.22 output:arm64-go122]) (push) Has been cancelled
Build / build (map[goarch:arm64 goos:darwin goversion:1.24 output:arm64-go124]) (push) Has been cancelled
Build / build (map[goarch:arm64 goos:darwin output:arm64]) (push) Has been cancelled
Build / build (map[goarch:arm64 goos:freebsd output:arm64]) (push) Has been cancelled
Build / build (map[goarch:arm64 goos:windows output:arm64]) (push) Has been cancelled
Build / build (map[goarch:mips gomips:hardfloat goos:linux output:mips-hardfloat]) (push) Has been cancelled
Build / build (map[goarch:mips gomips:softfloat goos:linux output:mips-softfloat]) (push) Has been cancelled
Build / build (map[goarch:mips64 goos:linux output:mips64]) (push) Has been cancelled
Build / build (map[goarch:mipsle gomips:hardfloat goos:linux output:mipsle-hardfloat]) (push) Has been cancelled
Build / build (map[goarch:mipsle gomips:softfloat goos:linux output:mipsle-softfloat]) (push) Has been cancelled
Build / Upload-Prerelease (push) Has been cancelled
Build / Upload-Release (push) Has been cancelled
Build / Docker (push) Has been cancelled
Test / test (1.20, macos-15-intel) (push) Has been cancelled
Test / test (1.20, macos-latest) (push) Has been cancelled
Test / test (1.20, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.20, ubuntu-latest) (push) Has been cancelled
Test / test (1.20, windows-latest) (push) Has been cancelled
Test / test (1.21, macos-15-intel) (push) Has been cancelled
Test / test (1.21, macos-latest) (push) Has been cancelled
Test / test (1.21, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.21, ubuntu-latest) (push) Has been cancelled
Test / test (1.21, windows-latest) (push) Has been cancelled
Test / test (1.22, macos-15-intel) (push) Has been cancelled
Test / test (1.22, macos-latest) (push) Has been cancelled
Test / test (1.22, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.22, ubuntu-latest) (push) Has been cancelled
Test / test (1.22, windows-latest) (push) Has been cancelled
Test / test (1.23, macos-15-intel) (push) Has been cancelled
Test / test (1.23, macos-latest) (push) Has been cancelled
Test / test (1.23, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.23, ubuntu-latest) (push) Has been cancelled
Test / test (1.23, windows-latest) (push) Has been cancelled
Test / test (1.24, macos-15-intel) (push) Has been cancelled
Test / test (1.24, macos-latest) (push) Has been cancelled
Test / test (1.24, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.24, ubuntu-latest) (push) Has been cancelled
Test / test (1.24, windows-latest) (push) Has been cancelled
Test / test (1.25, macos-15-intel) (push) Has been cancelled
Test / test (1.25, macos-latest) (push) Has been cancelled
Test / test (1.25, ubuntu-24.04-arm) (push) Has been cancelled
Test / test (1.25, ubuntu-latest) (push) Has been cancelled
Test / test (1.25, windows-latest) (push) Has been cancelled
Trigger CMFA Update / trigger-CMFA-update (push) Has been cancelled
chore: using tls.Config.GetCertificate/GetClientCertificate to load TLS certificates
2025-12-19 12:24:16 +08:00

566 lines
13 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 route
import (
"bytes"
"crypto/subtle"
"encoding/json"
"net"
"os"
"path/filepath"
"runtime/debug"
"strings"
"syscall"
"time"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/ntp"
"github.com/metacubex/mihomo/tunnel/statistic"
"github.com/metacubex/chi"
"github.com/metacubex/chi/cors"
"github.com/metacubex/chi/middleware"
"github.com/metacubex/chi/render"
"github.com/metacubex/http"
"github.com/metacubex/tls"
)
var (
uiPath = ""
httpServer *http.Server
tlsServer *http.Server
unixServer *http.Server
pipeServer *http.Server
embedMode = false
)
func SetEmbedMode(embed bool) {
embedMode = embed
}
type Traffic struct {
Up int64 `json:"up"`
Down int64 `json:"down"`
UpTotal int64 `json:"upTotal"`
DownTotal int64 `json:"downTotal"`
}
type Memory struct {
Inuse uint64 `json:"inuse"`
OSLimit uint64 `json:"oslimit"` // maybe we need it in the future
}
type Config struct {
Addr string
TLSAddr string
UnixAddr string
PipeAddr string
Secret string
Certificate string
PrivateKey string
ClientAuthType string
ClientAuthCert string
EchKey string
DohServer string
IsDebug bool
Cors Cors
}
type Cors struct {
AllowOrigins []string
AllowPrivateNetwork bool
}
func (c Cors) Apply(r chi.Router) {
r.Use(cors.New(cors.Options{
AllowedOrigins: c.AllowOrigins,
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
AllowPrivateNetwork: c.AllowPrivateNetwork,
MaxAge: 300,
}).Handler)
}
func ReCreateServer(cfg *Config) {
go start(cfg)
go startTLS(cfg)
go startUnix(cfg)
if inbound.SupportNamedPipe {
go startPipe(cfg)
}
}
func SetUIPath(path string) {
uiPath = C.Path.Resolve(path)
}
func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux {
r := chi.NewRouter()
cors.Apply(r)
if isDebug {
r.Mount("/debug", func() http.Handler {
r := chi.NewRouter()
r.Put("/gc", func(w http.ResponseWriter, r *http.Request) {
debug.FreeOSMemory()
})
handler := middleware.Profiler
r.Mount("/", handler())
return r
}())
}
r.Group(func(r chi.Router) {
if secret != "" {
r.Use(authentication(secret))
}
r.Get("/", hello)
r.Get("/logs", getLogs)
r.Get("/traffic", traffic)
r.Get("/memory", memory)
r.Get("/version", version)
r.Mount("/configs", configRouter())
r.Mount("/proxies", proxyRouter())
r.Mount("/group", groupRouter())
r.Mount("/rules", ruleRouter())
r.Mount("/connections", connectionRouter())
r.Mount("/providers/proxies", proxyProviderRouter())
r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/cache", cacheRouter())
r.Mount("/dns", dnsRouter())
if !embedMode { // disallow restart in embed mode
r.Mount("/restart", restartRouter())
}
r.Mount("/upgrade", upgradeRouter())
addExternalRouters(r)
})
if uiPath != "" {
r.Group(func(r chi.Router) {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath)))
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
})
}
if len(dohServer) > 0 && dohServer[0] == '/' {
r.Mount(dohServer, dohRouter())
}
return r
}
func start(cfg *Config) {
// first stop existing server
if httpServer != nil {
_ = httpServer.Close()
httpServer = nil
}
// handle addr
if len(cfg.Addr) > 0 {
l, err := inbound.Listen("tcp", cfg.Addr)
if err != nil {
log.Errorln("External controller listen error: %s", err)
return
}
log.Infoln("RESTful API listening at: %s", l.Addr().String())
server := &http.Server{
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
}
httpServer = server
if err = server.Serve(l); err != nil {
log.Errorln("External controller serve error: %s", err)
}
}
}
func startTLS(cfg *Config) {
// first stop existing server
if tlsServer != nil {
_ = tlsServer.Close()
tlsServer = nil
}
// handle tlsAddr
if len(cfg.TLSAddr) > 0 {
certLoader, err := ca.NewTLSKeyPairLoader(cfg.Certificate, cfg.PrivateKey, C.Path)
if err != nil {
log.Errorln("External controller tls listen error: %s", err)
return
}
l, err := inbound.Listen("tcp", cfg.TLSAddr)
if err != nil {
log.Errorln("External controller tls listen error: %s", err)
return
}
log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
tlsConfig := &tls.Config{Time: ntp.Now}
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return certLoader()
}
tlsConfig.ClientAuth = ca.ClientAuthTypeFromString(cfg.ClientAuthType)
if len(cfg.ClientAuthCert) > 0 {
if tlsConfig.ClientAuth == tls.NoClientCert {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
pool, err := ca.LoadCertificates(cfg.ClientAuthCert, C.Path)
if err != nil {
log.Errorln("External controller tls listen error: %s", err)
return
}
tlsConfig.ClientCAs = pool
}
if cfg.EchKey != "" {
err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path)
if err != nil {
log.Errorln("External controller tls serve error: %s", err)
return
}
}
server := &http.Server{
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
}
tlsServer = server
if err = server.Serve(tls.NewListener(l, tlsConfig)); err != nil {
log.Errorln("External controller tls serve error: %s", err)
}
}
}
func startUnix(cfg *Config) {
// first stop existing server
if unixServer != nil {
_ = unixServer.Close()
unixServer = nil
}
// handle addr
if len(cfg.UnixAddr) > 0 {
addr := C.Path.Resolve(cfg.UnixAddr)
dir := filepath.Dir(addr)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Errorln("External controller unix listen error: %s", err)
return
}
}
// https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/
//
// Note: As mentioned above in the security section, when a socket binds a socket to a valid pathname address,
// a socket file is created within the filesystem. On Linux, the application is expected to unlink
// (see the notes section in the man page for AF_UNIX) before any other socket can be bound to the same address.
// The same applies to Windows unix sockets, except that, DeleteFile (or any other file delete API)
// should be used to delete the socket file prior to calling bind with the same path.
_ = syscall.Unlink(addr)
l, err := inbound.Listen("unix", addr)
if err != nil {
log.Errorln("External controller unix listen error: %s", err)
return
}
_ = os.Chmod(addr, 0o666)
log.Infoln("RESTful API unix listening at: %s", l.Addr().String())
server := &http.Server{
Handler: router(cfg.IsDebug, "", cfg.DohServer, cfg.Cors),
}
unixServer = server
if err = server.Serve(l); err != nil {
log.Errorln("External controller unix serve error: %s", err)
}
}
}
func startPipe(cfg *Config) {
// first stop existing server
if pipeServer != nil {
_ = pipeServer.Close()
pipeServer = nil
}
// handle addr
if len(cfg.PipeAddr) > 0 {
if !strings.HasPrefix(cfg.PipeAddr, "\\\\.\\pipe\\") { // windows namedpipe must start with "\\.\pipe\"
log.Errorln("External controller pipe listen error: windows namedpipe must start with \"\\\\.\\pipe\\\"")
return
}
l, err := inbound.ListenNamedPipe(cfg.PipeAddr)
if err != nil {
log.Errorln("External controller pipe listen error: %s", err)
return
}
log.Infoln("RESTful API pipe listening at: %s", l.Addr().String())
server := &http.Server{
Handler: router(cfg.IsDebug, "", cfg.DohServer, cfg.Cors),
}
pipeServer = server
if err = server.Serve(l); err != nil {
log.Errorln("External controller pipe serve error: %s", err)
}
}
}
func safeEqual(a, b string) bool {
aBuf := utils.ImmutableBytesFromString(a)
bBuf := utils.ImmutableBytesFromString(b)
return subtle.ConstantTimeCompare(aBuf, bBuf) == 1
}
func authentication(secret string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// Browser websocket not support custom header
if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
token := r.URL.Query().Get("token")
if !safeEqual(token, secret) {
render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, ErrUnauthorized)
return
}
next.ServeHTTP(w, r)
return
}
header := r.Header.Get("Authorization")
bearer, token, found := strings.Cut(header, " ")
hasInvalidHeader := bearer != "Bearer"
hasInvalidSecret := !found || !safeEqual(token, secret)
if hasInvalidHeader || hasInvalidSecret {
render.Status(r, http.StatusUnauthorized)
render.JSON(w, r, ErrUnauthorized)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, render.M{"hello": "mihomo"})
}
func traffic(w http.ResponseWriter, r *http.Request) {
var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
var err error
wsConn, _, err = wsUpgrade(r, w)
if err != nil {
return
}
}
if wsConn == nil {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
}
tick := time.NewTicker(time.Second)
defer tick.Stop()
t := statistic.DefaultManager
buf := &bytes.Buffer{}
var err error
for range tick.C {
buf.Reset()
up, down := t.Now()
upTotal, downTotal := t.Total()
if err := json.NewEncoder(buf).Encode(Traffic{
Up: up,
Down: down,
UpTotal: upTotal,
DownTotal: downTotal,
}); err != nil {
break
}
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsWriteServerText(wsConn, buf.Bytes())
}
if err != nil {
break
}
}
}
func memory(w http.ResponseWriter, r *http.Request) {
var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
var err error
wsConn, _, err = wsUpgrade(r, w)
if err != nil {
return
}
}
if wsConn == nil {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
}
tick := time.NewTicker(time.Second)
defer tick.Stop()
t := statistic.DefaultManager
buf := &bytes.Buffer{}
var err error
first := true
for range tick.C {
buf.Reset()
inuse := t.Memory()
// make chat.js begin with zero
// this is shit var,but we need output 0 for first time
if first {
inuse = 0
first = false
}
if err := json.NewEncoder(buf).Encode(Memory{
Inuse: inuse,
OSLimit: 0,
}); err != nil {
break
}
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsWriteServerText(wsConn, buf.Bytes())
}
if err != nil {
break
}
}
}
type Log struct {
Type string `json:"type"`
Payload string `json:"payload"`
}
type LogStructuredField struct {
Key string `json:"key"`
Value string `json:"value"`
}
type LogStructured struct {
Time string `json:"time"`
Level string `json:"level"`
Message string `json:"message"`
Fields []LogStructuredField `json:"fields"`
}
func getLogs(w http.ResponseWriter, r *http.Request) {
levelText := r.URL.Query().Get("level")
if levelText == "" {
levelText = "info"
}
formatText := r.URL.Query().Get("format")
isStructured := false
if formatText == "structured" {
isStructured = true
}
level, ok := log.LogLevelMapping[levelText]
if !ok {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
var wsConn net.Conn
if r.Header.Get("Upgrade") == "websocket" {
var err error
wsConn, _, err = wsUpgrade(r, w)
if err != nil {
return
}
}
if wsConn == nil {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
}
ch := make(chan log.Event, 1024)
sub := log.Subscribe()
defer log.UnSubscribe(sub)
buf := &bytes.Buffer{}
go func() {
for logM := range sub {
select {
case ch <- logM:
default:
}
}
close(ch)
}()
for logM := range ch {
if logM.LogLevel < level {
continue
}
buf.Reset()
if !isStructured {
if err := json.NewEncoder(buf).Encode(Log{
Type: logM.Type(),
Payload: logM.Payload,
}); err != nil {
break
}
} else {
newLevel := logM.Type()
if newLevel == "warning" {
newLevel = "warn"
}
if err := json.NewEncoder(buf).Encode(LogStructured{
Time: time.Now().Format(time.TimeOnly),
Level: newLevel,
Message: logM.Payload,
Fields: []LogStructuredField{},
}); err != nil {
break
}
}
var err error
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsWriteServerText(wsConn, buf.Bytes())
}
if err != nil {
break
}
}
}
func version(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, render.M{"meta": C.Meta, "version": C.Version})
}