001: /*
002: HttpdBase4J: An embeddable Java web server framework that supports HTTP, HTTPS,
003: templated content and serving content from inside a jar or archive.
004: Copyright (C) 2007 Donald Munro
005:
006: This library is free software; you can redistribute it and/or
007: modify it under the terms of the GNU Lesser General Public
008: License as published by the Free Software Foundation; either
009: version 2.1 of the License, or (at your option) any later version.
010:
011: This library is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public
017: License along with this library; if not,see http://www.gnu.org/licenses/lgpl.txt
018: */
019:
020: package net.homeip.donaldm.httpdbase4j;
021:
022: import java.io.BufferedInputStream;
023: import java.io.BufferedOutputStream;
024: import java.io.ByteArrayInputStream;
025: import java.io.ByteArrayOutputStream;
026: import java.io.File;
027: import java.io.FileInputStream;
028: import java.io.FileOutputStream;
029: import java.io.IOException;
030: import java.io.InputStream;
031: import java.io.UnsupportedEncodingException;
032: import java.util.Date;
033: import java.util.TreeSet;
034:
035: import com.sun.net.httpserver.HttpExchange;
036:
037: /**
038: * <p>
039: * Support for combining several style sheets or javascripts into one
040: * request. For example the client could specify:
041: * </p><br>
042: * <code>
043: * <link rel="stylesheet" type="text/css" media="all"
044: * href="styles/1.css!+!2.css!+!morestyles/3.css" />
045: * </code>
046: * <p>
047: * Different stylesheets/scripts are separated by a separator string eg !+! in
048: * this case; if the stylesheet name includes a / then it is assumed to be a
049: * full directory name otherwise it uses the directory of the last entry that
050: * had a directory. </p><br>
051: * <code
052: * <script type="text/javascript" src="/scripts/1.js!+!2.js!+!3.js">
053: * </code>
054: * <p>
055: * 2.js and 3.js will be assumed to be in /scripts. </p>/<br>
056: * <p>
057: * If the previous directory did not have a directory then / is assumed eg
058: * </p><br>
059: * <code>
060: * <script type="text/javascript" src="1.js!+!2.js!+!3.js">
061: * </code><br>
062: * * 1.js, 2.js and 3.js are assumed to be in /.<br>
063: * <p>
064: * <b>This is non-standard HTML</b>, although it can be done in for example
065: * Apache using rewriting and server side scripts.
066: * (@see http://rakaz.nl/item/make_your_pages_load_faster_by_combining_and_compressing_javascript_and_css_files)
067: * </p>
068: * @see ArchiveCombinedRequest
069: * @see FileCombinedRequest
070: * @author Donald Munro
071: */
072: abstract public class CombinedRequest extends Request implements
073: Cloneable
074: //=========================================================================
075: {
076: /**
077: * File that contains all the individual request files combined.
078: */
079: protected File m_combinedFile = null;
080:
081: /**
082: * Buffer to store the contents of all the individual request files combined
083: * in the event that a temporary combined file could not be created.
084: */
085: protected byte[] m_combinedArray = null;
086:
087: /**
088: * Delimiter to separate files in script or style tag.
089: */
090: protected String m_delimiter = null;
091:
092: /**
093: * The extension for this CombinedRequest. (Extensions must be
094: * homogenous for a given CombinedRequest).
095: */
096: protected String m_extension = "";
097:
098: /**
099: * File extensions supported by this CombinedRequest.
100: */
101: protected String[] m_extensions = null;
102:
103: /**
104: * If true if one of the resources does not exist then exists returns
105: * false otherwise it only returns false if all of the resources don't exist
106: */
107: protected boolean m_strict = true;
108:
109: /**
110: * Create a CombinedRequest.
111: * @param httpd - The Httpd instance
112: * @param ex - The HttpExchange for the HTTP exchange this request
113: * is involved in.
114: * @throws UnsupportedEncodingException
115: * @throws IOException
116: */
117: public CombinedRequest(Httpd httpd, HttpExchange ex)
118: throws UnsupportedEncodingException, IOException
119: //---------------------------------------------------------------------
120: {
121: this (httpd, ex, RequestHandler.COMBINED_REQUEST_DELIMITER,
122: RequestHandler.COMBINED_REQUEST_EXTENSIONS);
123: }
124:
125: /**
126: * Create a CombinedRequest.
127: * @param httpd - The Httpd instance
128: * @param ex - The HttpExchange for the HTTP exchange this request
129: * is involved in.
130: * @param delimiter - The delimiter that separates files in the
131: * request tag. It is used as a regular expression so regular
132: * expression escapping should be used where applicable.
133: * @param extensions - The extensions supported by this request
134: * @throws UnsupportedEncodingException
135: * @throws IOException
136: */
137: public CombinedRequest(Httpd httpd, HttpExchange ex,
138: String delimiter, String... extensions)
139: throws UnsupportedEncodingException, IOException
140: //---------------------------------------------------------------------
141: {
142: super (httpd, ex);
143: m_delimiter = delimiter;
144: m_extensions = new String[extensions.length];
145: for (int i = 0; i < extensions.length; i++)
146: m_extensions[i] = extensions[i];
147: }
148:
149: /**
150: * Split the request into the individual request files and add
151: * files to list of files for this request. Calls addFile for
152: * each file.
153: * @throws UnsupportedEncodingException
154: */
155: protected void splitUp() throws UnsupportedEncodingException
156: //-----------------------------------------------------------
157: {
158: String[] reqs = m_uri.getPath().split(m_delimiter);
159: String cwd = "";
160: m_extension = Http.getExtension(new File(reqs[0]));
161: int i;
162: for (i = 0; i < m_extensions.length; i++) {
163: String extension = m_extensions[i].trim();
164: if (!extension.startsWith("."))
165: extension = "." + extension;
166: if (m_extension.compareToIgnoreCase(extension) == 0)
167: break;
168: }
169: if (i >= m_extensions.length)
170: throw new UnsupportedEncodingException(
171: "Unsupported extension " + m_extension);
172: for (i = 0; i < reqs.length; i++) {
173: String s = reqs[i];
174: if (m_extension.compareToIgnoreCase(Http
175: .getExtension(new File(s))) != 0)
176: throw new UnsupportedEncodingException(
177: "Only files of the same "
178: + " extension on the same line are supported ("
179: + m_extension + " " + s + ")");
180: int p = s.lastIndexOf('/');
181: if (p >= 0)
182: cwd = s.substring(0, p);
183: else
184: s = cwd + "/" + s;
185: addFile(s);
186: }
187: }
188:
189: /**
190: * Add a file to the list of files for this CombinedRequest.
191: * @param file
192: */
193: abstract protected void addFile(String file);
194:
195: public boolean isDirectory() {
196: return false;
197: }
198:
199: abstract public Date getDate();
200:
201: /**
202: * @inheritDoc
203: */
204: @Override
205: public long getContentLength()
206: //----------------------------
207: {
208: if (m_cacheFile != null) {
209: m_contentLength = m_cacheFile.length();
210: return m_contentLength;
211: }
212:
213: if ((m_combinedFile != null) && (m_combinedFile.exists())) {
214: m_contentLength = m_combinedFile.length();
215: return m_contentLength;
216: }
217:
218: if (m_combinedArray != null) {
219: m_contentLength = m_combinedArray.length;
220: return m_contentLength;
221: }
222: return -1;
223: }
224:
225: /**
226: * @inheritDoc
227: */
228: @Override
229: public InputStream getStream()
230: //----------------------------
231: {
232: return getStream(true);
233: }
234:
235: /**
236: * @inheritDoc
237: */
238: @Override
239: public InputStream getStream(boolean isEncoded)
240: //---------------------------------------------
241: {
242: if ((!isEncoded) || (m_cacheFile == null))
243: return _getRawStream();
244: else {
245: try {
246: return new FileInputStream(m_cacheFile);
247: } catch (Exception e) {
248: m_requestHeaders.add("Pragma", "no-cache");
249: m_encoding = null;
250: return _getRawStream();
251: }
252: }
253: }
254:
255: private InputStream _getRawStream()
256: //---------------------------------
257: {
258: if (!combineFiles())
259: return null;
260: if (m_combinedFile != null) {
261: try {
262: return new FileInputStream(m_combinedFile);
263: } catch (Exception e) {
264: m_combinedFile = null;
265: }
266: }
267: if (m_combinedArray != null)
268: return new ByteArrayInputStream(m_combinedArray);
269: return null;
270: }
271:
272: /**
273: * @inheritDoc
274: */
275: @Override
276: public String getExtension()
277: //--------------------------
278: {
279: return m_extension;
280: }
281:
282: public String getAbsolutePath() {
283: throw new UnsupportedOperationException("Not supported.");
284: }
285:
286: public String getName() {
287: throw new UnsupportedOperationException("Not supported.");
288: }
289:
290: public String getDir() {
291: throw new UnsupportedOperationException("Not supported.");
292: }
293:
294: public Request getDirRequest() {
295: throw new UnsupportedOperationException("Not supported.");
296: }
297:
298: public Request getChildRequest(String name) {
299: throw new UnsupportedOperationException("Not supported.");
300: }
301:
302: protected HttpHandleable getHandler()
303: //-----------------------------------
304: {
305: return m_httpd;
306: }
307:
308: protected Postable getPostHandler() {
309: throw new UnsupportedOperationException("Not supported.");
310: }
311:
312: public TreeSet<DirItemInterface> getDirListFiles(SORTBY sortBy) {
313: throw new UnsupportedOperationException("Not supported.");
314: }
315:
316: public TreeSet<DirItemInterface> getDirListFiles(boolean isLength,
317: SORTBY sortBy) {
318: throw new UnsupportedOperationException("Not supported.");
319: }
320:
321: public TreeSet<DirItemInterface> getDirListDirectories(SORTBY sortBy) {
322: throw new UnsupportedOperationException("Not supported.");
323: }
324:
325: abstract protected int getCount();
326:
327: abstract protected InputStream getItemStream(int i);
328:
329: private boolean combineFiles()
330: //----------------------------
331: {
332: BufferedInputStream bis = null;
333: BufferedOutputStream bos = null;
334: ByteArrayOutputStream combinedBuffer = null;
335: byte[] buffer = new byte[4096];
336: try {
337: m_combinedFile = null;
338: if ((m_cacheDir != null) && (m_cacheDir.exists())) {
339: try {
340: m_combinedFile = File.createTempFile("combine",
341: ".tmp", m_cacheDir);
342: } catch (Exception e) {
343: m_combinedFile = null;
344: }
345: if (m_combinedFile != null)
346: bos = new BufferedOutputStream(
347: new FileOutputStream(m_combinedFile));
348: }
349: if (m_combinedFile == null) {
350: combinedBuffer = new ByteArrayOutputStream();
351: bos = new BufferedOutputStream(combinedBuffer);
352: }
353: for (int i = 0; i < getCount(); i++) {
354: try {
355: bis = new BufferedInputStream(getItemStream(i));
356: while (true) {
357: int cb = bis.read(buffer);
358: if (cb == -1)
359: break;
360: bos.write(buffer, 0, cb);
361: }
362: bos.write(13);
363: bis.close();
364: bis = null;
365: } catch (Exception e) {
366: Httpd
367: .Log(Httpd.LogLevel.INFO,
368: "Combining files", e);
369: continue;
370: }
371: }
372: bos.close();
373: bos = null;
374: if (m_combinedFile == null)
375: m_combinedArray = combinedBuffer.toByteArray();
376: } catch (Exception e) {
377: Httpd.Log(Httpd.LogLevel.ERROR, "Combining files", e);
378: return false;
379: } finally {
380: if (bis != null)
381: try {
382: bis.close();
383: } catch (Exception e) {
384: }
385: if (bos != null)
386: try {
387: bos.close();
388: } catch (Exception e) {
389: }
390: }
391: return true;
392: }
393:
394: public String getETag(boolean refresh)
395: //------------------------------------
396: {
397: throw new UnsupportedOperationException("Not supported");
398: }
399:
400: public long getSize() {
401: throw new UnsupportedOperationException("Not supported yet.");
402: }
403: }
|