概述
PlayReady 测试服务器包括以编程方式触发各种服务器异常的特殊功能。 此功能允许客户端开发人员测试其设备和应用程序如何响应在生产环境中获取许可证期间可能发生的不同错误条件。
服务器异常测试
客户端开发人员可以使用这些服务器异常命令来验证其实现中的错误处理。 这包括测试方案,例如设备吊销、内部服务器错误、协议不匹配和域相关的异常。
事务示例
下面是服务器异常工作原理的示例:
请求 URL:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c065)
服务器响应:
HTTP/1.1 500 Internal Server Error
Cache-Control: private
Content-Length: 764
Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="utf-8" ?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>
System.Web.Services.Protocols.SoapException: Device Certificate Revoked.
at Microsoft.Media.Drm.RightsManager.ConvertRmServerException(RMServerException ex)
at Microsoft.Media.Drm.RightsManager.AcquireLicense(XmlDocument challenge)
</faultstring>
<faultactor>http://prtsprod-rightsmanager.azurewebsites.net/rightsmanager.asmx?cfg=(errorcode:0x8004c065)</faultactor>
<detail>
<Exception>
<StatusCode>0x8004c065</StatusCode>
</Exception>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
异常参数
泛型异常参数
| 参数 | DESCRIPTION | 示例 URL |
|---|---|---|
errorcode:XXXXXXXX |
请求服务器响应特定异常 | http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0xXXXXXXXX) |
设备和客户端异常
设备证书已吊销
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C065 |
DRM_E_DEVCERT_REVOKED |
服务器无法将许可证传送到吊销的客户端设备 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c065)
使用情况:测试客户端如何处理设备吊销方案。
服务器内部异常
内部服务器错误
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C600 |
DRM_E_SERVER_INTERNAL_ERROR |
服务器引发内部服务器异常 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c600)
用法:测试客户端对服务器端内部错误的复原能力。
无效消息
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C601 |
DRM_E_SERVER_INVALID_MESSAGE |
发送到服务器的请求无效 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c601)
用法:测试客户端处理格式不正确的请求响应。
协议版本不匹配
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C60B |
DRM_E_SERVER_PROTOCOL_VERSION_MISMATCH |
服务器不支持请求中指定的协议版本 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c60b)
用法:使用不支持的协议版本测试客户端行为。
服务特定的异常
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C604 |
DRM_E_SERVER_SERVICE_SPECIFIC |
服务器引发服务特定的异常(通常来自许可证处理程序) |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c604)
用法:测试客户端处理特定于服务的错误。
协议和重定向异常
协议重定向
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C60D |
DRM_E_SERVER_PROTOCOL_REDIRECT |
协议具有重定向 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c60d)
用法:测试服务器重定向的客户端处理。
Domain-Related 异常
域必需
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C605 |
DRM_E_SERVER_DOMAIN_REQUIRED |
服务器从客户端收到标准许可证请求,并要求客户端加入域才能接收域绑定许可证 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c605)
用法:测试域加入方案和客户端域感知。
续订域
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C606 |
DRM_E_SERVER_RENEW_DOMAIN |
服务器收到来自客户端的请求,其中包含域证书,包括过时的域版本。 要求客户端更新其域证书 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c606)
用法:测试域证书续订过程。
达到设备限制
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C602 |
DRM_E_SERVER_DEVICE_LIMIT_REACHED |
服务器愿意将客户端添加到域帐户,但该帐户已达到设备数限制 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c602)
用法:测试域设备限制的客户端处理。
不是域成员
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C60A |
DRM_E_SERVER_NOT_A_MEMBER |
服务器从客户端收到有效的域绑定许可证请求,但客户端以前已按服务从域中删除 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c60a)
用法:从域中删除时测试客户端行为。
未知帐户 ID
| 参数 | 例外 | DESCRIPTION |
|---|---|---|
errorcode:8004C60C |
DRM_E_SERVER_UNKNOWN_ACCOUNTID |
服务器从客户端收到特定帐户 ID 的域绑定许可证请求,但此 ID 对服务未知 |
示例:
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c60c)
用法:测试未知域帐户方案的客户端处理。
测试方案
基本异常测试
async function testServerExceptions() {
const exceptions = [
{ name: 'Device Revoked', code: '0x8004c065' },
{ name: 'Internal Error', code: '0x8004c600' },
{ name: 'Invalid Message', code: '0x8004c601' },
{ name: 'Protocol Mismatch', code: '0x8004c60b' }
];
const results = [];
for (const exception of exceptions) {
try {
const url = `http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:${exception.code})`;
const response = await testLicenseAcquisition(url);
results.push({
test: exception.name,
result: 'UNEXPECTED_SUCCESS',
error: 'Expected exception but got success'
});
} catch (error) {
results.push({
test: exception.name,
result: 'EXPECTED_EXCEPTION',
errorCode: error.statusCode,
message: error.message
});
}
}
return results;
}
域异常测试
async function testDomainExceptions() {
const domainExceptions = [
{ name: 'Domain Required', code: '0x8004c605' },
{ name: 'Renew Domain', code: '0x8004c606' },
{ name: 'Device Limit Reached', code: '0x8004c602' },
{ name: 'Not a Member', code: '0x8004c60a' },
{ name: 'Unknown Account', code: '0x8004c60c' }
];
const results = [];
for (const exception of domainExceptions) {
try {
const url = `http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:${exception.code})`;
await testLicenseAcquisition(url);
results.push({
test: exception.name,
result: 'FAIL',
reason: 'Expected domain exception but got success'
});
} catch (error) {
results.push({
test: exception.name,
result: 'PASS',
exceptionType: exception.name,
statusCode: error.statusCode
});
}
}
return results;
}
客户端可靠性测试
async function testClientRobustness() {
// Test how client handles rapid exception scenarios
const rapidTests = [
'0x8004c065', // Device revoked
'0x8004c600', // Internal error
'0x8004c601', // Invalid message
'0x8004c60b' // Protocol mismatch
];
const startTime = Date.now();
const promises = rapidTests.map(code => {
const url = `http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:${code})`;
return testLicenseAcquisitionWithTimeout(url, 5000);
});
const results = await Promise.allSettled(promises);
const endTime = Date.now();
return {
totalTime: endTime - startTime,
results: results.map((result, index) => ({
errorCode: rapidTests[index],
status: result.status,
handled: result.status === 'rejected' // We expect rejections
}))
};
}
客户端实现准则
异常处理最佳做法
class PlayReadyExceptionHandler {
handleLicenseAcquisitionError(error) {
switch (error.statusCode) {
case 0x8004C065: // Device revoked
return this.handleDeviceRevoked();
case 0x8004C600: // Internal server error
return this.handleServerError(error);
case 0x8004C601: // Invalid message
return this.handleInvalidMessage(error);
case 0x8004C60B: // Protocol version mismatch
return this.handleProtocolMismatch(error);
case 0x8004C605: // Domain required
return this.handleDomainRequired(error);
case 0x8004C606: // Renew domain
return this.handleDomainRenewal(error);
default:
return this.handleUnknownError(error);
}
}
handleDeviceRevoked() {
// Device is revoked - cannot proceed
throw new Error('Device has been revoked and cannot play protected content');
}
handleServerError(error) {
// Retry with exponential backoff
return this.retryWithBackoff(error.originalRequest);
}
handleDomainRequired(error) {
// Initiate domain joining process
return this.joinDomain(error.domainServiceUrl);
}
handleDomainRenewal(error) {
// Renew domain certificate
return this.renewDomainCertificate(error.domainServiceUrl);
}
}
C# 异常处理
public class PlayReadyExceptionHandler
{
public async Task<bool> HandleLicenseException(Exception ex)
{
if (ex is SoapException soapEx)
{
var statusCode = ExtractStatusCode(soapEx);
switch (statusCode)
{
case 0x8004C065: // Device revoked
return HandleDeviceRevoked();
case 0x8004C600: // Internal server error
return await HandleServerError(soapEx);
case 0x8004C605: // Domain required
return await HandleDomainRequired(soapEx);
case 0x8004C606: // Renew domain
return await HandleDomainRenewal(soapEx);
default:
return HandleUnknownError(soapEx);
}
}
return false;
}
private uint ExtractStatusCode(SoapException soapEx)
{
// Extract status code from SOAP fault detail
var detail = soapEx.Detail;
var statusNode = detail?.SelectSingleNode("//StatusCode");
if (statusNode != null && uint.TryParse(statusNode.InnerText.Replace("0x", ""),
NumberStyles.HexNumber, null, out uint statusCode))
{
return statusCode;
}
return 0;
}
}
验证和测试
预期行为
使用服务器异常进行测试时,请验证客户端:
- 正确分析异常 - 正确提取错误代码和消息
- 正常处理 - 不会崩溃或挂起异常
- 提供用户反馈 - 向用户显示适当的错误消息
- 实现重试逻辑 - 使用退避重试适当的错误
- 支持域作 - 正确处理与域相关的异常
测试验证清单
- [ ] 客户端正确标识异常类型
- [ ] 显示适当的用户消息
- [ ] 客户端不会在任何异常类型上崩溃
- [ ] 重试逻辑适用于可恢复的错误
- [ ] 正确触发域加入/续订过程
- [ ] 安全处理设备吊销
- [ ] 错误条件下的性能仍可接受
最佳做法
客户端开发
- 综合错误处理 - 处理所有记录的异常类型
- 用户体验 - 提供清晰、可作的错误消息
- 重试策略 - 为暂时性错误实现适当的重试逻辑
- 安全性 - 确保设备吊销阻止内容访问
- 测试 - 在开发过程中测试所有异常方案
错误恢复
- 正常降级 - 尽可能继续作
- 清除通信 - 通知用户问题与解决方案
- 自动恢复 - 在适当情况下尝试自动解决
- 回退选项 - 提供替代内容或服务
- 日志记录 - 记录调试和分析的异常
相关文档
- PlayReady 测试服务器服务 - 主测试服务器功能
- 查询字符串语法 - 参数语法参考
- 测试输出保护 - 输出保护测试
- PlayReady 测试服务器 - 完整服务器文档
支持资源
业务查询
- 电子邮件: playready@microsoft.com
作查询
技术支持
- 支持门户: PlayReady 技术支持
培训信息
- 电子邮件: plyrdyev@microsoft.com