1. Introducción
Hola y bienvenidos al segundo post sobre Spring Cloud de nuestro blog.
En esta ocasión vamos a hablar del servidor Zuul
desarrollado por Netflix OSS y que básicamente resuelve las siguientes dos cuestiones dentro de un entorno cloud.
- Enrutamiento
- Filtrado
La idea principal es que parte del tráfico de nuestra aplicación pueda ir sobre un proxy que se encargue de realizar el enrutamiento y filtrado hacia otros microservicios de forma fácil y transparente.
Recomiendo la lectura del anterior post donde hablábamos del registro y descubrimiento de microservicios en el servidor Eureka
, ya que utilizaremos parte de lo descrito en él.
Os podéis descargar el código de ejemplo de mi GitHub aquí.
Tecnologías empleadas:
- Java 8
- Gradle 3.1
- SpringBoot 1.5.2.RELEASE
- Spring 4.3.7.RELEASE
- SpringCloud 1.1.5.RELEASE
2. Eureka server
Necesitaremos Eureka para poder realizar el registro y descubrimiento de servicios. Recordar que simplemente basta con utilizar la anotación @EnableEurekaServer
.
group 'com.jorgehernandezramirez.spring.springboot' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven' apply plugin: 'spring-boot' dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR6' } } sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.spring.springcloud.eureka.Application" } repositories { mavenCentral() } dependencies { compile 'org.springframework.cloud:spring-cloud-starter-eureka-server' }
package com.jorgehernandezramirez.spring.springcloud.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class Application { public Application(){ //For Spring } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
3. Microservicio de ejemplo. Backend
Nos creamos un microservicio que llamaremos Backend. La idea es que el tráfico que entre por el proxy Zuul sera redirigido hacia aquí.
Mostramos el build.gradle
group 'com.jorgehernandezramirez.spring.springboot' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven' apply plugin: 'spring-boot' dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR2' } } sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.spring.springcloud.backend.Application" } repositories { mavenCentral() } dependencies { compile 'org.springframework.cloud:spring-cloud-starter-eureka' }
Indicamos el nombre del microservicio que será registrado en eureka
Indicamos que vamos a arrancar en el puerto 8080 y la conexión a Eureka
server: port: 8080 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: hostname: localhost nonSecurePort: 8080
Expondremos un servicio de usuarios. A continuación creamos el dto UserDto
package com.jorgehernandezramirez.spring.springcloud.backend.service.dto; public class UserDto { private String id; private String name; private String surname; public UserDto(){ super(); } public UserDto(String id, String name, String surname) { this.id = id; this.name = name; this.surname = surname; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSurname() { return surname; } public void setSurname(String surname) { this.surname = surname; } @Override public String toString() { return "UserDto{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", surname='" + surname + '\'' + '}'; } }
Definimos el controlador UserController
package com.jorgehernandezramirez.spring.springcloud.backend.controller; import com.jorgehernandezramirez.spring.springcloud.backend.service.dto.UserDto; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.List; @RestController @RequestMapping("/user") public class UserController { public UserController(){ //For Spring } @RequestMapping(method = RequestMethod.GET) public List<UserDto> getUsers() { return Arrays.asList(new UserDto("1", "admin", "admin"), new UserDto("2", "jorge", "hernandez")); } @RequestMapping(value = "/admin", method = RequestMethod.GET) public List<UserDto> getUserAdmins() { return Arrays.asList(new UserDto("1", "admin", "admin")); } }
4. Zuul
Ahora viene lo verdaderamente relevante. Vamos a mostrar la continuación los ficheros principales que necesitaremos para configurar Zuul.
Configuración para realizar el enturamiento
Hacemos uso de la dependencia org.springframework.cloud:spring-cloud-starter-zuul
. Además, si queremos que nuestro servidor Zuul se registre en eureka deberemos incluir la dependencia org.springframework.cloud:spring-cloud-starter-eureka
group 'com.jorgehernandezramirez.spring.springboot' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven' apply plugin: 'spring-boot' apply plugin: 'io.spring.dependency-management' dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR6' } } sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.spring.springcloud.zuul.Application" } repositories { mavenCentral() } dependencies { compile('org.springframework.cloud:spring-cloud-starter-zuul') compile('org.springframework.cloud:spring-cloud-starter-eureka') compile('org.springframework.boot:spring-boot-starter-web') }
Establecemos un nombre con el que registrarnos en eureka
Para habilitar el servidor Zuul basta con utilizar la anotación @EnableZuulProxy
package com.jorgehernandezramirez.spring.springcloud.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @EnableZuulProxy @SpringBootApplication public class Application { public Application(){ //For Spring } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
A continuación mostramos el fichero de configuración application.yml
server: port: 8099 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: hostname: localhost nonSecurePort: 8099 zuul: routes: backend: path: /backend/** url: http://localhost:8080/ ribbonbackend: path: /ribbonbackend/** serviceId: BACKEND
- En primer lugar indicamos que vamos a arrancar en el puerto 8099
- En la segunda parte de la configuración indicamos la conexión hacia eureka para realizar tanto el registro como el descubrimiento de servicios
- Por último indicamos la configuración de enrutamiento de Zuul
Notar que podemos especificar por cada route el path y la url a la que queremos redirigir. En caso de utilizar la etiqueta url estaremos indicando que se trata de una url absoluta, mientras que si utilizamos la etiqueta serviceId estaremos haciendo uso del descubrimiento de servicios a través de Ribbon. De esta forma se solicitará a eureka la instancias disponibles de BACKEND, eligiendo una de ellas y finalmente atacando al servicio expuesto.
Configuración para realizar el Filtrado
Existen 4 tipos de filtros dentro de zuul
- pre. Filtro que se ejecuta antes del enrutado
- routing. Filtro que maneja el enrutado actual
- post. Filtro que se ejecuta después del enrutado
- error. Filtro que se ejecutan cuando ocurre un error en el manejo de la petición
Para más información ir a la web de Spring
Mostramos la clase MyZuulFilter con la que vamos a realizar el filtrado de las peticiones entrantes. En este caso se trata filtro de tipo pre.
package com.jorgehernandezramirez.spring.springcloud.zuul.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; public class MyZuulFilter extends ZuulFilter { private static Logger LOGGER = LoggerFactory.getLogger(MyZuulFilter.class); private static final String FILTERTYPE = "pre"; private static final Integer FILTERORDER = 1; public MyZuulFilter(){ //For Spring } @Override public String filterType() { return FILTERTYPE; } @Override public int filterOrder() { return FILTERORDER; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { final HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); LOGGER.info("{} petición a {}", request.getMethod(), request.getRequestURL().toString()); return null; } }
Registramos nuestro filtro en el contexto de Spring
package com.jorgehernandezramirez.spring.springcloud.zuul.configuration; import com.jorgehernandezramirez.spring.springcloud.zuul.filter.MyZuulFilter; import com.netflix.zuul.ZuulFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ZuulConfiguration { public ZuulConfiguration(){ //For Spring } @Bean public ZuulFilter buildZuulFilter(){ return new MyZuulFilter(); } }
5. Probando la aplicación
En primer lugar deberemos levantar los tres microservicios. Para ello:
Compilar cada uno
Desplegar cada uno
Atacamos finalmente al servicio Zuul
La respuesta obtenida es
[{“id”:”1″,”name”:”admin”,”surname”:”admin”},{“id”:”2″,”name”:”jorge”,”surname”:”hernandez”}]