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'
}
server:
   port: 8761
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

spring:
  application:
     name: backend

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

spring:
  application:
     name: zuul

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

gradle clean build

Desplegar cada uno

java -jar JAR_IN_THE_BUILD_FOLDER

Atacamos finalmente al servicio Zuul

curl -X GET -H "Accept: application/json" http://localhost:8099/ribbonbackend/user

La respuesta obtenida es

[{“id”:”1″,”name”:”admin”,”surname”:”admin”},{“id”:”2″,”name”:”jorge”,”surname”:”hernandez”}]