在接收到部分 REST 响应时传递它们
Passing partial REST responses as they are received
我有一个 ASP.NET MVC 应用程序,它使用 RestSharp 连接到第 3 方,以便接收域名列表,然后在结果中稍后提供有关每个域的更多详细信息。
我相信响应是以部分数据包的形式出现的,RestSharp 在以反序列化格式输出数据之前一直在等待,直到收到所有数据包。
有问题的有效负载如下所示。 "header" 首先被填充,这是我想立即 return 对视图的响应。其余数据对时间不太敏感:
[
{"header":{"names":["test.1","test.2","test.3","test.4","test.5","test.6"]}}
,
{"name":"test.1","can":"transfer?"}
,
{"name":"test.2","can":"transfer?"}
,
{"name":"test.3","can":"transfer?"}
,
{"name":"test.4","can":"transfer?"}
,
{"name":"test.5","can":"transfer?"}
,
{"name":"test.6","can":"register"}
]
目前有两种执行方式,一种是同步的,一种是异步的:
public T ExecuteGetRequest<T>(RestRequest request) where T : class
{
try
{
IRestResponse response = _client.Execute(request);
if (response.ErrorException == null)
{
return JsonConvert.DeserializeObject<T>(response.Content);
}
return null;
}
catch (Exception ex)
{
return null;
}
}
public Task<T> ExecuteGetRequestAsync<T>(RestRequest request) where T : new()
{
try
{
var source = new TaskCompletionSource<T>();
_client.ExecuteAsync<T>(request, (response) => { source.SetResult(response.Data); });
return source.Task;
}
catch (Exception ex)
{
return null;
}
}
有一个本地 API 调用应用程序使用异步实现搜索,如下所示:
public async Task<DomainSearchResults> DomainSearchAsync(string domain)
{
var request = _client.CreateRequest("domain-search/{domain}", Method.GET);
request.AddUrlSegment("domain", domain);
var response = await _client.ExecuteGetRequestAsync<List<DomainSearch>>(request);
return new DomainSearchResults(response);
}
然后解释响应并为客户端提供相关搜索结果。
从某种意义上说,当所有数据都已由第 3 方发送后,对象会 returned 到视图并相应地填充,这在某种意义上工作得很好。但是,请求的完全完成最多可能需要 20 秒,这对用户来说并不是特别有帮助。
有没有一种方法可以调整 ExecuteGetRequestAsync 以在收到完整响应之前开始将不完整的响应发送回调用视图?
我最初的尝试看起来很像这样:
public Task<T> ExecuteGetRequestAsyncIncomplete<T>(RestRequest request) where T : new()
{
try
{
var source = new TaskCompletionSource<T>();
_client.ExecuteAsync<T>(request, (response) =>
{
source.SetResult(response.Data);
if (response.StatusCode == HttpStatusCode.PartialContent)
{
// Somehow return part of this response
}
});
return source.Task;
}
catch (Exception ex)
{
return null;
}
}
更新
从@Evk 的回答开始,下面是对 return 部分结果的新调用,专门针对这种情况:
public async Task<T> ExecuteGetRequestPartial<T>(RestRequest request) where T : new()
{
try
{
var source = new TaskCompletionSource<T>();
request.ResponseWriter = (st) => {
using (var reader = new StreamReader(st))
{
var sb = new StringBuilder();
// read response 100 chars at a time
char[] buffer = new char[1];
int read = 0;
while ((read = reader.Read(buffer, 0, buffer.Length)) > 0)
{
sb.Append(buffer, 0, read);
// now here you have your partial response
// you need to somehow parse it and feed to your view
// note that you should wait until you get some meaningful part, like first "header" element
// for example at some point there might be ["header":{"names":["te < partial response
if (sb.ToString().Contains("header") && sb.ToString().EndsWith("}"))
{
sb.Append("}]");
source.SetResult(JsonConvert.DeserializeObject<T>(sb.ToString()));
return;
}
}
// at this point you have full response in sb
}
};
await _client.ExecuteGetTaskAsync<T>(request);
return await source.Task;
}
catch (Exception ex)
{
return default(T);
}
}
简而言之,缓冲区已减少到 1 个字符,以便我们知道我们在字符串中的位置。然后为了将部分结果变为有效 JSON,我们检查对象的结尾,然后通过手动将额外的“}]”添加到结果并 return 关闭它。
Evk 不错!
首先,http状态"Partial Content"与你的情况无关。它用于响应范围为 header 的请求。
您需要的是读取响应流,而不是等待完整的响应被传递和反序列化。使用常规 HttpWebRequest 更容易做到这一点,但如果您想使用 RestSharp,也可以。请注意,您将必须手动反序列化 partial(因此,无效)json。这是一个草图:
public static Task<T> ExecuteGetRequestAsync<T>(RestRequest request) where T : new() {
var client = new RestClient("http://google.com");
try {
var source = new TaskCompletionSource<T>();
request.ResponseWriter = (st) => {
using (var reader = new StreamReader(st)) {
var sb = new StringBuilder();
// read response 100 chars at a time
char[] buffer = new char[100];
int read = 0;
while ((read = reader.Read(buffer, 0, buffer.Length)) > 0) {
sb.Append(buffer, 0, read);
// now here you have your partial response
// you need to somehow parse it and feed to your view
// note that you should wait until you get some meaningful part, like first "header" element
// for example at some point there might be ["header":{"names":["te < partial response
}
// at this point you have full response in sb
}
};
client.ExecuteAsync<T>(request, null);
return source.Task;
}
catch (Exception ex) {
return null;
}
}
我有一个 ASP.NET MVC 应用程序,它使用 RestSharp 连接到第 3 方,以便接收域名列表,然后在结果中稍后提供有关每个域的更多详细信息。
我相信响应是以部分数据包的形式出现的,RestSharp 在以反序列化格式输出数据之前一直在等待,直到收到所有数据包。
有问题的有效负载如下所示。 "header" 首先被填充,这是我想立即 return 对视图的响应。其余数据对时间不太敏感:
[
{"header":{"names":["test.1","test.2","test.3","test.4","test.5","test.6"]}}
,
{"name":"test.1","can":"transfer?"}
,
{"name":"test.2","can":"transfer?"}
,
{"name":"test.3","can":"transfer?"}
,
{"name":"test.4","can":"transfer?"}
,
{"name":"test.5","can":"transfer?"}
,
{"name":"test.6","can":"register"}
]
目前有两种执行方式,一种是同步的,一种是异步的:
public T ExecuteGetRequest<T>(RestRequest request) where T : class
{
try
{
IRestResponse response = _client.Execute(request);
if (response.ErrorException == null)
{
return JsonConvert.DeserializeObject<T>(response.Content);
}
return null;
}
catch (Exception ex)
{
return null;
}
}
public Task<T> ExecuteGetRequestAsync<T>(RestRequest request) where T : new()
{
try
{
var source = new TaskCompletionSource<T>();
_client.ExecuteAsync<T>(request, (response) => { source.SetResult(response.Data); });
return source.Task;
}
catch (Exception ex)
{
return null;
}
}
有一个本地 API 调用应用程序使用异步实现搜索,如下所示:
public async Task<DomainSearchResults> DomainSearchAsync(string domain)
{
var request = _client.CreateRequest("domain-search/{domain}", Method.GET);
request.AddUrlSegment("domain", domain);
var response = await _client.ExecuteGetRequestAsync<List<DomainSearch>>(request);
return new DomainSearchResults(response);
}
然后解释响应并为客户端提供相关搜索结果。
从某种意义上说,当所有数据都已由第 3 方发送后,对象会 returned 到视图并相应地填充,这在某种意义上工作得很好。但是,请求的完全完成最多可能需要 20 秒,这对用户来说并不是特别有帮助。
有没有一种方法可以调整 ExecuteGetRequestAsync 以在收到完整响应之前开始将不完整的响应发送回调用视图?
我最初的尝试看起来很像这样:
public Task<T> ExecuteGetRequestAsyncIncomplete<T>(RestRequest request) where T : new()
{
try
{
var source = new TaskCompletionSource<T>();
_client.ExecuteAsync<T>(request, (response) =>
{
source.SetResult(response.Data);
if (response.StatusCode == HttpStatusCode.PartialContent)
{
// Somehow return part of this response
}
});
return source.Task;
}
catch (Exception ex)
{
return null;
}
}
更新
从@Evk 的回答开始,下面是对 return 部分结果的新调用,专门针对这种情况:
public async Task<T> ExecuteGetRequestPartial<T>(RestRequest request) where T : new()
{
try
{
var source = new TaskCompletionSource<T>();
request.ResponseWriter = (st) => {
using (var reader = new StreamReader(st))
{
var sb = new StringBuilder();
// read response 100 chars at a time
char[] buffer = new char[1];
int read = 0;
while ((read = reader.Read(buffer, 0, buffer.Length)) > 0)
{
sb.Append(buffer, 0, read);
// now here you have your partial response
// you need to somehow parse it and feed to your view
// note that you should wait until you get some meaningful part, like first "header" element
// for example at some point there might be ["header":{"names":["te < partial response
if (sb.ToString().Contains("header") && sb.ToString().EndsWith("}"))
{
sb.Append("}]");
source.SetResult(JsonConvert.DeserializeObject<T>(sb.ToString()));
return;
}
}
// at this point you have full response in sb
}
};
await _client.ExecuteGetTaskAsync<T>(request);
return await source.Task;
}
catch (Exception ex)
{
return default(T);
}
}
简而言之,缓冲区已减少到 1 个字符,以便我们知道我们在字符串中的位置。然后为了将部分结果变为有效 JSON,我们检查对象的结尾,然后通过手动将额外的“}]”添加到结果并 return 关闭它。
Evk 不错!
首先,http状态"Partial Content"与你的情况无关。它用于响应范围为 header 的请求。
您需要的是读取响应流,而不是等待完整的响应被传递和反序列化。使用常规 HttpWebRequest 更容易做到这一点,但如果您想使用 RestSharp,也可以。请注意,您将必须手动反序列化 partial(因此,无效)json。这是一个草图:
public static Task<T> ExecuteGetRequestAsync<T>(RestRequest request) where T : new() {
var client = new RestClient("http://google.com");
try {
var source = new TaskCompletionSource<T>();
request.ResponseWriter = (st) => {
using (var reader = new StreamReader(st)) {
var sb = new StringBuilder();
// read response 100 chars at a time
char[] buffer = new char[100];
int read = 0;
while ((read = reader.Read(buffer, 0, buffer.Length)) > 0) {
sb.Append(buffer, 0, read);
// now here you have your partial response
// you need to somehow parse it and feed to your view
// note that you should wait until you get some meaningful part, like first "header" element
// for example at some point there might be ["header":{"names":["te < partial response
}
// at this point you have full response in sb
}
};
client.ExecuteAsync<T>(request, null);
return source.Task;
}
catch (Exception ex) {
return null;
}
}