From b71eb61be4521be4bcb9475eba6e309c3ee2a454 Mon Sep 17 00:00:00 2001 From: Per Wendel Date: Thu, 14 May 2015 14:55:37 +0200 Subject: [PATCH] Refactoring of #237 and #240 --- src/main/java/spark/utils/GzipUtils.java | 70 +++++++++++++++++++ .../java/spark/webserver/BytesSerializer.java | 7 +- .../spark/webserver/DefaultSerializer.java | 6 +- .../webserver/InputStreamSerializer.java | 7 +- .../java/spark/webserver/MatcherFilter.java | 13 +++- src/main/java/spark/webserver/Serializer.java | 29 +++++++- .../java/spark/webserver/SerializerChain.java | 13 ++++ 7 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 src/main/java/spark/utils/GzipUtils.java diff --git a/src/main/java/spark/utils/GzipUtils.java b/src/main/java/spark/utils/GzipUtils.java new file mode 100644 index 0000000000..a601b97b4c --- /dev/null +++ b/src/main/java/spark/utils/GzipUtils.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 - Per Wendel + * + * Licensed 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 spark.utils; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.zip.GZIPOutputStream; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * GZIP utility class. + * + * @author Edward Raff + * @author Per Wendel + */ +public class GzipUtils { + + private static final String ACCEPT_ENCODING = "Accept-Encoding"; + private static final String CONTENT_ENCODING = "Content-Encoding"; + + private static final String GZIP = "gzip"; + + // Hide constructor + private GzipUtils() { + + } + + /** + * Checks if the HTTP request/response accepts and wants GZIP and i that case wraps the response output stream in a + * {@link java.util.zip.GZIPOutputStream}. + * + * @param httpRequest the HTTP servlet request. + * @param httpResponse the HTTP servlet response. + * @return if accepted and wanted a {@link java.util.zip.GZIPOutputStream} otherwise the unchanged response + * output stream. + * @throws IOException in case of IO error. + */ + public static OutputStream checkAndWrap(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws + IOException { + OutputStream outputStream = httpResponse.getOutputStream(); + + // GZIP Support handled here. First we must ensure that we want to use gzip, and that the client supports gzip + boolean acceptsGzip = Collections.list(httpRequest.getHeaders(ACCEPT_ENCODING)).stream().anyMatch(s -> s.contains(GZIP)); + boolean wantGzip = httpResponse.getHeaders(CONTENT_ENCODING).contains(GZIP); + + if (acceptsGzip && wantGzip) { + outputStream = new GZIPOutputStream(outputStream, true); + } + + return outputStream; + } + +} diff --git a/src/main/java/spark/webserver/BytesSerializer.java b/src/main/java/spark/webserver/BytesSerializer.java index 56f04d10a2..da12d8de2c 100644 --- a/src/main/java/spark/webserver/BytesSerializer.java +++ b/src/main/java/spark/webserver/BytesSerializer.java @@ -20,10 +20,15 @@ import java.io.OutputStream; import java.nio.ByteBuffer; +/** + * Bytes serializer. + * + * @author alex + */ public class BytesSerializer extends Serializer { @Override - public boolean canHandle(Object element) { + public boolean canProcess(Object element) { return element instanceof byte[] || element instanceof ByteBuffer; } diff --git a/src/main/java/spark/webserver/DefaultSerializer.java b/src/main/java/spark/webserver/DefaultSerializer.java index d003652e69..ec6b81f6b9 100644 --- a/src/main/java/spark/webserver/DefaultSerializer.java +++ b/src/main/java/spark/webserver/DefaultSerializer.java @@ -21,14 +21,14 @@ import java.io.UnsupportedEncodingException; /** - * Serilizer that writes the result of toString to output in UTF-8 encoding + * Serializer that writes the result of toString to output in UTF-8 encoding * - * @author alsoto + * @author alex */ public class DefaultSerializer extends Serializer { @Override - public boolean canHandle(Object element) { + public boolean canProcess(Object element) { return true; } diff --git a/src/main/java/spark/webserver/InputStreamSerializer.java b/src/main/java/spark/webserver/InputStreamSerializer.java index 268616acb8..0517f2e4d5 100644 --- a/src/main/java/spark/webserver/InputStreamSerializer.java +++ b/src/main/java/spark/webserver/InputStreamSerializer.java @@ -22,10 +22,15 @@ import spark.utils.IOUtils; +/** + * Input stream serializer. + * + * @author alex + */ public class InputStreamSerializer extends Serializer { @Override - public boolean canHandle(Object element) { + public boolean canProcess(Object element) { return element instanceof InputStream; } diff --git a/src/main/java/spark/webserver/MatcherFilter.java b/src/main/java/spark/webserver/MatcherFilter.java index 743321f858..b7df548a1e 100755 --- a/src/main/java/spark/webserver/MatcherFilter.java +++ b/src/main/java/spark/webserver/MatcherFilter.java @@ -17,6 +17,7 @@ package spark.webserver; import java.io.IOException; +import java.io.OutputStream; import java.util.List; import javax.servlet.Filter; @@ -40,6 +41,7 @@ import spark.route.HttpMethod; import spark.route.RouteMatch; import spark.route.SimpleRouteMatcher; +import spark.utils.GzipUtils; /** * Filter for matching of filters and routes. @@ -240,7 +242,14 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo if (httpResponse.getContentType() == null) { httpResponse.setContentType("text/html; charset=utf-8"); } - serializerChain.process(httpResponse.getOutputStream(), bodyContent); + + // Check if gzip is wanted/accepted and in that case handle that + OutputStream outputStream = GzipUtils.checkAndWrap(httpRequest, httpResponse); + + // serialize the body to output stream + serializerChain.process(outputStream, bodyContent); + + outputStream.flush();//needed for GZIP stream. NOt sure where the HTTP response actually gets cleaned up } } else if (chain != null) { chain.doFilter(httpRequest, httpResponse); @@ -253,4 +262,4 @@ public void destroy() { private static final String NOT_FOUND = "

