Pull to refresh

Использование RESTful контроллеров для ресурсов AngularJS

Reading time5 min
Views42K
Как упоминалось ранее, Ангуляр предоставляет класс $resource для повышения уровня абстракции между кодом на стороне клиента и серверным API. Поэтому теперь выполнять операции CRUD по сети довольно легко. Но что происходит, когда необходимо выполнить команду для RESTful ресурса, что выходит за пределы стандартных методов CRUD (т. е. создания, чтения, обновления, удаления)? К счастью, Ангуляр достаточно хорошо работает с RESTful «контроллерами».

В соответствии со Сводом правил по REST API Марка Массе, есть четыре архетипа ресурсов:

  • Документ — представление ресурса.
  • Коллекция — набор данных на сервере.
  • Хранилище — набор данных на клиенте.
  • Контроллер — выполняемое действие.

Документ, Коллекция и Хранилище представляют ресурсы. Контроллеры, с другой стороны, отвечают за изменение ресурсов. Можно использовать глаголы в HTTP для обозначения действия над ресурсом, но для заурядных CRUD-операций это не всегда имеет смысл. Возьмем в качестве примера следующие ресурсы:

  • /messages
  • /messages/4

Имея Коллекцию сообщений (первый ресурс) и отдельное сообщение (второй ресурс), как сообщить об очистке всех сообщений? Или, как переместить данное сообщение в архив? CRUD не вполне отвечает таким требованиям. Но контроллеры отлично подходят для операций подобного типа:

  • /messages/clear-all
  • /messages/4/archive

Здесь мы используем Контроллеры «clear-all» и «archive» для изменения Коллекции и архивирования Документа, соответственно.

Начав использовать контроллеры для наших ресурсов, мы пришли к схеме URL, имеющей большую вариативность. $resource Ангуляра позволяет проделывать такое, но придется использовать несколько «умных» особенностей чтобы настроить связывание данных.

Исходя из приведенной выше концепции работы с сообщениями, в контексте Ангуляра мы должны были бы определить наш ресурс, используя следующий шаблон:

  • /messages/:id/:controller

Проблема в том, что, в настоящее время, возникает некоторая неоднозначность вокруг первого URL-параметра. Является ли «:id» ссылкой на, основывающийся на Коллекции, Контроллер? Или это ссылка ID Документа?

Чтобы устранить эту неопределенность, Ангуляр позволяет определить несколько параметров в одной и той же части URL шаблона:

  • /messages/:listController:id/:docController

Обратите внимание, что вторая часть шаблона URL содержит два параметра:

  • :listController
  • :id

Пока использую только один из них, в то время, как Ангуляр построит RESTful-ресурс должным образом. Чтобы продемонстрировать это, написал демку, в которой определяется, а затем задействуется ресурс сообщения как было показано выше:

<!doctype html>
<html ng-app="Demo">
<head>
    <meta charset="utf-8" />
    <title> Использование RESTful контроллеров для ресурсов AngularJS</title>
 
    <!--
        Т. к. работаем с ресурсами, то должны загрузить модули
        AngularJS и ngResource.
    -->
    <script type="text/javascript" src="../angular-1.0.2/angular.js"></script>
    <script type="text/javascript" src="../angular-1.0.2/angular-resource.js"></script>
    <script type="text/javascript">
 
 
        // Говорим Ангуляру загрузить ngResource перед загрузкой 
        // основного модуля приложения.
        var app = angular.module( "Demo", [ "ngResource" ] );
 
        // Запускаем, когда app готово.
        app.run(
            function( $resource ) {
 
 
                // При определении ресурса, мы получаем несколько действий
                // из коробки, таких как как get() и query(), основанных на
                // стандартных командах HTTP. Но можем также использовать RESTful
                // контроллер для изменения состояния ресурса с помощью
                // действия, которое выходит за рамки обычных операций CRUD.
                var messages = $resource(
                    "./api.cfm/messages/:listController:id/:docController",
                    {
                        id: "@id",
                        listController: "@listController",
                        docController: "@docController"
                    },
                    {
                        clear: {
                            method: "POST",
                            params: {
                                listController: "clear-all"
                            }
                        },
                        archive: {
                            method: "POST",
                            params: {
                                docController: "archive"
                            }
                        }
                    }
                );
 
                // Теперь наш ресурс определен, давайте вызовем
                // его с различными параметрами.
 
                // GET без ID.
                messages.query();
 
                // POST с контроллером списка.
                messages.clear();
 
                // GET с ID.
                messages.get(
                    {
                        id: 4
                    }
                );
 
                // POST с контроллером документа.
                messages.archive(
                    {
                        id: 8
                    }
                );
 
            }
        );
 
    </script>
</head>
<body>
    <!--  Намеренно оставлена пустой. -->
</body>
</html>

Обратите внимание, что мой ангуляровский ресурс рассматривается двумя различными контроллерами:

  • :ListController – действует на коллекцию сообщений.
  • :DocController – действует на конкретное сообщение.

Когда выше задействовались методы ресурса, то, в конечном итоге, было сделано четыре запроса по следующим адресам:


Как можно увидеть Ангуляр правильно определил URL-адреса в соответствии с REST. Кайф!

Если интересно, вот файл тестового API:

<!--- Получение сырого ресурсного пути, который был запрошен. --->
<cfset resourcePath = cgi.path_info />
 
 
<!---
    ПРИМЕЧАНИЕ: Кажется, что ColdFusion 10 подвис, пока я не сделал запрос для получения тела POST. Не знаю, почему.
--->
<cfif ( cgi.request_method neq "GET" )>
 
    <cfset requestBody = getHTTPRequestData().content />
 
</cfif>
 
 
<!---
   Определяем тип запроса, который пришел, на основе шаблона  запрошенного ресурсного пути.
--->
<cfif reFind( "^/messages$", resourcePath )>
 
    <cfset response = "GET without ID." />
 
<cfelseif reFind( "^/messages/clear-all$", resourcePath )>
 
    <cfset response = "POST with clear-all Controller." />
 
<cfelseif reFind( "^/messages/\d+$", resourcePath )>
 
    <cfset response = "GET with ID." />
 
<cfelseif reFind( "^/messages/\d+/archive+$", resourcePath )>
 
    <cfset response = "POST with archive controller" />
 
<cfelse>
 
    <cfset response = "Hmm, couldn't match resource." />
 
</cfif>
 
 
<!---
     Преобразуем ответ в JSON. Ангуляр будет знать, как его распарсить.
--->
<cfset serializedResponse = serializeJSON( response ) />
 
<!--- Добавляем заголовок для отладки пути. --->
<cfheader
    name="X-Debug-Path"
    value="#cgi.path_info#"
    />
 
<!--- Возврат потока ответов обратно к клиенту. --->
<cfcontent
    type="application/json"
    variable="#charsetDecode( serializedResponse, 'utf-8' )#"
    />


По некоторым причинам, ColdFusion 10 зависал при POST запросах, до тех пор, пока я не получал доступ к телу запроса (через функцию getHttpRequestData()). Такое происходило только, если тело запроса содержало JSON (JavaScript Object Notation) данные. Когда оно было пустым, запрос не вис. Не разбираюсь досконально в обработке запросов и не знаю, имеет ли это смысл, однако, замечу, что ColdFusion 9 такого не вытворяет.

Оригинал
Tags:
Hubs:
Total votes 9: ↑8 and ↓1+7
Comments25

Articles