20. 对响应数据进行压缩原理
20. 对响应数据进行压缩原理
前言
为了节省带宽,在响应数据比较大的情况下,可以对响应数据进行压缩,返回给前端页面压缩数据。
一、配置
# 开启压缩
server.compression.enabled=true
server.compression.mime-types=application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
server.compression.min-response-size=2KB
二、原理
1.参数的注入
(1)从 AbstractApplicationContext 的 onRefresh()方法开始
@Override
protected void onRefresh() {
super.onRefresh();
try {
//创建web容器服务
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
(2)createWebServer( )
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
//这里创建ServletWebServerFactory,通过spring容器获取:getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class)
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
//获取WebServer
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
(3)WebServerFactoryCustomizerBeanPostProcessor
bean 的后置处理器的 postProcessBeforeInitialization 方法会在 bean 初始化过程中被调用
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
postProcessBeforeInitialization( )
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
获取 WebServerFactoryCustomizer
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
//根据类型获取WebServerFactoryCustomizer
return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
调用 ServletWebServerFactoryCustomizer 的 customize( )方法
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(this.serverProperties::getPort).to(factory::setPort);
map.from(this.serverProperties::getAddress).to(factory::setAddress);
map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
map.from(this.serverProperties::getSsl).to(factory::setSsl);
map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
//从serverProperties中获取Compression,并设置到ServletWebServerFactory中
map.from(this.serverProperties::getCompression).to(factory::setCompression);
map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
registrar.register(factory);
}
}
(4)Compression 类中的几个参数
public class Compression {
//开关
private boolean enabled = false;
private String[] mimeTypes = new String[] {
"text/html", "text/xml", "text/plain", "text/css", "text/javascript",
"application/javascript", "application/json", "application/xml" };
private String[] excludedUserAgents = null;
//默认最小2KB
private DataSize minResponseSize = DataSize.ofKilobytes(2);
}
此时压缩参数被设置到了 ServletWebServerFactory 中
2.CompressionHttpHandlerFactory 的创建
(5)获取 WebServer
this.webServer = factory.getWebServer(getSelfInitializer());
getWebServer( )
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Builder builder = this.delegate.createBuilder(this);
DeploymentManager manager = createManager(initializers);
return getUndertowWebServer(builder, manager, getPort());
}
getUndertowWebServer( )
protected UndertowServletWebServer getUndertowWebServer(Builder builder, DeploymentManager manager, int port) {
//创建HttpHandlerFactory
List<HttpHandlerFactory> httpHandlerFactories = this.delegate.createHttpHandlerFactories(this,
new DeploymentManagerHttpHandlerFactory(manager));
return new UndertowServletWebServer(builder, httpHandlerFactories, getContextPath(), port >= 0);
}
(6)createHttpHandlerFactories( )
List<HttpHandlerFactory> createHttpHandlerFactories(AbstractConfigurableWebServerFactory webServerFactory,
HttpHandlerFactory... initialHttpHandlerFactories) {
//这里从webServerFactory获取Compression
List<HttpHandlerFactory> factories = createHttpHandlerFactories(webServerFactory.getCompression(),
this.useForwardHeaders, webServerFactory.getServerHeader(), webServerFactory.getShutdown(),
initialHttpHandlerFactories);
if (isAccessLogEnabled()) {
factories.add(new AccessLogHttpHandlerFactory(this.accessLogDirectory, this.accessLogPattern,
this.accessLogPrefix, this.accessLogSuffix, this.accessLogRotate));
}
return factories;
}
static List<HttpHandlerFactory> createHttpHandlerFactories(Compression compression, boolean useForwardHeaders,
String serverHeader, Shutdown shutdown, HttpHandlerFactory... initialHttpHandlerFactories) {
List<HttpHandlerFactory> factories = new ArrayList<>(Arrays.asList(initialHttpHandlerFactories));
//compression不为null,并且开启了压缩,创建CompressionHttpHandlerFactory
if (compression != null && compression.getEnabled()) {
factories.add(new CompressionHttpHandlerFactory(compression));
}
if (useForwardHeaders) {
factories.add(Handlers::proxyPeerAddress);
}
if (StringUtils.hasText(serverHeader)) {
factories.add((next) -> Handlers.header(next, "Server", serverHeader));
}
if (shutdown == Shutdown.GRACEFUL) {
factories.add(Handlers::gracefulShutdown);
}
return factories;
}
(7)创建完 HttpHandlerFactory,构造 UndertowServletWebServer
new UndertowServletWebServer(builder, httpHandlerFactories, getContextPath(), port >= 0)
public UndertowServletWebServer(Builder builder, Iterable<HttpHandlerFactory> httpHandlerFactories,
String contextPath, boolean autoStart) {
super(builder, httpHandlerFactories, autoStart);
this.contextPath = contextPath;
this.manager = findManager(httpHandlerFactories);
}
3.压缩的预言
(8)UndertowWebServer.start( )
@Override
public void start() throws WebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
if (!this.autoStart) {
return;
}
if (this.undertow == null) {
//创建Undertow
this.undertow = createUndertowServer();
}
this.undertow.start();
this.started = true;
String message = getStartLogMessage();
logger.info(message);
}
catch (Exception ex) {
try {
PortInUseException.ifPortBindingException(ex, (bindException) -> {
List<Port> failedPorts = getConfiguredPorts();
failedPorts.removeAll(getActualPorts());
if (failedPorts.size() == 1) {
throw new PortInUseException(failedPorts.get(0).getNumber());
}
});
throw new WebServerException("Unable to start embedded Undertow", ex);
}
finally {
stopSilently();
}
}
}
}
createUndertowServer( )
private Undertow createUndertowServer() {
this.closeables = new ArrayList<>();
this.gracefulShutdown = null;
HttpHandler handler = createHttpHandler();
this.builder.setHandler(handler);
return this.builder.build();
}
createHttpHandler( )
@Override
protected HttpHandler createHttpHandler() {
HttpHandler handler = super.createHttpHandler();
if (StringUtils.hasLength(this.contextPath)) {
handler = Handlers.path().addPrefixPath(this.contextPath, handler);
}
return handler;
}
创建 HttpHandler 的执行链
protected HttpHandler createHttpHandler() {
HttpHandler handler = null;
for (HttpHandlerFactory factory : this.httpHandlerFactories) {
//获取HttpHandler
handler = factory.getHandler(handler);
if (handler instanceof Closeable) {
this.closeables.add((Closeable) handler);
}
if (handler instanceof GracefulShutdownHandler) {
Assert.isNull(this.gracefulShutdown, "Only a single GracefulShutdownHandler can be defined");
this.gracefulShutdown = (GracefulShutdownHandler) handler;
}
}
return handler;
}
getHandler( )
CompressionHttpHandlerFactory.java
@Override
public HttpHandler getHandler(HttpHandler next) {
if (!this.compression.getEnabled()) {
return next;
}
ContentEncodingRepository repository = new ContentEncodingRepository();
repository.addEncodingHandler("gzip", new GzipEncodingProvider(), 50,
Predicates.and(getCompressionPredicates(this.compression)));
return new EncodingHandler(repository).setNext(next);
}
getCompressionPredicates( )获取压缩的预言条件
private static Predicate[] getCompressionPredicates(Compression compression) {
List<Predicate> predicates = new ArrayList<>();
//字节长度的预言
predicates.add(new MaxSizePredicate((int) compression.getMinResponseSize().toBytes()));
//MimeType的预言
predicates.add(new CompressibleMimeTypePredicate(compression.getMimeTypes()));
if (compression.getExcludedUserAgents() != null) {
for (String agent : compression.getExcludedUserAgents()) {
RequestHeaderAttribute agentHeader = new RequestHeaderAttribute(new HttpString(HttpHeaders.USER_AGENT));
//UserAgent的预言
predicates.add(Predicates.not(Predicates.regex(agentHeader, agent)));
}
}
return predicates.toArray(new Predicate[0]);
}
(9)MaxSizePredicate
private static class MaxSizePredicate implements Predicate {
private final Predicate maxContentSize;
MaxSizePredicate(int size) {
this.maxContentSize = Predicates.requestLargerThan(size);
}
@Override
public boolean resolve(HttpServerExchange value) {
//响应头包含Content-Length
if (value.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) {
return this.maxContentSize.resolve(value);
}
return true;
}
}
maxContentSize.resolve( )
@Override
public boolean resolve(final HttpServerExchange exchange) {
//获取Content-Length 长度
final String length = exchange.getResponseHeaders().getFirst(Headers.CONTENT_LENGTH);
if (length == null) {
return false;
}
//校验是否大于给定的大小
return Long.parseLong(length) > size;
}
(10)CompressibleMimeTypePredicate
private static class CompressibleMimeTypePredicate implements Predicate {
//保存配置的mimeTypes
private final List<MimeType> mimeTypes;
CompressibleMimeTypePredicate(String[] mimeTypes) {
this.mimeTypes = new ArrayList<>(mimeTypes.length);
for (String mimeTypeString : mimeTypes) {
this.mimeTypes.add(MimeTypeUtils.parseMimeType(mimeTypeString));
}
}
@Override
public boolean resolve(HttpServerExchange value) {
//获取Content-Type
String contentType = value.getResponseHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (contentType != null) {
try {
MimeType parsed = MimeTypeUtils.parseMimeType(contentType);
for (MimeType mimeType : this.mimeTypes) {
//校验响应头中的Content-Type是否能匹配上配置的mimeTypes
if (mimeType.isCompatibleWith(parsed)) {
return true;
}
}
}
catch (InvalidMimeTypeException ex) {
return false;
}
}
return false;
}
}
(11)RegularExpressionPredicate
@Override
public boolean resolve(final HttpServerExchange value) {
//获取User-Agent
String input = matchAttribute.readAttribute(value);
if(input == null) {
return false;
}
Matcher matcher = pattern.matcher(input);
final boolean matches;
//正则匹配
if (requireFullMatch) {
matches = matcher.matches();
} else {
matches = matcher.find();
}
if (traceEnabled) {
UndertowLogger.PREDICATE_LOGGER.tracef("Regex pattern [%s] %s input [%s] for %s.", pattern.toString(), (matches ? "MATCHES" : "DOES NOT MATCH" ), input, value);
}
if (matches) {
Map<String, Object> context = value.getAttachment(PREDICATE_CONTEXT);
if(context == null) {
value.putAttachment(PREDICATE_CONTEXT, context = new TreeMap<>());
}
int count = matcher.groupCount();
for (int i = 0; i <= count; ++i) {
if (traceEnabled) {
UndertowLogger.PREDICATE_LOGGER.tracef("Storing regex match group [%s] as [%s] for %s.", i, matcher.group(i), value);
}
context.put(Integer.toString(i), matcher.group(i));
}
}
return matches;
}
总结
使用压缩功能的条件:
1、 开启压缩 enabled=true;
2、 MaxSizePredicate,校验 Content-Length;
3、 CompressibleMimeTypePredicate,校验 Content-Type;
4、 RegularExpressionPredicate,校验 User-Agent;