Pregunta Comprender REST: verbos, códigos de error y autenticación


Estoy buscando una forma de ajustar las API en torno a las funciones predeterminadas en mis aplicaciones web basadas en PHP, bases de datos y CMS.

Miré alrededor y encontré varios esqueletos "esqueleto". Además de las respuestas en mi pregunta, hay Tónico, un marco REST que me gusta porque es muy liviano.

ME GUSTA REST lo mejor por su simplicidad y me gustaría crear una arquitectura de API basada en él. Estoy tratando de entender los principios básicos y aún no lo he entendido del todo. Por lo tanto, una serie de preguntas.

1. ¿Lo estoy entendiendo bien?

Digamos que tengo un recurso "usuarios". Podría configurar varios URI como ese:

/api/users     when called with GET, lists users
/api/users     when called with POST, creates user record
/api/users/1   when called with GET, shows user record
               when called with PUT, updates user record
               when called with DELETE, deletes user record

¿Es esta una representación correcta de una arquitectura RESTful hasta el momento?

2. Necesito más verbos

Crear, actualizar y eliminar puede ser suficiente en teoría, pero en la práctica tendré la necesidad de muchos más verbos. Me doy cuenta de que estas son cosas que podría estar incrustado en una solicitud de actualización, pero son acciones específicas que pueden tener códigos de retorno específicos y no me gustaría incluirlos en una sola acción.

Algunos que vienen a la mente en el ejemplo del usuario son:

activate_login
deactivate_login
change_password
add_credit

¿Cómo expresaría acciones como las de una arquitectura RESTful URL?

Mi instinto sería hacer una llamada GET a una URL como

/api/users/1/activate_login 

y esperar un código de estado de vuelta.

Sin embargo, eso se desvía de la idea de usar verbos HTTP. ¿Qué piensas?

3. Cómo devolver mensajes de error y códigos

Una gran parte de la belleza de REST proviene del uso de métodos HTTP estándar. En caso de error, emito un encabezado con un código de estado de error 3xx, 4xx o 5xx. Para una descripción detallada del error, puedo usar el cuerpo (¿verdad?). Hasta aquí todo bien. Pero, ¿cuál sería la forma de transmitir un código de error propietario esto es más detallado al describir lo que salió mal (por ejemplo, "no se pudo conectar a la base de datos", o "error de inicio de sesión en la base de datos")? Si lo pongo en el cuerpo junto con el mensaje, tengo que analizarlo después. ¿Hay un encabezado estándar para este tipo de cosas?

4. Cómo hacer la autenticación

  • ¿Qué aspecto tendría una autenticación basada en claves API según los principios de REST?
  • ¿Hay puntos fuertes contra el uso de sesiones al autenticar un cliente REST, aparte de eso, es una flagrante violación del principio REST? :) (Es medio broma, la autenticación basada en sesiones funcionaría bien con mi infraestructura existente).

563
2018-01-04 19:55


origen


Respuestas:


Noté esta pregunta un par de días tarde, pero siento que puedo agregar algo de información. Espero que esto pueda ser útil para su empresa RESTful.


Punto 1: ¿Lo estoy entendiendo bien?

Usted entendió bien. Esa es una representación correcta de una arquitectura RESTful. Puede encontrar la siguiente matriz de Wikipedia muy útil para definir tus sustantivos y verbos:


Cuando se trata de un Colección URI como: http://example.com/resources/

  • OBTENER: Enumere los miembros de la colección, complete con sus URI de miembro para una navegación adicional. Por ejemplo, enumere todos los autos en venta.

  • PONER: Significado definido como "reemplazar toda la colección con otra colección".

  • ENVIAR: Cree una nueva entrada en la colección donde la colección asigna automáticamente el ID. La ID creada generalmente se incluye como parte de los datos devueltos por esta operación.

  • BORRAR: Significado definido como "eliminar toda la colección".


