3

We are using Spring Cloud Gateway before multiple microservices with consul as service discovery. There are several microservices developed in different languages.

Please find build.gradle for the application

buildscript {
    ext {
        springBootVersion = '2.1.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/milestone' }
}

ext {
    set('springCloudVersion', 'Greenwich.RELEASE')
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.cloud:spring-cloud-starter-consul-config'
    implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    // https://mvnrepository.com/artifact/io.netty/netty-tcnative-boringssl-static
    compile group: 'io.netty', name: 'netty-tcnative-boringssl-static', version: '2.0.20.Final'
    runtimeOnly 'org.springframework.boot:spring-boot-devtools'
    compileOnly 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

Below is the example of API gateway configuration
application.yaml

server:
  port: 10000
  http:
    port: 9000
  # enable HTTP2
  http2:
    enabled: true
  # enable compression
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
  ssl:
    enabled: true
    key-store: /var/.conf/self-signed.p12
    key-store-type: PKCS12
    key-store-password: "something"
    key-alias: athenasowl
    trust-store: /var/.conf/self-signe.p12
    trust-store-password: "something"
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          predicates:
          - Path="'/api/' + serviceId + '/**'"
          filters:
          - RewritePath="'/api/' + serviceId + '/(?<remaining>.*)'", "serviceId + '/${remaining}'"
management:
  security:
    enabled: false
  server:
    port: 10001
    ssl:
      enabled: false
  endpoint:
    gateway:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "*"
    health:
      sensitive: false
logging:
  level:
    root: DEBUG
    org:
      springframework:
        web: INFO
  pattern:
    console: "%-5level %d{dd-MM-yyyy HH:mm:ss,SSS} [%F:%L] VTC : %msg%n"
    file: "%-5level %d{dd-MM-yyyy HH:mm:ss,SSS} [%F:%L] VTC : %msg%n"
  file: /tmp/log_files/apigateway.log
security:
  basic:
    enabled: false

There are a few configuration issues which we are facing, they are listed below:

  • Rewrite URL prefixed with /api/ to respective serviceId registered on consul: We tried to configure predicate to get path prefixed with api to rewrite path and remove api, but still it's not working. So there is another service /hello-service/ registered with consul server, but we want to do API call with /api/hello-service/
  • Redirect unmatched request to default path: We want to redirect all unmatched request to UI.
  • Redirecting HTTP to HTTPS on spring cloud gateway: We want to force all request coming to spring gateway to be https
  • Forwarding HTTPS request to HTTP serviceId registered with consul: Services registered with consul are on HTTP except for the API gateway, we want to be able to send HTTPS request to HTTP backend i.e. terminating HTTPS at API Gateway only.

Any help in solving the above issue would be good

Edit 1: After some help from @spencergibb, we had setup the spring cloud gateway with https. But There are some additional issues which we faced

  • If HTTPS is enabled on both API gateway and service both, we received below error

javax.net.ssl.SSLException: handshake timed out at io.netty.handler.ssl.SslHandler.handshake(...)(Unknown Source) ~[netty-handler-4.1.31.Final.jar:4.1.31.

  • If HTTPS is enabled on only API gateway, we received below error

    There was an unexpected error (type=Not Found, status=404). org.springframework.web.server.ResponseStatusException: 404 NOT_FOUND and received

    for path https://localhost:8443/api/hello-service/hello/message

    Unable to Connect

    for path http://localhost:8080/hello-service/hello/message

Please find the link for the sample applications

Instructions:

  • navigate to consul directory and Start consul server using command ./consul agent -dev
  • run api-gateway spring boot gradle project
  • run rest-demo spring boot gradle project

Edit 2

Thank You @spencergibb, We were able to successfully apply ssl on gateway and call the registered services on HTTP. Since Spring Webflux with Netty does not support listening on two ports, we created an additional tcp server bind to http port based on this answer.

There is still some issue we are facing with RewritePath for /api/ rule

  predicates:
    - name: Path
      args:
        pattern: "'/api/'+serviceId.toLowerCase()+'/**'"
  filters:
    - name: RewritePath
      args:
        regexp: "'/api/' + serviceId.toLowerCase() + '/(?<remaining>.*)'"
        replacement: "'/${remaining}'"

below is the complete trace for the request

DEBUG 13-02-2019 03:32:01 [FilteringWebHandler.java:86] VTC : Sorted gatewayFilterFactories: [OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@257505fd}, order=-2147482648}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter@400caab4}, order=-2147473648}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@36e2c50b}, order=-1}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@66f0c66d}, order=0}, OrderedGatewayFilter{delegate=org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory$$Lambda$360/1720581802@5821f2e6, order=0}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@27119239}, order=10000}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerClientFilter@568a9d8f}, order=10100}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@6ba77da3}, order=2147483646}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@73c24516}, order=2147483647}, OrderedGatewayFilter{delegate=GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@461a9938}, order=2147483647}] TRACE 13-02-2019 03:32:01 [RouteToRequestUrlFilter.java:59] VTC : RouteToRequestUrlFilter start TRACE 13-02-2019 03:32:02 [NettyWriteResponseFilter.java:68] VTC : NettyWriteResponseFilter start TRACE 13-02-2019 03:32:02 [GatewayMetricsFilter.java:101] VTC : Stopping timer 'gateway.requests' with tags [tag(outcome=CLIENT_ERROR),tag(routeId=rewrite_response_upper),tag(routeUri=http://httpbin.org:80),tag(status=NOT_FOUN

Nitishkumar Singh
  • 1,781
  • 1
  • 15
  • 33
  • I can try and help Monday – spencergibb Feb 10 '19 at 05:26
  • Thank you @spencergibb meanwhile, we will also try to solve some of those issues. – Nitishkumar Singh Feb 10 '19 at 05:29
  • Hi @spencergibb did you get any chance to look into it? – Nitishkumar Singh Feb 11 '19 at 13:52
  • You need to have a slash before the replacement in `RewritePath` `"/serviceId + '/${remaining}'"`. To match the UI create a route with path `/**` manually. Redirecting http to https, if there's not something in spring security (which there likely is) you'd need to write a filter. For https to http it should work like that automatically, what version are you using? – spencergibb Feb 12 '19 at 16:51
  • All the dependencies are associated with spring boot version `2.1.2•RELEASE` – Nitishkumar Singh Feb 12 '19 at 17:24
  • Hi @spencergibb, we tried the mentioned approach but it didn't work with http or https. We have also included git repo link above for the demo applications. – Nitishkumar Singh Feb 12 '19 at 20:09
  • So you can't use the shortcut for configuring the discovery locator https://gist.github.com/spencergibb/7e49b72e55bc29767dad1faf460cc056 – spencergibb Feb 12 '19 at 21:31
  • Removing http2 and httpclient.ssl settings from gateway config let me get all the way thru. Gist above has full settings – spencergibb Feb 12 '19 at 21:35
  • Thank you so much @spencergibb, but `RewritePath` is not working as expected, have included more information about it. – Nitishkumar Singh Feb 13 '19 at 03:48
  • @spencergibb - can you please look at https://stackoverflow.com/questions/54662179/consul-with-spring-cloud-gateway-inter-service-communication as well? NitishkumarSingh - sorry for hijacking your thread. – Mubin Feb 13 '19 at 09:17
  • @NitishkumarSingh I don't understand what isn't working, those logs don't tell me anything. I successfully made requests with your project. – spencergibb Feb 13 '19 at 15:59
  • @Mubin indeed you shouldn't ask about other questions. I monitor the tag. – spencergibb Feb 13 '19 at 15:59
  • @spencergibb: sorry about the confusion, it worked. Thank you so much. Can you please put it as answer, so I can accept the answer. – Nitishkumar Singh Feb 14 '19 at 17:53
  • will do that right now – spencergibb Feb 14 '19 at 21:02

1 Answers1

2

A number of things were needed

  1. disable http2
  2. Disable ssl configuration of httpclient
  3. Update locator predicates and filters to use verbose configuration.

Here is the resulting portions of application.yml

server:
  port: 8443
  http:
    port: 8080
  servlet:
  # enable HTTP2
#  http2:
#    enabled: true
  # enable compression

# ... removed for brevity

spring:
  application:
    name: api-gateway
  cloud:
    consul:
      enabled: true
    gateway:
#      httpclient:
#        ssl:
#          handshake-timeout-millis: 10000
#          close-notify-flush-timeout-millis: 3000
#          close-notify-read-timeout-millis: 0
#      routes:
      # - id: ui_path_route
      #   predicates:
      #   - Path="'/**'"
      #   filters:
      #   - RewritePath="'/**'", "/ui"
      discovery:
        instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
        locator:
          enabled: true
          predicates:
          - name: Path
            args:
              pattern: "'/api/' + serviceId + '/**'"
          filters:
          - name: RewritePath
            args:
              regexp: "'/api/' + serviceId + '/(?<remaining>.*)'"
              replacement: "'/${remaining}'"
#... removed for brevity
spencergibb
  • 24,471
  • 6
  • 69
  • 75