springboot +nginx 配置http2

发布时间 2023-06-05 18:06:53作者: rm-rf*

说明

  • nginx端使用http2+https,如果不使用https,浏览器会默认走http1.1
  • 后台使用http2,不使用https,因为内部服务之间没必要每次校验证书

nginx配置

# user  root;
worker_processes  auto;

error_log  D://nginx-log/error.log;
# error_log /dev/null;

#pid        logs/nginx.pid;

events {
    worker_connections  20000;
}

http {
    access_log      off;
    include         mime.types;
    default_type    application/octet-stream;

    # 部分优化
    sendfile        on;
    # 等数据包累积到一定大小才发送
    tcp_nopush      on;
    #尽快发送数据包,和上面的二者相互矛盾。实际上,它们确实可以一起用,最终的效果是先填满包,再尽快发送。
    tcp_nodelay     on;
    # 服务端为每个TCP连接最多可以保持多长时间,Nginx 的默认值是75秒,有些浏览器最多只保持60秒,所以我统一设置为 60
    keepalive_timeout  60;

    # 开启gzip
    gzip  on;
    # 允许压缩的最小字节数
    gzip_min_length 100;
    # IE浏览器1-6版本禁用gzip
    gzip_disable "msie6";
    # 启用gzip的文件类型
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;

    upstream gateway{
        server 127.0.0.1:43797;
    }

    # http server
    server {
       listen           80;
       listen           [::]:80;
       server_name      yalong.com;
       return           301 https://yalong.com$request_uri;
    }

    # HTTPS server
    server {
        listen                    443 ssl http2;
        listen                    [::]:443 ssl http2;
        server_name               yalong.com;
        ssl_certificate           C:/myssl/selfsigned.crt;
        ssl_certificate_key       C:/myssl/selfsigned.key;
        ssl_session_cache         shared:SSL:100m;
        ssl_session_timeout       1d;
        ssl_buffer_size           1400;
        ssl_protocols             TLSv1.1 TLSv1.2 TLSv1.3;
        ssl_ciphers               EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
        ssl_prefer_server_ciphers on;
        location ^~ / {
                       # 后台服务使用h2c,这样不用校验两次证书
                       proxy_pass http://gateway/;
                       proxy_set_header host $host;
                       proxy_set_header X-Real-IP      $remote_addr;
                       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                       add_header Kss-Upstream $upstream_addr;
                    }
        }

}

java代码配置

    <properties>
        <java.version>8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <!-- 只要4.x支持http2 -->
        <okhttp.version>4.10.0</okhttp.version>
    </properties>

    <dependencies>
<!--   feign也使用okhttp,因为项目中更改了resttemplate的默认客户端为okhttp,并设置了h2c -->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-openfeign</artifactId>-->
<!--        </dependency>-->

<!--        <dependency>-->
<!--            <groupId>io.github.openfeign</groupId>-->
<!--            <artifactId>feign-okhttp</artifactId>-->
<!--        </dependency>-->

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>${okhttp.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 用undertow替换tomcat,这样java8即可支持http2/h2c -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
   </dependencies>
server:
  port: 43797
  compression:
    enabled: true
    min-response-size: 1024
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
  http2:
    # 开启h2(HTTP/2 over TLS)
    enabled: true
  undertow:
    # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
    io-threads: 4
    # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
    worker-threads: 20
    # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
    # 每块buffer的空间大小,越小的空间被利用越充分
    buffer-size: 1024
    # 是否分配的直接内存
    direct-buffers: true
  ssl:
    # 关闭ssl,使用h2c(HTTP/2 over TCP),这样内部服务间调用就不需要使用https,使用http即可,并且是http2协议
    # https://docs.spring.io/spring-boot/docs/2.5.8/reference/htmlsingle/#howto-configure-http2-h2c
    enabled: false
#    key-store: classpath:keystore.p12
#    key-store-password: 123456
#    key-store-type: PKCS12
#    protocol: TLSv1.3


package com.example.demo.config;

import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.*;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;


/**
 * @author YaLong
 * @date 2023/6/2
 */
@Configuration
@Slf4j
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        ClientHttpRequestFactory factory = httpRequestFactory();
        return new RestTemplate(factory);
    }

    /**
     * 工厂
     */
    private ClientHttpRequestFactory httpRequestFactory() {
        return new OkHttp3ClientHttpRequestFactory(okHttpConfigClient());
    }

    private SSLSocketFactory getSslSocketFactory() {
        try {
            SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
            sslContext.init(null, trustAllCerts(), new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private TrustManager[] trustAllCerts() {
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
    }

    private  X509TrustManager getX509TrustManager() {
        X509TrustManager trustManager = null;
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
            }
            trustManager = (X509TrustManager) trustManagers[0];
        } catch (Exception e) {
            e.printStackTrace();
        }

        return trustManager;
    }

    /**
     * 客户端
     */
    private OkHttpClient okHttpConfigClient() {
        return new OkHttpClient().newBuilder()
                .connectionPool(pool())
                .connectTimeout(30, TimeUnit.SECONDS)
                .retryOnConnectionFailure(Boolean.TRUE)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                // 若要使用h2c,必须用H2_PRIOR_KNOWLEDGE
//                .protocols(Arrays.asList(Protocol.HTTP_1_1,Protocol.HTTP_2))
                .protocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE))
                .hostnameVerifier((hostname, session) -> true)
                .sslSocketFactory(getSslSocketFactory(), getX509TrustManager())
                .build();
    }

    /**
     * 连接池
     */
    public ConnectionPool pool() {
        return new ConnectionPool(200, 5, TimeUnit.MINUTES);
    }
}