All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wl4g.infra.common.remoting.Netty4ClientHttpRequestFactory Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 ~ 2025 the original author or authors. James Wong 
 *
 * 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 com.wl4g.infra.common.remoting;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static com.wl4g.infra.common.lang.Assert2.*;
import static com.wl4g.infra.common.lang.TypeConverts.safeLongToInt;
import static java.lang.Runtime.getRuntime;

import java.io.Closeable;
import java.io.IOException;
import java.net.URI;

import javax.net.ssl.SSLException;

import javax.annotation.Nullable;
import com.wl4g.infra.common.remoting.standard.HttpHeaders;
import static com.wl4g.infra.common.remoting.standard.HttpMediaType.MULTIPART_FORM_DATA;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.SocketChannelConfig;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;

/**
 * {@link ClientHttpRequestFactory} implementation that uses
 * Netty 4 to create requests.
 * 

* Allows to use a pre-configured {@link EventLoopGroup} instance: useful for * sharing across multiple clients. *

* Note that this implementation consistently closes the HTTP connection on each * request. * * @author James Wong <[email protected]> * @version 2020年7月01日 v1.0.0 * @see */ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory, Closeable { private final boolean defaultEventLoopGroup; private final EventLoopGroup eventLoopGroup; @Nullable private SslContext sslContext; private boolean debug = false; private long connectTimeout; private long readTimeout; private int maxResponseSize; @Nullable private volatile Bootstrap bootstrap; /** * Create a new {@code Netty4ClientHttpRequestFactory} with a default * {@link NioEventLoopGroup}. */ public Netty4ClientHttpRequestFactory() { this(false, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, DEFAULT_MAX_RESPONSE_SIZE); } /** * Create a new {@code Netty4ClientHttpRequestFactory} with a default * {@link NioEventLoopGroup}. */ public Netty4ClientHttpRequestFactory(boolean debug) { // see:io.netty.channel.DefaultChannelConfig#DEFAULT_CONNECT_TIMEOUT this(debug, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, DEFAULT_MAX_RESPONSE_SIZE); } /** * Create a new {@code Netty4ClientHttpRequestFactory} with a default * {@link NioEventLoopGroup}. * * @param debug * @param connectTimeoutMs * @param readTimeoutMs * @param maxResponseSize */ public Netty4ClientHttpRequestFactory(boolean debug, int connectTimeoutMs, int readTimeoutMs, int maxResponseSize) { this(new NioEventLoopGroup(getRuntime().availableProcessors() * 2), debug); setConnectTimeout(connectTimeoutMs <= 0 ? DEFAULT_CONNECT_TIMEOUT : connectTimeoutMs); setReadTimeout(readTimeoutMs <= 0 ? DEFAULT_READ_TIMEOUT : readTimeoutMs); setMaxResponseSize(maxResponseSize <= 0 ? DEFAULT_MAX_RESPONSE_SIZE : maxResponseSize); } /** * Create a new {@code Netty4ClientHttpRequestFactory} with the given * {@link EventLoopGroup}. *

* NOTE: the given group will not be * {@linkplain EventLoopGroup#shutdownGracefully() shutdown} by this * factory; doing so becomes the responsibility of the caller. * * @param debug */ public Netty4ClientHttpRequestFactory(EventLoopGroup eventLoopGroup, boolean debug) { // notNull(eventLoopGroup, "EventLoopGroup must not be null"); this.eventLoopGroup = eventLoopGroup; this.defaultEventLoopGroup = isNull(eventLoopGroup); this.debug = debug; } /** * Set the default maximum response size. *

* By default this is set to {@link #DEFAULT_MAX_RESPONSE_SIZE}. * * @since 4.1.5 * @see HttpObjectAggregator#HttpObjectAggregator(int) */ public void setMaxResponseSize(int maxResponseSize) { this.maxResponseSize = maxResponseSize; } /** * Set the SSL context. When configured it is used to create and insert an * {@link io.netty.handler.ssl.SslHandler} in the channel pipeline. *

* A default client SslContext is configured if none has been provided. */ public void setSslContext(SslContext sslContext) { this.sslContext = sslContext; } /** * Set the underlying connect timeout (in milliseconds). A timeout value of * 0 specifies an infinite timeout. * * @see ChannelConfig#setConnectTimeoutMillis(int) */ public void setConnectTimeout(long connectTimeout) { this.connectTimeout = connectTimeout; } /** * Set the underlying URLConnection's read timeout (in milliseconds). A * timeout value of 0 specifies an infinite timeout. * * @see ReadTimeoutHandler */ public void setReadTimeout(long readTimeout) { this.readTimeout = readTimeout; } /** * Create nttp request of netty. * * @param uri * @param httpMethod * @param requestHeaders * @return * @throws IOException */ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, HttpHeaders requestHeaders) throws IOException { return new Netty4ClientHttpRequest(getBootstrap(uri, requestHeaders), uri, httpMethod); } /** * Template method for changing properties on the given * {@link SocketChannelConfig}. *

* The default implementation sets the connect timeout based on the set * property. * * @param config * the channel configuration */ protected void configureChannel(SocketChannelConfig config) { if (connectTimeout >= 0) { config.setConnectTimeoutMillis(safeLongToInt(connectTimeout)); } } private SslContext getSslContext() { if (sslContext == null) { sslContext = buildClientSslContext(); } return sslContext; } private SslContext buildClientSslContext() { try { return SslContextBuilder.forClient().build(); } catch (SSLException ex) { throw new IllegalStateException("Could not create default client SslContext", ex); } } private Bootstrap getBootstrap(URI uri, HttpHeaders requestHeaders) { boolean isSecure = (uri.getPort() == 443 || "https".equalsIgnoreCase(uri.getScheme())); // if (isSecure) { // return createBootstrap(uri, true, requestHeaders); // } else if (isNull(bootstrap)) { // this.bootstrap = createBootstrap(uri, false, requestHeaders); // } // return bootstrap; return createBootstrap(uri, isSecure, requestHeaders); } private Bootstrap createBootstrap(final URI uri, final boolean isSecure, final HttpHeaders requestHeaders) { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup) .channel(NioSocketChannel.class) .handler(new HttpChannelInitializer(uri, isSecure, requestHeaders)); return bootstrap; } @Override public void close() throws IOException { if (defaultEventLoopGroup) { // Clean up the EventLoopGroup if we created it in the constructor try { eventLoopGroup.shutdownGracefully().sync(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } } /** * {@link HttpChannelInitializer} */ private class HttpChannelInitializer extends ChannelInitializer { final private URI uri; final private boolean isSecure; final private HttpHeaders requestHeaders; HttpChannelInitializer(final URI uri, final boolean isSecure, final HttpHeaders requestHeaders) { this.uri = uri; this.isSecure = isSecure; this.requestHeaders = requestHeaders; } @Override protected void initChannel(SocketChannel ch) throws Exception { configureChannel(ch.config()); ChannelPipeline pipe = ch.pipeline(); if (debug) { pipe.addLast(new LoggingHandler(LogLevel.INFO)); } if (isSecure) { notNull(getSslContext(), "sslContext should not be null"); pipe.addLast(getSslContext().newHandler(ch.alloc(), uri.getHost(), uri.getPort())); } pipe.addLast(new HttpClientCodec()); if (nonNull(requestHeaders) && MULTIPART_FORM_DATA.isCompatibleWith(requestHeaders.getContentType())) { // Remove the following line if you don't want automatic // content decompression. pipe.addLast("inflater", new HttpContentDecompressor()); // to be used since huge file transfer pipe.addLast("chunkedWriter", new ChunkedWriteHandler()); } else { pipe.addLast(new HttpObjectAggregator(maxResponseSize)); } if (readTimeout > 0) { pipe.addLast(new ReadTimeoutHandler(safeLongToInt(readTimeout), MILLISECONDS)); } } } public static final int DEFAULT_MAX_RESPONSE_SIZE = 1024 * 1024 * 10; // see:io.netty.channel.DefaultChannelConfig#DEFAULT_CONNECT_TIMEOUT public static final int DEFAULT_CONNECT_TIMEOUT = 10_000; public static final int DEFAULT_READ_TIMEOUT = 30_000; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy