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