Enhance MTProxy checker by introducing a new -probe mode deep-strict for stricter response validation. Update README and Docker documentation to clarify probing modes, exit codes, and the new MTPROXY_TDLIB_HELPER and MTPROXY_TDLIB_TIMEOUT environment variables for external helper integration. Refactor connection handling and error reporting to improve robustness and clarity in response verification.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
// Package tdlibexec runs an optional external helper (e.g. Node + TDLib) for proxy verification.
|
||||
// Contract: helper argv = [helperPath, proxyURL]; stdout must contain a JSON line:
|
||||
//
|
||||
// {"ok":true,"error":"","exit_code":0}
|
||||
package tdlibexec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Result is the parsed outcome of a helper invocation.
|
||||
type Result struct {
|
||||
OK bool
|
||||
ExitCode int
|
||||
Error string
|
||||
DurationMs int64
|
||||
}
|
||||
|
||||
type jsonLine struct {
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
}
|
||||
|
||||
// Run executes helperPath with a single argument proxyURL and parses the last JSON object from stdout.
|
||||
func Run(ctx context.Context, helperPath, proxyURL string) (Result, error) {
|
||||
start := time.Now()
|
||||
ms := func() int64 { return time.Since(start).Milliseconds() }
|
||||
|
||||
if strings.TrimSpace(helperPath) == "" {
|
||||
return Result{}, errors.New("empty helper path")
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, helperPath, proxyURL)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
duration := ms()
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return Result{OK: false, ExitCode: 4, Error: "tdlib helper: " + ctx.Err().Error(), DurationMs: duration}, ctx.Err()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
r, perr := parseStdout(stdout.Bytes(), stderr.Bytes(), duration)
|
||||
if perr != nil {
|
||||
return Result{OK: false, ExitCode: 1, Error: perr.Error(), DurationMs: duration}, nil
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
var ee *exec.ExitError
|
||||
if errors.As(err, &ee) {
|
||||
if r, perr := parseStdout(stdout.Bytes(), stderr.Bytes(), duration); perr == nil {
|
||||
if r.ExitCode == 0 && !r.OK {
|
||||
r.ExitCode = ee.ExitCode()
|
||||
}
|
||||
if r.ExitCode == 0 && !r.OK {
|
||||
r.ExitCode = 2
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
msg := strings.TrimSpace(stdout.String())
|
||||
if msg == "" {
|
||||
msg = strings.TrimSpace(stderr.String())
|
||||
}
|
||||
if msg == "" {
|
||||
msg = err.Error()
|
||||
}
|
||||
code := ee.ExitCode()
|
||||
if code < 0 || code > 4 {
|
||||
code = 2
|
||||
}
|
||||
return Result{OK: false, ExitCode: code, Error: msg, DurationMs: duration}, nil
|
||||
}
|
||||
|
||||
return Result{}, fmt.Errorf("tdlib helper: %w", err)
|
||||
}
|
||||
|
||||
func parseStdout(stdout, stderr []byte, duration int64) (Result, error) {
|
||||
lines := strings.Split(string(stdout), "\n")
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if line == "" || line[0] != '{' {
|
||||
continue
|
||||
}
|
||||
var j jsonLine
|
||||
if err := json.Unmarshal([]byte(line), &j); err != nil {
|
||||
continue
|
||||
}
|
||||
if j.ExitCode == 0 && !j.OK {
|
||||
j.ExitCode = 2
|
||||
}
|
||||
if j.Error == "" && !j.OK {
|
||||
j.Error = "tdlib reported failure"
|
||||
}
|
||||
return Result{OK: j.OK, ExitCode: j.ExitCode, Error: j.Error, DurationMs: duration}, nil
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
return Result{}, fmt.Errorf("no json in stdout: stderr=%s", strings.TrimSpace(string(stderr)))
|
||||
}
|
||||
return Result{}, fmt.Errorf("no json line in helper stdout")
|
||||
}
|
||||
Reference in New Issue
Block a user