在球衣拦截器中缩小 html 和 js

minify html and js in jersey Interceptor

使用了 jersey mvc 和 jsp,所有对 html 或 js 文件的请求都是通过@Template 或 Viewable 完成的。 示例;

  @GET
  @Path(JS_URL + "{type}")
  @Template(name = "grid")
  @Produces("application/javascript")
  public Response buildJSGrid(@DefaultValue("") @PathParam("type") String type) {
     Grid grid = new Grid(type);
....
     return Response.ok(grid).build();
  }

其中网格是 grid.jsp 内含纯 javascript 的文件

<%@ page contentType="application/javascript;charset=UTF-8" language="java" %>

.......

也可能使用 html 和 js 的其他变体,例如;

@GET
  @Path(FORM_URL + "{type}")
  @Template(name = "form")
  @Produces(MediaType.TEXT_HTML)
  public Response buildAccountForm(@DefaultValue("") @PathParam("type") String type) {
     Form form = new Form(type);
....
     return Response.ok(form).build();
  }

其中表单为 form.jsp,其中 html 和 js 位于 <script>..</script>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

...

我需要在发送给客户端之前缩小结果 js 和 html/js,我尝试使用 https://code.google.com/archive/p/htmlcompressor/ 库,但是需要将 String 传递给 htmlCompressor.compress(input);

尝试使用 WriterInterceptor

public class MinifyJsInterceptor implements WriterInterceptor {
  @Override
  public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
// here need to convert outputStream to InputStream and after to String ?
// result string to htmlCompressor.compress(resultString);
// after that convert result minify string back to resultOutputStream and set to context ?
context.setOutputStream(new GZIPOutputStream(resultOutputStream));

这是正确的方法吗?而且我无法将该输出流转换为字符串 谢谢

--更新

回答问题; html + js 表示在一些jsp 是html 标记和js 代码

    <div id="form" style="width: 500px; display: none">
      <div class="w2ui-page page-0">
        <div class="w2ui-field">
    </div>....

