http 응답 검증 로직 추가

This commit is contained in:
김형인
2025-06-11 00:36:58 +09:00
parent 7ef6825368
commit cd8c5e177b
8 changed files with 120 additions and 36 deletions

View File

@@ -227,11 +227,24 @@ namespace UVC.Data
}
IDataObject? dataObject = null;
result = result.Trim();
if (!string.IsNullOrEmpty(result))
{
result = result.Trim();
try
{
string? responseData = info.ResponseMask.Apply(result);
// responseData가 null인 경우는 올바른 응답이 아니므로 처리하지 않음
if (responseData == null)
{
throw new Exception($"유효하지 않은 response data. {result}");
}
else
{
result = responseData.Trim();
}
if (result.StartsWith("{"))
{
if (info.DataMapper != null)

View File

@@ -1,5 +1,6 @@
#nullable enable
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
@@ -34,6 +35,7 @@ namespace UVC.Data
private int _repeatCount = 0; // 반복 횟수, 0은 무한 반복
private int _repeatInterval = 1000; // 반복 간격 (ms)
private DataMapper? _dataMapper = null; // 데이터 매퍼
private ResponseMask _responseMask = new ResponseMask(); // response 데이터 성공 여부와 데이터를 구분하는 마스크
private int _maxRetryCount = 3;
private int _retryDelay = 1000; // 밀리초
private bool _updatedDataOnly = false; // 업데이트된 데이터만 받을 여부
@@ -83,6 +85,11 @@ namespace UVC.Data
/// </summary>
public DataMapper? DataMapper => _dataMapper;
/// <summary>
/// response에 적용되는 데이터 마스크
/// </summary>
public ResponseMask ResponseMask => _responseMask;
/// <summary>
/// 최대 재시도 횟수
/// </summary>
@@ -125,6 +132,17 @@ namespace UVC.Data
return this;
}
/// <summary>
/// HTTP 파이프라인에 적용할 response 마스크를 설정하고 업데이트된 파이프라인 구성을 반환합니다.
/// </summary>
/// <param name="responseMask">HTTP response에 적용할 <see cref="DataMask"/>입니다.</param>
/// <returns>지정된 response 마스크가 적용된 업데이트된 <see cref="HttpPipeLineInfo"/> 인스턴스입니다.</returns>
public HttpPipeLineInfo setResponseMask(ResponseMask responseMask)
{
_responseMask = responseMask;
return this;
}
/// <summary>
/// HTTP 요청이 완료된 후 호출될 핸들러를 설정합니다.
/// 변경 된 데이터는 IDataObject로 전달됩니다.
@@ -186,4 +204,41 @@ namespace UVC.Data
}
}
public class ResponseMask
{
private string successKey = "message";
private string successValue = "success";
private string dataKey = "data";
public string SuccessKey { get; }
public string SuccessValue { get; }
public string DataKey { get; }
public ResponseMask() { }
public ResponseMask(string successKey, string successValue, string dataKey)
{
this.successKey = successKey;
this.successValue = successValue;
this.dataKey = dataKey;
}
public string? Apply(string response)
{
JObject responseObject = JObject.Parse(response);
if(responseObject.TryGetValue(successKey, out JToken? successToken) &&
responseObject.TryGetValue(dataKey, out JToken? dataToken) &&
successToken?.ToString().ToLower() == successValue)
{
// 성공적인 응답 처리 로직
return dataToken?.ToString(); // 실제로는 dataKey에 해당하는 데이터만 반환할 수 있습니다.
}
else
{
return null; // 실패한 응답 처리 로직
}
}
}
}

View File

