写得比较匆忙,本文主要以整体微服务架构简述开头,按照问题排查过程叙述,加以springboot启动源码浅析和解决办法。

如有错误请指正

先说版本Spring boot 2.0.4。Spring cloud F版 jdk1.8

 

我们目前的微服务架构,当用户登录后会发放JWT的token令牌给前端,之后的请求都将此token放到http的header中传入后台。

但是前端调用时发现之前正常的请求报错400 bad request。查找资料说是请求头过长导致。

 

首先排查权限系统默认的tomcat的header请求,发现默认大小不够。配置:

server.tomcat.maxhttpheadersize==102400

未解决。

 

联想到我们微服务商是通过gateway网关转发的。准备去调整gateway的server.tomcat.maxhttpheadersize。但是gateway是由webflux开发的使用netty服务器的。没有这个配置项。

 

查看资料发现存在另外一个配置server.maxhttpheadersize同样可以设置header大小。

于是配置server.maxhttpheadersize==102400 启动,但是发现并不生效。

于是决定debug启动过程,发现ServerProperties中读取到了配置102400,但是不生效。

再深入 看netty启动过程。发现nettry并没有配置使用ServerProperties配置类。

 

分析启动过程,

springboot项目启动,调用
SpringApplication.run(Application.class, args);

内部调用构造器

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = deduceWebApplicationType();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这里通过deduceWebApplicationType();给webApplicaitonType赋值

	private WebApplicationType deduceWebApplicationType() {
		if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
				&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : WEB_ENVIRONMENT_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

这里根据存在的类判断是哪种模式,如果是

org.springframework.web.reactive.DispatcherHandler

并且不存在

org.glassfish.jersey.server.ResourceConfi
org.springframework.web.servlet.DispatcherServlet

返回

WebApplicationType.REACTIVE

再往下看到run方法内调用createApplicationContext

context = createApplicationContext();

 

	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

这里根据前面得到的REACTIVE服之context为

	public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

继承于ReactiveWebServerApplicationContext类。调用createWebServer方法给webServer赋值。

	private void createWebServer() {
		WebServer localServer = this.webServer;
		if (localServer == null) {
			this.webServer = getWebServerFactory().getWebServer(getHttpHandler());
		}
		initPropertySources();
	}

因为我们这里启用的netty,所以调用到NettyReactiveWebServerFactory工厂类,而此类中通过

private HttpServer createHttpServer() {
		return HttpServer.builder().options((options) -> {
			options.listenAddress(getListenAddress());
			if (getSsl() != null && getSsl().isEnabled()) {
				SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(
						getSsl(), getSslStoreProvider());
				sslServerCustomizer.customize(options);
			}
			if (getCompression() != null && getCompression().getEnabled()) {
				CompressionCustomizer compressionCustomizer = new CompressionCustomizer(
						getCompression());
				compressionCustomizer.customize(options);
			}
			applyCustomizers(options);
		}).build();
	}

生成httpServer。而在HttpServer总通过builder构建者的options.可以配置请求大小。

	public static final class Builder extends ServerOptions.Builder<Builder> {

		public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
		public static final int DEFAULT_MAX_HEADER_SIZE         = 8192;
		public static final int DEFAULT_MAX_CHUNK_SIZE          = 8192;
		public static final boolean DEFAULT_VALIDATE_HEADERS    = true;
		public static final int DEFAULT_INITIAL_BUFFER_SIZE     = 128;

		private BiPredicate<HttpServerRequest, HttpServerResponse> compressionPredicate;

		private int minCompressionResponseSize = -1;
		private int maxInitialLineLength = DEFAULT_MAX_INITIAL_LINE_LENGTH;
		private int maxHeaderSize = DEFAULT_MAX_HEADER_SIZE;
		private int maxChunkSize = DEFAULT_MAX_CHUNK_SIZE;
		private boolean validateHeaders = DEFAULT_VALIDATE_HEADERS;
		private int initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE;
		public final Builder maxHeaderSize(int value) {
			if (value <= 0) {
				throw new IllegalArgumentException("maxHeaderSize must be strictly positive");
			}
			this.maxHeaderSize = value;
			return get();
		}

这里HttpServer是final类和不变类,通过构建者声称的HttpServer无法被修改。

所以这里我们想要自己修改这个值,只能将整个NettyReactiveWebServerFactory工厂类在他自动注入前注入,然后运行到autoconfiguration时,就不会再此注入原有的NettyReactiveWebServerFactory类了。

这里放出重写的代码

    private HttpServer createHttpServer() {
        return HttpServer.builder().options((options) -> {
            options.maxHeaderSize(102400);
            options.listenAddress(getListenAddress());
            if (getSsl() != null && getSsl().isEnabled()) {
                SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(
                        getSsl(), getSslStoreProvider());
                sslServerCustomizer.customize(options);
            }
            if (getCompression() != null && getCompression().getEnabled()) {
                CompressionCustomizer compressionCustomizer = new CompressionCustomizer(
                        getCompression());
                compressionCustomizer.customize(options);
            }
            applyCustomizers(options);
        }).build();
    }
    @Bean
    @Order(-1)
    public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
        return new NettyReactiveWebServerFactory();
    }

启动调试,发现102400已经被配置到了netty中。

 

 

 

本地调试启动权限系统,网关,前端调用。过了。

到这里解决了spring cloud gateway中netty自定义header请求限制大小问题。

但是,部署到dev环境,请求,失败。。。

打印日志调试,发现权限系统和网关配置的都没问题。

而且报错请求根本没进入gateway。

这就又想到我们在k8s集群内,有个ingress在前面,又去配置ingress-controller的配置项。配置

client-header-buffer-size == 100k

重启pod。 调用,成功。

 

其实前面还有一层slb,4层负载,只不过4层负载不到http的应用层,所以没有配置大小;到ingress是http服务器,虽然协议没规定一定要限制大小;但是ingress的默认nginx配置了16k。所以不符合需求。这就导致了这次的问题。

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