Spring-cloud-gateway: NoSuchMethodError using gateway 2.1.0.M1 with boot 2.1.0.M4

Created on 29 Sep 2018  路  15Comments  路  Source: spring-cloud/spring-cloud-gateway

Hi,

using Spring Cloud Gateway 2.1.0.M1 with gives me this errors:

log:

java.lang.NoSuchMethodError: reactor.netty.http.client.HttpClient.noChunkedTransfer()Lreactor/netty/http/client/HttpClient;
    at org.springframework.cloud.gateway.filter.NettyRoutingFilter.filter(NettyRoutingFilter.java:104) ~[spring-cloud-gateway-core-2.1.0.M1.jar:2.1.0.M1]
    at org.springframework.cloud.gateway.handler.FilteringWebHandler$GatewayFilterAdapter.filter(FilteringWebHandler.java:135) ~[spring-cloud-gateway-core-2.1.0.M1.jar:2.1.0.M1]
    at org.springframework.cloud.gateway.filter.OrderedGatewayFilter.filter(OrderedGatewayFilter.java:44) ~[spring-cloud-gateway-core-2.1.0.M1.jar:2.1.0.M1]
    at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:117) ~[spring-cloud-gateway-core-2.1.0.M1.jar:2.1.0.M1]
    at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) ~[reactor-core-3.2.0.RELEASE.jar:3.2.0.RELEASE]
    at reactor.core.publisher.MonoLift.subscribe(MonoLift.java:45) ~[reactor-core-3.2.0.RELEASE.jar:3.2.0.RELEASE]
...

http-client:
org.apache.http.NoHttpResponseException: localhost:8080 failed to respond

bug

Most helpful comment

The next release will be in a few weeks that will work against 2.1.0.RELEASE

All 15 comments

Can you share your dependencies. Looks like you have an old version of reactor Netty

build.gradle:

buildscript {
    ext {
        springBootVersion = '2.1.0.M4'
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

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

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 10

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


ext {
    springCloudVersion = 'Greenwich.M1'
}

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-actuator')
    implementation('org.springframework.cloud:spring-cloud-starter-config')
    implementation('org.springframework.cloud:spring-cloud-starter-gateway')
    implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
    implementation('org.springframework.cloud:spring-cloud-starter-sleuth')
    runtimeOnly('org.springframework.boot:spring-boot-devtools')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

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

bildschirmfoto von 2018-09-29 17-01-09

@octopus-prime i have run into the same issue and fixed it by override NettyRoutingFilter like this -

/*
 * Copyright 2013-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.springframework.cloud.gateway.filter;

import java.net.URI;
import java.util.List;

import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.NettyPipeline;
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.client.HttpClientResponse;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.config.HttpClientProperties;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.Type;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;

import static org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.filterRequest;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setAlreadyRouted;

/**
 * @author Spencer Gibb
 * @author Biju Kunjummen
 */
public class NettyRoutingFilter implements GlobalFilter, Ordered {

    private final HttpClient httpClient;
    private final ObjectProvider<List<HttpHeadersFilter>> headersFilters;
    private final HttpClientProperties properties;

    public NettyRoutingFilter(HttpClient httpClient,
                              ObjectProvider<List<HttpHeadersFilter>> headersFilters,
                              HttpClientProperties properties) {
        this.httpClient = httpClient;
        this.headersFilters = headersFilters;
        this.properties = properties;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

        String scheme = requestUrl.getScheme();
        if (isAlreadyRouted(exchange) || (!"http".equals(scheme) && !"https".equals(scheme))) {
            return chain.filter(exchange);
        }
        setAlreadyRouted(exchange);

        ServerHttpRequest request = exchange.getRequest();

        final HttpMethod method = HttpMethod.valueOf(request.getMethod().toString());
        final String url = requestUrl.toString();

        HttpHeaders filtered = filterRequest(this.headersFilters.getIfAvailable(),
                exchange);

        final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
        filtered.forEach(httpHeaders::set);

        String transferEncoding = request.getHeaders().getFirst(HttpHeaders.TRANSFER_ENCODING);
        boolean chunkedTransfer = "chunked".equalsIgnoreCase(transferEncoding);

        boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);

        HttpClient client = this.httpClient.chunkedTransfer(chunkedTransfer);

        Flux<HttpClientResponse> responseFlux = client
                .request(method)
                .uri(url)
                .send((req, nettyOutbound) -> {
                    req.headers(httpHeaders);

                    if (preserveHost) {
                        String host = request.getHeaders().getFirst(HttpHeaders.HOST);
                        req.header(HttpHeaders.HOST, host);
                    }
                    return nettyOutbound
                            .options(NettyPipeline.SendOptions::flushOnEach)
                            .send(request.getBody().map(dataBuffer ->
                                    ((NettyDataBuffer) dataBuffer).getNativeBuffer()));
                }).responseConnection((res, connection) -> {
                    ServerHttpResponse response = exchange.getResponse();
                    // put headers and status so filters can modify the response
                    HttpHeaders headers = new HttpHeaders();

                    res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue()));

                    if (headers.getContentType() != null) {
                        exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, headers.getContentType());
                    }

                    HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
                            this.headersFilters.getIfAvailable(), headers, exchange, Type.RESPONSE);

                    response.getHeaders().putAll(filteredResponseHeaders);
                    HttpStatus status = HttpStatus.resolve(res.status().code());
                    if (status != null) {
                        response.setStatusCode(status);
                    } else if (response instanceof AbstractServerHttpResponse) {
                        // https://jira.spring.io/browse/SPR-16748
                        ((AbstractServerHttpResponse) response).setStatusCodeValue(res.status().code());
                    } else {
                        throw new IllegalStateException("Unable to set status code on response: " + res.status().code() + ", " + response.getClass());
                    }

                    // Defer committing the response until all route filters have run
                    // Put client response as ServerWebExchange attribute and write response later NettyWriteResponseFilter
                    exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
                    exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);

                    return Mono.just(res);
                });

        if (properties.getResponseTimeout() != null) {
            //TODO: figure out how to make this a 504
            responseFlux = responseFlux.timeout(properties.getResponseTimeout(),
                    Mono.error(new TimeoutException("Response took longer than timeout: " +
                            properties.getResponseTimeout())));
        }

        return responseFlux.then(chain.filter(exchange));
    }
}

Very little change was needed -

        HttpClient client = this.httpClient.chunkedTransfer(chunkedTransfer);

This issue is appeared due to HttpClient interface has been changed at reactor-netty-0.8.0.RELAESE, which is used as dependency within Spring-Boot 2.1.0.M4.

Gateway 2.1.0.M1 was built against boot 2.1.0.M3

Facing the same issue with Spring boot 2.1.0.RELEASE. Its using gateway 2.1.0.M1.

The next release will be in a few weeks that will work against 2.1.0.RELEASE

Is it fixed yet ?

It is fixed, release towards the end of the week

Is there any workaround till then ?

Use Finchley and boot 2.0.x or use snapshots with boot 2.1.0

18.11.12 use spring-cloud Finchley.SR2 & boot 2.0.6.Release is OK. @sagar-patro

What if we are using Greenwich.M1?

@Alos

Gateway 2.1.0.M1 was built against boot 2.1.0.M3

That was Greenwich.M1.

To use boot 2.1.0 you need to use Greenwich snapshots until our next release (today or tomorrow).

Thanks!

Was this page helpful?
0 / 5 - 0 ratings