Cuando se trata de un Miembro URI como: http://example.com/resources/7HOU57Y

  • OBTENER: Recuperar una representación del miembro direccionado de la colección expresada en un tipo MIME apropiado.

  • PONER: Actualice el miembro direccionado de la colección o créelo con la ID especificada.

  • ENVIAR: Trata al miembro direccionado como una colección por derecho propio y crea un nuevo subordinado de él.

  • BORRAR: Eliminar el miembro direccionado de la colección.


Punto 2: necesito más verbos

En general, cuando crees que necesitas más verbos, en realidad puede significar que tus recursos deben volver a identificarse. Recuerde que en REST siempre actúa sobre un recurso o sobre una colección de recursos. Lo que elijas como recurso es bastante importante para tu definición de API.

Activar / desactivar inicio de sesión: Si está creando una nueva sesión, entonces puede considerar "la sesión" como el recurso. Para crear una nueva sesión, use POST para http://example.com/sessions/ con las credenciales en el cuerpo. Para caducar, use PUT o un DELETE (dependiendo de si tiene la intención de mantener un historial de la sesión) para http://example.com/sessions/SESSION_ID.

Cambia la contraseña: Esta vez el recurso es "el usuario". Necesitarás un PUT para http://example.com/users/USER_ID con las contraseñas viejas y nuevas en el cuerpo. Está actuando sobre el recurso "usuario" y una contraseña de cambio es simplemente una solicitud de actualización. Es bastante similar a la instrucción UPDATE en una base de datos relacional.

Mi instinto sería hacer una llamada GET   a una URL como    /api/users/1/activate_login

Esto va en contra de un principio fundamental de REST: el uso correcto de los verbos HTTP. Cualquier solicitud GET nunca debe dejar ningún efecto secundario.

Por ejemplo, una solicitud GET nunca debe crear una sesión en la base de datos, devolver una cookie con una nueva ID de sesión o dejar ningún residuo en el servidor. El verbo GET es como la instrucción SELECT en un motor de base de datos. Recuerde que la respuesta a cualquier solicitud con el verbo GET debe ser almacenable en caché cuando se solicite con los mismos parámetros, al igual que cuando solicita una página web estática.


Punto 3: Cómo devolver mensajes de error y códigos 

Considere los códigos de estado HTTP 4xx o 5xx como categorías de error. Puedes elaborar el error en el cuerpo.

Error al conectarse a la base de datos: / Login de base de datos incorrecta: En general, debería usar un error 500 para este tipo de errores. Este es un error del lado del servidor. El cliente no hizo nada mal. 500 errores normalmente se consideran "recuperables". es decir, el cliente puede volver a intentar la misma solicitud exacta y esperar que tenga éxito una vez resueltos los problemas del servidor. Especifique los detalles en el cuerpo, para que el cliente pueda proporcionarnos un contexto a los humanos.

La otra categoría de errores sería la familia 4xx, que en general indica que el cliente hizo algo mal. En particular, esta categoría de errores normalmente indica al cliente que no es necesario volver a intentar la solicitud tal como está, porque continuará fallando permanentemente. es decir, el cliente necesita cambiar algo antes de volver a intentar esta solicitud. Por ejemplo, los errores "Recurso no encontrado" (HTTP 404) o "Solicitud mal formada" (HTTP 400) entrarían en esta categoría.


Punto 4: Cómo hacer la autenticación

Como se señaló en el punto 1, en lugar de autenticar a un usuario, es posible que desee pensar en crear una sesión. Se le devolverá una nueva "ID de sesión", junto con el código de estado HTTP apropiado (200: Acceso otorgado o 403: Acceso denegado).

Luego le preguntará a su servidor RESTful: "¿PUEDE OBTENER el recurso para este ID de sesión?".

No hay un modo autenticado - REST no tiene estado: usted crea una sesión, le pide al servidor que le proporcione recursos usando esta ID de sesión como parámetro, y al cerrar la sesión deja caer o expira la sesión.


589
2018-01-07 19:13



En pocas palabras, estás haciendo esto completamente al revés.

