S2S API 响应代码和错误处理
了解 Singular 的 S2S API 响应代码并实施适当的错误处理,可确保与可靠的数据传输和归因准确性进行稳健集成。
响应架构
HTTP 状态代码行为
关键理解:与 Singular 的 API 集成时,所有响应都会返回 HTTP 200 状态代码,需要验证响应体的 "status "字段,以确定成功("ok")或失败("error")。
当状态为"error"时,响应有效载荷包括一个"reason "字段,提供详细的错误信息。
响应结构:无论成功或失败,所有 API 响应都遵循一致的 JSON 格式,从而在所有端点交互中实现标准化解析和错误处理。
错误处理策略
实施最佳实践
实施全面的错误处理策略,确保数据完整性和系统可靠性。
推荐方法:
- 重试机制:实施指数后退重试,可配置最大尝试次数
- 顺序处理:在重试期间保持请求顺序,以确保数据一致性
- 错误分类:区分可重试错误和不可重试错误(无效参数不应重试
- 全面日志记录:记录所有失败请求,包括原始参数、错误信息、设备标识符和时间戳
- 监控和警报:跟踪重试尝试并实施重大故障通知系统
指数退信实施
指数回退可防止服务器在出现临时故障时不堪重负,同时提供高效的重试策略。
重试策略:
- 初始延迟:首次故障后开始延迟 1-2 秒
- 后退倍增器:每次重试的延迟时间加倍(2 秒 → 4 秒 → 8 秒 → 16 秒
- 最大尝试次数:配置限制(建议:3-5 次尝试
- 最大延迟:将延迟上限设定为合理的阈值(例如 60 秒
- 抖动:添加随机抖动以防止雷鸣群效应
import requests
import time
import random
NON_RETRYABLE_KEYWORDS = ['invalid', 'missing', 'should have', 'no device id']
def is_retryable_error(reason):
"""A validation/parameter error is not retryable; anything else is."""
reason = (reason or '').lower()
return not any(keyword in reason for keyword in NON_RETRYABLE_KEYWORDS)
def exponential_backoff_request(url, params, max_retries=3):
"""Send an S2S API request with exponential-backoff retry logic."""
base_delay = 1 # seconds
max_delay = 60 # cap
for attempt in range(max_retries):
try:
response = requests.post(
url,
data=params,
headers={'Content-Type': 'application/x-www-form-urlencoded'},
timeout=10,
)
# Valid responses always return HTTP 200; check the body's status field.
if response.status_code == 200:
data = response.json()
if data.get('status') == 'ok':
return {'success': True, 'data': data}
reason = data.get('reason', '')
if not is_retryable_error(reason):
return {'success': False, 'reason': reason, 'retryable': False}
# Retryable error: fall through to the backoff delay.
except requests.exceptions.RequestException as error:
# Network/timeout errors are retryable.
if attempt == max_retries - 1:
return {'success': False, 'reason': str(error), 'retryable': True}
# Wait before the next attempt, with jitter to avoid a thundering herd.
if attempt < max_retries - 1:
delay = min(base_delay * (2 ** attempt), max_delay)
time.sleep(delay + random.uniform(0, 1))
return {'success': False, 'reason': 'max retries exceeded', 'retryable': True}
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class SingularApiClient {
private static final int MAX_RETRIES = 3;
private static final long BASE_DELAY_MS = 1000;
private static final long MAX_DELAY_MS = 60000;
private static final HttpClient client = HttpClient.newHttpClient();
public static ApiResponse exponentialBackoffRequest(String url, Map<String, String> params) {
for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/x-www-form-urlencoded")
.timeout(Duration.ofSeconds(10))
.POST(HttpRequest.BodyPublishers.ofString(buildFormBody(params)))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
// Valid responses always return HTTP 200; check the body's status field.
if (response.statusCode() == 200) {
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
if ("ok".equals(json.get("status").getAsString())) {
return new ApiResponse(true, response.body(), false);
}
String reason = json.has("reason")
? json.get("reason").getAsString() : "Unknown error";
if (!isRetryableError(reason)) {
return new ApiResponse(false, reason, false);
}
// Retryable error: fall through to the backoff delay.
}
} catch (Exception error) {
// Network/timeout errors are retryable.
if (attempt == MAX_RETRIES - 1) {
return new ApiResponse(false, error.getMessage(), true);
}
}
// Wait before the next attempt, with jitter to avoid a thundering herd.
if (attempt < MAX_RETRIES - 1) {
long delay = Math.min(BASE_DELAY_MS * (1L << attempt), MAX_DELAY_MS);
long jitter = (long) (Math.random() * 1000);
try {
Thread.sleep(delay + jitter);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
}
}
}
return new ApiResponse(false, "Max retries exceeded", true);
}
private static boolean isRetryableError(String reason) {
String lower = reason.toLowerCase();
for (String keyword : new String[] {"invalid", "missing", "should have", "no device id"}) {
if (lower.contains(keyword)) {
return false;
}
}
return true;
}
private static String buildFormBody(Map<String, String> params) {
StringBuilder body = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (body.length() > 0) {
body.append('&');
}
body.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
.append('=')
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
}
return body.toString();
}
}
class ApiResponse {
public final boolean success;
public final String message;
public final boolean retryable;
public ApiResponse(boolean success, String message, boolean retryable) {
this.success = success;
this.message = message;
this.retryable = retryable;
}
}
日志和监控
全面的日志记录可快速排除故障,并提供集成健康状况的可见性。
日志基本信息:
- 请求详情:包含所有参数的完整请求 URL(屏蔽敏感数据
- 响应数据:HTTP 状态代码、响应正文和标头
- 错误上下文:错误信息、原因字段和错误分类
- 设备信息:用于特定用户故障诊断的设备标识符
- 时间戳:请求时间、响应时间和重试时间戳
- 重试元数据:尝试次数、重试延迟和最终结果
监控指标:按类型跟踪错误率、重试成功率、平均响应时间和失败请求量,以主动识别集成问题。
成功响应
成功的 API 响应表明 Singular 的系统接受并处理了请求。
HTTP 200 - 成功
| HTTP 响应 | 描述 |
|---|---|
200 - ok
|
200 - ok响应体中无错误或原因,表示请求已成功发送到队列进行处理。 成功指标:状态字段包含 "ok",响应中没有 "原因 "字段。 响应结构:
处理 注意:"ok "状态确认请求已排队等待处理,但并不保证立即在报告中可见--在Singular平台中验证数据前需要一定的处理时间。 |
错误响应
错误响应提供有关请求失败的详细信息,以便快速排除故障和解决问题。
参数错误
缺少所需参数
| HTTP 响应 | 说明 |
|---|---|
200 - error
|
带原因的响应: 原因:请求中缺少所需参数或参数值为空/空。 不可重试错误:此错误表明请求结构无效。在未解决参数问题之前不要重试。 解决方法:
响应示例:
常见缺失参数: |
平台错误
平台值无效
| HTTP 响应 | 说明 |
|---|---|
200 - error
|
带原因的响应: 原因:平台参数值不在支持的平台列表中或格式不正确(区分大小写)。 不可重试错误:无效参数值需要更正后才能重新提交。 支持的平台:
解决方案:
响应示例:
常见错误:使用 "台式机 "而不是 "个人电脑"、小写平台名称或不支持的平台值 |
设备标识符错误
设备标识符缺失
| HTTP 响应 | 说明 |
|---|---|
200 - error
|
带原因的响应: 原因:请求缺少指定平台所需的设备标识符。 不可重试错误:归因需要设备标识符。如果没有有效标识符,则无法处理请求。 解决方法:
响应示例:
|
200 - error
|
带原因的响应: 原因:指定了平台,但缺少该平台所需的设备标识符或提供的标识符类型不正确。 不可重试错误:平台标识符不匹配导致无法归因。需要正确的标识符。 常见情况:
解决方法
响应示例:
|
网络和基础设施错误
非 200 HTTP 状态代码
| HTTP 响应 | 说明 |
|---|---|
4xx / 5xx
|
任何非 200 HTTP 状态代码都表示基础设施或网络问题。 可重试错误:这些错误通常为瞬时错误,实施指数退信重试机制。 常见状态代码:
解决方法
限制速率:如果收到 429 错误,降低请求速率并实施请求节流以保持在限制范围内。 |
错误分类
了解错误类型可实现适当的重试逻辑和更快的问题解决。
可重试与不可重试
不可重试错误
这些错误表明请求参数或配置无效,需要在重新提交前进行手动更正。
| 错误模式 | 需要采取的行动 |
|---|---|
missing argument: {param}
|
在请求中添加缺失的参数 |
invalid platform: {platform}
|
将平台值更正为支持的选项 |
no device ID supplied
|
包含所需的设备标识符 |
platform: {platform} should have an {identifier} param
|
为指定平台添加正确的标识符 |
可重试错误
这些错误表示暂时性问题,可通过使用指数退行重试来解决。
| 错误类型 | 重试策略 |
|---|---|
| 非 200 HTTP 状态代码 | 使用指数反演重试(2-3 次尝试) |
| 网络超时 | 重试,指数后退(3-5 次尝试) |
| 连接错误 | 重试,指数级延迟(3-5 次尝试) |
| 速率限制错误 (429) | 延长回退延迟,降低请求速率 |
决策逻辑:根据原因字段内容对错误进行分类。 包含 "无效"、"缺失 "或 "本应 "的错误通常不可重试。 基础结构错误(超时、连接失败、5xx 代码)可重试。
故障排除指南
解决常见 API 集成问题的快速参考。
常见问题及解决方案
参数问题
| 症状 | 解决方法 |
|---|---|
| 缺少参数:a |
在
|
| 平台无效错误 |
使用正确的区分大小写的平台值
|
| 未提供设备 ID |
包含特定平台的设备标识符
|
| 平台应具有标识符 |
根据平台匹配标识符类型
|
请求验证核对表
飞行前验证:在发送请求前验证这些项目,以防止出现常见错误。
-
包含 SDK 密钥 (
a) 且正确无误(不是报告 API 密钥 -
平台 (
p) 值与支持列表完全匹配 -
应用程序标识符 (
i) 与平台类型相匹配(iOS 为捆绑 ID,Android 为软件包名称 - 适合平台的设备标识符,不能为空/空
-
包含 IP 地址 (
ip) 或use_ip=true -
移动平台包含操作系统版本 (
ve) - 包含终端所需的所有参数
-
必要时对参数值进行 URL 编码(尤其是
e参数中的 JSON
支持资源
附加帮助
如果在执行错误处理和故障排除后问题仍然存在,请使用附加帮助:
- 文档:查看完整的S2S API 文档
- 测试:使用SDK 控制台验证集成
- 支持:联系 Singular 支持,提供错误日志、请求示例和设备标识符
- 状态:检查服务事件的 Singular 系统状态