2017-02-07 20 views
1

У меня есть приложение ASP.NET MVC, которое использует RestSharp для подключения к третьей стороне, чтобы получить список доменных имен, а затем более подробную информацию о каждом домене позже в результате.Передача частичных ответов REST при их получении

Я считаю, что ответ приходит в частичных пакетах, и RestSharp ожидает, пока все пакеты не будут получены до вывода данных в десериализованном формате.

Полезная нагрузка выглядит следующим образом. Сначала заголовок «заголовок» заполняется, и это ответ, который я хочу немедленно вернуться к просмотру. Остальная часть данных меньше времени чувствительны:

[ 
{"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); 
} 

что тогда inpterprets ответ и дает клиенту соответствующие результаты поиска.

Это прекрасно работает в том смысле, что когда все данные были отправлены третьей стороной, объект возвращается в представление и заполняется соответствующим образом. Однако полное завершение запроса может занять до 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; 
    } 
} 

UPDATE

Работа на от ответа @Evk «s, вот что новый вызов вернуть частичный результат выглядит, в частности, для этого сценария:

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 полукокса, так что мы знаем, где мы находимся в ул ING. Затем, чтобы повернуть частичный результат в действительный JSON, мы проверяем конец объекта, а затем закрываем его, вручную добавляя дополнительный «)] к результату и возвращаем его.

Nice one Evk!

ответ

1

Во-первых, http-статус «Частичный контент» не имеет никакого отношения к вашему делу. Это для ответов на запросы с заголовком Range.

Вместо этого вам нужен поток ответов отклика, поскольку он не ждет полного ответа, который должен быть доставлен и десериализован. Это проще сделать с помощью обычного HttpWebRequest, но если вы хотите использовать RestSharp, это также возможно. Обратите внимание, что вам придется вручную десериализовать частично (так, недействительно) 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; 
     } 
    }