| /* |
| * 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.coyote.http11.filters; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.zip.Deflater; |
| import java.util.zip.GZIPOutputStream; |
| |
| /** |
| * Extension of {@link GZIPOutputStream} to workaround for a couple of long |
| * standing JDK bugs |
| * (<a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4255743">Bug |
| * 4255743</a> and |
| * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4813885">Bug |
| * 4813885</a>) so the GZIP'd output can be flushed. |
| */ |
| public class FlushableGZIPOutputStream extends GZIPOutputStream { |
| public FlushableGZIPOutputStream(OutputStream os) throws IOException { |
| super(os); |
| } |
| |
| private static final byte[] EMPTYBYTEARRAY = new byte[0]; |
| private boolean hasData = false; |
| |
| /** |
| * Here we make sure we have received data, so that the header has been for |
| * sure written to the output stream already. |
| */ |
| @Override |
| public synchronized void write(byte[] bytes, int i, int i1) |
| throws IOException { |
| super.write(bytes, i, i1); |
| hasData = true; |
| } |
| |
| @Override |
| public synchronized void write(int i) throws IOException { |
| super.write(i); |
| hasData = true; |
| } |
| |
| @Override |
| public synchronized void write(byte[] bytes) throws IOException { |
| super.write(bytes); |
| hasData = true; |
| } |
| |
| @Override |
| public synchronized void flush() throws IOException { |
| if (!hasData) { |
| return; // do not allow the gzip header to be flushed on its own |
| } |
| |
| // trick the deflater to flush |
| /** |
| * Now this is tricky: We force the Deflater to flush its data by |
| * switching compression level. As yet, a perplexingly simple workaround |
| * for |
| * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html |
| */ |
| if (!def.finished()) { |
| def.setInput(EMPTYBYTEARRAY, 0, 0); |
| |
| def.setLevel(Deflater.NO_COMPRESSION); |
| deflate(); |
| |
| def.setLevel(Deflater.DEFAULT_COMPRESSION); |
| deflate(); |
| |
| out.flush(); |
| } |
| |
| hasData = false; // no more data to flush |
| } |
| |
| /* |
| * Keep on calling deflate until it runs dry. The default implementation |
| * only does it once and can therefore hold onto data when they need to be |
| * flushed out. |
| */ |
| @Override |
| protected void deflate() throws IOException { |
| int len; |
| do { |
| len = def.deflate(buf, 0, buf.length); |
| if (len > 0) { |
| out.write(buf, 0, len); |
| } |
| } while (len != 0); |
| } |
| |
| } |