Объединение запросов API

#javascript #json #asp.net-mvc-3 #api #iis-7

#javascript #json #asp.net-mvc-3 #API #iis-7

Вопрос:

Я создаю REST API, и я играл с идеей разрешить объединение запросов от клиентов. Под объединением я подразумеваю, что они могут отправлять один запрос, содержащий несколько «реальных» запросов, и они доставляются клиенту вместе. Обычно javascript запрашивает ajax. Что-то вроде этого:

 POST /bundlerequest

["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10amp;page=1", "/products", "/product/categories" ] 
  

(Объединенный запрос может быть только запросами GET, по крайней мере, на данный момент)
Это предназначено для возврата чего-то вроде этого

 {
    "success" : ["/person/3243", "/person/3243/friends", "/comments/3243?pagesize=10amp;page=1", "/products", "/product/categories" ],
    "error" : [],
    "completiontime" : 94,
    other relevant metadata...
    "responses" : [
        {"key" : "/person/3243" , "data" : {"name" : "John", ...} },
        {"key" : "/person/3243/friends" , "data" : [{"name": "Peter", "commonfriends" : 5, ...}] },
        etc...
    ]
}
  

Преимущества этого объединения в том, что оно уменьшает количество запросов, и это особенно важно, например, на мобильных устройствах.

Итак, мой первый вопрос: является ли мой подход к этому хорошим? У кого-нибудь есть опыт выполнения чего-то подобного?

AFAIK распространенным способом решения этой проблемы является написание кода на стороне сервера для возврата объединенных данных, которые, я считаю, актуальны для клиента (ов). (Например, поток пользователей Twitter делает это, объединяя информацию о человеке, последние твиты, последние личные сообщения и т. Д.) Но это делает API очень самоуверенным, и когда клиенту нужны изменения, серверу может потребоваться изменить, чтобы приспособиться к оптимизации.

И второй вопрос заключается в том, как это реализовать?

Мой серверный сервер ASP.NET MVC 3 и IIS 7. Должен ли я реализовать это в приложении, выполнив действие bundlerequest, которое внутренне вызывает другие действия, указанные в запросе?

Может ли это быть реализовано в IIS 7 напрямую? Написание модуля, который прозрачно перехватывает запросы к /bundlerequest, а затем вызывает все соответствующие вспомогательные запросы, делая приложение совершенно не осведомленным о происходящем объединении? Это также позволило бы мне реализовать это независимым от приложения способом.

Комментарии:

1. Это звучит как классная идея.

2. Все ли эти URL-адреса будут возвращать только JSON? Или некоторые могут возвращать фрагменты HTML и другие вещи?

3. @DarinDimitrov да, только JSON, однако я вижу потенциальные проблемы, связанные с тем, что пользователь выходит из системы или, например, не имеет доступа к частям API, а затем может перенаправляться на страницу входа в систему. Это проблемы, которые мне придется решать.

Ответ №1:

Вы могли бы использовать асинхронный контроллер для агрегирования этих запросов на сервере. Давайте сначала начнем с определения модели представления, которая будет возвращена контроллером:

 public class BundleRequest
{
    public string[] Urls { get; set; }
}

public class BundleResponse
{
    public IList<string> Success { get; set; }
    public IList<string> Error { get; set; }
    public IList<Response> Responses { get; set; }
}

public class Response
{
    public string Key { get; set; }
    public object Data { get; set; }
}
  

затем контроллер:

 public class BundleController : AsyncController
{
    public void IndexAsync(BundleRequest request)
    {
        AsyncManager.OutstandingOperations.Increment();
        var tasks = request.Urls.Select(url =>
        {
            var r = WebRequest.Create(url);
            return Task.Factory.FromAsync<WebResponse>(r.BeginGetResponse, r.EndGetResponse, url);
        }).ToArray();

        Task.Factory.ContinueWhenAll(tasks, completedTasks =>
        {
            var bundleResponse = new BundleResponse
            {
                Success = new List<string>(),
                Error = new List<string>(),
                Responses = new List<Response>()
            };
            foreach (var task in completedTasks)
            {
                var url = task.AsyncState as string;
                if (task.Exception == null)
                {
                    using (var response = task.Result)
                    using (var stream = response.GetResponseStream())
                    using (var reader = new StreamReader(stream))
                    {
                        bundleResponse.Success.Add(url);
                        bundleResponse.Responses.Add(new Response
                        {
                            Key = url,
                            Data = new JavaScriptSerializer().DeserializeObject(reader.ReadToEnd())
                        });
                    }
                }
                else
                {
                    bundleResponse.Error.Add(url);
                }
            }
            AsyncManager.Parameters["response"] = bundleResponse;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ActionResult IndexCompleted(BundleResponse response)
    {
        return Json(response, JsonRequestBehavior.AllowGet);
    }
}
  

и теперь мы можем вызвать его:

 var urls = [ 
    '@Url.Action("index", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)', 
    '@Url.Action("friends", "person", new { id = 3243 }, Request.Url.Scheme, Request.Url.Host)', 
    '@Url.Action("index", "comments", new { id = 3243, pagesize = 10, page = 1 }, Request.Url.Scheme, Request.Url.Host)',
    '@Url.Action("index", "products", null, Request.Url.Scheme, Request.Url.Host)', 
    '@Url.Action("categories", "product", null, Request.Url.Scheme, Request.Url.Host)' 
];
$.ajax({
    url: '@Url.Action("Index", "Bundle")',
    type: 'POST',
    contentType: 'application/json; charset=utf-8',
    data: JSON.stringify(urls),
    success: function(bundleResponse) {
        // TODO: do something with the response
    }
});
  

Конечно, может потребоваться некоторая настройка, чтобы адаптировать это к вашим конкретным потребностям. Например, вы упомянули отправку AJAX-запросов с истекшим сроком действия сеанса, которые могут перенаправлять на страницу входа в систему и, следовательно, не фиксировать ошибку. Это действительно PITA в ASP.NET . Фил Хаак опубликовал в блоге возможный способ обойти это нежелательное поведение с помощью RESTful. Вам просто нужно добавить пользовательский заголовок к запросам.

Комментарии:

1. Спасибо за очень подробный ответ, а также указывающий мне правильное направление для решения проблемы аутентификации форм. Похоже, именно то, что мне нужно. Я буду основывать свою реализацию на этом и посмотрю, как это вписывается в мое приложение.

Ответ №2:

Я предлагаю посмотреть веб-api wcf