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 as 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    <b>Note, this servlet does not Gzip data, it only serves data that
022    has already been gzip'ed.</b>
023    <p>
024    To 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>
039    Then, 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;
048    hello
049    &lt;/body&gt;
050    &lt;/html&gt;
051    </pre>
052    </blockquote>
053    That will load the <i><code>myscript.js.gz</code></i> 
054    file (and if myscript.js.gz is not present, a servlet exception
055    will be logged). Note, the ".gz" extension is added automatically
056    if not specified in the filename.
057    <p>
058    There are two modes in serving files. In both cases, the file
059    to be retrieved <u>must already exist as a gzip file on 
060    the server</u>.
061    <ol>
062    <li>
063    Retrieve a arbitrary gzip'ed file from the server. There are 3 
064    required 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
069    specified 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
073    gunzipped). 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
077    US-ASCII, ISO-8859-1 or UTF-8</dd>
078    </dl>
079    Example: <code>
080    /gzip?file=foo.html.gz&mimetype=text/html&encoding=ISO-8859-1
081    </code>
082    </li>
083    
084    <li>
085    Retrieve a Javascript file (convenience method): This will automatically
086    set 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
088    only 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    
098    This path to the included file must be in absolute (start with a
099    '/') or relative form.
100    <p>
101    Absolute path names are like HTML absolute names and 
102    start from the document root directory of the web server. 
103    <p>
104    Relative names (relative to the invoking page) are <b>not</b>
105    supported by this servlet.
106    
107    @author hursh jain
108    */
109    public 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    
130    File docroot;
131    
132    String d_charset = "text/html; charset=ISO-8859-1";
133    
134    public 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    /**
143    Returns the specified gzip file. If the specified name 
144    does not end with ".gz", ".gz" will automatically
145    be tacked on to the specified name (so files served by
146    this method must always end with ".gz"
147    */  
148    public void doGet(final HttpServletRequest req, final HttpServletResponse res) 
149    throws 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      
166    void sendFile(final String file, final HttpServletRequest req, final HttpServletResponse res) 
167    throws 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    
178    String js_content_type = "text/javascript; charset=utf-8";
179    
180    void sendJSFile(final String file, final HttpServletRequest req, final HttpServletResponse res) 
181    throws 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    
189    void push(final ServletOutputStream out, final String file) 
190    throws 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    
200    File 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    }