No debería acercarse a esto desde qué URL debería usar. Las URL aparecerán "de forma gratuita" una vez que haya decidido qué recursos son necesarios para su sistema Y cómo representará esos recursos, y las interacciones entre los recursos y el estado de la aplicación.

Citar Roy Fielding

Una API REST debe gastar casi todo   su esfuerzo descriptivo en la definición del   tipo (s) de medios utilizados para representar   recursos y aplicación de manejo   estado, o en la definición extendida   nombres de relaciones y / o   marcado de hipertexto habilitado para existente   tipos de medios estándar. Cualquier esfuerzo gastado   describiendo qué métodos usar en qué   Los URI de interés deberían ser completamente   definido dentro del alcance de la   reglas de procesamiento para un tipo de medio   (y, en la mayoría de los casos, ya definido   por tipos de medios existentes). [Fracaso   aquí implica que fuera de banda   la información está impulsando la interacción   en lugar de hipertexto.]

Las personas siempre comienzan con los URI y piensan que esta es la solución, y luego tienden a perder un concepto clave en la arquitectura REST, notablemente, como se mencionó anteriormente, "El fracaso aquí implica que la información fuera de banda está impulsando la interacción en lugar del hipertexto. "

Para ser honesto, muchos ven un montón de URI y algunos GET y PUT y POST y piensan que REST es fácil. RESTO no es fácil. RPC a través de HTTP es fácil, es fácil mover bloques de datos de un lado a otro a través de cargas HTTP. REST, sin embargo, va más allá de eso. REST es un protocolo agnóstico. HTTP es muy popular y apto para sistemas REST.

REST vive en los tipos de medios, sus definiciones y cómo la aplicación dirige las acciones disponibles a esos recursos a través de hipertexto (enlaces, efectivamente).

Hay diferentes puntos de vista acerca de los tipos de medios en los sistemas REST. Algunos favorecen las cargas útiles específicas de la aplicación, mientras que otros prefieren elevar los tipos de medios existentes a los roles que son apropiados para la aplicación. Por ejemplo, por un lado, tiene esquemas XML específicos diseñados para su aplicación, en lugar de usar algo como XHTML como representación, tal vez a través de microformatos y otros mecanismos.

Ambos enfoques tienen su lugar, creo, el XHTML funciona muy bien en escenarios que se superponen tanto a la web impulsada por humanos como a la máquina, mientras que los primeros tipos de datos más específicos me hacen sentir mejor facilitando las interacciones de máquina a máquina. Considero que la mejora de los formatos de los productos básicos puede dificultar la negociación de los contenidos. "application / xml + yourresource" es mucho más específico como tipo de medio que "application / xhtml + xml", ya que este último se puede aplicar a muchas cargas que pueden o no ser algo que a un cliente de máquina realmente le interese, ni puede determinar sin introspección.

Sin embargo, XHTML funciona muy bien (obviamente) en la web humana donde los navegadores web y el renderizado son muy importantes.

Su aplicación lo guiará en ese tipo de decisiones.

Parte del proceso de diseño de un sistema REST es descubrir los recursos de primera clase en su sistema, junto con los recursos de apoyo derivados necesarios para respaldar las operaciones en los recursos primarios. Una vez que se descubren los recursos, entonces la representación de esos recursos, así como los diagramas de estado que muestran el flujo de recursos a través del hipertexto dentro de las representaciones, porque el próximo desafío.

Recuerde que cada representación de un recurso, en un sistema de hipertexto, combina la representación del recurso real junto con las transiciones de estado disponibles para el recurso. Considere cada recurso como un nodo en un gráfico, con los enlaces como las líneas que salen de ese nodo a otros estados. Estos enlaces informan a los clientes no solo lo que se puede hacer, sino también lo que se requiere para que se realicen (ya que un buen enlace combina el URI y el tipo de medio requerido).

Por ejemplo, puede tener:

<link href="http://example.com/users" rel="users" type="application/xml+usercollection"/>
<link href="http://example.com/users?search" rel="search" type="application/xml+usersearchcriteria"/>

