001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.resource;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.InputStream;
022import java.net.URI;
023import java.net.URL;
024import java.util.List;
025import javax.servlet.http.HttpServletRequest;
026
027import org.springframework.core.io.AbstractResource;
028import org.springframework.core.io.Resource;
029
030/**
031 * A {@code ResourceResolver} that delegates to the chain to locate a resource
032 * and then attempts to find a variation with the ".gz" extension.
033 *
034 * <p>The resolver gets involved only if the "Accept-Encoding" request header
035 * contains the value "gzip" indicating the client accepts gzipped responses.
036 *
037 * @author Jeremy Grelle
038 * @author Rossen Stoyanchev
039 * @author Sam Brannen
040 * @since 4.1
041 */
042public class GzipResourceResolver extends AbstractResourceResolver {
043
044        @Override
045        protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath,
046                        List<? extends Resource> locations, ResourceResolverChain chain) {
047
048                Resource resource = chain.resolveResource(request, requestPath, locations);
049                if (resource == null || (request != null && !isGzipAccepted(request))) {
050                        return resource;
051                }
052
053                try {
054                        Resource gzipped = new GzippedResource(resource);
055                        if (gzipped.exists()) {
056                                return gzipped;
057                        }
058                }
059                catch (IOException ex) {
060                        logger.trace("No gzip resource for [" + resource.getFilename() + "]", ex);
061                }
062
063                return resource;
064        }
065
066        private boolean isGzipAccepted(HttpServletRequest request) {
067                String value = request.getHeader("Accept-Encoding");
068                return (value != null && value.toLowerCase().contains("gzip"));
069        }
070
071        @Override
072        protected String resolveUrlPathInternal(String resourceUrlPath,
073                        List<? extends Resource> locations, ResourceResolverChain chain) {
074
075                return chain.resolveUrlPath(resourceUrlPath, locations);
076        }
077
078
079        private static final class GzippedResource extends AbstractResource implements EncodedResource {
080
081                private final Resource original;
082
083                private final Resource gzipped;
084
085                public GzippedResource(Resource original) throws IOException {
086                        this.original = original;
087                        this.gzipped = original.createRelative(original.getFilename() + ".gz");
088                }
089
090                @Override
091                public InputStream getInputStream() throws IOException {
092                        return this.gzipped.getInputStream();
093                }
094
095                @Override
096                public boolean exists() {
097                        return this.gzipped.exists();
098                }
099
100                @Override
101                public boolean isReadable() {
102                        return this.gzipped.isReadable();
103                }
104
105                @Override
106                public boolean isOpen() {
107                        return this.gzipped.isOpen();
108                }
109
110                @Override
111                public URL getURL() throws IOException {
112                        return this.gzipped.getURL();
113                }
114
115                @Override
116                public URI getURI() throws IOException {
117                        return this.gzipped.getURI();
118                }
119
120                @Override
121                public File getFile() throws IOException {
122                        return this.gzipped.getFile();
123                }
124
125                @Override
126                public long contentLength() throws IOException {
127                        return this.gzipped.contentLength();
128                }
129
130                @Override
131                public long lastModified() throws IOException {
132                        return this.gzipped.lastModified();
133                }
134
135                @Override
136                public Resource createRelative(String relativePath) throws IOException {
137                        return this.gzipped.createRelative(relativePath);
138                }
139
140                @Override
141                public String getFilename() {
142                        return this.original.getFilename();
143                }
144
145                @Override
146                public String getDescription() {
147                        return this.gzipped.getDescription();
148                }
149
150                @Override
151                public String getContentEncoding() {
152                        return "gzip";
153                }
154        }
155
156}