diff --git a/config.go b/config.go index 8d3eac4..7b7d2c0 100644 --- a/config.go +++ b/config.go @@ -23,6 +23,10 @@ var AlwaysIgnoreDomains = []string{ "autopatchos.starrails.com", } +var AlwaysIgnoreUrls = []string{ + "/query_security_file", +} + var BlockUrls = []string{ "/data_abtest_api/config/experiment/list", "/common/hkrpg_global/announcement/api/getAlertPic", diff --git a/main.go b/main.go index c310c6e..fbccdd2 100644 --- a/main.go +++ b/main.go @@ -136,6 +136,11 @@ func main() { return req, nil } + if matchURL(path, AlwaysIgnoreUrls) { + zlog.Warn().Str("url", req.URL.String()).Msg("PASS URL") + return req, nil + } + if matchDomain(host, RedirectDomains) { if matchURL(path, BlockUrls) { full := req.URL.String() diff --git a/script/README_Note.md b/script/README_Note.md index 7cba9b4..04a14fd 100644 --- a/script/README_Note.md +++ b/script/README_Note.md @@ -1,4 +1,4 @@ # Changelog ### UPDATE -- Fix bug in macos \ No newline at end of file +- Support linux, macos, window \ No newline at end of file diff --git a/script/publish/publish-script b/script/publish/publish-script index 86e79d7..948d3ba 100644 Binary files a/script/publish/publish-script and b/script/publish/publish-script differ diff --git a/script/publish/publish.go b/script/publish/publish.go index b7aeb9c..24d4b96 100644 --- a/script/publish/publish.go +++ b/script/publish/publish.go @@ -9,12 +9,19 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "path/filepath" + "strconv" "strings" "time" ) +const ( + apiRequestTimeout = 45 * time.Second + maxHTTPAttempts = 5 +) + // API responses structures type CommonResponse struct { Status bool `json:"status"` @@ -83,7 +90,7 @@ func main() { gameIdsFlag := flag.String("game-ids", "", "Comma-separated Game IDs (defaults to ENV_GAME_IDS)") filesFlag := flag.String("files", "", "Comma-separated list of files to upload (defaults to scanning prebuild/)") cTypeFlag := flag.String("type", "", "Component type: LAUNCHER, PROXY, SERVER (defaults to ENV_COMPONENT_TYPE or PROXY)") - + flag.Parse() // 1. Resolve settings from Env or Flags @@ -324,24 +331,93 @@ func getFileInfoAndHash(path string) (int64, string, error) { return stat.Size(), hex.EncodeToString(hasher.Sum(nil)), nil } +func apiEndpoint(apiURL, path string) string { + return strings.TrimRight(apiURL, "/") + path +} + +func retryDelay(attempt int) time.Duration { + delay := time.Duration(1< 20*time.Second { + return 20 * time.Second + } + return delay +} + +func isRetryableStatus(statusCode int) bool { + return statusCode == http.StatusRequestTimeout || + statusCode == http.StatusTooEarly || + statusCode == http.StatusTooManyRequests || + statusCode >= http.StatusInternalServerError +} + +func isAccepted(statusCode int, accepted ...int) bool { + for _, code := range accepted { + if statusCode == code { + return true + } + } + return false +} + +func doHTTPWithRetry(label string, client *http.Client, buildRequest func() (*http.Request, error), accepted ...int) ([]byte, int, error) { + var lastErr error + for attempt := 1; attempt <= maxHTTPAttempts; attempt++ { + req, err := buildRequest() + if err != nil { + return nil, 0, err + } + + resp, err := client.Do(req) + if err != nil { + lastErr = err + if attempt < maxHTTPAttempts { + delay := retryDelay(attempt) + fmt.Fprintf(os.Stderr, "%s attempt %d/%d failed: %v. Retrying in %s...\n", label, attempt, maxHTTPAttempts, err, delay) + time.Sleep(delay) + continue + } + return nil, 0, err + } + + bodyBytes, readErr := io.ReadAll(resp.Body) + _ = resp.Body.Close() + if readErr != nil { + return nil, resp.StatusCode, readErr + } + + if isAccepted(resp.StatusCode, accepted...) { + return bodyBytes, resp.StatusCode, nil + } + + if !isRetryableStatus(resp.StatusCode) || attempt == maxHTTPAttempts { + return bodyBytes, resp.StatusCode, nil + } + + lastErr = fmt.Errorf("status %d: %s", resp.StatusCode, strings.TrimSpace(string(bodyBytes))) + delay := retryDelay(attempt) + fmt.Fprintf(os.Stderr, "%s attempt %d/%d returned %v. Retrying in %s...\n", label, attempt, maxHTTPAttempts, lastErr, delay) + time.Sleep(delay) + } + + return nil, 0, lastErr +} + func refreshRobotToken(apiURL, robotToken string) (string, error) { - url := fmt.Sprintf("%s/robot-tokens/refresh", apiURL) - req, err := http.NewRequest("POST", url, nil) + endpoint := apiEndpoint(apiURL, "/robot-tokens/refresh") + client := &http.Client{Timeout: apiRequestTimeout} + bodyBytes, statusCode, err := doHTTPWithRetry("refresh robot token", client, func() (*http.Request, error) { + req, err := http.NewRequest("POST", endpoint, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+robotToken) + return req, nil + }, http.StatusOK) if err != nil { return "", err } - req.Header.Set("Authorization", "Bearer "+robotToken) - - client := &http.Client{Timeout: 15 * time.Second} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - bodyBytes, _ := io.ReadAll(resp.Body) - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("refresh token failed with status %d: %s", resp.StatusCode, string(bodyBytes)) + if statusCode != http.StatusOK { + return "", fmt.Errorf("refresh token failed with status %d: %s", statusCode, string(bodyBytes)) } var cr CommonResponse @@ -362,25 +438,30 @@ func refreshRobotToken(apiURL, robotToken string) (string, error) { } func getPresignedURL(apiURL, accessToken, fileName, contentType string, size int64) (*PreSignedResponse, error) { - u := fmt.Sprintf("%s/media/presigned?fileName=%s&content_type=%s&size=%d", - apiURL, fileName, contentType, size) - - req, err := http.NewRequest("GET", u, nil) + endpoint, err := url.Parse(apiEndpoint(apiURL, "/media/presigned")) if err != nil { return nil, err } - req.Header.Set("Authorization", "Bearer "+accessToken) + query := endpoint.Query() + query.Set("fileName", fileName) + query.Set("content_type", contentType) + query.Set("size", strconv.FormatInt(size, 10)) + endpoint.RawQuery = query.Encode() - client := &http.Client{Timeout: 15 * time.Second} - resp, err := client.Do(req) + client := &http.Client{Timeout: apiRequestTimeout} + bodyBytes, statusCode, err := doHTTPWithRetry("get presigned URL", client, func() (*http.Request, error) { + req, err := http.NewRequest("GET", endpoint.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + return req, nil + }, http.StatusOK) if err != nil { return nil, err } - defer resp.Body.Close() - - bodyBytes, _ := io.ReadAll(resp.Body) - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to get presigned URL (status %d): %s", resp.StatusCode, string(bodyBytes)) + if statusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get presigned URL (status %d): %s", statusCode, string(bodyBytes)) } var cr CommonResponse @@ -443,7 +524,7 @@ func uploadFileToS3(path string, presigned *PreSignedResponse) error { } func completePreSignedUpload(apiURL, accessToken, tokenID, hash string) (string, error) { - url := fmt.Sprintf("%s/media/presigned/complete", apiURL) + endpoint := apiEndpoint(apiURL, "/media/presigned/complete") metaJSON, _ := json.Marshal(map[string]string{"sha256": hash}) dto := PreSignedCompleteDto{ @@ -453,23 +534,21 @@ func completePreSignedUpload(apiURL, accessToken, tokenID, hash string) (string, payload, _ := json.Marshal(dto) - req, err := http.NewRequest("POST", url, bytes.NewReader(payload)) + client := &http.Client{Timeout: apiRequestTimeout} + bodyBytes, statusCode, err := doHTTPWithRetry("complete presigned upload", client, func() (*http.Request, error) { + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + req.Header.Set("Content-Type", "application/json") + return req, nil + }, http.StatusOK) if err != nil { return "", err } - req.Header.Set("Authorization", "Bearer "+accessToken) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{Timeout: 30 * time.Second} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - bodyBytes, _ := io.ReadAll(resp.Body) - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("complete upload failed (status %d): %s", resp.StatusCode, string(bodyBytes)) + if statusCode != http.StatusOK { + return "", fmt.Errorf("complete upload failed (status %d): %s", statusCode, string(bodyBytes)) } var cr CommonResponse @@ -490,27 +569,25 @@ func completePreSignedUpload(apiURL, accessToken, tokenID, hash string) (string, } func createComponent(apiURL, accessToken string, dto CreateComponentRequest) (string, error) { - url := fmt.Sprintf("%s/components", apiURL) + endpoint := apiEndpoint(apiURL, "/components") payload, _ := json.Marshal(dto) - req, err := http.NewRequest("POST", url, bytes.NewReader(payload)) + client := &http.Client{Timeout: apiRequestTimeout} + bodyBytes, statusCode, err := doHTTPWithRetry("create component", client, func() (*http.Request, error) { + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + req.Header.Set("Content-Type", "application/json") + return req, nil + }, http.StatusCreated, http.StatusOK) if err != nil { return "", err } - req.Header.Set("Authorization", "Bearer "+accessToken) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{Timeout: 30 * time.Second} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - bodyBytes, _ := io.ReadAll(resp.Body) - if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("create component failed (status %d): %s", resp.StatusCode, string(bodyBytes)) + if statusCode != http.StatusCreated && statusCode != http.StatusOK { + return "", fmt.Errorf("create component failed (status %d): %s", statusCode, string(bodyBytes)) } var cr CommonResponse @@ -533,25 +610,30 @@ func createComponent(apiURL, accessToken string, dto CreateComponentRequest) (st } func findExistingComponent(apiURL, accessToken, cType, platform, version string) (string, error) { - u := fmt.Sprintf("%s/components?type=%s&platform=%s&search=%s", - apiURL, cType, platform, version) - - req, err := http.NewRequest("GET", u, nil) + endpoint, err := url.Parse(apiEndpoint(apiURL, "/components")) if err != nil { return "", err } - req.Header.Set("Authorization", "Bearer "+accessToken) + query := endpoint.Query() + query.Set("type", cType) + query.Set("platform", platform) + query.Set("search", version) + endpoint.RawQuery = query.Encode() - client := &http.Client{Timeout: 15 * time.Second} - resp, err := client.Do(req) + client := &http.Client{Timeout: apiRequestTimeout} + bodyBytes, statusCode, err := doHTTPWithRetry("search existing component", client, func() (*http.Request, error) { + req, err := http.NewRequest("GET", endpoint.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + return req, nil + }, http.StatusOK) if err != nil { return "", err } - defer resp.Body.Close() - - bodyBytes, _ := io.ReadAll(resp.Body) - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to search components (status %d): %s", resp.StatusCode, string(bodyBytes)) + if statusCode != http.StatusOK { + return "", fmt.Errorf("failed to search components (status %d): %s", statusCode, string(bodyBytes)) } var pr struct { @@ -582,27 +664,25 @@ func findExistingComponent(apiURL, accessToken, cType, platform, version string) } func updateComponent(apiURL, accessToken, id string, dto UpdateComponentRequest) (string, error) { - url := fmt.Sprintf("%s/components/%s", apiURL, id) + endpoint := apiEndpoint(apiURL, "/components/"+url.PathEscape(id)) payload, _ := json.Marshal(dto) - req, err := http.NewRequest("PUT", url, bytes.NewReader(payload)) + client := &http.Client{Timeout: apiRequestTimeout} + bodyBytes, statusCode, err := doHTTPWithRetry("update component", client, func() (*http.Request, error) { + req, err := http.NewRequest("PUT", endpoint, bytes.NewReader(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + req.Header.Set("Content-Type", "application/json") + return req, nil + }, http.StatusOK) if err != nil { return "", err } - req.Header.Set("Authorization", "Bearer "+accessToken) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{Timeout: 30 * time.Second} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - bodyBytes, _ := io.ReadAll(resp.Body) - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("update component failed (status %d): %s", resp.StatusCode, string(bodyBytes)) + if statusCode != http.StatusOK { + return "", fmt.Errorf("update component failed (status %d): %s", statusCode, string(bodyBytes)) } var cr CommonResponse diff --git a/script/release.json b/script/release.json index 35f7972..3c8041b 100644 --- a/script/release.json +++ b/script/release.json @@ -1,5 +1,5 @@ { - "tag": "1.2-03", - "title": "PreBuild Version 1.2 - 03" + "tag": "1.3-01", + "title": "PreBuild Version 1.3 - 01" } \ No newline at end of file