Su documentación hablará sobre el campo rel llamado "users" y el tipo de medio de "application / xml + youruser".

Estos enlaces pueden parecer redundantes, todos están hablando con el mismo URI, más o menos. Pero no lo son.

Esto se debe a que para la relación "usuarios", ese enlace se refiere a la colección de usuarios, y puede usar la interfaz uniforme para trabajar con la colección (GET para recuperarlos todos, BORRAR para eliminarlos todos, etc.)

Si publica POST en esta URL, deberá pasar un documento "application / xml + usercollection", que probablemente solo contenga una única instancia de usuario dentro del documento para que pueda agregarlo o no, tal vez, para agregar varias a una vez. Tal vez su documentación sugiera que simplemente puede pasar un tipo de usuario único, en lugar de la colección.

Puede ver lo que la aplicación requiere para realizar una búsqueda, tal como se define en el enlace de "búsqueda" y su tipo de medios. La documentación para el tipo de medio de búsqueda le dirá cómo se comporta y qué esperar como resultado.

El punto de partida aquí, sin embargo, es que los URI en sí mismos son básicamente sin importancia. La aplicación tiene el control de los URI, no de los clientes. Más allá de unos pocos "puntos de entrada", sus clientes deben confiar en los URI proporcionados por la aplicación para su trabajo.

El cliente necesita saber cómo manipular e interpretar los tipos de medios, pero no necesita preocuparse de dónde va.

Estos dos enlaces son semánticamente idénticos en los ojos de los clientes:

<link href="http://example.com/users?search" rel="search" type="application/xml+usersearchcriteria"/>
<link href="http://example.com/AW163FH87SGV" rel="search" type="application/xml+usersearchcriteria"/>

Por lo tanto, concéntrese en sus recursos. Concéntrese en las transiciones de estado en la aplicación y cómo se logra mejor.


76
2018-01-08 02:39



re 1: Esto se ve bien hasta ahora. Recuerde devolver el URI del usuario recién creado en un encabezado "Ubicación:" como parte de la respuesta a POST, junto con un código de estado "201 Creado".

re 2: La activación a través de GET es una mala idea, e incluir el verbo en el URI es un olor de diseño. Es posible que desee considerar devolver un formulario en un GET. En una aplicación web, esto sería un formulario HTML con un botón de enviar; en el caso de uso de API, es posible que desee devolver una representación que contenga un URI para PUT a para activar la cuenta. Por supuesto, también puede incluir este URI en la respuesta en POST a / users. El uso de PUT garantizará que su solicitud sea idempotente, es decir, que pueda enviarse de nuevo de manera segura si el cliente no está seguro del éxito. En general, piense en qué recursos puede convertir sus verbos (tipo de "sustantivización de verbos"). Pregúntese con qué método está más estrechamente alineada su acción específica. P.ej. change_contraseña -> PUT; desactivar -> probablemente DELETE; add_credit -> posiblemente POST o PUT. Señale al cliente los URI apropiados incluyéndolos en sus representaciones.

re 3. No invente nuevos códigos de estado, a menos que crea que son tan genéricos que merecen ser estandarizados globalmente. Intente utilizar el código de estado más apropiado disponible (lea sobre todos ellos en RFC 2616). Incluye información adicional en el cuerpo de respuesta. Si realmente, realmente estás seguro de que quieres inventar un nuevo código de estado, piénsalo de nuevo; si aún así lo cree, asegúrese de elegir al menos la categoría correcta (1xx -> OK, 2xx -> informativo, 3xx -> redirección; 4xx-> error de cliente, 5xx -> error de servidor). ¿Mencioné que inventar nuevos códigos de estado es una mala idea?

