001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org)
002 // The Molly framework is freely distributable under the terms of an
003 // MIT-style license. For details, see the molly pages web site at:
004 // http://www.mollypages.org/. Use, modify, have fun !
005
006 package fc.web.servlet;
007
008 import javax.servlet.*;
009 import javax.servlet.http.*;
010 import java.io.*;
011 import java.util.*;
012 import fc.web.servlet.*;
013 import fc.io.*;
014
015 /**
016 Sends the specified file a binary stream after setting the
017 ContenEncoding to be gzip. (the specified file should have
018 a .gz extension, this is mandated because it helps catch
019 user errors, for example forgetting to gzip the file).
020 <p>
021 To use, add something like the following to web.xml:
022 <blockquote>
023 <pre>
024 <servlet>
025 <servlet-name>gzip</servlet-name>
026 <servlet-class>fc.web.servlet.gzFileServlet</servlet-class>
027 </servlet>
028 ...
029 <!-- an example mapping, your taste may vary -->
030 <servlet-mapping>
031 <servlet-name>gzip</servlet-name>
032 <url-pattern>/gzip</url-pattern>
033 </servlet-mapping>
034 </pre>
035 </blockquote>
036 Then, in this example, one can say, in your HTML document:
037 <blockquote>
038 <pre>
039 <html>
040 <head>
041 <title>Untitled</title>
042 <font color=blue><script src="/gzip?js=myscript.js"></script></font>
043 </head>
044 <body>
045 hello
046 </body>
047 </html>
048 </pre>
049 </blockquote>
050 That will load the <i><code>myscript.js.gz</code></i>
051 file (and if myscript.js.gz is not present, a servlet exception
052 will be logged). Note, the ".gz" extension is added automatically
053 if not specified in the filename.
054 <p>
055 There are two modes in serving files. In both cases, the file
056 to be retrieved <u>must already exist as a gzip file on
057 the server</u>.
058 <ol>
059 <li>
060 Retrieve a arbitrary gzip'ed file from the server. There are 3
061 required parameters:
062 <dl>
063 <dt>file</dt>
064 <dd>the filename (if the specified name does not have a
065 .gz extension, ".gz" will be added automatically to the
066 specified name). [see <a href="#path"> path note below</a>]
067 </dd>
068 <dt>mimetype</dt>
069 <dd>The mimetype of the retrieved file (<i>after</i> it has been
070 gunzipped). This will commonly be <code>text/html</code> or
071 <code>text/javascript</code></dd>
072 <dt>encoding</dt>
073 <dd>The text encoding of the specified file. Commonly, it is
074 US-ASCII, ISO-8859-1 or UTF-8</dd>
075 </dl>
076 Example: <code>
077 /gzip?file=foo.html.gz&mimetype=text/html&encoding=ISO-8859-1
078 </code>
079 </li>
080
081 <li>
082 Retrieve a Javascript file (convenience method): This will automatically
083 set the content-type to be <i>text/javascript; charset=utf-8</i>
084 (so the mime or charset do not have to be specified). One need
085 only specify:
086 <dd>
087 <dt>js</dt>
088 <dl>path to the gzip'ed file. [see <a href="#path"> path note below</a>]</dl>
089 </dd>
090 </li>
091 </ol>
092 <hr>
093 <h3>Path name for the specified file</h3>
094
095 This path to the included file must be in absolute (start with a
096 '/') or relative form.
097 <p>
098 Absolute path names are like HTML absolute names and
099 start from the document root directory of the web server.
100 <p>
101 Relative names (relative to the invoking page) are <b>not</b>
102 supported by this servlet.
103
104 @author hursh jain
105 */
106 public final class GzipFileServlet extends javax.servlet.http.HttpServlet
107 {
108 /*
109 context page
110 /WEBAPP/ /bar/baz/foo.js
111 /WEBAPP/ /bar/img/zap.img
112
113 from foo.js ->
114 a) "../img/zap.img" [relative]
115 b) "/WEBAPP/bar/img/zap.img" [absolute #1]
116 c) "/bar/img/zap.img" [absolute #2, WEBAPP added by us]
117
118 we really want (a) and (b), (c) is not-intuitive.
119 (a) and (b) correspond to tradional relative, absolute paths
120 (where absolute is always from the web server document root]
121
122 (a) is too tricky, we don't know the path to the invoking
123 page in this servlet (so cannot do relative to that
124 unknown path).
125 */
126
127 File docroot;
128
129 String d_charset = "text/html; charset=ISO-8859-1";
130
131 public void init(ServletConfig conf) throws ServletException
132 {
133 super.init();
134 ServletContext context = conf.getServletContext();
135 ServletContext root_context = context.getContext("/");
136 docroot = new File(root_context.getRealPath("/"));
137 }
138
139 /**
140 Returns the specified gzip file. If the specified name
141 does not end with ".gz", ".gz" will automatically
142 be tacked on to the specified name (so files served by
143 this method must always end with ".gz"
144 */
145 public void doGet(final HttpServletRequest req, final HttpServletResponse res)
146 throws ServletException, IOException
147 {
148 final String file = req.getParameter("file");
149 if (file != null) {
150 sendFile(file, req, res);
151 return;
152 }
153
154 final String js = req.getParameter("js");
155 if (js != null) {
156 sendJSFile(js, req, res);
157 return;
158 }
159
160 throw new ServletException("Bad request. I need either the \"file\" or the \"jsfile\" parameter.");
161 }
162
163 void sendFile(final String file, final HttpServletRequest req, final HttpServletResponse res)
164 throws ServletException, IOException
165 {
166 final String mime = WebUtil.getRequiredParam(req, "mimetype");
167 final String encoding = WebUtil.getRequiredParam(req, "encoding");
168 final String content_type = mime + "; charset=" + encoding;
169 res.setContentType(content_type);
170 res.setHeader("Content-Encoding", "gzip");
171 ServletOutputStream out = res.getOutputStream();
172 push(out, file);
173 }
174
175 String js_content_type = "text/javascript; charset=utf-8";
176
177 void sendJSFile(final String file, final HttpServletRequest req, final HttpServletResponse res)
178 throws ServletException, IOException
179 {
180 res.setContentType(js_content_type);
181 res.setHeader("Content-Encoding", "gzip");
182 final ServletOutputStream out = res.getOutputStream();
183 push(out, file);
184 }
185
186 void push(final ServletOutputStream out, final String file)
187 throws IOException
188 {
189 final File f = makeFile(file);
190 final byte[] buf = IOUtil.fileToByteArray(f);
191 final BufferedOutputStream bout = new BufferedOutputStream(out);
192 bout.write(buf, 0, buf.length);
193 bout.flush();
194 bout.close();
195 }
196
197 File makeFile(String path) throws IOException
198 {
199 if (! path.endsWith(".gz")) {
200 path += ".gz";
201 }
202
203 File f = new File(docroot, path);
204
205 if (! f.exists() || ! f.isFile()) {
206 throw new IOException("\nERROR: file: [" + path + "] not found");
207 }
208 return f;
209 }
210 }