blob: 17d86813bbd98a2a2129b7bab83223513da0b647 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.sling.sample.filter;
import static org.apache.sling.api.SlingConstants.ATTR_REQUEST_CONTENT;
import static org.apache.sling.api.wrappers.SlingRequestPaths.INCLUDE_REQUEST_URI;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Map;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper;
import org.apache.sling.engine.RequestUtil;
/**
* The <code>ZipFilter</code> is an optionally installable global filter,
* which must be first in the filter chain for the compression functionality to
* be effective.
* <p>
* <b>NOTES</b>
* <p>
* When considering this filter, please keep the following notes in mind:
* <ul>
* <li>This filter serves as a model for implementing filters.
* <li>If using this filter it must be registered with a
* <code>filter.scope</code> parameter of <i><code>request</code></i> and
* a <code>filter.order</code> value which is smaller than any other
* <code>request</code> scope filter.
* <li>With small response data and fast networks this filter is not effective
* and in fact even slows response time down !!
* </ul>
*
*/
@Component(enabled=false)
@Service
@Properties({
@Property(name="service.description", value="Sample Request Filter"),
@Property(name="filter.scope", value="request", propertyPrivate=true),
@Property(name="filter.order", intValue=-2147483648, propertyPrivate=true)
})
public class ZipFilter implements Filter {
/**
* Does nothing. This filter has not initialization requirement.
*/
public void init(FilterConfig filterConfig) {
}
/**
* Checks the <code>Accept-Encoding</code> request header for its values.
* If <i>gzip</i> or <i>x-gzip</i> is desired, the response stream is
* compressed using the GZip algorithm. If <i>deflate</i> is desired, the
* response stream is compressed using the standard Zip Algorithm. Otherwise
* the response stream is transferred unmodified (<i>identity</i>
* encoding).
*/
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
// request is Servlet API or Sling API included, do not filter
if (req.getAttribute(INCLUDE_REQUEST_URI) != null
|| req.getAttribute(ATTR_REQUEST_CONTENT) != null) {
chain.doFilter(req, res);
return;
}
SlingHttpServletRequest request = (SlingHttpServletRequest) req;
SlingHttpServletResponse response = (SlingHttpServletResponse) res;
// check for compress header
String enc = request.getHeader("Accept-Encoding");
if (enc == null || enc.length() == 0) {
// assume identity, no wrapping
chain.doFilter(request, response);
return;
}
// get settings
enc = this.getEncoding(enc);
if ("gzip".equals(enc) || "x-gzip".equals(enc)) {
// mark the response encoded
response.setHeader("Content-Encoding", enc);
response = new DeflaterComponentResponse(response, false);
} else if ("deflate".equals(enc)) {
// mark the response encoded
response.setHeader("Content-Encoding", enc);
response = new DeflaterComponentResponse(response, true);
}
// continue filtering
chain.doFilter(request, response);
// finish the output
if (response instanceof DeflaterComponentResponse) {
((DeflaterComponentResponse) response).finish();
}
}
/**
* Does nothing. This filter has not destroyal requirement.
*/
public void destroy() {
}
// ---------- internal -----------------------------------------------------
/**
* Parses the <code>Accept-Encoding</code> header and returns the token
* with the highest <i>q</i> value or <code>null</code> if the header is
* empty.
*/
String getEncoding(String encodingHeader) {
String select = null;
double q = Double.MIN_VALUE;
Map<String, Double> settings = RequestUtil.parserAcceptHeader(encodingHeader);
for (Iterator<Map.Entry<String, Double>> ti = settings.entrySet().iterator(); ti.hasNext();) {
Map.Entry<String, Double> token = ti.next();
Double qv = token.getValue();
if (qv.doubleValue() > q) {
q = qv.doubleValue();
select = token.getKey();
}
}
return select;
}
// ---------- internal classes ---------------------------------------------
/**
* Compressing <code>ComponentResponseWrapper</code> which compresses the
* output stream using GZip or Zip algorithm depending on the constructor
* flag <code>deflate</code>.
* <p>
* This wrapper is only
*/
private static class DeflaterComponentResponse extends
SlingHttpServletResponseWrapper {
private boolean deflate;
private DeflaterServletOutputStream stream;
private PrintWriter writer;
public DeflaterComponentResponse(SlingHttpServletResponse delegatee,
boolean deflate) {
super(delegatee);
this.deflate = deflate;
}
public ServletOutputStream getOutputStream() throws IOException {
if (this.stream != null) {
return this.stream;
} else if (this.writer != null) {
throw new IllegalStateException(
"Writer has already been obtained");
}
this.stream = new DeflaterServletOutputStream(
this.getDeflaterOutputStream());
return this.stream;
}
public PrintWriter getWriter() throws IOException {
if (this.writer != null) {
return this.writer;
} else if (this.stream != null) {
throw new IllegalStateException(
"OutputStream has already been obtained");
}
DeflaterOutputStream base = this.getDeflaterOutputStream();
String enc = this.getCharacterEncoding();
this.writer = new PrintWriter(new OutputStreamWriter(base, enc));
return this.writer;
}
public void flushBuffer() throws IOException {
if (this.writer != null) {
this.writer.flush();
} else if (this.stream != null) {
this.stream.flush();
}
super.flushBuffer();
}
void finish() throws IOException {
if (this.writer != null) {
this.writer.flush();
} else if (this.stream != null) {
this.stream.getDeflaterStream().finish();
}
}
protected DeflaterOutputStream getDeflaterOutputStream()
throws IOException {
OutputStream base = super.getOutputStream();
return this.deflate
? new DeflaterOutputStream(base)
: new GZIPOutputStream(base);
}
private static class DeflaterServletOutputStream extends
ServletOutputStream {
private DeflaterOutputStream deflaterStream;
DeflaterServletOutputStream(DeflaterOutputStream deflaterStream) {
this.deflaterStream = deflaterStream;
}
DeflaterOutputStream getDeflaterStream() {
return this.deflaterStream;
}
public void write(int b) throws IOException {
this.deflaterStream.write(b);
}
public void write(byte[] b) throws IOException {
this.deflaterStream.write(b);
}
public void write(byte[] b, int off, int len) throws IOException {
this.deflaterStream.write(b, off, len);
}
public void flush() throws IOException {
this.deflaterStream.flush();
}
public void close() throws IOException {
this.deflaterStream.close();
}
}
}
}