re 4. Si de alguna manera es posible, use el marco de autenticación integrado en HTTP. Consulte la forma en que Google realiza la autenticación en GData. En general, no coloque claves de API en sus URI. Intente evitar las sesiones para mejorar la escalabilidad y admitir el almacenamiento en caché: si la respuesta a una solicitud difiere debido a algo que sucedió anteriormente, por lo general se ha vinculado a una instancia específica del proceso del servidor. Es mucho mejor convertir el estado de la sesión en un estado de cliente (por ejemplo, hacer que forme parte de solicitudes posteriores) o hacerlo explícito convirtiéndolo en un estado de recurso (servidor), es decir, darle su propio URI.


29
2018-01-04 20:39



1. Tienes la idea correcta sobre cómo diseñar tus recursos, en mi humilde opinión. No cambiaría nada.

2. En lugar de intentar extender HTTP con más verbos, considere a qué se pueden reducir los verbos propuestos en términos de los métodos y recursos básicos de HTTP. Por ejemplo, en lugar de un activate_login verbo, puede configurar recursos como: /api/users/1/login/active que es un booleano simple. Para activar un inicio de sesión, simplemente PUT un documento que dice 'verdadero' o 1 o lo que sea. Para desactivar, PUT un documento que está vacío o dice 0 o falso.

Del mismo modo, para cambiar o establecer contraseñas, simplemente hazlo PUTs a /api/users/1/password.

Siempre que necesite agregar algo (como un crédito) piense en términos de POSTs. Por ejemplo, podrías hacer un POST a un recurso como /api/users/1/credits con un cuerpo que contiene el número de créditos para agregar. UN PUT en el mismo recurso podría usarse para sobrescribir el valor en lugar de agregar. UN POST con un número negativo en el cuerpo restaría, y así sucesivamente.

3. Recomiendo encarecidamente no extender los códigos básicos de estado HTTP. Si no puede encontrar uno que coincida exactamente con su situación, elija el más cercano y ponga los detalles del error en el cuerpo de la respuesta. Además, recuerde que los encabezados HTTP son extensibles; su aplicación puede definir todos los encabezados personalizados que desee. Una aplicación en la que trabajé, por ejemplo, podría devolver un 404 Not Found bajo circunstancias múltiples. En lugar de hacer que el cliente analice el cuerpo de respuesta por el motivo, simplemente agregamos un nuevo encabezado, X-Status-Extended, que contenía nuestras extensiones de código de estado de propiedad. Por lo tanto, es posible que vea una respuesta como:

HTTP/1.1 404 Not Found    
X-Status-Extended: 404.3 More Specific Error Here

De esta forma, un cliente HTTP como un navegador web aún sabrá qué hacer con el código 404 normal, y un cliente HTTP más sofisticado puede elegir mirar el X-Status-Extendedencabezado para obtener información más específica.

4. Para la autenticación, recomiendo usar la autenticación HTTP si puede. Pero en mi humilde opinión, no hay nada de malo con el uso de la autenticación basada en cookies si es más fácil para usted.


20
2018-01-04 21:28



REST Básicos

REST tiene una restricción de interfaz uniforme, que establece que el cliente REST debe basarse en estándares en lugar de detalles específicos de la aplicación del servicio REST real, por lo que el cliente REST no se romperá por cambios menores, y probablemente será reutilizable.

Entonces, hay un contrato entre el cliente REST y el servicio REST. Si usa HTTP como protocolo subyacente, los siguientes estándares son parte del contrato:

  • HTTP 1.1
    • definiciones de métodos
    • definiciones del código de estado
    • encabezados de control de caché
    • aceptar y encabezados de tipo de contenido
    • encabezados de autenticación
  • IRI (utf8 URI)
  • cuerpo (elija uno)
  • hipervínculos
    • qué debería contenerlos (elegir uno)
      • enviando encabezados de enlace
      • enviar una respuesta hipermedia, p. html, átomo + xml, hal + json, ld + json e hidra, etc.
    • semántica
      • usar relaciones de enlace IANA y probablemente relaciones de enlace personalizado
      • utilizar un vocabulario RDF específico de la aplicación

