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 com.sun.net.httpserver.Headers;
023: import com.sun.net.httpserver.HttpExchange;
024: import java.io.BufferedInputStream;
025: import java.io.BufferedOutputStream;
026: import java.io.File;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.UnsupportedEncodingException;
031: import java.net.URI;
032: import java.net.URLDecoder;
033: import java.util.ArrayList;
034: import java.util.Arrays;
035: import java.util.Date;
036: import java.util.Iterator;
037: import java.util.List;
038: import java.util.Map;
039: import java.util.Set;
040: import java.util.StringTokenizer;
041: import java.util.TreeSet;
042: import java.util.regex.Matcher;
043: import java.util.regex.Pattern;
044: import java.util.zip.DeflaterOutputStream;
045: import java.util.zip.GZIPOutputStream;
046: import net.homeip.donaldm.httpdbase4j.Httpd.LogLevel;
047: import org.mozilla.intl.chardet.nsDetector;
048: import org.mozilla.intl.chardet.nsICharsetDetectionObserver;
049:
050: /**
051: * Abstraction of an HTTP request
052: * @see ArchiveRequest
053: * @see FileRequest
054: * @see CombinedRequest
055: * @author Donald Munro
056: */
057: abstract public class Request implements DirItemInterface
058: //=======================================================
059: {
060: /**
061: * Enumeration of HTTP methods
062: */
063: static public enum HTTP_METHOD {
064: GET, HEAD, POST, PUT, DELETE, UNKNOWN
065: }
066:
067: /**
068: The HttpExchange instance for this request.
069: * @see com.sun.net.httpserver.HttpExchange
070: */
071: protected HttpExchange m_ex = null;
072:
073: /**
074: The Httpd instance within which the request occurred.
075: @see net.homeip.donaldm.httpdbase4j.Httpd
076: */
077: protected Httpd m_httpd = null;
078:
079: /**
080: The request URI
081: @see net.homeip.donaldm.httpdbase4j.Httpd
082: */
083: protected URI m_uri = null;
084:
085: /**
086: The request headers
087: */
088: protected CloneableHeaders m_requestHeaders = null;
089:
090: /**
091: The HTTP method
092: */
093: protected HTTP_METHOD m_method = null;
094:
095: /**
096: The HTTP method as a String
097: */
098: protected String m_methodString = null;
099:
100: /**
101: * Request URI path
102: */
103: protected String m_path = null;
104:
105: /**
106: * Request GET parameters
107: */
108: protected CloneableHeaders m_getParameters = null;
109:
110: /**
111: * Request POST parameters
112: */
113: protected CloneableHeaders m_postParameters = null;
114:
115: protected String m_encoding = null;
116:
117: protected long m_contentLength = Long.MIN_VALUE;
118:
119: /**
120: * If true then the output will be compressed with gzip/deflate if the
121: * client indicates that it supports compression.
122: */
123: protected boolean m_compress = true;
124:
125: protected File m_compressedFile = null;
126:
127: protected String m_eTag = null;
128:
129: protected File m_cacheFile = null;
130:
131: static protected File m_cacheDir;
132:
133: static {
134: File tmpDir = new File(System.getProperty("java.io.tmpdir"));
135: if ((tmpDir.exists()) && (tmpDir.canWrite())) {
136: tmpDir = new File(tmpDir, "HttpdBase4J");
137: tmpDir.mkdirs();
138: if ((!tmpDir.exists()) || (!tmpDir.canWrite())) {
139: tmpDir = new File(System.getProperty("java.io.tmpdir"));
140: m_cacheDir = new File(tmpDir, "HttpdBase4J-Cache");
141: } else
142: m_cacheDir = new File(tmpDir, "Cache");
143: m_cacheDir.mkdirs();
144: }
145: if ((!m_cacheDir.exists()) || (!m_cacheDir.canWrite()))
146: m_cacheDir = null;
147: }
148:
149: private boolean m_isGet = false;
150:
151: /**
152: * Create a Request instance.
153: * @param httpd The Httpd instance within which the request occurred.
154: * @see net.homeip.donaldm.httpdbase4j.Httpd
155: * @param ex The HttpExchange instance for this request.
156: * @see com.sun.net.httpserver.HttpExchange
157: * @throws UnsupportedEncodingException
158: * @throws IOException
159: */
160: public Request(Httpd httpd, HttpExchange ex)
161: throws UnsupportedEncodingException, IOException
162: //-----------------------------------------------------------------
163: {
164: m_httpd = httpd;
165: m_ex = ex;
166: m_methodString = ex.getRequestMethod().trim().toUpperCase();
167: if (m_methodString.compareTo("GET") == 0)
168: m_method = HTTP_METHOD.GET;
169: else if (m_methodString.compareTo("HEAD") == 0)
170: m_method = HTTP_METHOD.HEAD;
171: else if (m_methodString.compareTo("POST") == 0)
172: m_method = HTTP_METHOD.POST;
173: else if (m_methodString.compareTo("PUT") == 0)
174: m_method = HTTP_METHOD.PUT;
175: else if (m_methodString.compareTo("DELETE") == 0)
176: m_method = HTTP_METHOD.DELETE;
177: else
178: m_method = HTTP_METHOD.UNKNOWN;
179: m_uri = ex.getRequestURI().normalize();
180: m_path = m_uri.getPath();
181: if (m_path.startsWith("/"))
182: m_path = m_path.substring(1);
183: m_requestHeaders = new CloneableHeaders(ex.getRequestHeaders());
184: m_getParameters = processParameters(m_uri.getQuery());
185: String requestBody = getRequestString(ex.getRequestBody(),
186: m_requestHeaders);
187: switch (m_method) {
188: case GET:
189: case HEAD:
190: m_isGet = true;
191: break;
192:
193: default:
194: String contentType = getContentType();
195: if ((contentType != null)
196: && (contentType.trim().toLowerCase()
197: .startsWith("application/x-www-form-urlencoded")))
198: ;
199: m_postParameters = processParameters(requestBody);
200: }
201:
202: }
203:
204: public Request(Request request)
205: //-----------------------------
206: {
207: m_httpd = request.m_httpd;
208: m_ex = request.m_ex;
209: m_method = request.m_method;
210: m_methodString = request.m_methodString;
211: m_uri = request.m_uri;
212: m_path = request.m_path;
213:
214: try {
215: m_requestHeaders = (CloneableHeaders) request.m_requestHeaders
216: .clone();
217: m_getParameters = (CloneableHeaders) request.m_getParameters
218: .clone();
219: switch (m_method) {
220: case GET:
221: case HEAD:
222: m_isGet = true;
223: break;
224:
225: default:
226: m_postParameters = (CloneableHeaders) request.m_postParameters
227: .clone();
228: }
229: } catch (CloneNotSupportedException e) {
230: e.printStackTrace(System.err);
231: }
232: }
233:
234: /**
235: * Check whether the resource exists.
236: * @return <i>true</i> if the resource exists, <i>false</i> if it does not
237: */
238: abstract public boolean exists();
239:
240: /**
241: * Check whether the resource is readable
242: * @return <i>true</i> if the resource is readable,
243: * <i>false</i> if it is not.
244: */
245: abstract public boolean isReadable();
246:
247: /**
248: * Check whether the resource is a directory
249: * @return <i>true</i> if the resource is a directory,
250: * <i>false</i> if it is not.
251: */
252: abstract public boolean isDirectory();
253:
254: /**
255: * Return the length of the resource.
256: * @return the resource length or -1 if the length could not
257: * be determined.
258: */
259: abstract public long getContentLength();
260:
261: /**
262: * Return the full path of the resource
263: * @return the full path of the resource
264: */
265: abstract public String getAbsolutePath();
266:
267: /**
268: * Return the name of the resource (ie the final component in the path after
269: * the final /)
270: * @return the name of the resource
271: */
272: abstract public String getName();
273:
274: /**
275: * Return the file extension of the resource (ie the text after the final .
276: * in the path)
277: * @return the extension of the resource
278: */
279: abstract public String getExtension();
280:
281: /**
282: * Return the date of the resource
283: * @return the name of the resource
284: */
285: abstract public Date getDate();
286:
287: /**
288: * Return the directory of the resource (ie all components of the path before
289: * the final /)
290: * @return the path of the resource
291: */
292: abstract public String getDir();
293:
294: /**
295: * Return the directory of the resource (ie all components of the path before
296: * the final / as a Request contructed from this Request)
297: * @return A Request for the directory containing this request
298: */
299: abstract public Request getDirRequest();
300:
301: /**
302: * Create a request from the current directory request combined with a
303: * file or directory in the current directory
304: *
305: * @param name The name of a file or directory in the current request if the
306: * current request is a directory
307: * @return A new Request or null if request is not a directory
308: */
309: abstract public Request getChildRequest(String name);
310:
311: /**
312: * Return the handler for this request (a class implementing the
313: * HttpHandleable interface).
314: * @return The request handler
315: * @see net.homeip.donaldm.httpdbase4j.HttpHandleable
316: */
317: abstract protected HttpHandleable getHandler();
318:
319: /**
320: * Return the POST handler for this request (a class implementing the
321: * Postable interface).
322: * @return The request POST handler or null if no POST handler is defined
323: * @see Postable
324: */
325: abstract protected Postable getPostHandler();
326:
327: /**
328: * Return a list of files in a resource directory.
329: * @param sortBy Specify how the files should be sorted. This is an instance
330: * of DirItemInterface.SORTBY ie NAME, DATE or SIZE).
331: * @return A TreeSet (sortable set) of DirItemInterface implementing
332: * instances. Returns null if the resource is not a directory.
333: * @see net.homeip.donaldm.httpdbase4j.DirItemInterface
334: */
335: abstract public TreeSet<DirItemInterface> getDirListFiles(
336: DirItemInterface.SORTBY sortBy);
337:
338: /**
339: * Return a list of directories in a resource directory.
340: * @param sortBy Specify how the files should be sorted. This is an instance
341: * of DirItemInterface.SORTBY ie NAME, DATE or SIZE).
342: * @return A TreeSet (sortable set) of DirItemInterface implementing
343: * instances. Returns null if the resource is not a directory.
344: * @see net.homeip.donaldm.httpdbase4j.DirItemInterface
345: */
346: abstract public TreeSet<DirItemInterface> getDirListDirectories(
347: DirItemInterface.SORTBY sortBy);
348:
349: /**
350: * Return a stream of the resource contents.
351: * @param isEncoded If true will return a stream for the encoded (eg gzip or
352: * deflate) resource or if cacheing is allowed for this request and the
353: * encoded resource is in the cache then a stream for the cached version.
354: * If false returns a stream for the unencoded resource. If there is no
355: * encoding ie the client does not support encoding then both true and
356: * false return a stream for the unencoded resource.
357: * @return A stream of the resource contents.
358: */
359: abstract public InputStream getStream(boolean isEncoded);
360:
361: /**
362: * @return A stream of the resource contents (encoded content is returned if
363: * available).
364: */
365:
366: /**
367: * @return A stream of the resource contents (encoded content is returned if
368: * available).
369: */
370: public InputStream getStream()
371: //----------------------------
372: {
373: return getStream(true);
374: }
375:
376: /**
377: * @param refresh If true recalculate the tag hash even if it has already
378: * been calculated, if false reuse the cached value
379: * @return The ETag cacheing hash for this request
380: */
381: abstract public String getETag(boolean refresh);
382:
383: public boolean checkClientCache()
384: //-------------------------
385: {
386: if (!m_httpd.getCaching())
387: return false;
388: String modDateStr = m_requestHeaders
389: .getFirst("If-Modified-Since");
390: if (modDateStr != null) {
391: Date modDate = Http.getDate(modDateStr);
392: if (modDate != null) {
393: Date reqDate = getDate();
394: System.out.println(modDate.getTime() + " "
395: + reqDate.getTime());
396: if (reqDate != null)
397: if (modDate.after(reqDate))
398: return true;
399: else
400: return false;
401: }
402: }
403:
404: String clientEtag = m_requestHeaders.getFirst("If-None-Match");
405: if (clientEtag != null) {
406: clientEtag = clientEtag.replaceAll("\"", "");
407: getETag(false);
408: if (m_eTag.trim().compareTo(clientEtag.trim()) == 0)
409: return true;
410: }
411:
412: return false;
413: }
414:
415: /**
416: * @return The request URI
417: */
418: public URI getURI() {
419: return m_uri;
420: }
421:
422: /**
423: * @return true if the Request result should be cached (checks for
424: * Cache-Control and Pragma:NoCache headers)
425: */
426: public boolean isCacheable()
427: //--------------------------
428: {
429: List<String> pragmas = m_requestHeaders.get("Pragma");
430: if (pragmas != null) {
431: for (Iterator<String> it = pragmas.iterator(); it.hasNext();) {
432: String s = it.next().trim();
433: if (s.compareToIgnoreCase("no-cache") == 0)
434: return false;
435: }
436: }
437: List<String> controls = m_requestHeaders.get("Cache-Control");
438: if (controls != null) {
439: for (Iterator<String> it = controls.iterator(); it
440: .hasNext();) {
441: String control = it.next().trim();
442: if ((control != null)
443: && (control.compareToIgnoreCase("no-cache") == 0))
444: return false;
445: if ((control != null)
446: && (control.compareToIgnoreCase("private") == 0))
447: return false;
448: }
449: }
450: /*
451: if ( (m_requestHeaders.containsKey("If-None-Match")) ||
452: (m_requestHeaders.containsKey("If-Modified-Since")) )
453: return true; */
454: return true;
455: }
456:
457: static private Pattern IE_PATTERN = Pattern
458: .compile("Mozilla/.*MSIE ([0-9]\\.[0-9]).*");
459:
460: /**
461: * Determine the compression encodings supported by the client.
462: * @return A String array with the encodings accepted by the client,
463: * ordered by most desired encoding:
464: * gzip = GZip encoded
465: * deflate = Deflate encoded
466: * txt = No compression
467: */
468: public String[] compressEncoding()
469: //--------------------------------
470: {
471: String encoding = m_requestHeaders.getFirst("Accept-Encoding");
472: if (encoding != null)
473: encoding = encoding.toLowerCase();
474: String[] encodings = null;
475: String agent = m_requestHeaders.getFirst("User-Agent");
476: if ((agent != null)
477: && (agent.toLowerCase().indexOf("opera") < 0)) {
478: Matcher matcher = IE_PATTERN.matcher(agent);
479: String ver = null;
480: if (matcher.matches())
481: ver = matcher.group(1);
482: double version = 0;
483: if (ver != null) {
484: try {
485: version = Double.parseDouble(ver);
486: } catch (Exception e) {
487: }
488: }
489: if ((version < 6)
490: || ((version == 6) && (agent.toUpperCase().indexOf(
491: "EV1") < 0)))
492: encoding = null;
493: }
494: if (encoding == null) {
495: encodings = new String[1];
496: encodings[0] = "txt";
497: } else {
498: String[] encs = encoding.split(",");
499: encodings = new String[encs.length + 1];
500: System.arraycopy(encs, 0, encodings, 0, encs.length);
501: encodings[encs.length] = "txt";
502: }
503: return encodings;
504: }
505:
506: public boolean getContent(long id, HttpHandleable handler)
507: //--------------------------------------------------------
508: {
509: m_compressedFile = m_cacheFile = null;
510: m_encoding = null;
511: String[] encodings = compressEncoding();
512: if ((encodings.length == 1)
513: && (encodings[0].compareTo("txt") == 0))
514: return true;
515:
516: try {
517: if (m_cacheDir != null)
518: m_compressedFile = File.createTempFile("content",
519: ".tmp", m_cacheDir);
520: } catch (Exception e) {
521: m_compressedFile = null;
522: }
523: if (m_compressedFile == null)
524: return true;
525: ;
526: BufferedInputStream bis = null;
527: BufferedOutputStream bos = null;
528: byte[] buffer = new byte[4096];
529: try {
530: for (int i = 0; i < encodings.length; i++) {
531: m_encoding = encodings[i];
532: m_cacheFile = new File(m_cacheDir,
533: ((m_eTag == null) ? Long.toString(id) : m_eTag)
534: + "." + m_encoding);
535: if (handler.onIsCacheable(-1, m_ex, this )) {
536: File f = handler.onGetCachedFile(id, m_ex, this );
537: if ((f == null) && (m_cacheFile.exists())) {
538: m_compressedFile = m_cacheFile;
539: break;
540: }
541: if (f != null) {
542: m_compressedFile = f;
543: break;
544: }
545: }
546: if (bis == null)
547: bis = new BufferedInputStream(getStream(false));
548: try {
549: if ((m_encoding.compareTo("gzip") == 0)
550: && (m_compressedFile != null)) {
551: bos = new BufferedOutputStream(
552: new GZIPOutputStream(
553: new FileOutputStream(
554: m_compressedFile)));
555: while (true) {
556: int cb = bis.read(buffer);
557: if (cb == -1)
558: break;
559: bos.write(buffer, 0, cb);
560: }
561: bos.close();
562: bos = null;
563: break;
564: }
565:
566: if ((m_encoding.compareTo("deflate") == 0)
567: && (m_compressedFile != null)) {
568: bos = new BufferedOutputStream(
569: new DeflaterOutputStream(
570: new FileOutputStream(
571: m_compressedFile)));
572: while (true) {
573: int cb = bis.read(buffer);
574: if (cb == -1)
575: break;
576: bos.write(buffer, 0, cb);
577: }
578: bos.close();
579: bos = null;
580: break;
581: }
582: if ((m_encoding.compareTo("txt") == 0)
583: || (m_compressedFile == null)) {
584: m_compressedFile = m_cacheFile = null;
585: return true;
586: }
587: } catch (IOException e) {
588: m_compressedFile = null;
589: }
590: m_cacheFile = null;
591: }
592: } catch (Exception e) {
593: Httpd.Log(Httpd.LogLevel.ERROR, "Request content encoding",
594: e);
595: }
596:
597: finally {
598: if (bis != null)
599: try {
600: bis.close();
601: } catch (Exception e) {
602: }
603: if (bos != null)
604: try {
605: bos.close();
606: } catch (Exception e) {
607: }
608: }
609: if (handler.onIsCacheable(-1, m_ex, this )) {
610: if (m_cacheFile == null)
611: return false;
612: m_cacheFile.delete();
613: m_compressedFile.renameTo(m_cacheFile);
614: m_compressedFile = null;
615: } else {
616: m_cacheFile = m_compressedFile;
617: //m_cacheFile.deleteOnExit();
618: }
619: return true;
620: }
621:
622: /**
623: * Return whether this request is an HTTP GET or HEAD
624: * @return true if this request is an HTTP GET or HEAD
625: */
626: public boolean isGETorHEAD() {
627: return m_isGet;
628: }
629:
630: /**
631: * @return The request method as an HTTP_METHOD enumeration
632: */
633: public HTTP_METHOD getMethod() {
634: return m_method;
635: }
636:
637: /**
638: * @return The request method as a String
639: */
640: public String getMethodString() {
641: return m_methodString;
642: }
643:
644: /**
645: * @return The request URI path
646: */
647: public String getPath() {
648: return m_path;
649: }
650:
651: /**
652: * @return The request GET parameters
653: */
654: public CloneableHeaders getGETParameters() {
655: return m_getParameters;
656: }
657:
658: /**
659: * @return The request POST parameters
660: */
661: public CloneableHeaders getPOSTParameters() {
662: return m_postParameters;
663: }
664:
665: /**
666: * @return The content type of the request
667: */
668: public String getContentType()
669: //----------------------------
670: {
671: return m_requestHeaders.getFirst("Content-Type");
672: }
673:
674: protected byte[] m_postData = null;
675:
676: /**
677: * Get request contents (eg a POST request contents) as a String
678: * @param is InputStream of request contents
679: * @param headers Request headers
680: * @return A String representation of the request contents or an empty string
681: * @throws IOException
682: * <b>Note:</b>Uses jchardet for charset detection (http://jchardet.sourceforge.net/)
683: */
684: protected String getRequestString(InputStream is, Headers headers)
685: throws IOException
686: //-----------------------------------------------------------
687: {
688: if (m_postData == null)
689: m_postData = getRequestBytes(is, headers);
690: if (m_postData == null)
691: return "";
692: int len = m_postData.length;
693: nsDetector charSetDetector = new nsDetector();
694: final ArrayList<String> charsets = new ArrayList<String>();
695: charSetDetector.Init(new nsICharsetDetectionObserver() {
696: @Override
697: public void Notify(String charset) {
698: charsets.add(charset);
699: }
700: });
701: charSetDetector.DoIt(m_postData, len, false);
702: boolean isAscii = charSetDetector.isAscii(m_postData, len);
703: charSetDetector.DataEnd();
704:
705: if (isAscii)
706: return new String(m_postData);
707: else if (charsets.size() <= 0)
708: return new String(m_postData);
709: else {
710: for (int i = 0; i < charsets.size(); i++) {
711: try {
712: return new String(m_postData, charsets.get(i));
713: } catch (UnsupportedEncodingException e) {
714: Httpd.Log(LogLevel.INFO,
715: "Could not create a String with "
716: + " charset " + charsets.get(i), e);
717: continue;
718: } catch (Exception e) {
719: Httpd
720: .Log(
721: LogLevel.ERROR,
722: "Error decoding request body. Could"
723: + "not create a String with charset "
724: + charsets.get(i), e);
725: }
726: }
727: }
728: return new String(m_postData);
729: }
730:
731: /**
732: * Get request contents (eg a POST request contents) into a byte array.
733: * @param is InputStream of request contents
734: * @param headers Request headers
735: * @return An array of bytes of the request contents or null
736: * @throws IOException
737: * <b>Note:</b>Uses jchardet for charset detection (http://jchardet.sourceforge.net/)
738: */
739: protected byte[] getRequestBytes(InputStream is, Headers headers)
740: throws IOException
741: //-----------------------------------------------------------
742: {
743: String contentLen = headers.getFirst("Content-Length");
744: if (contentLen == null)
745: return null;
746: int len = -1;
747: try {
748: len = Integer.parseInt(contentLen);
749: } catch (Exception e) {
750: len = -1;
751: }
752: if (len <= 0)
753: return null;
754:
755: byte data[] = null;
756: byte[] ret = data = new byte[len];
757: int l = Http.readStream(is, data, len);
758: if ((l >= 0) && (l != len))
759: ret = Arrays.copyOf(data, l);
760: return ret;
761: }
762:
763: /**
764: * Parses parameters in the form key=value&key2=value2)
765: * @param queryString
766: * @return A key-value Map of the parameters
767: * @throws UnsupportedEncodingException
768: */
769: protected CloneableHeaders processParameters(String queryString)
770: throws UnsupportedEncodingException
771: //--------------------------------------------------------------------
772: {
773: CloneableHeaders parameters = new CloneableHeaders();
774: if (queryString == null)
775: return parameters;
776: queryString = queryString.replace('+', ' ');
777: StringTokenizer st = new StringTokenizer(queryString, "&");
778: while (st.hasMoreTokens()) {
779: String field = st.nextToken();
780: String k = null, v = "";
781: int index = field.indexOf('=');
782: if (index > 0) {
783: k = URLDecoder.decode(field.substring(0, index),
784: "UTF-8");
785: v = URLDecoder.decode(field.substring(index + 1),
786: "UTF-8").trim();
787: if (v.startsWith("\""))
788: v = v.substring(1);
789: if (v.endsWith("\""))
790: v = v.substring(0, v.length() - 1);
791: } else
792: k = URLDecoder.decode(field, "UTF-8");
793: k = k.toLowerCase();
794: parameters.add(k, v);
795: }
796: return parameters;
797: }
798:
799: /**
800: * Implementation of Cloneable interface for Request
801: * @return The cloned Request
802: */
803: public Object clone() throws CloneNotSupportedException
804: //-----------------------------------------------------
805: {
806: Request klone = (Request) super .clone();
807:
808: klone.m_ex = m_ex;
809: klone.m_httpd = m_httpd;
810: klone.m_requestHeaders = (CloneableHeaders) m_requestHeaders
811: .clone();
812: klone.m_method = m_method;
813: klone.m_getParameters = (CloneableHeaders) m_getParameters
814: .clone();
815: klone.m_postParameters = (CloneableHeaders) m_postParameters
816: .clone();
817: return klone;
818: }
819:
820: private void _appendParams(CloneableHeaders m, StringBuffer sb)
821: //-------------------------------------------------------------
822: {
823: if (m == null) {
824: sb.append(Httpd.EOL);
825: return;
826: }
827: Set<Map.Entry<String, List<String>>> headers = m.entrySet();
828: for (Iterator<Map.Entry<String, List<String>>> i = headers
829: .iterator(); i.hasNext();) {
830: Map.Entry<String, List<String>> e = i.next();
831: sb.append(e.getKey() + ": ");
832: List<String> hv = e.getValue();
833: for (Iterator<String> j = hv.iterator(); j.hasNext();)
834: sb.append(j.next() + " ");
835: sb.append(Httpd.EOL);
836: }
837: }
838:
839: private Object nullv(Object o) {
840: if (o == null)
841: return "";
842: return o;
843: }
844:
845: @Override
846: public String toString()
847: //----------------------
848: {
849: StringBuffer sb = new StringBuffer();
850: sb.append("m_path: ");
851: sb.append(nullv(m_path));
852: sb.append(Httpd.EOL);
853: sb.append("m_methodString: ");
854: sb.append(nullv(m_methodString));
855: sb.append(Httpd.EOL);
856: sb.append("m_encoding: ");
857: sb.append(nullv(m_encoding));
858: sb.append(Httpd.EOL);
859: sb.append("m_cacheDir: ");
860: sb.append(nullv(m_cacheDir));
861: sb.append(Httpd.EOL);
862: sb.append("m_cacheFile: ");
863: sb.append(nullv(m_cacheFile));
864: sb.append(Httpd.EOL);
865: sb.append("m_contentLength: ");
866: sb.append(m_contentLength);
867: sb.append(Httpd.EOL);
868: sb.append("Exchange:");
869: sb.append(Httpd.EOL);
870: if (m_ex != null)
871: sb.append(Http.strExchange(m_ex));
872: sb.append("GET Parameters:");
873: sb.append(Httpd.EOL);
874: _appendParams(m_getParameters, sb);
875: sb.append("POST Parameters:");
876: sb.append(Httpd.EOL);
877: _appendParams(m_postParameters, sb);
878:
879: return super .toString() + sb.toString();
880: }
881:
882: protected void finalize() throws Throwable
883: //----------------------------------------
884: {
885: if ((m_cacheFile == m_compressedFile) && (m_cacheFile != null))
886: m_cacheFile.delete();
887: }
888: }
|