mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
187 lines
5.7 KiB
Go
187 lines
5.7 KiB
Go
package launchcode
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"ant-chrome/backend/internal/browser"
|
|
)
|
|
|
|
const (
|
|
defaultRuntimeSessionTimeout = 45 * time.Second
|
|
minRuntimeSessionTimeout = 1 * time.Second
|
|
maxRuntimeSessionTimeout = 2 * time.Minute
|
|
)
|
|
|
|
type RuntimeSessionRequest struct {
|
|
RuntimeRequest
|
|
LaunchRequestParams
|
|
TimeoutMs int `json:"timeoutMs"`
|
|
}
|
|
|
|
func decodeRuntimeSessionRequest(r *http.Request) (RuntimeSessionRequest, int, string) {
|
|
if r.Method != http.MethodPost {
|
|
return RuntimeSessionRequest{}, http.StatusMethodNotAllowed, "method not allowed"
|
|
}
|
|
|
|
var req RuntimeSessionRequest
|
|
dec := json.NewDecoder(io.LimitReader(r.Body, 1<<20))
|
|
dec.DisallowUnknownFields()
|
|
if err := dec.Decode(&req); err != nil {
|
|
return RuntimeSessionRequest{}, http.StatusBadRequest, "invalid request body"
|
|
}
|
|
return req, http.StatusOK, ""
|
|
}
|
|
|
|
func normalizeRuntimeSessionTimeout(timeoutMs int) time.Duration {
|
|
if timeoutMs <= 0 {
|
|
return defaultRuntimeSessionTimeout
|
|
}
|
|
|
|
timeout := time.Duration(timeoutMs) * time.Millisecond
|
|
if timeout < minRuntimeSessionTimeout {
|
|
return minRuntimeSessionTimeout
|
|
}
|
|
if timeout > maxRuntimeSessionTimeout {
|
|
return maxRuntimeSessionTimeout
|
|
}
|
|
return timeout
|
|
}
|
|
|
|
func (s *LaunchServer) handleRuntimeSession(w http.ResponseWriter, r *http.Request) {
|
|
startAt := time.Now()
|
|
clientIP := remoteIP(r.RemoteAddr)
|
|
selector := LaunchSelector{}
|
|
|
|
req, status, errMsg := decodeRuntimeSessionRequest(r)
|
|
if errMsg != "" {
|
|
writeJSON(w, status, map[string]interface{}{
|
|
"ok": false,
|
|
"error": errMsg,
|
|
})
|
|
s.appendLaunchLog(r.Method, r.URL.Path, clientIP, "", selector, LaunchRequestParams{}, false, status, errMsg, "", "", startAt)
|
|
return
|
|
}
|
|
|
|
selector = mergeRuntimeSelector(req.RuntimeRequest)
|
|
if selector.IsEmpty() {
|
|
errMsg = "selector is required"
|
|
writeJSON(w, http.StatusBadRequest, map[string]interface{}{
|
|
"ok": false,
|
|
"error": errMsg,
|
|
})
|
|
s.appendLaunchLog(r.Method, r.URL.Path, clientIP, "", selector, req.LaunchRequestParams, false, http.StatusBadRequest, errMsg, "", "", startAt)
|
|
return
|
|
}
|
|
if err := validateRuntimeSelector(selector); err != nil {
|
|
errMsg = err.Error()
|
|
writeJSON(w, http.StatusBadRequest, map[string]interface{}{
|
|
"ok": false,
|
|
"error": errMsg,
|
|
})
|
|
s.appendLaunchLog(r.Method, r.URL.Path, clientIP, selector.Code, selector, req.LaunchRequestParams, false, http.StatusBadRequest, errMsg, "", "", startAt)
|
|
return
|
|
}
|
|
|
|
req.LaunchRequestParams = normalizeLaunchRequestParams(req.LaunchRequestParams)
|
|
profile, launchCode, status, errMsg := s.launchBySelector(selector, req.LaunchRequestParams)
|
|
if errMsg != "" {
|
|
writeJSON(w, status, map[string]interface{}{
|
|
"ok": false,
|
|
"error": errMsg,
|
|
})
|
|
s.appendLaunchLog(r.Method, r.URL.Path, clientIP, launchCode, selector, req.LaunchRequestParams, false, status, errMsg, "", "", startAt)
|
|
return
|
|
}
|
|
|
|
waitTimeout := normalizeRuntimeSessionTimeout(req.TimeoutMs)
|
|
profile, ready, err := s.prepareRuntimeSession(profile, waitTimeout)
|
|
if err != nil {
|
|
writeJSON(w, mapProfileWriteErrorStatus(err), map[string]interface{}{
|
|
"ok": false,
|
|
"error": err.Error(),
|
|
})
|
|
s.appendLaunchLog(r.Method, r.URL.Path, clientIP, launchCode, selector, req.LaunchRequestParams, false, mapProfileWriteErrorStatus(err), err.Error(), "", "", startAt)
|
|
return
|
|
}
|
|
if profile == nil {
|
|
errMsg = "runtime session is not available"
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]interface{}{
|
|
"ok": false,
|
|
"error": errMsg,
|
|
})
|
|
s.appendLaunchLog(r.Method, r.URL.Path, clientIP, launchCode, selector, req.LaunchRequestParams, false, http.StatusServiceUnavailable, errMsg, "", "", startAt)
|
|
return
|
|
}
|
|
if launchCode != "" && profile.LaunchCode == "" {
|
|
profile.LaunchCode = launchCode
|
|
}
|
|
|
|
responseStatus := http.StatusAccepted
|
|
if ready {
|
|
responseStatus = http.StatusOK
|
|
}
|
|
payload := s.runtimeSessionPayload(profile, waitTimeout, ready)
|
|
writeJSON(w, responseStatus, payload)
|
|
s.appendLaunchLog(r.Method, r.URL.Path, clientIP, launchCode, selector, req.LaunchRequestParams, ready, responseStatus, "", profile.ProfileId, profile.ProfileName, startAt)
|
|
}
|
|
|
|
func (s *LaunchServer) prepareRuntimeSession(profile *browser.Profile, timeout time.Duration) (*browser.Profile, bool, error) {
|
|
normalized := s.normalizeRuntimeProfile(profile)
|
|
if normalized == nil {
|
|
return nil, false, nil
|
|
}
|
|
if normalized.DebugReady {
|
|
s.SetActiveProfile(normalized)
|
|
return normalized, true, nil
|
|
}
|
|
if timeout <= 0 {
|
|
return normalized, false, nil
|
|
}
|
|
|
|
if waiter, ok := s.starter.(BrowserDebugWaiter); ok {
|
|
waited, ready, err := waiter.WaitInstanceDebugReady(normalized.ProfileId, normalized.DebugPort, timeout)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if waited != nil {
|
|
normalized = s.normalizeRuntimeProfile(waited)
|
|
}
|
|
if normalized != nil && ready && normalized.DebugReady {
|
|
s.SetActiveProfile(normalized)
|
|
return normalized, true, nil
|
|
}
|
|
return normalized, normalized != nil && normalized.DebugReady, nil
|
|
}
|
|
|
|
deadline := time.Now().Add(timeout)
|
|
for {
|
|
snapshot, _, errMsg := s.statusProfile(normalized.ProfileId)
|
|
if errMsg != "" {
|
|
return nil, false, runtimeRequestError(errMsg)
|
|
}
|
|
if snapshot != nil {
|
|
normalized = snapshot
|
|
}
|
|
if normalized != nil && normalized.DebugReady {
|
|
s.SetActiveProfile(normalized)
|
|
return normalized, true, nil
|
|
}
|
|
if time.Now().After(deadline) {
|
|
return normalized, false, nil
|
|
}
|
|
time.Sleep(250 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func (s *LaunchServer) runtimeSessionPayload(profile *browser.Profile, timeout time.Duration, ready bool) map[string]interface{} {
|
|
payload := s.profileRuntimePayload(profile)
|
|
payload["ready"] = ready
|
|
payload["timeoutMs"] = timeout.Milliseconds()
|
|
payload["waitTimedOut"] = !ready
|
|
payload["retryable"] = !ready
|
|
return payload
|
|
}
|