    <script type="text/javascript">
      var uiElement = (function () {
        var config = {
            onOpen: function (event) {
      event.onComplete = function () {
        $('#formContainer').w2render('form');
      }
     ...
    }());
    </script>

在客户端上

请求的文件
         $('#tempContainer').load('that file name - also dynamic', function (data, status, xhr) {
           uiElement.init();
           w2ui[layout].content(layout_main, w2ui[uiElement.name]);
         });

你真的 return 资源方法中的 js 文件吗?

        some js and html + js files are dynamic build, example;
        grid.jsp contains inside

<%@ page contentType="application/javascript;charset=UTF-8" language="java" %>

          var uiElement = (function () {
            var config = {
              grid: {
                name: ${it.name},
                listUrl:'${it.entityListUrl}',
                formUrl:'${it.entityFormUrl}',
                columns: ${it.columns},
                records: ${it.records},           
}}

资源方法中的 el 表达式和设置中有 ${it..} 值

@GET
      @Path(JS_URL + "{type}")
      @Template(name = "grid")
      @Produces("application/javascript")
      public Response buildJSGrid(@DefaultValue("") @PathParam("type") String type) {
         Grid grid = new Grid(type);
    ....
         return Response.ok(grid).build();
      }}

以及来自客户端的 js 'file' 被

调用
         $.getScript('dynamic js file name' - it is dynamic too).done(function (script, status, xhr) {
           //console.log(xhr.responseText);
           uiElement.init();
           w2ui[layout].content(layout_main, w2ui[uiElement.name]);
         });



       also some html blocks build dynamic


{ 
     <c:if test="${it.recid != 0}">
          <div class="w2ui-field">
            <label>active:</label>
            <div>
              <input name="active" type="checkbox"/>
            </div>
          </div>
            </c:if>
}

--更新描述, 网格生成器;

用于构建任何网格的一个资源和一个模板,

  @GET
  @Path(GRID + "{type}")
  @Template(name = W2UI_VIEW_PREFIX + "grid/grid")
  @Produces(MEDIA_TYPE_APPLICATION_JAVASCRIPT)
  public Response buildGrid(@DefaultValue("") @PathParam("type") String type) {
    for (W2UI ui : W2UI.values()) {
      if (type.equals(ui.getName())) {
        W2UIElement grid = ui.getUI();
        return Response.ok(grid).build();
      }
    }
    return Response.noContent().build();
  }

也可能通过 Viewable(模板,模型)使用不同的模板(jsp 文件)

menu.jsp 模板菜单生成器中的某处

List<MenuItem> items..
MenuItem item1 = new MenuItem(W2UI.TASK_GRID, W2UIService.GRID);
items.add(item1);

其中

W2UIService.GRID is string url for client js request and for server method resource @Path() anno.

public enum W2UI {
  TASK_GRID("task_grid", "tasks", Type.SCRIPT){
    @Override
    public W2UIElement getUI() {
      return new TaskGrid(getName());
    }
  },
.....
}

TaskGrid 是用 js 代码为 grid.jsp 模板填充的模型,因此可以轻松添加具有不同数据集和按钮的任何类型的网格。

组件类型(Type.SCRIPT)由 $.getScript() 在客户端处理,Type.HTML 由 $('#tempContainer').load()

---更新工厂和供应商;

@Provider
@Priority(200)
@HtmlMinify
public class HtmlMinifyInterceptor implements WriterInterceptor {
  @Inject private HtmlCompressor compressor;

...

public class HtmlMinifierFactory implements Factory<HtmlCompressor> {
  private HtmlCompressor compressor;

  @Override
  public HtmlCompressor provide() {
    if (null == compressor) compressor = new HtmlCompressor();
    ClosureJavaScriptCompressor jsCompressor = new ClosureJavaScriptCompressor();
    jsCompressor.setCompilationLevel(CompilationLevel.SIMPLE_OPTIMIZATIONS);

..

@ApplicationPath("/")
public class MainRsConfig extends ResourceConfig {
  public MainRsConfig() {
..
    register(new AbstractBinder() {
      @Override
      protected void configure() {
        bindFactory(HtmlMinifierFactory.class).to(HtmlCompressor.class).in(Singleton.class);
      }
    });
..

您可以使用 ByteArrayOutputStream 的自定义实现作为 WriterInterceptorContextOutputStream 的包装:

import com.googlecode.htmlcompressor.compressor.Compressor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class HtmlMinifyOutputStream extends ByteArrayOutputStream {

    private OutputStream origOut;
    private Compressor compressor;

    public HtmlMinifyOutputStream(OutputStream origOut, Compressor compressor) {
        this.origOut = origOut;
        this.compressor = compressor;
    }

    public void close() throws IOException {
        super.close();
        String compressedBody = compressor.compress(new String(this.buf));
        this.origOut.write(compressedBody.getBytes());
        this.origOut.close();
    }
}

HtmlMinifyOutputStream 可用于 WriterInterceptor 实现。 HtmlCompressor 实例被注入:

import com.googlecode.htmlcompressor.compressor.Compressor;
import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import java.io.*;

@Provider
@HtmlMinify
public class MinifyHtmlInterceptor implements WriterInterceptor {

    @Inject
    private Compressor compressor;

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new HtmlMinifyOutputStream(outputStream, compressor));
        context.proceed();
    }
}

@HtmlMinify 是一个 NameBinding annotation, used to activate the MinifyHtmlInterceptor on specific resource methods. (see https://jersey.java.net/documentation/latest/filters-and-interceptors.html#d0e9988):

import javax.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@NameBinding
@Retention(value = RetentionPolicy.RUNTIME)
public @interface HtmlMinify {}

HtmlCompressor 每个应用程序只能创建一次并同时使用,因为:

HtmlCompressor and XmlCompressor classes are considered thread safe* and can be used in multi-thread environment
(https://code.google.com/archive/p/htmlcompressor/)

这是一个 HK2 factory (see: Implementing Custom Injection Provider),它创建压缩器实例并启用内联 css 和 javascript 压缩:

import com.googlecode.htmlcompressor.compressor.Compressor;
import com.googlecode.htmlcompressor.compressor.HtmlCompressor;
import org.glassfish.hk2.api.Factory;

public class HtmlCompressorFactory implements Factory<Compressor> {

    private HtmlCompressor compressor;

    @Override
    public Compressor provide() {
        if(compressor == null) {
            compressor = new HtmlCompressor();
        }
        compressor.setCompressJavaScript(true);
        compressor.setCompressCss(true);
        return compressor;
    }

    @Override
    public void dispose(Compressor compressor) {}
}

工厂是registered with an AbstractBinder:

final ResourceConfig rc = new ResourceConfig().packages("com.example");
rc.register(new AbstractBinder() {
    @Override
    protected void configure() {
        bindFactory(HtmlCompressorFactory.class).to(Compressor.class).in(Singleton.class);
    }
});

如果启用内联 javascript 或内联 css 压缩:

HTML compressor with default settings doesn't require any dependencies.
Inline CSS compression requires YUI compressor library.
Inline JavaScript compression requires either YUI compressor library (by default) or Google Closure Compiler library.
(https://code.google.com/archive/p/htmlcompressor/)

我使用 maven,所以我将此依赖项添加到我的 pom.xml:

<dependency>
    <groupId>com.yahoo.platform.yui</groupId>
    <artifactId>yuicompressor</artifactId>
    <version>2.4.8</version>
</dependency>

如果你想使用 Google Closure Compiler 使用这个依赖:

<dependency>
    <groupId>com.google.javascript</groupId>
    <artifactId>closure-compiler</artifactId>
    <version>r2388</version>
</dependency>

并激活它:

compressor.setJavaScriptCompressor(new ClosureJavaScriptCompressor());
compressor.setCompressJavaScript(true);
compressor.setCssCompressor(new YuiCssCompressor());
compressor.setCompressCss(true);
return compressor;

如果要压缩纯 JavaScript 或 CSS 文件,则不能使用 htmlcompressor。此库仅支持 HTML 具有内联 CSS/JS 的文件。但是你可以实现一个 MinifyJsInterceptorMinifyCssInterceptor 模拟 MinifyHtmlInterceptor,它直接使用 YUI-Compressor and/or Google Closure 库。

对于 gzip 压缩,您应该实施另一个拦截器。因此可以单独配置缩小和压缩。如果激活多个拦截器,使用javax.annotation.Priority来控制执行顺序。 (参见:https://jersey.java.net/documentation/latest/filters-and-interceptors.html#d0e9927