@@ -2,7 +2,9 @@
using Newtonsoft.Json.Linq;
using SampleProject.Config;
using System;
using System.Collections.Generic;
using UVC.Log;
using UVC.network;
using UVC.Tests;
@@ -135,9 +137,12 @@ namespace UVC.Data
{
MQTTPipeLineInfo info = infoList[topic];
IDataObject? dataObject = null;
message = message.Trim();
if (!string.IsNullOrEmpty(message))
{
message = message.Trim();
try
{
if (message.StartsWith("{"))
{
JObject source = JObject.Parse(message);
@@ -148,8 +153,9 @@ namespace UVC.Data
JArray source = JArray.Parse(message);
if (info.DataMapper != null) dataObject = info.DataMapper.Map(source);
}
}
if (dataObject != null) dataObject = DataRepository.Instance.AddData(topic, dataObject, info.UpdatedDataOnly);
// 갱신 된 데이터가 있는 경우 핸들러 호출
if (info.UpdatedDataOnly)
{
@@ -160,6 +166,13 @@ namespace UVC.Data
info.Handler?.Invoke(dataObject);
}
}
catch (Exception ex)
{
// 예외 발생 시 로깅 또는 처리
ULog.Error($"Error processing message for topic '{topic}': {ex.Message}", ex);
}
}
}
}
/// <summary>

View File

@@ -323,13 +323,15 @@ namespace UVC.network
string payload = Encoding.UTF8.GetString(message.Payload.Data, message.Payload.Offset, message.Payload.Count);
ULog.Debug($"MQTT OnTopic {topic.Filter.OriginalFilter} => {payload}");
ServerLog.LogMqtt(MQTTDomain, MQTTPort.ToString(), topic.Filter.OriginalFilter, payload, DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
if(topicHandler.TryGetValue(topic.Filter.OriginalFilter, out var handler))
if(payload.Trim().Length > 0)
{
if (topicHandler.TryGetValue(topic.Filter.OriginalFilter, out var handler))
{
// 등록된 핸들러가 있으면 호출
handler.Invoke(topic.Filter.OriginalFilter, payload);
}
}
}
/// <summary>
/// 지정된 토픽으로 메시지를 발행(publish)합니다.

View File

@@ -222,7 +222,7 @@ namespace UVC.Tests.Data
bool handlerCalled = false;
DataObject? receivedData = null;
var mockResponse = @"{""name"": ""테스트"", ""value"": 123}";
var mockResponse = @"{""message"": ""Success"", ""data"": {""name"": ""테스트"", ""value"": 123}}";
// DataMask와 DataMapper 설정
var dataMask = new DataMask();
@@ -277,7 +277,7 @@ namespace UVC.Tests.Data
bool handlerCalled = false;
IDataObject? receivedData = null;
var mockResponse = @"[{""name"": ""항목1"", ""value"": 10}, {""name"": ""항목2"", ""value"": 20}]";
var mockResponse = @"{""message"": ""Success"", ""data"": [{""name"": ""항목1"", ""value"": 10}, {""name"": ""항목2"", ""value"": 20}]}";
// 배열용 DataMask 설정
var dataMask = new DataMask
@@ -701,9 +701,9 @@ namespace UVC.Tests.Data
// 여러 응답을 순차적으로 반환하기 위한 응답 데이터 설정
string[] mockResponses = new string[]
{
@"{""id"": 1, ""status"": ""pending"", ""timestamp"": ""2025-06-09T10:00:00Z""}",
@"{""id"": 1, ""status"": ""processing"", ""timestamp"": ""2025-06-09T10:00:10Z""}",
@"{""id"": 1, ""status"": ""completed"", ""timestamp"": ""2025-06-09T10:00:20Z""}"
@"{""message"": ""Success"", ""data"": {""id"": 1, ""status"": ""pending"", ""timestamp"": ""2025-06-09T10:00:00Z""}}",
@"{""message"": ""Success"", ""data"": {""id"": 1, ""status"": ""processing"", ""timestamp"": ""2025-06-09T10:00:10Z""}}",
@"{""message"": ""Success"", ""data"": {""id"": 1, ""status"": ""completed"", ""timestamp"": ""2025-06-09T10:00:20Z""}}"
};
// Mock 응답 설정
@@ -780,7 +780,7 @@ namespace UVC.Tests.Data
int repeatInterval = 100;
// Mock 응답 설정
string mockResponse = @"{""id"": 2, ""status"": ""running"", ""timestamp"": ""2025-06-09T11:00:00Z""}";
string mockResponse = @"{""message"": ""Success"", ""data"": {""id"": 2, ""status"": ""running"", ""timestamp"": ""2025-06-09T11:00:00Z""}}";
MockHttpRequester.SetResponse(testUrl, mockResponse);
// DataMask와 DataMapper 설정
@@ -846,8 +846,8 @@ namespace UVC.Tests.Data
int repeatInterval2 = 150;
// Mock 응답 설정
string mockResponse1 = @"{""id"": 3, ""name"": ""작업1""}";
string mockResponse2 = @"{""id"": 4, ""name"": ""작업2""}";
string mockResponse1 = @"{""message"": ""Success"", ""data"": {""id"": 3, ""name"": ""작업1""}}";
string mockResponse2 = @"{""message"": ""Success"", ""data"": {""id"": 4, ""name"": ""작업2""}}";
MockHttpRequester.SetResponse(testUrl1, mockResponse1);
MockHttpRequester.SetResponse(testUrl2, mockResponse2);
@@ -931,7 +931,7 @@ namespace UVC.Tests.Data
int repeatInterval = 100;
// Mock 응답 설정
string mockResponse = @"{""id"": 5, ""message"": ""자동 중단 테스트""}";
string mockResponse = @"{""message"": ""Success"", ""data"": {""id"": 5, ""message"": ""자동 중단 테스트""}}";
MockHttpRequester.SetResponse(testUrl, mockResponse);
// DataMask 설정

