Para prevenir el abuso, puedes considerar añadir un *límitación del ratio* (*rate limiting*) para tus APIs. Por ejemplo, puedes querer limitar el uso del API de cada usuario que no sea como mucho 100 llamadas al API dentro de un periodo de 10 minutos. Si demasiadas peticiones son recibidas de un usuario dentro del periodo de tiempo declarado , una respuesta con código de estado 429 (significa "Demasiadas peticiones") puede ser devuelto.
Para prevenir el abuso, puedes considerar añadir un *límitación del rango (rate limiting)* para tus APIs. Por ejemplo,
puedes querer limitar el uso del API de cada usuario a un máximo de 100 llamadas al API dentro de un periodo de 10 minutos.
Si se reciben demasiadas peticiones de un usuario dentro del periodo de tiempo declarado, deveríá devolverse una respuesta con código de estado 429 (que significa "Demasiadas peticiones").
Para activar la limitación de ratio, la clase [[yii\web\User::identityClass|user identity class]] debe implementar [[yii\filters\RateLimitInterface]].
Para activar la limitación de rango, la clase [[yii\web\User::identityClass|user identity class]] debe implementar [[yii\filters\RateLimitInterface]].
Este interface requiere la implementación de tres métodos:
*`getRateLimit()`: devuelve el número máximo de peticiones permitidas y el periodo de tiempo (p.e., `[100, 600]` significa que como mucho puede haber 100 llamadas al API dentro de 600 segundos).
*`loadAllowance()`: devuelve el número de peticiones que quedan permitidas y el tiempo (fecha/hora) UNIX con el último límite del ratio que ha sido comprobado.
*`saveAllowance()`: guarda ambos, el número que quedan de peticiones permitidas y el actual tiempo (fecha/hora) UNIX .
*`loadAllowance()`: devuelve el número de peticiones que quedan permitidas y el tiempo (fecha/hora) UNIX
con el último límite del rango que ha sido comprobado.
*`saveAllowance()`: guarda ambos, el número restante de peticiones permitidas y el tiempo actual (fecha/hora) UNIX .
Tu puedes usar dos columnas en la tabla de usuario para guardar la información de lo permitido y la fecha/hora (timestamp). Con ambas definidas, entonces `loadAllowance()` y `saveAllowance()` pueden ser implementadas para leer y guardar los valores de las dos columnas correspondientes al actual usuario autenticado. Para mejorar el desempeño, también puedes considerar almacenar esas piezas de información en caché o almacenamiento NoSQL.
Puedes usar dos columnas en la tabla de usuario para guardar la información de lo permitido y la fecha/hora (timestamp). Con ambas definidas,
entonces `loadAllowance()` y `saveAllowance()` pueden ser utilizados para leer y guardar los valores de las dos columnas correspondientes al actual usuario autenticado.
Para mejorar el desempeño, también puedes considerar almacenar esas piezas de información en caché o almacenamiento NoSQL.
Una vez que la clase de identidad implementa el requerido interface, Yii puede usar automáticamente [[yii\filters\RateLimiter]] configurado como una acción de filtrado para [[yii\rest\Controller]] y mejorar la comprobación del limitador de ratio. El limitador de ratio puede lanzar una [[yii\web\TooManyRequestsHttpException]] cuando el límite del ratio es excedido.
Una vez que la clase de identidad implementa la interfaz requerida, Yii utilizará automáticamente [[yii\filters\RateLimiter]]
configurado como un filtro de acción para que [[yii\rest\Controller]] compruebe el límite de rango. El limitador de rango
lanzará una excepeción [[yii\web\TooManyRequestsHttpException]] cuando el límite del rango sea excedido.
Puedes configurar el limitador de ratio en tu clase REST de la controladora como sigue:
Puedes configurar el limitador de rango
en tu clase controlador REST como sigue:
```php
publicfunctionbehaviors()
...
...
@@ -25,10 +33,12 @@ public function behaviors()
}
```
Cuando la limitación de ratio está activada, por defecto cada respuesta puede ser enviada con las siguientes cabeceras de HTTP conteniendo la información actual del límite de ratio:
Cuando se activa el límite de rango, por defecto todas las respuestas serán enviadas con la siguiente cabecera HTTP conteniendo
información sobre el límite actual de rango:
*`X-Rate-Limit-Limit`, el máximo número de peticiones permitidas en un periodo de tiempo
*`X-Rate-Limit-Remaining`, el número de peticiones restantes en el periodo de tiempo actual
*`X-Rate-Limit-Reset`, el número de segundos a esperar para pedir el máximo número de peticiones permitidas
Puedes desactivar estas cabeceras configurando [[yii\filters\RateLimiter::enableRateLimitHeaders]] a false, tal y como en el anterior ejemplo.
Puedes desactivar estas cabeceras configurando [[yii\filters\RateLimiter::enableRateLimitHeaders]] a false,
Cuando se maneja una petición al API RESTful, una aplicación realiza usualmente los siguientes pasos que están relacionados con el formato de la respuesta:
Cuando se maneja una petición al API RESTful, una aplicación realiza usualmente los siguientes pasos que están relacionados
con el formato de la respuesta:
1. Determinar varios factores que pueden afectar al formato de la respuesta, como son el tipo de medio, lenguaje, versión, etc.
Este proceso es también conocido como [Negociación de contenido (content negotiation)](http://en.wikipedia.org/wiki/Content_negotiation).
2. La conversión de objetos recursos en arrays, está descrito en la sección [Recursos (Resources)](rest-resources.md).
Esto es realizado por el serializador [[yii\rest\Serializer]].
> Tip: Serializar es convertir un elemento a un formato que nos permita guardardarlo de forma permanente, de modo que posteriormente, al recuperarlo, nos de una copia igual a la del elemento antes de ser inicialmente serializado.
3. La conversión de arrays en cadenas con el formato determinado por el paso de negociación de contenido. Esto es realizado por [[yii\web\ResponseFormatterInterface|response formatters]] registrado con el componente de la aplicación [[yii\web\Response::formatters|response]].
Este proceso es también conocido como [negociación de contenido (content negotiation)](http://en.wikipedia.org/wiki/Content_negotiation).
2. La conversión de objetos recurso en arrays, como está descrito en la sección [Recursos (Resources)](rest-resources.md).
Esto es realizado por la clase [[yii\rest\Serializer]].
3. La conversión de arrays en cadenas con el formato determinado por el paso de negociación de contenido. Esto es
realizado por los [[yii\web\ResponseFormatterInterface|response formatters]] registrados con el
componente de la aplicación [[yii\web\Response::formatters|response]].
## Negociación de contenido (Content Negotiation) <a name="content-negotiation"></a>
Yii soporta la negoiciación de contenido a través del filtro [[yii\filters\ContentNegotiator]]. La clase controladora base del API RESTful [[yii\rest\Controller]] está equipada con este filtro bajo el nombre `contentNegotiator`.
El filtro provee tanto un formato de respuesta de negociación como una negociación de lenguaje. Por ejemplo, si la petición API RESTful contiene la siguiente cabecera,
Yii soporta la negociación de contenido a través del filtro [[yii\filters\ContentNegotiator]]. La clase de controlador base
del API RESTful [[yii\rest\Controller]] está equipada con este filtro bajo el nombre `contentNegotiator`.
El filtro provee tanto un formato de respuesta de negociación como una negociación de lenguaje. Por ejemplo, si la petición API RESTful
contiene la siguiente cabecera,
```
Accept: application/json; q=1.0, */*; q=0.1
```
puede obtener una respuesta en formato JSON, como lo que sigue:
puede obtener una respuesta en formato JSON, como a continuación:
En la parte de atrás, antes de que sea ejecutada una acción de la controladora del API RESTful, el filtro [[yii\filters\ContentNegotiator]] comprobará en la cabecera HTTP el `Accept` de la petición y pondrá como `'json'`[[yii\web\Response::format|response format]]. Después de que la acción sea ejecutada y devuelva el recurso objeto resultante o una colección resultante,
[[yii\rest\Serializer]] convertirá el resultado en un array. Y finalmente, [[yii\web\JsonResponseFormatter]] serializará el array en una cadena JSON incluyéndola en el cuerpo de la respuesta.
Por defecto, el API RESTful soporta tanto el formato JSON como el XML. Para soportar un nuevo formato, debes configurar la propiedad [[yii\filters\ContentNegotiator::formats|formats]] del filtro `contentNegotiator` tal y como sigue, en las clases de la controladora del API:
Detrás de escena, antes de que sea ejecutada una acción del controlador del API RESTful, el filtro [[yii\filters\ContentNegotiator]]
comprobará la cabecera HTTP `Accept` de la petición y definirá el [[yii\web\Response::format|response format]]
como `'json'`. Después de que la acción sea ejecutada y devuelva el objeto recurso o la colección resultante,
[[yii\rest\Serializer]] convertirá el resultado en un array. Y finalmente, [[yii\web\JsonResponseFormatter]]
serializará el array en una cadena JSON incluyéndola en el cuerpo de la respuesta.
Por defecto, el API RESTful soporta tanto el formato JSON como el XML. Para soportar un nuevo formato, debes configurar
la propiedad [[yii\filters\ContentNegotiator::formats|formats]] del filtro `contentNegotiator` tal y como sigue,
en las clases del controlador del API:
```php
useyii\web\Response;
...
...
@@ -69,15 +77,21 @@ public function behaviors()
}
```
Las claves de la propiedad `formats` son los tipos MIME soportados, mientras que los valores son los nombre de formato de respuesta correspondientes, los cuales han de estar soportados en [[yii\web\Response::formatters]].
Las claves de la propiedad `formats` son los tipos MIME soportados, mientras que los valores son los nombres de formato de respuesta correspondientes,
los cuales deben ser soportados en [[yii\web\Response::formatters]].
## Serialización de Datos <a name="data-serializing"></a>
Como hemos descrito antes, [[yii\rest\Serializer]] es la pieza central para convertir recursos objeto o colecciones en arrays. Reconoce objetos tanto implementando [[yii\base\ArrayableInterface]] como [[yii\data\DataProviderInterface]]. El primer formateador es implementado principalmente para recursos objeto, mientras que el segundo para recursos collección.
Como hemos descrito antes, [[yii\rest\Serializer]] es la pieza central responsable de convertir
objetos recurso o colecciones en arrays. Reconoce objetos tanto implementando [[yii\base\ArrayableInterface]]
como [[yii\data\DataProviderInterface]]. El primer formateador es implementado principalmente para objetos recursos,
mientras que el segundo para recursos collección.
Puedes configurar el serializador poniendo la propiedad [[yii\rest\Controller::serializer]] con un array de configuración.
Por ejemplo, a veces puedes querer simplificar la ayuda al trabajo de desarrollo del cliente incluyendo información de la paginación directamente en el cuerpo de la respuesta. Para hacer esto, configura la propiedad [[yii\rest\Serializer::collectionEnvelope]] como sigue:
Puedes configurar el serializador definiendo la propiedad [[yii\rest\Controller::serializer]] con un array de configuración.
Por ejemplo, a veces puedes querer ayudar a simplificar el trabajo de desarrollo del cliente incluyendo información de la paginación
directamente en el cuerpo de la respuesta. Para hacer esto, configura la propiedad [[yii\rest\Serializer::collectionEnvelope]]
Con los recursos y las clases controladoras preparadas, puedes acceder a los recursos usando una URL como `http://localhost/index.php?r=user/create`, parecida a la que usas con aplicaciones Web normales.
Con las clases de controlador y recurso preparadas, puedes acceder a los recursos usando una URL como
`http://localhost/index.php?r=user/create`, parecida a la que usas con aplicaciones Web normales.
En la práctica, querrás usualmente usar URLs más bonitas y obtener ventajas de los comandos de acciones (verbos) HTTP.
Por ejemplo, una petición `POST /users` puede permitir el acceso a la acción `user/create`.
Esto puede realizarse fácilmente configurando el componente de la aplicación `urlManager` en la configuración tal y como sigue:
En la práctica, querrás usualmente usar URLs limpias y obtener ventajas de los verbos HTTP.
Por ejemplo, una petición `POST /users` significaría acceder a la acción `user/create`.
Esto puede realizarse fácilmente configurando el componente de la aplicación `urlManager`
como sigue:
```php
'urlManager'=>[
...
...
@@ -18,8 +20,10 @@ Esto puede realizarse fácilmente configurando el componente de la aplicación `
]
```
En comparación con la gestión de URL en las aplicaciones Web, lo nuevo de lo anterior es el uso de [[yii\rest\UrlRule]] para el enrutado de las peticiones con el API RESTful. Esta clase especial que contiene la norma para gestionar las URLs puede crear todo un conjunto de URLs hijas para mantener el enrutado y la creación de URLs para la/s especificada/s controlador/as.
Por ejemplo, el código anterior es aproximadamente equivalente a las siguientes reglas:
En comparación con la gestión de URL en las aplicaciones Web, lo principalmente nuevo de lo anterior es el uso de
[[yii\rest\UrlRule]] para el enrutamiento de las peticiones con el API RESTful. Esta clase especial de regla URL creará
un conjunto completo de reglas URL hijas para soportar el enrutamiento y creación de URL para el/los controlador/es especificados.
Por ejemplo, el código anterior es equivalente a las siguientes reglas:
```php
[
...
...
@@ -33,19 +37,20 @@ Por ejemplo, el código anterior es aproximadamente equivalente a las siguientes
]
```
Y los siguientes puntos finales del API son mantenidos por la siguiente regla:
Y los siguientes puntos finales del API son mantenidos por esta regla:
*`GET /users`: listado de todos los usuarios página a página;
*`HEAD /users`: enseña ĺa información resumén del usuario listado;
*`GET /users`: lista de todos los usuarios página a página;
*`HEAD /users`: muestra ĺa información resumén del usuario listado;
*`POST /users`: crea un nuevo usuario;
*`GET /users/123`: devuelve los detalles del usuario 123;
*`HEAD /users/123`: enseña la información resúmen del usuario 123;
*`HEAD /users/123`: muestra la información resúmen del usuario 123;
*`PATCH /users/123` y `PUT /users/123`: actualizan al usuario 123;
*`DELETE /users/123`: borra el usuario 123;
*`OPTIONS /users`: presenta las acciones finales soportadas por`/users`;
*`OPTIONS /users/123`: presenta las acciones finales que soporta`/users/123`.
*`OPTIONS /users`: muestra los verbos soportados de acuerdo al punto final`/users`;
*`OPTIONS /users/123`: muestra los verbos soportados de acuerdo al punto final`/users/123`.
Puedes configurar las opciones `only` y `except` para explícitamente listar las acciones a soportar y cuales desabilitar, respectivamente. Por ejemplo,
Puedes configurar las opciones `only` y `except` para explícitamente listar cuáles acciones a soportar o cuáles
deshabilitar, respectivamente. Por ejemplo,
```php
[
...
...
@@ -55,8 +60,8 @@ Puedes configurar las opciones `only` y `except` para explícitamente listar la
],
```
También puedes configurar `patterns` o `extraPatterns` para redifinir patrones existentes o añadir nuevos patrones que soportan esta regla.
Por ejemplo, para soportar la nueva acción `search` por`GET /users/search`, configura la opción `extraPatterns` como sigue,
También puedes configurar las propiedades `patterns` o `extraPatterns` para redifinir patrones existentes o añadir nuevos soportados por esta regla.
Por ejemplo, para soportar una nueva acción `search` por el punto final`GET /users/search`, configura la opción `extraPatterns` como sigue,
```php
[
...
...
@@ -65,8 +70,23 @@ Por ejemplo, para soportar la nueva acción `search` por `GET /users/search`, co
'extraPatterns'=>[
'GET search'=>'search',
],
]
```
Queda advertido que la ID de la controladora `user` aparece finalmente en plural tal que`users`.
Esto es debido a que [[yii\rest\UrlRule]] pluraliza de forma automáticalos IDs de las controladoras para ser usadas en los puntos finales.
Puedes desactivar este comportamiento poniendo a false [[yii\rest\UrlRule::pluralize]] , o si quieres usar algunos nombres especiales, debes configurar la propiedad [[yii\rest\UrlRule::controller]]. Dése cuenta que la pluralización de puntos finales del RESTful no siempre añade simplemente una "s" l final de la id de la controladora. Una controladora cuyo ID termina en "x", por ejemplo "BoxController" (con ID `box`), tiene el punto final del RESTful pluralizada a `boxes` por [[yii\rest\UrlRule]].
Puedes haber notado que el ID del controlador `user` aparece en formato plural `users` en los puntos finales de las URLs.
Esto se debe a que [[yii\rest\UrlRule]] automáticamente pluraliza los IDs de los controladores al crear reglas URL hijas.
Puedes desactivar este comportamiento definiendo la propiedad [[yii\rest\UrlRule::pluralize]] como false.
> Info: La pluralización de los IDs de los controladores es realizada por [[yii\helpers\Inflector::pluralize()]]. Este método respeta
reglas especiales de pluralización. Por ejemplo, la palabra `box` (caja) será pluralizada como `boxes` en vez de `boxs`.
En caso de que la pluralización automática no encaje en tus requerimientos, puedes además configurar la propiedad
[[yii\rest\UrlRule::controller]] para especificar exlpícitamente cómo mapear un nombre utilizado en un punto final URL
a un ID de controlador. Por ejemplo, el siguiente código mapea el nombre `u` al ID del controlador `user`.
Una buena API ha de ser *versionada*: los cambios y las nuevas características son implementadas en las nuevas versiones del API, en vez de estar continuamente modificando sólo una versión. Al contrario que en las aplicaciones Web, en las cuales tienes total control del código de ambas partes lado del cliente y lado del servidor, las APIs están destinadas a ser usadas por los clientes fuera de tu control. Por esta razón, compatibilidades hacia atrás (BC Backward compatibility) de las APIs ha de ser mantenida siempre que sea posible. Si es necesario un cambio que puede romper la BC, debes de introducirla en la nueva versión del API, e incrementar el número de versión. Los clientes que la usan pueden continuar usando la antigua versión de trabajo del API; los nuevos y actualizados clientes pueden obtener la nueva funcionalidad de la nueva versión del API.
Una buena API ha de ser *versionada*: los cambios y las nuevas características son implementadas en las nuevas versiones del API, en vez de estar continuamente modificando sólo una versión. Al contrario que en las aplicaciones Web, en las cuales tienes total control del código de ambas partes lado del cliente y lado del servidor,
las APIs están destinadas a ser usadas por los clientes fuera de tu control. Por esta razón, compatibilidad hacia atrás (BC Backward compatibility)
de las APIs ha de ser mantenida siempre que sea posible. Si es necesario un cambio que puede romper la BC, debes de introducirla en la nueva versión del API, e incrementar el número de versión. Los clientes que la usan pueden continuar usando la antigua versión de trabajo del API; los nuevos y actualizados clientes pueden obtener la nueva funcionalidad de la nueva versión del API.
> Tip: referirse a [Semántica del versionado](http://semver.org/) para más información en el diseño del número de versión del API.
> Tip: referirse a [Semántica del versionado](http://semver.org/)
para más información en el diseño del número de versión del API.
Una manera común de implementar el versionado de la API es embeber el número de versión en las URLs del AP.
Por ejemplo, `http://example.com/v1/users` se inicia por la versión 1 de la API del la parte final `/users`.
Una manera común de implementar el versionado de la API es embeber el número de versión en las URLs de la API.
For example, `http://example.com/v1/users` se refiere al punto final `/users` de la versión 1 de la API.
Otro método de versionado de la API , la cual está ganando predominancia recientemente, es poner el número de versión en las cabeceras de la petición HTTP. Esto se suele hacer típicamente a través la cabecera `Accept` :
Otro método de versionado de la API,
la cual está ganando predominancia recientemente, es poner el número de versión en las cabeceras de la petición HTTP. Esto se suele hacer típicamente a través la cabecera `Accept`:
```
// vía parámetros
Accept: application/json; version=v1
// vía de el tipo de contenido del vendedor
// vía de el tipo de contenido del proveedor
Accept: application/vnd.company.myapp-v1+json
```
Ambos métodos tienen sus pros y sus contras, y hay gran cantidad de debates sobre cada uno. Debajo puedes ver una estrategia práctica para el versionado de la API que es una mezcla de estos dos métodos:
Ambos métodos tienen sus pros y sus contras, y hay gran cantidad de debates sobre cada uno. Debajo puedes ver una estrategia
práctica para el versionado de la API que es una mezcla de estos dos métodos:
* Pon cada versión superior del API en un módulo separado cuya ID es el número de la versión principal. (p.e. `v1`, `v2`).
Naturalmente, las URLs del API pueden contener números de versión superiores.
* Dentro de cada versión superior (y por tanto, dentro del correspondiente módulo), usa la cabecera de HTTP `Accept` para determinar el número de la menor versión y escribe código condicional para responder a la menor versión en consecuencia.
* Pon cada versión superior de la implementación de la API en un módulo separado cuyo ID es el número de la versión mayor (p.e. `v1`, `v2`).
Naturalmente, las URLs de la API contendrán números de versión mayores.
* Dentro de cada versión mayor (y por lo tanto, dentro del correspondiente módulo), usa la cabecera de HTTP `Accept`
para determinar el número de la versión menor y escribe código condicional para responder a la menor versión como corresponde.
Para cada módulo sirviendo una versión superior, el módulo debe incluir los recursos y la clase controladora que especifican la versión. Para mejor separar la responsabilidad del código, puedes conservar un conjunto de recursos base y clases de controladores comunes, y hacer subclases de ellas en cada uno de los módulos de versión individual. Dentro de las subclases, impementa el código concreto como es `Model::fields()`.
Para cada módulo sirviendo una versión mayor, el módulo debe incluir las clases de recursos y y controladores
que especifican la versión. Para separar mejor la responsabilidad del código, puedes conservar un conjunto común de
clases base de recursos y controladores, y hacer subclases de ellas en cada versión individual del módulo. Dentro de las subclases,
impementa el código concreto como por ejemplo `Model::fields()`.
Tu código puede estar organizado como lo que sigue:
...
...
@@ -53,7 +62,7 @@ api/
Post.php
```
La configuración de su aplicación puede tener este aspecto:
La configuración de tu aplicación puede tener este aspecto:
```php
return[
...
...
@@ -81,15 +90,22 @@ return [
];
```
Como consecuencia de el anterior código, `http://example.com/v1/users` puede devolver la lista de usuarios de la versión 1, mientras
`http://example.com/v2/users`puede devolver la versión 2 de los usuarios.
Como consecuencia del código anterior, `http://example.com/v1/users` devolverá la lista de usuarios en la versión 1, mientras
`http://example.com/v2/users`devolverá la versión 2 de los usuarios.
Gracias a los módulos, el código de las diferentes principales versiónes puede ser aislado. Pero, los módulos, hacen posible reusar el código a través de los módulos vía clases base comunes y otros recursos compartidos.
Gracias a los módulos, el código de las diferentes principales versiones puede ser aislado. Pero los módulos hacen posible
reutilizar el código a través de los módulos vía clases base comunes y otros recursos compartidos.
Para traficar con los números de versión menores, puede obtener las ventajas de el contenido de las capacidades de las conductas de negociación provistas por el [[yii\filters\ContentNegotiator|contentNegotiator]]. La conducta `contentNegotiator` puede poner la propiead [[yii\web\Response::acceptParams]] cuando determina cuál tipo de contenido a soportar.
Para tratar con versiones menores, puedes tomar ventaja de la característica de negociación de contenido
provista por el comportamiento (behavior) [[yii\filters\ContentNegotiator|contentNegotiator]]. El comportamiento `contentNegotiator`
definirá la propiedad [[yii\web\Response::acceptParams]] cuando determina qué tipo
de contenido soportar.
Por ejemplo, si una peticiónes enviada con la cabecera HTTP `Accept: application/json; version=v1`, entonces la conducta de negociación, [[yii\web\Response::acceptParams]] puede contener el valor `['version' => 'v1']`.
Por ejemplo, si una petición es enviada con la cabecera HTTP `Accept: application/json; version=v1`,
después de la negociación de contenido, [[yii\web\Response::acceptParams]] contendrá el valor `['version' => 'v1']`.
Basado en la información de versión contenida en `acceptParams`, puedes escribir código condicional en lugares como acciones, clases de recursos, serializadores, etc. para proveer la funcionalidad apropiada.
Basado en la información de versión contenida en `acceptParams`, puedes escribir código condicional en lugares
como acciones, clases de recursos, serializadores, etc. para proveer la funcionalidad apropiada.
Desde la menor versión, por definición, es necesario mantener la compatibilidad hacia atrás, con suerte no tendrás demasiadas versiones a comporbar en tu código. De otra manera, probablemente puede ocurrir que necesites crear una versión principal.
Dado que por definición las versiones menores requireren mantener la compatibilidad hacia atrás, con suerte no tendrás demasiadas
comprobaciones de versión en tu código. De otra manera, probablemente puede ocurrir que necesites crear una versión mayor.