|
@@ -2,6 +2,7 @@ package job
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"encoding/json"
|
|
"encoding/json"
|
|
|
|
|
+ "time"
|
|
|
|
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
|
@@ -28,6 +29,26 @@ type XrayTrafficJob struct {
|
|
|
// refetch for the rest.
|
|
// refetch for the rest.
|
|
|
const clientStatsSnapshotMaxClients = 5000
|
|
const clientStatsSnapshotMaxClients = 5000
|
|
|
|
|
|
|
|
|
|
+// externalInformTimeout bounds the traffic-notify POST. Run() is scheduled
|
|
|
|
|
+// every 5s under cron.SkipIfStillRunning, so an unbounded call to a receiver
|
|
|
|
|
+// that accepts the connection and then stalls does not merely delay one
|
|
|
|
|
+// notification: it holds the job, and every following tick is skipped for as
|
|
|
|
|
+// long as the stall lasts. AddTraffic — quota enforcement, auto-renew — and
|
|
|
|
|
+// the online/websocket work all sit in that same tick (#6115).
|
|
|
|
|
+const externalInformTimeout = 3 * time.Second
|
|
|
|
|
+
|
|
|
|
|
+// externalInformClient is kept separate from fasthttp's shared default client
|
|
|
|
|
+// so this endpoint's timeouts and connection handling cannot be influenced by,
|
|
|
|
|
+// or influence, any other caller. Idempotent-call retries stay off on purpose:
|
|
|
|
|
+// the payload carries per-tick deltas, so an attempt that reached the receiver
|
|
|
|
|
+// but failed on the response leg would be counted twice if it were resent.
|
|
|
|
|
+var externalInformClient = &fasthttp.Client{
|
|
|
|
|
+ ReadTimeout: externalInformTimeout,
|
|
|
|
|
+ WriteTimeout: externalInformTimeout,
|
|
|
|
|
+ MaxIdleConnDuration: time.Minute,
|
|
|
|
|
+ MaxConnsPerHost: 4,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// NewXrayTrafficJob creates a new traffic collection job instance.
|
|
// NewXrayTrafficJob creates a new traffic collection job instance.
|
|
|
func NewXrayTrafficJob() *XrayTrafficJob {
|
|
func NewXrayTrafficJob() *XrayTrafficJob {
|
|
|
return new(XrayTrafficJob)
|
|
return new(XrayTrafficJob)
|
|
@@ -197,6 +218,9 @@ func (j *XrayTrafficJob) Run() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) {
|
|
func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) {
|
|
|
|
|
+ if len(inboundTraffics) == 0 && len(clientTraffics) == 0 {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
informURL, err := j.settingService.GetExternalTrafficInformURI()
|
|
informURL, err := j.settingService.GetExternalTrafficInformURI()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
logger.Warning("get ExternalTrafficInformURI failed:", err)
|
|
logger.Warning("get ExternalTrafficInformURI failed:", err)
|
|
@@ -218,9 +242,10 @@ func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traf
|
|
|
request.Header.SetContentType("application/json; charset=UTF-8")
|
|
request.Header.SetContentType("application/json; charset=UTF-8")
|
|
|
request.SetBody(requestBody)
|
|
request.SetBody(requestBody)
|
|
|
request.SetRequestURI(informURL)
|
|
request.SetRequestURI(informURL)
|
|
|
|
|
+ request.Header.SetConnectionClose()
|
|
|
response := fasthttp.AcquireResponse()
|
|
response := fasthttp.AcquireResponse()
|
|
|
defer fasthttp.ReleaseResponse(response)
|
|
defer fasthttp.ReleaseResponse(response)
|
|
|
- if err := fasthttp.Do(request, response); err != nil {
|
|
|
|
|
|
|
+ if err := externalInformClient.DoTimeout(request, response, externalInformTimeout); err != nil {
|
|
|
logger.Warning("POST ExternalTrafficInformURI failed:", err)
|
|
logger.Warning("POST ExternalTrafficInformURI failed:", err)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|