View File

@@ -43,14 +43,15 @@ namespace UVC.Tests.Data
{
Setup();
Debug.Log("===== MQTTPipeLine 테스트 시작 =====");
// 하나씩 테스트 해야 함
//await RunTestAsync(nameof(ExecutePipeLine_AllTopics_RegistersAndHandlesMessages), ExecutePipeLine_AllTopics_RegistersAndHandlesMessages);
//await RunTestAsync(nameof(RemoveTopic_ShouldStopReceivingMessages), RemoveTopic_ShouldStopReceivingMessages);
//await RunTestAsync(nameof(UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData), UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData);
await RunTestAsync(nameof(UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData), UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData);
//await RunTestAsync(nameof(UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData), UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData);
//RunTest(nameof(OnTopicMessage_ValidJsonObject_CallsHandler), OnTopicMessage_ValidJsonObject_CallsHandler);
//RunTest(nameof(OnTopicMessage_JsonArray_CallsHandler), OnTopicMessage_JsonArray_CallsHandler);
//RunTest(nameof(OnTopicMessage_EmptyMessage_DoesNotCallHandler), OnTopicMessage_EmptyMessage_DoesNotCallHandler);
//RunTest(nameof(OnTopicMessage_InvalidJson_DoesNotCallHandler), OnTopicMessage_InvalidJson_DoesNotCallHandler);
RunTest(nameof(OnTopicMessage_InvalidJson_DoesNotCallHandler), OnTopicMessage_InvalidJson_DoesNotCallHandler);
Debug.Log("===== MQTTPipeLine 테스트 완료 =====");
}

View File

@@ -80,7 +80,7 @@ namespace UVC.Tests
}
else
{
response = GetResponse(url);
response = "{\"message\":\"Success\", \"data\":" + GetResponse(url) + "}";
}
if (typeof(T) == typeof(string))

View File

@@ -7,8 +7,8 @@ namespace UVC.Tests
public static void RunAllTests()
{
//new DataMapperTests().TestAll();
//new HttpPipeLineTests().TestAll();
new MQTTPipeLineTests().TestAll();
new HttpPipeLineTests().TestAll();
//new MQTTPipeLineTests().TestAll();
}
}
}