404 Not found

"; private static final String INTERNAL_ERROR = "

500 Internal Error

"; -} +} \ No newline at end of file diff --git a/src/main/java/spark/webserver/Serializer.java b/src/main/java/spark/webserver/Serializer.java index eddffa0aee..a6adf47696 100644 --- a/src/main/java/spark/webserver/Serializer.java +++ b/src/main/java/spark/webserver/Serializer.java @@ -28,12 +28,24 @@ public abstract class Serializer { private Serializer next; + /** + * Sets the next serializer in the chain. + * + * @param serializer the next serializer. + */ public void setNext(Serializer serializer) { this.next = serializer; } + /** + * Wraps {@link spark.webserver.Serializer#process(java.io.OutputStream, Object)} and calls next serializer in chain. + * + * @param outputStream the output stream. + * @param element the element to process. + * @throws IOException IOException in case of IO error. + */ public void processElement(OutputStream outputStream, Object element) throws IOException { - if (canHandle(element)) { + if (canProcess(element)) { process(outputStream, element); } else { if (next != null) { @@ -42,7 +54,20 @@ public void processElement(OutputStream outputStream, Object element) throws IOE } } - public abstract boolean canHandle(Object element); + /** + * Checks if the serializer implementation can process the element type. + * + * @param element the element to process. + * @return true if the serializer can process the provided element. + */ + public abstract boolean canProcess(Object element); + /** + * Processes the provided element and serializes to output stream. + * + * @param outputStream the output stream. + * @param element the element. + * @throws IOException In the case of IO error. + */ public abstract void process(OutputStream outputStream, Object element) throws IOException; } diff --git a/src/main/java/spark/webserver/SerializerChain.java b/src/main/java/spark/webserver/SerializerChain.java index 7aa878116f..31bbe8a4e6 100644 --- a/src/main/java/spark/webserver/SerializerChain.java +++ b/src/main/java/spark/webserver/SerializerChain.java @@ -19,10 +19,16 @@ import java.io.IOException; import java.io.OutputStream; +/** + * Chain of serializers for the output. + */ public class SerializerChain { private Serializer root; + /** + * Constructs a serializer chain. + */ public SerializerChain() { DefaultSerializer defaultSerializer = new DefaultSerializer(); @@ -36,6 +42,13 @@ public SerializerChain() { this.root = bytesSerializer; } + /** + * Process the output. + * + * @param outputStream the output stream to write to. + * @param element the element to serialize. + * @throws IOException in the case of IO error. + */ public void process(OutputStream outputStream, Object element) throws IOException { this.root.processElement(outputStream, element); }