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    &lt;servlet&gt;
025      &lt;servlet-name&gt;gzip&lt;/servlet-name&gt;
026      &lt;servlet-class&gt;fc.web.servlet.gzFileServlet&lt;/servlet-class&gt;
027    &lt;/servlet&gt;
028    ...
029    &lt;!-- an example mapping, your taste may vary --&gt;
030    &lt;servlet-mapping&gt;
031      &lt;servlet-name&gt;gzip&lt;/servlet-name&gt;
032      &lt;url-pattern&gt;/gzip&lt;/url-pattern&gt;
033    &lt;/servlet-mapping&gt;
034    </pre>
035    </blockquote>
036    Then, in this example, one can say, in your HTML document:
037    <blockquote>
038    <pre>
039    &lt;html&gt;
040    &lt;head&gt;
041      &lt;title&gt;Untitled&lt;/title&gt;
042      <font color=blue>&lt;script src="/gzip?js=myscript.js"&gt;&lt;/script&gt;</font>
043    &lt;/head&gt;
044    &lt;body&gt;
045    hello
046    &lt;/body&gt;
047    &lt;/html&gt;
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    }