概觀
PlayReady Test Server 包含以程式設計方式觸發各種伺服器例外狀況的特殊功能。 這項功能可讓客戶端開發人員測試其裝置和應用程式如何回應在生產環境中取得授權期間可能發生的不同錯誤狀況。
伺服器例外狀況測試
用戶端開發人員可以使用這些伺服器例外狀況命令來驗證其實作中的錯誤處理。 這包括測試案例,例如裝置撤銷、內部伺服器錯誤、通訊協定不符,以及網域相關例外狀況。
交易範例
以下是伺服器例外狀況運作方式的範例:
要求 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>
例外狀況參數
泛型例外狀況參數
| 參數 | 說明 | 範例 URL |
|---|---|---|
errorcode:XXXXXXXX |
要求伺服器以特定例外狀況回應 | http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0xXXXXXXXX) |
裝置和客戶端例外狀況
裝置憑證已撤銷
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C065 |
DRM_E_DEVCERT_REVOKED |
伺服器無法將授權傳遞至撤銷的用戶端裝置 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c065)
使用方式:測試用戶端如何處理裝置撤銷案例。
伺服器內部例外狀況
內部伺服器錯誤
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C600 |
DRM_E_SERVER_INTERNAL_ERROR |
伺服器擲回內部伺服器例外狀況 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c600)
使用方式:測試客戶端對伺服器端內部錯誤的復原能力。
無效的訊息
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C601 |
DRM_E_SERVER_INVALID_MESSAGE |
傳送至伺服器的要求無效 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c601)
使用方式:測試客戶端處理格式不正確的要求回應。
通訊協定版本不符
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C60B |
DRM_E_SERVER_PROTOCOL_VERSION_MISMATCH |
伺服器不支援要求中指定的通訊協定版本 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c60b)
使用方式:使用不支援的通訊協定版本測試客戶端行為。
服務特定例外狀況
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C604 |
DRM_E_SERVER_SERVICE_SPECIFIC |
伺服器擲回服務特定例外狀況(通常是來自授權處理程式) |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c604)
使用方式:測試服務特定錯誤的客戶端處理。
通訊協定和重新導向例外狀況
通訊協定重新導向
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C60D |
DRM_E_SERVER_PROTOCOL_REDIRECT |
通訊協定具有重新導向 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c60d)
使用方式:測試伺服器重新導向的客戶端處理。
Domain-Related 例外狀況
需要網域
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C605 |
DRM_E_SERVER_DOMAIN_REQUIRED |
伺服器收到來自客戶端的標準授權要求,並要求用戶端加入網域,才能接收網域系結授權 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c605)
使用方式:測試加入網域案例和用戶端網域感知。
更新網域
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C606 |
DRM_E_SERVER_RENEW_DOMAIN |
伺服器收到來自用戶端的要求,其中包含過期的網域版本。 要求用戶端更新其網域憑證 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c606)
使用方式:測試網域憑證更新程式。
已達到裝置限制
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C602 |
DRM_E_SERVER_DEVICE_LIMIT_REACHED |
伺服器願意將用戶端新增至網域帳戶,但帳戶已達到裝置數目限制 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c602)
使用方式:測試網域裝置限制的客戶端處理。
不是網域成員
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C60A |
DRM_E_SERVER_NOT_A_MEMBER |
伺服器收到來自用戶端的有效網域系結授權要求,但用戶端先前已依服務從網域中移除 |
範例︰
http://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(errorcode:0x8004c60a)
使用方式:從網域移除時測試客戶端行為。
未知的帳戶標識碼
| 參數 | 例外狀況 | 說明 |
|---|---|---|
errorcode:8004C60C |
DRM_E_SERVER_UNKNOWN_ACCOUNTID |
伺服器從用戶端收到特定帳戶標識碼的網域系結授權要求,但服務未知此標識符 |
範例︰
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 Test Server 服務 - 主要測試伺服器功能
- 查詢字串語法 - 參數語法參考
- 測試輸出保護 - 輸出 保護測試
- PlayReady 測試伺服器 - 完整伺服器檔
支援資源
商務查詢
- 電子郵件: playready@microsoft.com
作業查詢
- 網站: http://wmlalicensing.com/
- 電子郵件: ipla@microsoft.com
技術支援
- 支援入口網站: PlayReady 技術支援
訓練資訊
- 電子郵件: plyrdyev@microsoft.com