REST tiene una restricción sin estado, que declara que la comunicación entre el servicio REST y el cliente debe ser sin estado. Esto significa que el servicio REST no puede mantener los estados del cliente, por lo que no puede tener un almacenamiento de sesión del lado del servidor. Tienes que autenticar cada solicitud individual. Entonces, por ejemplo, HTTP basic auth (parte del estándar HTTP) está bien, porque envía el nombre de usuario y la contraseña con cada solicitud.

Para responderte preguntas

  1. Si puede ser.

    Solo para mencionar, los clientes no se preocupan por la estructura IRI, se preocupan por la semántica, porque siguen enlaces que tienen relaciones de enlace o atributos de datos vinculados (RDF).

    Lo único importante sobre los IRI es que un solo IRI debe identificar solo un recurso. Se permite que un solo recurso, como un usuario, tenga muchos IRI diferentes.

    Es bastante simple por qué usamos buenos IRI como /users/123/password; es mucho más fácil escribir la lógica de enrutamiento en el servidor cuando comprendes el IRI simplemente leyéndolo.

  2. Tiene más verbos, como PUT, PATCH, OPTIONS y aún más, pero no necesita más de ellos ... En lugar de agregar verbos nuevos, debe aprender a agregar nuevos recursos.

    activate_login -> PUT /login/active true deactivate_login -> PUT /login/active false change_password -> PUT /user/xy/password "newpass" add_credit -> POST /credit/raise {details: {}}

    (El inicio de sesión no tiene sentido desde la perspectiva de REST, debido a la restricción sin estado).

  3. A sus usuarios no les importa por qué existe el problema. Quieren saber solo si hay éxito o error, y probablemente un mensaje de error que puedan entender, por ejemplo: "Lo sentimos, pero no pudimos guardar su publicación", etc.

    Los encabezados de estado HTTP son sus encabezados estándar. Todo lo demás debería estar en el cuerpo, creo. Un solo encabezado no es suficiente para describir, por ejemplo, mensajes de error multilingües detallados.

  4. La restricción sin estado (junto con la caché y las restricciones del sistema en capas) garantiza que el servicio se adapte bien. Seguramente no desea mantener millones de sesiones en el servidor, cuando puede hacer lo mismo con los clientes ...

    El cliente externo obtiene un token de acceso si el usuario le concede acceso usando el cliente principal. Después de eso, el cliente externo envía el token de acceso con cada solicitud. Hay soluciones más complicadas, por ejemplo, puede firmar todas las solicitudes, etc. Para más detalles, consulte el manual de OAuth.

Literatura relacionada


12
2017-09-17 22:24



Para los ejemplos que indicó, usaría lo siguiente:

activate_login

POST /users/1/activation

deactivate_login

DELETE /users/1/activation

Cambia la contraseña

PUT /passwords (esto supone que el usuario está autenticado)

añadir crédito

POST /credits (esto supone que el usuario está autenticado)

Para los errores, debe devolver el error en el cuerpo en el formato en el que recibió la solicitud, de modo que si recibe:

DELETE /users/1.xml

Enviarías la respuesta en XML, lo mismo sería cierto para JSON, etc.

Para la autenticación, debe usar la autenticación http.


11
2018-01-04 20:07



  1. Use la publicación cuando no sepa cómo se vería el nuevo URI de recursos (usted crea un nuevo usuario, la aplicación asignaría al nuevo usuario su identificación), PONGA para actualizar o crear recursos que sepa cómo se representarán (ejemplo : PUT /myfiles/thisismynewfile.txt)
  2. devolver la descripción del error en el cuerpo del mensaje
  3. Puede usar la autenticación HTTP (si es suficiente) Los servicios web deben ser stateles

6
2018-01-04 20:06



Sugeriría (como primer paso) que PUT solo debe usarse para actualizar las entidades existentes. POST debería usarse para crear nuevos. es decir

/api/users     when called with PUT, creates user record

no se siente bien conmigo Sin embargo, el resto de la primera sección (ver el uso del verbo) parece lógico.


5
2018-01-04 20:03