Los servicios Web, de una forma u otra, han existido durante más de dos décadas. Por ejemplo, los servicios XML-RPC aparecieron a finales de la década de 1990, seguidos poco después por otros escritos en la rama SOAP. Servicios en el estilo arquitectónico resto también hizo la escena hace unas dos décadas, poco después de los pioneros XML-RPC y SOAP. Los servicios de estilo REST (en adelante, Restful) ahora dominan en sitios populares como eBay, Facebook y Twitter. A pesar de las alternativas a los servicios web para la computación distribuida (E. G., los servicios web Restful siguen siendo atractivos por varias razones:

  • Los servicios Restful se basan en la infraestructura y los protocolos existentes, en particular, los servidores web y los protocolos HTTP/HTTPS. Una organización que tiene sitios web basados en HTML puede agregar fácilmente servicios web para clientes interesados más en los datos y la funcionalidad subyacente que en la presentación HTML., Amazon, por ejemplo, ha sido pionero en hacer que la misma información y funcionalidad estén disponibles a través de sitios web y servicios web, ya sea basados en SOAP o Restful.

  • Los servicios Restful tratan HTTP como una API, evitando así la complicada superposición de software que ha venido a caracterizar el enfoque basado en SOAP para los servicios web. Por ejemplo, la API Restful admite las operaciones estándar CRUD (Create-Read-Update-Delete) a través de los verbos HTTP POST-GET-PUT-DELETE, respectivamente; los códigos de estado HTTP informan al solicitante si una solicitud tuvo éxito o por qué falló.,

  • Los servicios web Restful pueden ser tan simples o complicados como sea necesario. Restful es un estilo-de hecho, muy flexible-más que un conjunto de prescripciones sobre cómo deben diseñarse y estructurarse los servicios. (El operador desventaja es que puede ser difícil determinar lo que no cuenta como un servicio Restful.)

  • para un consumidor o cliente, los servicios web Restful son neutrales en cuanto al idioma y la plataforma. El cliente realiza solicitudes en HTTP (S) y recibe respuestas de texto en un formato adecuado para el intercambio de datos moderno (por ejemplo, JSON).,

  • casi todos los lenguajes de programación de propósito general tienen al menos un soporte adecuado (y a menudo fuerte) para HTTP/HTTPS, lo que significa que los clientes de servicios web pueden escribirse en esos lenguajes.

este artículo explora los servicios Restful ligeros en Java a través de un ejemplo de código completo.

El servicio web RESTful novels

El servicio web Restful novels consta de tres clases definidas por el programador:

  • la clase Novel representa una novela con solo tres propiedades: un ID generado por máquina, un autor y un título., Las propiedades podrían expandirse para obtener más realismo, pero quiero mantener este ejemplo simple.
  • la clase Novels consiste en utilidades para varias tareas: convertir una codificación de texto plano de un Novel o una lista de ellas en XML o JSON; soportar las operaciones CRUD en la colección novels; e inicializar la colección a partir de datos almacenados en un archivo. La clase Novels media entre las instancias Novel y el servlet.,
  • la clase NovelsServlet deriva de HttpServlet, un software robusto y flexible que ha existido desde los primeros Java empresariales de finales de la década de 1990. El código servlet se centra en procesar las solicitudes de los clientes y generar las respuestas apropiadas, dejando los detalles diabólicos a las utilidades en la clase Novels.

algunos frameworks Java, como Jersey (JAX-RS) y Restlet, están diseñados para servicios Restful., Sin embargo, el HttpServlet por sí solo proporciona una API ligera, flexible, potente y bien probada para la prestación de dichos servicios. Demostraré esto con el ejemplo de las novelas.

desplegar el servicio web novels

desplegar el servicio web novels requiere un servidor web, por supuesto. Mi elección es Tomcat, pero el servicio debería funcionar (famosas últimas palabras!) si está alojado en, por ejemplo, Jetty o incluso un servidor de aplicaciones Java. El código y un README que resume cómo instalar Tomcat están disponibles en mi sitio web., También hay un script Apache Ant documentado que construye el servicio novels (o cualquier otro servicio o sitio web) y lo implementa bajo Tomcat o su equivalente.

Tomcat está disponible para descargar desde su sitio web. Una vez que lo instale localmente, deje que TOMCAT_HOME sea el directorio de instalación., Hay dos subdirectorios de interés inmediato:

  • el directorio TOMCAT_HOME/bin contiene scripts de inicio y parada para sistemas tipo Unix (startup.sh y shutdown.sh) y Windows (startup.bat y shutdown.bat). Tomcat se ejecuta como una aplicación Java. El contenedor de servlet del servidor web se llama Catalina. (En Jetty, el servidor web y el contenedor tienen el mismo nombre.) Una vez que se inicie Tomcat, ingrese en un navegador para ver una amplia documentación, incluidos ejemplos.,

  • el directorio TOMCAT_HOME/webapps es el predeterminado para los sitios web y servicios web implementados. La forma sencilla de implementar un sitio web o servicio web es copiar un archivo JAR con una extensión .war (por lo tanto, un archivo WAR) a TOMCAT_HOME/webapps o un subdirectorio del mismo. Tomcat luego descomprime el archivo WAR en su propio directorio. Por ejemplo, Tomcat sería diferente novels.war en un subdirectorio con el nombre novels, dejando novels.war como-es., Un sitio web o servicio puede eliminarse eliminando el archivo WAR y actualizarse sobrescribiendo el archivo WAR con una nueva versión. Por cierto, el primer paso para depurar un sitio web o servicio es comprobar que Tomcat ha desempaquetado el archivo WAR; si no, el sitio o servicio no se publicó debido a un error fatal en el código o la configuración.,

  • debido a que Tomcat escucha de forma predeterminada en el puerto 8080 para las solicitudes HTTP, comienza una URL de solicitud para Tomcat en la máquina local:

    Access a programmer-deployed WAR file by adding the WAR file’s name but without the .war extension:

    If the service was deployed in a subdirectory (e.g., myapps) of TOMCAT_HOME, this would be reflected in the URL:

    I’ll offer more details about this in the testing section near the end of the article.

As noted, the ZIP file on my homepage contains an Ant script that compiles and deploys a website or service. (A copy of novels.war is also included in the ZIP file.) For the novels example, a sample command (with % as the command-line prompt) is:

% ant -Dwar.name=novels deploy

Este comando compila archivos fuente Java y luego crea un archivo implementable llamado novels.war, deja este archivo en el directorio actual y lo copia en TOMCAT_HOME/webapps., Si todo va bien, una solicitud GET (utilizando un navegador o una utilidad de línea de comandos, como curl) sirve como primera prueba:

% curl http://localhost:8080/novels/

Tomcat está configurado, de forma predeterminada, para implementaciones en caliente: el servidor web no necesita actualizar o quitar una aplicación web.

el servicio novels a nivel de Código

volvamos al ejemplo de novels pero a nivel de código. Considere la clase Novel a continuación:

ejemplo 1., La clase Novel

esta clase implementa el método compareTo de la interfaz Comparable porque Novel las instancias se almacenan en un subproceso seguro ConcurrentHashMap, que no impone un orden ordenado. Al responder a las solicitudes para ver la colección, el servicio novels ordena una colección (un ArrayList) extraída del mapa; la implementación de compareTo impone un orden ordenado ascendente por Novel ID.,

La clase Novels contiene varias funciones de utilidad:

Ejemplo 2. La clase de utilidad Novels

el método más complicado es populate, que lee desde un archivo de texto contenido en el archivo WAR desplegado. El archivo de texto contiene la colección inicial de novelas. Para abrir el archivo de texto, el método populate necesita el ServletContext, un mapa Java que contiene toda la información crítica sobre el servlet incrustado en el contenedor servlet., El archivo de texto, a su vez, contiene registros como este:

Jane Austen!Persuasion

La línea se analiza en dos partes (autor y título) separadas por el símbolo bang (!). El método construye una instancia Novel, establece las propiedades autor y título y agrega la novela a la colección, que actúa como un almacén de datos en memoria.

la clase Novels también tiene utilidades para codificar la colección de novelas en XML o JSON, dependiendo del formato que prefiera el solicitante., XML es el valor predeterminado, pero JSON está disponible a petición. Un paquete ligero de XML a JSON proporciona el JSON. Más detalles sobre la codificación están a continuación.

Ejemplo 3. La clase NovelsServlet

recuerda que la clase NovelsServlet anterior extiende la clase HttpServlet, que a su vez extiende la clase GenericServlet, que implementa la interfaz Servlet p>

NovelsServlet extends HttpServlet extends GenericServlet implements Servlet

como su nombre lo aclara, el HttpServlet está diseñado para servlets entregados a través de HTTP(S)., La clase proporciona métodos vacíos con el nombre de los verbos de solicitud HTTP estándar (oficialmente, métodos):

  • doPost (Post = Create)
  • doGet (Get = Read)
  • doPut (Put = Update)
  • doDelete (delete = delete)

también se cubren algunos verbos HTTP adicionales. Una extensión del HttpServlet, como el NovelsServlet, anula cualquier método de interés do, dejando los demás como no-ops., El NovelsServletanula siete de los métodos do.

cada uno de los métodos CRUD HttpServlet toma los mismos dos argumentos. Aquí está doPost como ejemplo:

public void doPost(HttpServletRequest request, HttpServletResponse response) {

el argumento requestes un mapa de la información de la solicitud HTTP, y el response proporciona un flujo de salida al solicitante., Un método como doPost se estructura de la siguiente manera:

  • lea la información request, tomando cualquier acción que sea apropiada para generar una respuesta. Si falta información o es deficiente, genere un error.
  • Use la información de solicitud extraída para realizar la operación CRUD apropiada (en este caso, cree un Novel) y luego codifique una respuesta apropiada al solicitante usando el flujo de salida response para hacerlo., En el caso de doPost, la respuesta es una confirmación de que se ha creado una nueva novela y se ha añadido a la colección. Una vez que se envía la respuesta, el flujo de salida se cierra, lo que también cierra la conexión.

más sobre el método do anula

una solicitud HTTP tiene una estructura relativamente simple. Aquí hay un bosquejo en el familiar HTTP 1.,1 Formato, con comentarios introducidos por signos de doble nitidez:

la línea de inicio comienza con el verbo HTTP (en este caso,GET) y el URI (Uniform Resource Identifier), que es el sustantivo (en este caso,novels) que nombra el recurso de destino. Los encabezados consisten en pares clave-valor, con dos puntos que separan la clave de la izquierda de los valores de la derecha., Se requiere el encabezado con claveHost(sin distinción de mayúsculas y minúsculas); el nombre de hostlocalhostes la dirección simbólica de la máquina local en la máquina local, y el número de Puerto8080es el predeterminado para el servidor web Tomcat que espera solicitudes HTTP. (De forma predeterminada, Tomcat escucha en el puerto 8443 para solicitudes HTTPS.) Los elementos del encabezado pueden aparecer en orden arbitrario. En este ejemplo, el valor del encabezado

Accept-typees el tipo MIMEtext/plain.,

algunas solicitudes (en particular, POST y PUT) tienen cuerpos, mientras que otras (en particular, GET y DELETE) no. Si hay un cuerpo (tal vez vacío), dos nuevas líneas separan las cabeceras del cuerpo; el cuerpo HTTP consiste en pares clave-valor. Para las solicitudes sin cuerpo, los elementos de encabezado, como la cadena de consulta, se pueden usar para enviar información., Aquí hay una solicitud aGET el recurso/novels con el ID de 2:

GET /novels?id=2

la cadena de consulta comienza con el signo de interrogación y, en general, consiste en pares clave-valor, aunque es posible una clave sin un valor.

El HttpServlet, con métodos tales como getParameter y getParameterMap, bien oculta la distinción entre las peticiones HTTP con y sin cuerpo., En las novelas ejemplo, la etiqueta getParameter método se utiliza para extraer la información necesaria de la etiqueta GET, POST y DELETE solicitudes. (Manejar una solicitud PUT requiere código de nivel inferior porque Tomcat no proporciona un mapa de parámetros viable para las solicitudes PUT.,) Aquí, a modo de ejemplo, hay un fragmento del método doPost en la solicitud NovelsServlet override:

para una solicitud DELETE, el enfoque es esencialmente el mismo:

el necesita distinguir entre dos sabores de una solicitud GET: un sabor significa «obtener todo», mientras que el otro significa obtener uno especificado., Si la URL de solicitud GET contiene una cadena de consulta cuya clave es un ID, entonces la solicitud se interpreta como «get a specified one»:

http://localhost:8080/novels?id=2 ## GET specified

Si no hay una cadena de consulta, la solicitud GET se interpreta como «get all»:

http://localhost:8080/novels ## GET all

algunos detalles diabólicos

El diseño del servicio novels refleja cómo funciona un servidor web basado en Java como Tomcat. En el inicio, Tomcat crea un grupo de subprocesos del que se extraen los controladores de solicitudes, un enfoque conocido como el modelo de un subproceso por solicitud., Las versiones modernas de Tomcat también utilizan E/S sin bloqueo para aumentar el rendimiento.

el servicio novels se ejecuta como una sola instancia de la clase NovelsServlet, que a su vez mantiene una sola colección de novelas. En consecuencia, una condición de carrera surgiría, por ejemplo, si estas dos solicitudes se procesaran simultáneamente:

  • Una solicitud cambia la colección agregando una nueva novela.
  • La otra solicitud obtiene todas las novelas de la colección.

el resultado es indeterminado, dependiendo exactamente de cómo se superponen las operaciones de lectura y escritura., Para evitar este problema, el servicio novels utiliza un subproceso seguro ConcurrentMap. Las claves para este mapa se generan con un subproceso seguro AtomicInteger. Aquí está el segmento de código relevante:

public class Novels {
private ConcurrentMap<Integer, Novel> novels;
private AtomicInteger mapKey;
...

de forma predeterminada, una respuesta a una solicitud de cliente se codifica como XML. El programa novels utiliza la antigua clase XMLEncoder para simplificar; una opción mucho más rica es la biblioteca JAX-B., El código es sencillo:

el parámetro Object es un ArrayList ordenado de novelas (en respuesta a una solicitud «get all»); o una sola instancia Novel (en respuesta a una solicitud get one); o 5feb5f60bb»> (un mensaje de confirmación).

si un encabezado de solicitud HTTP hace referencia a JSON como un tipo deseado, el XML se convierte en JSON., Aquí está el cheque en el doGet método de la etiqueta NovelsServlet:

String accept = request.getHeader("accept"); // "accept" is case insensitive
if (accept != null && accept.contains("json")) json = true;

El Novels casas de clase toJson método, que convierte el XML a JSON:

El NovelsServlet comprueba los errores de varios tipos. Por ejemplo, una solicitud POST debe incluir un autor y un título para la nueva novela., Si falta cualquiera, el doPost método lanza una excepción:

if (author == null || title == null)
throw new RuntimeException(Integer.toString(HttpServletResponse.SC_BAD_REQUEST));

El SC en el SC_BAD_REQUEST es el código de estado y el BAD_REQUEST tiene el estándar HTTP numérico valor de 400. Si el verbo HTTP en una solicitud es TRACE, se devuelve un código de estado diferente:

probar el servicio novels

probar un servicio web con un navegador es complicado., Entre los verbos CRUD, los navegadores modernos solo generan solicitudes POST (Create) y GET (Read). Incluso una solicitud POST es un desafío desde un navegador, ya que los valores clave para el cuerpo deben incluirse; esto generalmente se hace a través de un formulario HTML. Una utilidad de línea de comandos como curl es una mejor manera de hacerlo, como lo ilustra esta sección con algunos comandos curl, que se incluyen en el ZIP en mi sitio web.,

Aquí hay algunas pruebas de ejemplo sin la salida correspondiente:

% curl localhost:8080/novels/
% curl localhost:8080/novels?id=1
% curl --header "Accept: application/json" localhost:8080/novels/

el primer comando solicita todas las novelas, que están codificadas por defecto en XML. El segundo comando solicita la novel con un ID de 1, que está codificado en XML. El último comando agrega un elemento de encabezado Accept con application/json como el tipo MIME deseado. El comando get one también podría usar este elemento de encabezado. Estas solicitudes tienen respuestas JSON en lugar de XML.,

los siguientes dos comandos crean una nueva novela en la colección y confirman la adición:

% curl --request POST --data "author=Tolstoy&title=War and Peace" localhost:8080/novels/
% curl localhost:8080/novels?id=4

a PUT comando en curl se asemeja a un POST comando excepto que el cuerpo PUT no usa sintaxis estándar. La documentación para el método doPut en el método NovelsServlet entra en detalles, pero la versión corta es que Tomcat no genera un mapa adecuado en las solicitudes PUT., Aquí está el comando PUT y un comando de confirmación:

% curl --request PUT --data "id=3#title=This is an UPDATE" localhost:8080/novels/
% curl localhost:8080/novels?id=3

el segundo comando confirma la actualización.

finalmente, el comandoDELETE funciona como se esperaba:

% curl --request DELETE localhost:8080/novels?id=2
% curl localhost:8080/novels/

la solicitud es que se elimine la novela con el ID de 2. El segundo comando muestra las novelas restantes.

la web.archivo de configuración xml

aunque es oficialmente opcional, un archivo de configuración web.xml es un pilar en un sitio web o servicio de nivel de producción., El archivo de configuración permite especificar el enrutamiento, la seguridad y otras características de un sitio o servicio independientemente del código de implementación. La configuración del servicio novels maneja el enrutamiento proporcionando un patrón de URL para las solicitudes enviadas a este servicio:

el elemento servlet-name proporciona una abreviatura (novels) para el nombre de clase completamente calificado del servlet (novels.NovelsServlet), y este nombre se usa en div id=»f0d4722fe0″> elemento a continuación.,

recuerde que una URL para un servicio desplegado tiene el nombre de archivo WAR justo después del número de Puerto:

la barra justo después del número de Puerto comienza el URI conocido como la ruta al recurso solicitado, en este caso, el servicio novels; por lo tanto, el términonovels se produce después de la primera barra simple.

En el web.xml archivo de la etiqueta url-pattern se especifica como /*, lo que significa que cualquier ruta que comienza con /novelas., Supongamos que Tomcat encuentra una URL de solicitud artificial, como esta:

la configuración web.xml especifica que esta solicitud también debe enviarse al servlet novels porque el patrón /* cubre /foobar. Por lo tanto, la URL artificial tiene el mismo resultado que la legítima que se muestra arriba.

un archivo de configuración de nivel de producción puede incluir información sobre seguridad, tanto a nivel de cable como de roles de usuario., Incluso en este caso, el archivo de configuración sería solo dos o tres veces el tamaño del archivo de ejemplo.

Wrapping up

el HttpServlet está en el Centro de las tecnologías web de Java. Un sitio web o servicio web, como el servicio novels, extiende esta clase, anulando los verbos de interés do. Un framework Restful como Jersey (JAX-RS) o Restlet hace esencialmente lo mismo al proporcionar un servlet personalizado, que luego actúa como el endpoint HTTP(S) para las solicitudes contra una aplicación web escrita en el framework.,

una aplicación basada en servlet tiene acceso, por supuesto, a cualquier biblioteca Java requerida en la aplicación web. Si la aplicación sigue el principio de separación de preocupaciones, entonces el código servlet sigue siendo atractivo: el código comprueba una solicitud, emitiendo el error apropiado si hay deficiencias; de lo contrario, el código llama a cualquier funcionalidad que pueda ser requerida (por ejemplo, consultar una base de datos, codificar una respuesta en un formato especificado), y luego envía la respuesta al solicitante., Los tipos HttpServletRequest y HttpServletResponse facilitan el trabajo específico del servlet de leer la solicitud y escribir la respuesta.

Java tiene API que van desde lo muy simple hasta lo muy complicado. Si necesita entregar algunos servicios Restful usando Java, mi consejo es probar el bajo problema HttpServlet antes de cualquier otra cosa.