0001: /*
0002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
0003: *
0004: * This file is part of Resin(R) Open Source
0005: *
0006: * Each copy or derived work must preserve the copyright notice and this
0007: * notice unmodified.
0008: *
0009: * Resin Open Source is free software; you can redistribute it and/or modify
0010: * it under the terms of the GNU General Public License as published by
0011: * the Free Software Foundation; either version 2 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * Resin Open Source is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
0017: * of NON-INFRINGEMENT. See the GNU General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU General Public License
0021: * along with Resin Open Source; if not, write to the
0022: *
0023: * Free Software Foundation, Inc.
0024: * 59 Temple Place, Suite 330
0025: * Boston, MA 02111-1307 USA
0026: *
0027: * @author Scott Ferguson
0028: */
0029:
0030: package com.caucho.server.connection;
0031:
0032: import com.caucho.log.Log;
0033: import com.caucho.server.cache.AbstractCacheEntry;
0034: import com.caucho.server.cache.AbstractCacheFilterChain;
0035: import com.caucho.server.dispatch.BadRequestException;
0036: import com.caucho.server.dispatch.InvocationDecoder;
0037: import com.caucho.server.session.CookieImpl;
0038: import com.caucho.server.session.SessionImpl;
0039: import com.caucho.server.session.SessionManager;
0040: import com.caucho.server.util.CauchoSystem;
0041: import com.caucho.server.webapp.ErrorPageManager;
0042: import com.caucho.server.webapp.WebApp;
0043: import com.caucho.util.Alarm;
0044: import com.caucho.util.CaseInsensitiveIntMap;
0045: import com.caucho.util.CharBuffer;
0046: import com.caucho.util.HTTPUtil;
0047: import com.caucho.util.L10N;
0048: import com.caucho.util.QDate;
0049: import com.caucho.vfs.ClientDisconnectException;
0050: import com.caucho.vfs.FlushBuffer;
0051: import com.caucho.vfs.TempBuffer;
0052: import com.caucho.vfs.WriteStream;
0053: import com.caucho.xml.XmlChar;
0054:
0055: import javax.servlet.ServletOutputStream;
0056: import javax.servlet.ServletResponse;
0057: import javax.servlet.http.Cookie;
0058: import javax.servlet.http.HttpServletResponse;
0059: import javax.servlet.http.HttpSession;
0060: import java.io.IOException;
0061: import java.io.OutputStream;
0062: import java.io.PrintWriter;
0063: import java.io.Writer;
0064: import java.util.ArrayList;
0065: import java.util.HashMap;
0066: import java.util.Locale;
0067: import java.util.logging.Level;
0068: import java.util.logging.Logger;
0069:
0070: /**
0071: * Encapsulates the servlet response, controlling response headers and the
0072: * response stream.
0073: */
0074: abstract public class AbstractHttpResponse implements CauchoResponse {
0075: static final protected Logger log = Logger
0076: .getLogger(AbstractHttpResponse.class.getName());
0077: static final L10N L = new L10N(AbstractHttpResponse.class);
0078:
0079: static final HashMap<String, String> _errors;
0080:
0081: protected static final CaseInsensitiveIntMap _headerCodes;
0082: protected static final int HEADER_CACHE_CONTROL = 1;
0083: protected static final int HEADER_CONTENT_TYPE = HEADER_CACHE_CONTROL + 1;
0084: protected static final int HEADER_CONTENT_LENGTH = HEADER_CONTENT_TYPE + 1;
0085: protected static final int HEADER_DATE = HEADER_CONTENT_LENGTH + 1;
0086: protected static final int HEADER_SERVER = HEADER_DATE + 1;
0087: protected static final int HEADER_CONNECTION = HEADER_SERVER + 1;
0088:
0089: protected CauchoRequest _originalRequest;
0090: protected CauchoRequest _request;
0091:
0092: protected int _statusCode;
0093: protected String _statusMessage;
0094:
0095: protected String _contentType;
0096: protected String _contentPrefix;
0097: protected String _charEncoding;
0098: protected boolean _hasCharEncoding;
0099:
0100: protected final ArrayList<String> _headerKeys = new ArrayList<String>();
0101: protected final ArrayList<String> _headerValues = new ArrayList<String>();
0102:
0103: protected final ArrayList<String> _footerKeys = new ArrayList<String>();
0104: protected final ArrayList<String> _footerValues = new ArrayList<String>();
0105:
0106: protected final ArrayList<Cookie> _cookiesOut = new ArrayList<Cookie>();
0107:
0108: private final AbstractResponseStream _originalResponseStream;
0109:
0110: private final ServletOutputStreamImpl _responseOutputStream;
0111: private final ResponseWriter _responsePrintWriter;
0112:
0113: private AbstractResponseStream _responseStream;
0114:
0115: // the raw output stream.
0116: protected WriteStream _rawWrite;
0117:
0118: // any stream that needs flusing before getting the writer.
0119: private FlushBuffer _flushBuffer;
0120:
0121: private boolean _isHeaderWritten;
0122: private boolean _isChunked;
0123: protected final QDate _calendar = new QDate(false);
0124:
0125: protected final CharBuffer _cb = new CharBuffer();
0126: protected final char[] _headerBuffer = new char[256];
0127:
0128: private String _sessionId;
0129: private boolean _hasSessionCookie;
0130:
0131: private Locale _locale;
0132: protected boolean _disableHeaders;
0133: protected boolean _disableCaching;
0134: protected long _contentLength;
0135: protected boolean _isClosed;
0136: protected boolean _hasSentLog;
0137:
0138: protected boolean _hasWriter;
0139: protected boolean _hasOutputStream;
0140:
0141: private AbstractCacheFilterChain _cacheInvocation;
0142:
0143: // the cache entry for a match/if-modified-since
0144: private AbstractCacheEntry _matchCacheEntry;
0145:
0146: // the new cache for a request getting filled
0147: private AbstractCacheEntry _newCacheEntry;
0148: private OutputStream _cacheStream;
0149: private Writer _cacheWriter;
0150:
0151: protected boolean _isNoCache;
0152: private boolean _allowCache;
0153: private boolean _isPrivateCache;
0154: private boolean _hasCacheControl;
0155:
0156: protected boolean _isTopCache;
0157:
0158: protected boolean _forbidForward;
0159: protected boolean _hasError;
0160:
0161: private final TempBuffer _tempBuffer = TempBuffer.allocate();
0162:
0163: protected AbstractHttpResponse() {
0164: _originalResponseStream = createResponseStream();
0165:
0166: _responseOutputStream = new ServletOutputStreamImpl();
0167: _responsePrintWriter = new ResponseWriter();
0168:
0169: _responseOutputStream.init(_originalResponseStream);
0170: _responsePrintWriter.init(_originalResponseStream);
0171: }
0172:
0173: protected AbstractResponseStream createResponseStream() {
0174: return new ResponseStream(this );
0175: }
0176:
0177: protected AbstractHttpResponse(CauchoRequest request) {
0178: this ();
0179:
0180: _request = request;
0181: _originalRequest = request;
0182:
0183: _responseOutputStream.init(_originalResponseStream);
0184: _responsePrintWriter.init(_originalResponseStream);
0185: }
0186:
0187: /**
0188: * If set true, client disconnect exceptions are no propagated to the
0189: * server code.
0190: */
0191: public boolean isIgnoreClientDisconnect() {
0192: if (!(_originalRequest instanceof AbstractHttpRequest))
0193: return true;
0194: else {
0195: return ((AbstractHttpRequest) _originalRequest)
0196: .isIgnoreClientDisconnect();
0197: }
0198: }
0199:
0200: /**
0201: * Return true for the top request.
0202: */
0203: public boolean isTop() {
0204: if (!(_request instanceof AbstractHttpRequest))
0205: return false;
0206: else {
0207: return ((AbstractHttpRequest) _request).isTop();
0208: }
0209: }
0210:
0211: /**
0212: * Returns the next response.
0213: */
0214: public ServletResponse getResponse() {
0215: return null;
0216: }
0217:
0218: /**
0219: * Initialize the response for a new request.
0220: *
0221: * @param stream the underlying output stream.
0222: */
0223: public void init(WriteStream stream) {
0224: _rawWrite = stream;
0225: if (_originalResponseStream instanceof ResponseStream)
0226: ((ResponseStream) _originalResponseStream).init(_rawWrite);
0227: }
0228:
0229: /**
0230: * Initialize the response for a new request.
0231: *
0232: * @param request the matching request.
0233: */
0234: public void init(CauchoRequest request) {
0235: _request = request;
0236: _originalRequest = request;
0237: }
0238:
0239: /**
0240: * Returns the corresponding request.
0241: */
0242: public CauchoRequest getRequest() {
0243: return _request;
0244: }
0245:
0246: /**
0247: * Sets the corresponding request.
0248: */
0249: public void setRequest(CauchoRequest req) {
0250: _request = req;
0251:
0252: if (_originalRequest == null)
0253: _originalRequest = req;
0254: }
0255:
0256: /**
0257: * Returns the corresponding original
0258: */
0259: public CauchoRequest getOriginalRequest() {
0260: return _originalRequest;
0261: }
0262:
0263: /**
0264: * Closes the request.
0265: */
0266: public void close() throws IOException {
0267: finish(true);
0268: // getStream().flush();
0269: }
0270:
0271: /**
0272: * Returns true for closed requests.
0273: */
0274: public boolean isClosed() {
0275: return _isClosed;
0276: }
0277:
0278: /**
0279: * Initializes the Response at the beginning of the request.
0280: */
0281: public void start() throws IOException {
0282: _statusCode = 200;
0283: _statusMessage = "OK";
0284:
0285: _headerKeys.clear();
0286: _headerValues.clear();
0287:
0288: _footerKeys.clear();
0289: _footerValues.clear();
0290:
0291: _hasSessionCookie = false;
0292: _cookiesOut.clear();
0293:
0294: _isHeaderWritten = false;
0295: _isChunked = false;
0296: _charEncoding = null;
0297: _hasCharEncoding = false;
0298: _contentType = null;
0299: _contentPrefix = null;
0300: _locale = null;
0301: if (_originalResponseStream instanceof ResponseStream)
0302: ((ResponseStream) _originalResponseStream).init(_rawWrite);
0303: _flushBuffer = null;
0304:
0305: _contentLength = -1;
0306: _disableHeaders = false;
0307: _disableCaching = false;
0308: _isClosed = false;
0309: _hasSentLog = false;
0310:
0311: _hasWriter = false;
0312: _hasOutputStream = false;
0313:
0314: _cacheInvocation = null;
0315: _matchCacheEntry = null;
0316: _newCacheEntry = null;
0317: _cacheStream = null;
0318: _cacheWriter = null;
0319: _isPrivateCache = false;
0320: _hasCacheControl = false;
0321: _allowCache = true;
0322: _isNoCache = false;
0323: _isTopCache = false;
0324:
0325: _sessionId = null;
0326:
0327: _forbidForward = false;
0328:
0329: _originalResponseStream.start();
0330:
0331: _responseStream = _originalResponseStream;
0332:
0333: _responseOutputStream.init(_responseStream);
0334: _responsePrintWriter.init(_responseStream);
0335: }
0336:
0337: /**
0338: * For a HEAD request, the response stream should write no data.
0339: */
0340: void setHead() {
0341: _originalResponseStream.setHead();
0342: }
0343:
0344: /**
0345: * For a HEAD request, the response stream should write no data.
0346: */
0347: protected final boolean isHead() {
0348: return _originalResponseStream.isHead();
0349: }
0350:
0351: /**
0352: * When set to true, RequestDispatcher.forward() is disallowed on
0353: * this stream.
0354: */
0355: public void setForbidForward(boolean forbid) {
0356: _forbidForward = forbid;
0357: }
0358:
0359: /**
0360: * Returns true if RequestDispatcher.forward() is disallowed on
0361: * this stream.
0362: */
0363: public boolean getForbidForward() {
0364: return _forbidForward;
0365: }
0366:
0367: /**
0368: * Set to true while processing an error.
0369: */
0370: public void setHasError(boolean hasError) {
0371: _hasError = hasError;
0372: }
0373:
0374: /**
0375: * Returns true if we're processing an error.
0376: */
0377: public boolean hasError() {
0378: return _hasError;
0379: }
0380:
0381: /**
0382: * Sets the cache entry so we can use it if the servlet returns
0383: * not_modified response.
0384: *
0385: * @param entry the saved cache entry
0386: */
0387: public void setMatchCacheEntry(AbstractCacheEntry entry) {
0388: _matchCacheEntry = entry;
0389: }
0390:
0391: /**
0392: * Sets the cache invocation to indicate that the response might be
0393: * cacheable.
0394: */
0395: public void setCacheInvocation(
0396: AbstractCacheFilterChain cacheInvocation) {
0397: AbstractCacheFilterChain oldCache = _cacheInvocation;
0398: _cacheInvocation = null;
0399:
0400: AbstractCacheEntry oldEntry = _newCacheEntry;
0401: _newCacheEntry = null;
0402:
0403: if (oldEntry != null)
0404: oldCache.killCaching(oldEntry);
0405:
0406: _cacheInvocation = cacheInvocation;
0407: }
0408:
0409: public void setTopCache(boolean isTopCache) {
0410: _isTopCache = isTopCache;
0411: }
0412:
0413: public void setStatus(int code) {
0414: setStatus(code, null);
0415: }
0416:
0417: public void setStatus(int code, String message) {
0418: if (code < 0)
0419: code = 500;
0420:
0421: if (message != null) {
0422: } else if (code == SC_OK)
0423: message = "OK";
0424:
0425: else if (code == SC_NOT_MODIFIED)
0426: message = "Not Modified";
0427:
0428: else if (message == null) {
0429: message = (String) _errors.get(String.valueOf(code));
0430:
0431: if (message == null)
0432: message = L.l("Internal Server Error");
0433: }
0434:
0435: _statusCode = code;
0436: _statusMessage = message;
0437: }
0438:
0439: public int getStatusCode() {
0440: return _statusCode;
0441: }
0442:
0443: public void sendError(int code) throws IOException {
0444: sendError(code, null);
0445: }
0446:
0447: /**
0448: * Sends an HTTP error to the browser.
0449: *
0450: * @param code the HTTP error code
0451: * @param value a string message
0452: */
0453: public void sendError(int code, String value) throws IOException {
0454: if (code == SC_NOT_MODIFIED && _matchCacheEntry != null) {
0455: setStatus(code, value);
0456: if (handleNotModified(_isTopCache))
0457: return;
0458: }
0459:
0460: if (isCommitted())
0461: throw new IllegalStateException(
0462: L
0463: .l("sendError() forbidden after buffer has been committed."));
0464:
0465: //_currentWriter = null;
0466: //setStream(_originalStream);
0467: resetBuffer();
0468:
0469: if (code != SC_NOT_MODIFIED)
0470: killCache();
0471:
0472: /* XXX: if we've already got an error, won't this just mask it?
0473: if (responseStream.isCommitted())
0474: throw new IllegalStateException("response can't sendError() after commit");
0475: */
0476:
0477: WebApp app = getRequest().getWebApp();
0478:
0479: ErrorPageManager errorManager = null;
0480: if (app != null)
0481: errorManager = app.getErrorPageManager();
0482:
0483: setStatus(code, value);
0484: try {
0485: if (code == SC_NOT_MODIFIED || code == SC_NO_CONTENT) {
0486: finish();
0487: return;
0488: } else if (errorManager != null) {
0489: errorManager.sendError(getOriginalRequest(), this ,
0490: code, _statusMessage);
0491: // _request.killKeepalive();
0492: // close, but don't force a flush
0493: // XXX: finish(false);
0494: finish();
0495: return;
0496: }
0497:
0498: setContentType("text/html");
0499: ServletOutputStream s = getOutputStream();
0500:
0501: s.println("<html>");
0502: if (!isCommitted()) {
0503: s.print("<head><title>");
0504: s.print(code);
0505: s.print(" ");
0506: s.print(_statusMessage);
0507: s.println("</title></head>");
0508: }
0509: s.println("<body>");
0510:
0511: s.print("<h1>");
0512: s.print(code);
0513: s.print(" ");
0514: s.print(_statusMessage);
0515: s.println("</h1>");
0516:
0517: if (code == HttpServletResponse.SC_NOT_FOUND) {
0518: s.println(L
0519: .l("{0} was not found on this server.",
0520: HTTPUtil.encodeString(getRequest()
0521: .getPageURI())));
0522: } else if (code == HttpServletResponse.SC_SERVICE_UNAVAILABLE) {
0523: s
0524: .println(L
0525: .l("The server is temporarily unavailable due to maintenance downtime or excessive load."));
0526: }
0527:
0528: String version = null;
0529:
0530: if (app == null) {
0531: } else if (app.getServer() != null
0532: && app.getServer().getServerHeader() != null) {
0533: version = app.getServer().getServerHeader();
0534: } else if (CauchoSystem.isTesting()) {
0535: } else
0536: version = com.caucho.Version.FULL_VERSION;
0537:
0538: if (version != null) {
0539: s.println("<p /><hr />");
0540: s.println("<small>");
0541:
0542: s.println(version);
0543:
0544: s.println("</small>");
0545: }
0546:
0547: s.println("</body></html>");
0548: } catch (Exception e) {
0549: log.log(Level.FINE, e.toString(), e);
0550: }
0551:
0552: _request.killKeepalive();
0553: // close, but don't force a flush
0554: finish();
0555: }
0556:
0557: /**
0558: * Sends a redirect to the browser. If the URL is relative, it gets
0559: * combined with the current url.
0560: *
0561: * @param url the possibly relative url to send to the browser
0562: */
0563: public void sendRedirect(String url) throws IOException {
0564: if (url == null)
0565: throw new NullPointerException();
0566:
0567: if (_originalResponseStream.isCommitted())
0568: throw new IllegalStateException(
0569: L
0570: .l("Can't sendRedirect() after data has committed to the client."));
0571:
0572: _responseStream.clearBuffer();
0573: _originalResponseStream.clearBuffer();
0574:
0575: _responseStream = _originalResponseStream;
0576: resetBuffer();
0577:
0578: setStatus(SC_MOVED_TEMPORARILY);
0579: String path = getAbsolutePath(url);
0580:
0581: CharBuffer cb = new CharBuffer();
0582:
0583: for (int i = 0; i < path.length(); i++) {
0584: char ch = path.charAt(i);
0585:
0586: if (ch == '<')
0587: cb.append("%3c");
0588: else
0589: cb.append(ch);
0590: }
0591:
0592: path = cb.toString();
0593:
0594: setHeader("Location", path);
0595: setHeader("Content-Type", "text/html");
0596:
0597: // The data is required for some WAP devices that can't handle an
0598: // empty response.
0599: ServletOutputStream out = getOutputStream();
0600: out.println("The URL has moved <a href=\"" + path
0601: + "\">here</a>");
0602: // closeConnection();
0603:
0604: if (_request instanceof AbstractHttpRequest) {
0605: AbstractHttpRequest request = (AbstractHttpRequest) _request;
0606:
0607: request.saveSession(); // #503
0608: }
0609:
0610: close();
0611: }
0612:
0613: /**
0614: * Switch to raw socket mode.
0615: */
0616: public void switchToRaw() throws IOException {
0617: throw new UnsupportedOperationException(L
0618: .l("raw mode is not supported in this configuration"));
0619: }
0620:
0621: /**
0622: * Switch to raw socket mode.
0623: */
0624: public WriteStream getRawOutput() throws IOException {
0625: throw new UnsupportedOperationException(L
0626: .l("raw mode is not supported in this configuration"));
0627: }
0628:
0629: /**
0630: * Returns the absolute path for a given relative path.
0631: *
0632: * @param path the possibly relative url to send to the browser
0633: */
0634: private String getAbsolutePath(String path) {
0635: int slash = path.indexOf('/');
0636:
0637: int len = path.length();
0638:
0639: for (int i = 0; i < len; i++) {
0640: char ch = path.charAt(i);
0641:
0642: if (ch == ':')
0643: return path;
0644: else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
0645: continue;
0646: else
0647: break;
0648: }
0649:
0650: WebApp app = getRequest().getWebApp();
0651:
0652: String hostPrefix = null;
0653: String host = _request.getHeader("Host");
0654: String serverName = app.getHostName();
0655:
0656: if (serverName == null || serverName.equals(""))
0657: serverName = _request.getServerName();
0658:
0659: int port = _request.getServerPort();
0660:
0661: if (hostPrefix != null && !hostPrefix.equals("")) {
0662: } else if (serverName.startsWith("http:")
0663: || serverName.startsWith("https:"))
0664: hostPrefix = serverName;
0665: else if (host != null) {
0666: hostPrefix = _request.getScheme() + "://" + host;
0667: } else {
0668: hostPrefix = _request.getScheme() + "://" + serverName;
0669:
0670: if (serverName.indexOf(':') < 0 && port != 0 && port != 80
0671: && port != 443)
0672: hostPrefix += ":" + port;
0673: }
0674:
0675: if (slash == 0)
0676: return hostPrefix + path;
0677:
0678: String uri = _request.getRequestURI();
0679: String queryString = null;
0680:
0681: int p = path.indexOf('?');
0682: if (p > 0) {
0683: queryString = path.substring(p + 1);
0684: path = path.substring(0, p);
0685: }
0686:
0687: p = uri.lastIndexOf('/');
0688:
0689: if (p >= 0)
0690: path = uri.substring(0, p + 1) + path;
0691:
0692: try {
0693: if (queryString != null)
0694: return hostPrefix
0695: + InvocationDecoder.normalizeUri(path) + '?'
0696: + queryString;
0697: else
0698: return hostPrefix
0699: + InvocationDecoder.normalizeUri(path);
0700: } catch (IOException e) {
0701: throw new RuntimeException(e);
0702: }
0703: }
0704:
0705: /**
0706: * Returns true if the response already contains the named header.
0707: *
0708: * @param name name of the header to test.
0709: */
0710: public boolean containsHeader(String name) {
0711: for (int i = 0; i < _headerKeys.size(); i++) {
0712: String oldKey = _headerKeys.get(i);
0713:
0714: if (oldKey.equalsIgnoreCase(name))
0715: return true;
0716: }
0717:
0718: if (name.equalsIgnoreCase("content-type"))
0719: return _contentType != null;
0720:
0721: if (name.equalsIgnoreCase("content-length"))
0722: return _contentLength >= 0;
0723:
0724: return false;
0725: }
0726:
0727: /**
0728: * Returns the value of an already set output header.
0729: *
0730: * @param name name of the header to get.
0731: */
0732: public String getHeader(String name) {
0733: for (int i = 0; i < _headerKeys.size(); i++) {
0734: String oldKey = (String) _headerKeys.get(i);
0735:
0736: if (oldKey.equalsIgnoreCase(name))
0737: return (String) _headerValues.get(i);
0738: }
0739:
0740: if (name.equalsIgnoreCase("content-type"))
0741: return _contentType;
0742:
0743: if (name.equalsIgnoreCase("content-length"))
0744: return _contentLength >= 0 ? String.valueOf(_contentLength)
0745: : null;
0746:
0747: return null;
0748: }
0749:
0750: /**
0751: * Sets a header, replacing an already-existing header.
0752: *
0753: * @param key the header key to set.
0754: * @param value the header value to set.
0755: */
0756: public void setHeader(String key, String value) {
0757: if (_disableHeaders)
0758: return;
0759: else if (value == null)
0760: throw new NullPointerException();
0761:
0762: if (setSpecial(key, value))
0763: return;
0764:
0765: int i = 0;
0766: boolean hasHeader = false;
0767:
0768: for (i = _headerKeys.size() - 1; i >= 0; i--) {
0769: String oldKey = _headerKeys.get(i);
0770:
0771: if (oldKey.equalsIgnoreCase(key)) {
0772: if (hasHeader) {
0773: _headerKeys.remove(i);
0774: _headerValues.remove(i);
0775: } else {
0776: hasHeader = true;
0777:
0778: _headerValues.set(i, value);
0779: }
0780: }
0781: }
0782:
0783: if (!hasHeader) {
0784: _headerKeys.add(key);
0785: _headerValues.add(value);
0786: }
0787: }
0788:
0789: /**
0790: * Adds a new header. If an old header with that name exists,
0791: * both headers are output.
0792: *
0793: * @param key the header key.
0794: * @param value the header value.
0795: */
0796: public void addHeader(String key, String value) {
0797: if (_disableHeaders)
0798: return;
0799:
0800: if (setSpecial(key, value))
0801: return;
0802:
0803: _headerKeys.add(key);
0804: _headerValues.add(value);
0805: }
0806:
0807: /**
0808: * Special processing for a special value.
0809: */
0810: protected boolean setSpecial(String key, String value) {
0811: int length = key.length();
0812: if (256 <= length)
0813: return false;
0814:
0815: key.getChars(0, length, _headerBuffer, 0);
0816:
0817: switch (_headerCodes.get(_headerBuffer, length)) {
0818: case HEADER_CACHE_CONTROL:
0819: if (value.startsWith("max-age")) {
0820: } else if (value.equals("x-anonymous")) {
0821: } else
0822: _hasCacheControl = true;
0823: return false;
0824:
0825: case HEADER_CONNECTION:
0826: if ("close".equalsIgnoreCase(value))
0827: _request.killKeepalive();
0828: return true;
0829:
0830: case HEADER_CONTENT_TYPE:
0831: setContentType(value);
0832: return true;
0833:
0834: case HEADER_CONTENT_LENGTH:
0835: _contentLength = Long.parseLong(value);
0836: return true;
0837:
0838: case HEADER_DATE:
0839: return true;
0840:
0841: case HEADER_SERVER:
0842: return false;
0843:
0844: default:
0845: return false;
0846: }
0847: }
0848:
0849: public void removeHeader(String key) {
0850: if (_disableHeaders)
0851: return;
0852:
0853: for (int i = _headerKeys.size() - 1; i >= 0; i--) {
0854: String oldKey = (String) _headerKeys.get(i);
0855:
0856: if (oldKey.equalsIgnoreCase(key)) {
0857: _headerKeys.remove(i);
0858: _headerValues.remove(i);
0859: return;
0860: }
0861: }
0862: }
0863:
0864: /**
0865: * Convenience for setting an integer header. An old header with the
0866: * same name will be replaced.
0867: *
0868: * @param name the header name.
0869: * @param value an integer to be converted to a string for the header.
0870: */
0871: public void setIntHeader(String name, int value) {
0872: _cb.clear();
0873: _cb.append(value);
0874: setHeader(name, _cb.toString());
0875: }
0876:
0877: /**
0878: * Convenience for adding an integer header. If an old header already
0879: * exists, both will be sent to the browser.
0880: *
0881: * @param key the header name.
0882: * @param value an integer to be converted to a string for the header.
0883: */
0884: public void addIntHeader(String key, int value) {
0885: _cb.clear();
0886: _cb.append(value);
0887: addHeader(key, _cb.toString());
0888: }
0889:
0890: /**
0891: * Convenience for setting a date header. An old header with the
0892: * same name will be replaced.
0893: *
0894: * @param name the header name.
0895: * @param value an time in milliseconds to be converted to a date string.
0896: */
0897: public void setDateHeader(String name, long value) {
0898: _calendar.setGMTTime(value);
0899:
0900: setHeader(name, _calendar.printDate());
0901: }
0902:
0903: /**
0904: * Convenience for adding a date header. If an old header with the
0905: * same name exists, both will be displayed.
0906: *
0907: * @param key the header name.
0908: * @param value an time in milliseconds to be converted to a date string.
0909: */
0910: public void addDateHeader(String key, long value) {
0911: _calendar.setGMTTime(value);
0912:
0913: addHeader(key, _calendar.printDate());
0914: }
0915:
0916: /**
0917: * Sets the content length of the result. In general, Resin will handle
0918: * the content length, but for things like long downloads adding the
0919: * length will give a valuable hint to the browser.
0920: *
0921: * @param length the length of the content.
0922: */
0923: public void setContentLength(int length) {
0924: _contentLength = length;
0925: }
0926:
0927: /**
0928: * Returns the value of the content-length header.
0929: */
0930: public long getContentLengthHeader() {
0931: return _contentLength;
0932: }
0933:
0934: /**
0935: * Sets the browser content type. If the value contains a charset,
0936: * the output encoding will be changed to match.
0937: *
0938: * <p>For example, to set the output encoding to use UTF-8 instead of
0939: * the default ISO-8859-1 (Latin-1), use the following:
0940: * <code><pre>
0941: * setContentType("text/html; charset=UTF-8");
0942: * </pre></code>
0943: */
0944: public void setContentType(String value) {
0945: if (isCommitted())
0946: return;
0947:
0948: if (_disableHeaders || value == null) {
0949: _contentType = null;
0950: return;
0951: } else if (value == "text/html" || value.equals("text/html")) {
0952: _contentType = "text/html";
0953: return;
0954: }
0955:
0956: _contentType = value;
0957:
0958: int length = value.length();
0959: int i;
0960: int ch;
0961:
0962: for (i = 0; i < length && value.charAt(i) != ';'
0963: && !Character.isWhitespace(value.charAt(i)); i++) {
0964: }
0965:
0966: if (i < length)
0967: _contentPrefix = _contentType.substring(0, i);
0968: else
0969: _contentPrefix = _contentType;
0970:
0971: while ((i = value.indexOf(';', i)) > 0) {
0972: int semicolon = i;
0973: for (i++; i < length
0974: && XmlChar.isWhitespace(value.charAt(i)); i++) {
0975: }
0976:
0977: int j;
0978: for (j = i + 1; j < length
0979: && !XmlChar.isWhitespace((ch = value.charAt(j)))
0980: && ch != '='; j++) {
0981: }
0982:
0983: if (length <= j)
0984: break;
0985: else if ((ch = value.charAt(i)) != 'c' && ch != 'C') {
0986: } else if (value.substring(i, j)
0987: .equalsIgnoreCase("charset")) {
0988: for (; j < length
0989: && XmlChar.isWhitespace(value.charAt(j)); j++) {
0990: }
0991:
0992: if (length <= j || value.charAt(j) != '=')
0993: continue;
0994:
0995: for (j++; j < length
0996: && XmlChar.isWhitespace(value.charAt(j)); j++) {
0997: }
0998:
0999: String encoding;
1000:
1001: if (j < length && value.charAt(j) == '"') {
1002: int k = ++j;
1003:
1004: for (; j < length && value.charAt(j) != '"'; j++) {
1005: }
1006:
1007: encoding = value.substring(k, j);
1008: } else {
1009: int k = j;
1010: for (k = j; j < length
1011: && !XmlChar.isWhitespace(ch = value
1012: .charAt(j)) && ch != ';'; j++) {
1013: }
1014:
1015: encoding = value.substring(k, j);
1016: }
1017:
1018: int tail = value.indexOf(';', semicolon + 1);
1019:
1020: StringBuilder sb = new StringBuilder();
1021: sb.append(value, 0, semicolon);
1022: if (tail > 0)
1023: sb.append(value, tail, value.length());
1024:
1025: _contentType = sb.toString();
1026:
1027: if (!_hasWriter) {
1028: _hasCharEncoding = true;
1029: _charEncoding = encoding;
1030: }
1031: break;
1032: } else
1033: i = j;
1034: }
1035:
1036: // XXX: conflict with servlet exception throwing order?
1037: try {
1038: _responseStream.setEncoding(_charEncoding);
1039: } catch (Exception e) {
1040: log.log(Level.WARNING, e.toString(), e);
1041: }
1042: }
1043:
1044: /**
1045: * Gets the content type.
1046: */
1047: public String getContentType() {
1048: if (_contentType == null)
1049: return null;
1050:
1051: String charEncoding = getCharacterEncoding();
1052:
1053: if (charEncoding != null)
1054: return _contentType + "; charset=" + charEncoding;
1055: else
1056: return _contentType;
1057: }
1058:
1059: /**
1060: * Gets the character encoding.
1061: */
1062: public String getCharacterEncoding() {
1063: if (_charEncoding != null)
1064: return _charEncoding;
1065:
1066: WebApp app = _request.getWebApp();
1067:
1068: String encoding = null;
1069:
1070: if (app != null)
1071: encoding = app.getCharacterEncoding();
1072:
1073: if (encoding != null)
1074: return encoding;
1075: else
1076: return "iso-8859-1";
1077: }
1078:
1079: /**
1080: * Sets the character encoding.
1081: */
1082: public void setCharacterEncoding(String encoding) {
1083: if (isCommitted())
1084: return;
1085: if (_hasWriter)
1086: return;
1087:
1088: _hasCharEncoding = true;
1089: if (encoding == null || encoding.equals("ISO-8859-1")
1090: || encoding.equals("")) {
1091: encoding = null;
1092: _charEncoding = "iso-8859-1";
1093: } else
1094: _charEncoding = encoding;
1095:
1096: try {
1097: _responseStream.setEncoding(encoding);
1098: } catch (Exception e) {
1099: log.log(Level.WARNING, e.toString(), e);
1100: }
1101: }
1102:
1103: String getRealCharacterEncoding() {
1104: return _charEncoding;
1105: }
1106:
1107: /**
1108: * Adds a cookie to the response.
1109: *
1110: * @param cookie the response cookie
1111: */
1112: public void addCookie(Cookie cookie) {
1113: _request.setHasCookie();
1114:
1115: if (_disableHeaders)
1116: return;
1117:
1118: if (cookie == null)
1119: return;
1120:
1121: _cookiesOut.add(cookie);
1122: }
1123:
1124: public Cookie getCookie(String name) {
1125: if (_cookiesOut == null)
1126: return null;
1127:
1128: for (int i = _cookiesOut.size() - 1; i >= 0; i--) {
1129: Cookie cookie = (Cookie) _cookiesOut.get(i);
1130:
1131: if (cookie.getName().equals(name))
1132: return cookie;
1133: }
1134:
1135: return null;
1136: }
1137:
1138: public ArrayList getCookies() {
1139: return _cookiesOut;
1140: }
1141:
1142: public void setSessionId(String id) {
1143: _sessionId = id;
1144:
1145: // XXX: server/1315 vs server/0506 vs server/170k
1146: // could also set the nocache=JSESSIONID
1147: setPrivateOrResinCache(true);
1148: }
1149:
1150: /**
1151: * Sets a footer, replacing an already-existing footer
1152: *
1153: * @param key the header key to set.
1154: * @param value the header value to set.
1155: */
1156: public void setFooter(String key, String value) {
1157: if (_disableHeaders)
1158: return;
1159: else if (value == null)
1160: throw new NullPointerException();
1161:
1162: int i = 0;
1163: boolean hasFooter = false;
1164:
1165: for (i = _footerKeys.size() - 1; i >= 0; i--) {
1166: String oldKey = _footerKeys.get(i);
1167:
1168: if (oldKey.equalsIgnoreCase(key)) {
1169: if (hasFooter) {
1170: _footerKeys.remove(i);
1171: _footerValues.remove(i);
1172: } else {
1173: hasFooter = true;
1174:
1175: _footerValues.set(i, value);
1176: }
1177: }
1178: }
1179:
1180: if (!hasFooter) {
1181: _footerKeys.add(key);
1182: _footerValues.add(value);
1183: }
1184: }
1185:
1186: /**
1187: * Adds a new footer. If an old footer with that name exists,
1188: * both footers are output.
1189: *
1190: * @param key the footer key.
1191: * @param value the footer value.
1192: */
1193: public void addFooter(String key, String value) {
1194: if (_disableHeaders)
1195: return;
1196:
1197: if (setSpecial(key, value))
1198: return;
1199:
1200: _footerKeys.add(key);
1201: _footerValues.add(value);
1202: }
1203:
1204: /**
1205: * Sets the ResponseStream
1206: */
1207: public void setResponseStream(AbstractResponseStream responseStream) {
1208: _responseStream = responseStream;
1209:
1210: _responseOutputStream.init(responseStream);
1211: _responsePrintWriter.init(responseStream);
1212: }
1213:
1214: /**
1215: * Gets the response stream.
1216: */
1217: public AbstractResponseStream getResponseStream() {
1218: return _responseStream;
1219: }
1220:
1221: /**
1222: * Gets the response stream.
1223: */
1224: public AbstractResponseStream getOriginalStream() {
1225: return _originalResponseStream;
1226: }
1227:
1228: /**
1229: * Returns true for a Caucho response stream.
1230: */
1231: public boolean isCauchoResponseStream() {
1232: return _responseStream.isCauchoResponseStream();
1233: }
1234:
1235: /**
1236: * Returns the ServletOutputStream for the response.
1237: */
1238: public ServletOutputStream getOutputStream() throws IOException {
1239: /*
1240: if (_hasWriter)
1241: throw new IllegalStateException(L.l("getOutputStream() can't be called after getWriter()."));
1242: */
1243:
1244: _hasOutputStream = true;
1245:
1246: /*
1247: // server/10a2
1248: if (! _hasWriter) {
1249: // jsp/0510 vs jsp/1b00
1250: // _responseStream.setOutputStreamOnly(true);
1251: }
1252: */
1253:
1254: return _responseOutputStream;
1255: }
1256:
1257: /**
1258: * Sets the flush buffer
1259: */
1260: public void setFlushBuffer(FlushBuffer flushBuffer) {
1261: _flushBuffer = flushBuffer;
1262: }
1263:
1264: /**
1265: * Gets the flush buffer
1266: */
1267: public FlushBuffer getFlushBuffer() {
1268: return _flushBuffer;
1269: }
1270:
1271: /**
1272: * Returns a PrintWriter for the response.
1273: */
1274: public PrintWriter getWriter() throws IOException {
1275: /*
1276: if (_hasOutputStream)
1277: throw new IllegalStateException(L.l("getWriter() can't be called after getOutputStream()."));
1278: */
1279:
1280: if (!_hasWriter) {
1281: _hasWriter = true;
1282:
1283: if (_charEncoding != null)
1284: _responseStream.setEncoding(_charEncoding);
1285: }
1286:
1287: return _responsePrintWriter;
1288: }
1289:
1290: /**
1291: * Returns the parent writer.
1292: */
1293: public PrintWriter getNextWriter() {
1294: return null;
1295: }
1296:
1297: /**
1298: * Encode the URL with the session jd.
1299: *
1300: * @param string the url to be encoded
1301: *
1302: * @return the encoded url
1303: */
1304: public String encodeURL(String string) {
1305: CauchoRequest request = getRequest();
1306:
1307: WebApp app = request.getWebApp();
1308:
1309: if (app == null)
1310: return string;
1311:
1312: if (request.isRequestedSessionIdFromCookie())
1313: return string;
1314:
1315: HttpSession session = request.getSession(false);
1316: if (session == null)
1317: return string;
1318:
1319: SessionManager sessionManager = app.getSessionManager();
1320: if (!sessionManager.enableSessionUrls())
1321: return string;
1322:
1323: CharBuffer cb = _cb;
1324: cb.clear();
1325:
1326: String altPrefix = sessionManager.getAlternateSessionPrefix();
1327:
1328: if (altPrefix == null) {
1329: // standard url rewriting
1330: int p = string.indexOf('?');
1331:
1332: if (p == 0) {
1333: cb.append(string);
1334: } else if (p > 0) {
1335: cb.append(string, 0, p);
1336: cb.append(sessionManager.getSessionPrefix());
1337: cb.append(session.getId());
1338: cb.append(string, p, string.length() - p);
1339: } else if ((p = string.indexOf('#')) >= 0) {
1340: cb.append(string, 0, p);
1341: cb.append(sessionManager.getSessionPrefix());
1342: cb.append(session.getId());
1343: cb.append(string, p, string.length() - p);
1344: } else {
1345: cb.append(string);
1346: cb.append(sessionManager.getSessionPrefix());
1347: cb.append(session.getId());
1348: }
1349: } else {
1350: int p = string.indexOf("://");
1351:
1352: if (p < 0) {
1353: cb.append(altPrefix);
1354: cb.append(session.getId());
1355:
1356: if (!string.startsWith("/")) {
1357: cb.append(_request.getContextPath());
1358: cb.append('/');
1359: }
1360: cb.append(string);
1361: } else {
1362: int q = string.indexOf('/', p + 3);
1363:
1364: if (q < 0) {
1365: cb.append(string);
1366: cb.append(altPrefix);
1367: cb.append(session.getId());
1368: } else {
1369: cb.append(string.substring(0, q));
1370: cb.append(altPrefix);
1371: cb.append(session.getId());
1372: cb.append(string.substring(q));
1373: }
1374: }
1375: }
1376:
1377: return cb.toString();
1378: }
1379:
1380: public String encodeRedirectURL(String string) {
1381: return encodeURL(string);
1382: }
1383:
1384: /**
1385: * @deprecated
1386: */
1387: public String encodeRedirectUrl(String string) {
1388: return encodeRedirectURL(string);
1389: }
1390:
1391: /**
1392: * @deprecated
1393: */
1394: public String encodeUrl(String string) {
1395: return encodeURL(string);
1396: }
1397:
1398: /*
1399: * jsdk 2.2
1400: */
1401:
1402: public void setBufferSize(int size) {
1403: _responseStream.setBufferSize(size);
1404: }
1405:
1406: public int getBufferSize() {
1407: return _responseStream.getBufferSize();
1408: }
1409:
1410: public void flushBuffer() throws IOException {
1411: // server/10sn
1412: _responseStream.flush();
1413: }
1414:
1415: public void flushHeader() throws IOException {
1416: _responseStream.flushBuffer();
1417: }
1418:
1419: public void setDisableAutoFlush(boolean disable) {
1420: // XXX: _responseStream.setDisableAutoFlush(disable);
1421: }
1422:
1423: /**
1424: * Returns true if some data has been sent to the browser.
1425: */
1426: public boolean isCommitted() {
1427: return _originalResponseStream.isCommitted();
1428: }
1429:
1430: public void reset() {
1431: reset(false);
1432: }
1433:
1434: public void resetBuffer() {
1435: _responseStream.clearBuffer();
1436: /*
1437: if (_currentWriter instanceof JspPrintWriter)
1438: ((JspPrintWriter) _currentWriter).clear();
1439: */
1440: }
1441:
1442: /**
1443: * Clears the response for a forward()
1444: *
1445: * @param force if not true and the response stream has committed,
1446: * throw the IllegalStateException.
1447: */
1448: void reset(boolean force) {
1449: if (!force && _originalResponseStream.isCommitted())
1450: throw new IllegalStateException(L
1451: .l("response cannot be reset() after committed"));
1452:
1453: _responseStream.clearBuffer();
1454: /*
1455: if (_currentWriter instanceof JspPrintWriter)
1456: ((JspPrintWriter) _currentWriter).clear();
1457: */
1458: _statusCode = 200;
1459: _statusMessage = "OK";
1460:
1461: _headerKeys.clear();
1462: _headerValues.clear();
1463:
1464: // cookiesOut.clear();
1465:
1466: _contentLength = -1;
1467: //_isNoCache = false;
1468: //_isPrivateCache = false;
1469:
1470: _charEncoding = null;
1471: _locale = null;
1472:
1473: _hasOutputStream = false;
1474: _hasWriter = false;
1475: try {
1476: _responseStream.setLocale(null);
1477: _responseStream.setEncoding(null);
1478: } catch (Exception e) {
1479: }
1480: }
1481:
1482: // XXX: hack to deal with forwarding
1483: public void clearBuffer() {
1484: _responseStream.clearBuffer();
1485: }
1486:
1487: public void setLocale(Locale locale) {
1488: _locale = locale;
1489:
1490: if (!_hasCharEncoding && !isCommitted()) {
1491: _charEncoding = getRequest().getWebApp().getLocaleEncoding(
1492: locale);
1493:
1494: try {
1495: if (_charEncoding != null) {
1496: // _originalStream.setEncoding(_charEncoding);
1497: _responseStream.setEncoding(_charEncoding);
1498: }
1499: } catch (IOException e) {
1500: }
1501: }
1502:
1503: CharBuffer cb = _cb;
1504: cb.clear();
1505: cb.append(locale.getLanguage());
1506: if (locale.getCountry() != null
1507: && !"".equals(locale.getCountry())) {
1508: cb.append("-");
1509: cb.append(locale.getCountry());
1510: if (locale.getVariant() != null
1511: && !"".equals(locale.getVariant())) {
1512: cb.append("-");
1513: cb.append(locale.getVariant());
1514: }
1515: }
1516:
1517: setHeader("Content-Language", cb.toString());
1518: }
1519:
1520: public Locale getLocale() {
1521: if (_locale != null)
1522: return _locale;
1523: else
1524: return Locale.getDefault();
1525: }
1526:
1527: // needed to support JSP
1528:
1529: public int getRemaining() {
1530: return _responseStream.getRemaining();
1531: }
1532:
1533: /**
1534: * Returns the number of bytes sent to the output.
1535: */
1536: public int getContentLength() {
1537: return _originalResponseStream.getContentLength();
1538: }
1539:
1540: public boolean disableHeaders(boolean disable) {
1541: boolean old = _disableHeaders;
1542: _disableHeaders = disable;
1543: return old;
1544: }
1545:
1546: public boolean disableCaching(boolean disable) {
1547: boolean old = _disableCaching;
1548: _disableCaching = disable;
1549: return old;
1550: }
1551:
1552: /**
1553: * Returns true if the headers have been written.
1554: */
1555: final public boolean isHeaderWritten() {
1556: return _isHeaderWritten;
1557: }
1558:
1559: /**
1560: * Returns true if the headers have been written.
1561: */
1562: final public void setHeaderWritten(boolean isWritten) {
1563: _isHeaderWritten = isWritten;
1564: }
1565:
1566: /**
1567: * Writes the continue
1568: */
1569: final void writeContinue() throws IOException {
1570: if (!_isHeaderWritten) {
1571: writeContinueInt(_rawWrite);
1572: _rawWrite.flush();
1573: }
1574: }
1575:
1576: /**
1577: * Writes the continue
1578: */
1579: protected void writeContinueInt(WriteStream os) throws IOException {
1580: }
1581:
1582: /**
1583: * Writes the headers to the stream. Called prior to the first flush
1584: * of data.
1585: *
1586: * @param os browser stream.
1587: * @param length length of the response if known, or -1 is unknown.
1588: * @return true if the content is chunked.
1589: */
1590: protected boolean writeHeaders(WriteStream os, int length)
1591: throws IOException {
1592: if (_isHeaderWritten)
1593: return _isChunked;
1594:
1595: // server/1373 for getBufferSize()
1596: boolean canCache = startCaching(true);
1597: _isHeaderWritten = true;
1598:
1599: if (_request.getMethod().equals("HEAD")) {
1600: _originalResponseStream.setHead();
1601: }
1602:
1603: WebApp webApp = _request.getWebApp();
1604:
1605: int majorCode = _statusCode / 100;
1606:
1607: if (webApp != null) {
1608: if (majorCode == 5)
1609: webApp.addStatus500();
1610: }
1611:
1612: HttpSession session = _originalRequest.getMemorySession();
1613: if (session instanceof SessionImpl)
1614: ((SessionImpl) session).saveBeforeHeaders();
1615:
1616: if (_sessionId != null && !_hasSessionCookie) {
1617: _hasSessionCookie = true;
1618:
1619: SessionManager manager = webApp.getSessionManager();
1620:
1621: String cookieName;
1622:
1623: if (_request.isSecure())
1624: cookieName = manager.getSSLCookieName();
1625: else
1626: cookieName = manager.getCookieName();
1627:
1628: CookieImpl cookie = new CookieImpl(cookieName, _sessionId);
1629: cookie.setVersion(manager.getCookieVersion());
1630: String domain = manager.getCookieDomain();
1631: if (domain != null)
1632: cookie.setDomain(domain);
1633: long maxAge = manager.getCookieMaxAge();
1634: if (maxAge > 0)
1635: cookie.setMaxAge((int) (maxAge / 1000));
1636: cookie.setPath("/");
1637:
1638: cookie.setPort(manager.getCookiePort());
1639: if (manager.getCookieSecure()) {
1640: cookie.setSecure(_request.isSecure());
1641: /*
1642: else if (manager.getCookiePort() == null)
1643: cookie.setPort(String.valueOf(_request.getServerPort()));
1644: */
1645: }
1646:
1647: addCookie(cookie);
1648: }
1649:
1650: _isChunked = writeHeadersInt(os, length);
1651:
1652: return _isChunked;
1653: }
1654:
1655: /**
1656: * Called to start caching.
1657: */
1658: protected boolean startCaching(boolean isByte) {
1659: if (_isHeaderWritten)
1660: return false;
1661: _isHeaderWritten = true;
1662:
1663: if (_statusCode == SC_OK && !_disableCaching) // && getBufferSize() > 0)
1664: return startCaching(_headerKeys, _headerValues,
1665: _contentType, _charEncoding, isByte);
1666: else
1667: return false;
1668: }
1669:
1670: /**
1671: * Tests to see if the response is cacheable.
1672: *
1673: * @param keys the header keys of the response
1674: * @param values the header values of the response
1675: * @param contentType the contentType of the response
1676: * @param charEncoding the character encoding of the response
1677: *
1678: * @return true if caching has started
1679: */
1680: boolean startCaching(ArrayList<String> keys,
1681: ArrayList<String> values, String contentType,
1682: String charEncoding, boolean isByte) {
1683: if (_cacheInvocation == null)
1684: return false;
1685: /*
1686: // jsp/17ah
1687: else if (_responseStream != _originalResponseStream) {
1688: return false;
1689: }
1690: */
1691: else if (!isCauchoResponseStream()) {
1692: return false;
1693: } else if (!(_originalRequest instanceof CauchoRequest)) {
1694: return false;
1695: } else if (!_allowCache) {
1696: return false;
1697: } else {
1698: CauchoRequest request = (CauchoRequest) _originalRequest;
1699:
1700: _newCacheEntry = _cacheInvocation.startCaching(request,
1701: this , keys, values, contentType, charEncoding,
1702: _contentLength);
1703:
1704: if (_newCacheEntry == null) {
1705: return false;
1706: } else if (isByte) {
1707: _cacheStream = _newCacheEntry.openOutputStream();
1708:
1709: if (_cacheStream != null)
1710: _originalResponseStream
1711: .setByteCacheStream(_cacheStream);
1712:
1713: return _cacheStream != null;
1714: } else {
1715: _cacheWriter = _newCacheEntry.openWriter();
1716:
1717: if (_cacheWriter != null)
1718: _originalResponseStream
1719: .setCharCacheStream(_cacheWriter);
1720:
1721: return _cacheWriter != null;
1722: }
1723: }
1724: }
1725:
1726: /**
1727: * Handle a SC_NOT_MODIFIED response. If we've got a cache, fill the
1728: * results from the cache.
1729: *
1730: * @param isTop if true, this is the top-level request.
1731: *
1732: * @return true if we filled from the cache
1733: */
1734: private boolean handleNotModified(boolean isTop) throws IOException {
1735: if (_statusCode != SC_NOT_MODIFIED) {
1736: return false;
1737: } else if (_matchCacheEntry != null) {
1738: if (_originalResponseStream.isCommitted())
1739: return false;
1740:
1741: // need to unclose because the not modified might be detected only
1742: // when flushing the data
1743: _originalResponseStream.clearClosed();
1744: _isClosed = false;
1745:
1746: /* XXX: complications with filters */
1747: if (_cacheInvocation != null
1748: && _cacheInvocation.fillFromCache(
1749: (CauchoRequest) _originalRequest, this ,
1750: _matchCacheEntry, isTop)) {
1751: _matchCacheEntry.updateExpiresDate();
1752: _cacheInvocation = null;
1753: _matchCacheEntry = null;
1754:
1755: finish(); // Don't force a flush to avoid extra TCP packet
1756:
1757: return true;
1758: }
1759: }
1760: // server/13dh
1761: else if (_cacheInvocation != null) {
1762: CauchoRequest req = (CauchoRequest) _originalRequest;
1763: WebApp app = req.getWebApp();
1764:
1765: long maxAge = app.getMaxAge(req.getRequestURI());
1766:
1767: if (maxAge > 0 && !containsHeader("Expires")) {
1768: setDateHeader("Expires", maxAge
1769: + Alarm.getCurrentTime());
1770: }
1771: }
1772:
1773: return false;
1774: }
1775:
1776: abstract protected boolean writeHeadersInt(WriteStream os,
1777: int length) throws IOException;
1778:
1779: /**
1780: * Sets true if the cache is only for the browser, but not
1781: * Resin's cache or proxies.
1782: *
1783: * <p>Since proxy caching also caches headers, cached pages with
1784: * session ids can't be cached in the browser.
1785: *
1786: * XXX: but doesn't this just mean that Resin shouldn't
1787: * send the session information back if the page is cached?
1788: * Because a second request where everything is identical
1789: * would see the same response except for the cookies.
1790: */
1791: public void setPrivateCache(boolean isPrivate) {
1792: // XXX: let the webApp override this?
1793: _isPrivateCache = isPrivate;
1794:
1795: // server/12dm
1796: _allowCache = false;
1797: }
1798:
1799: /**
1800: * Sets true if the cache is only for the browser and
1801: * Resin's cache but not proxies.
1802: */
1803: public void setPrivateOrResinCache(boolean isPrivate) {
1804: // XXX: let the webApp override this?
1805:
1806: _isPrivateCache = isPrivate;
1807: }
1808:
1809: /**
1810: * Returns the value of the private cache.
1811: */
1812: public boolean getPrivateCache() {
1813: return _isPrivateCache;
1814: }
1815:
1816: /**
1817: * Returns true if the response should contain a Cache-Control: private
1818: */
1819: protected boolean isPrivateCache() {
1820: return !_hasCacheControl && _isPrivateCache;
1821: }
1822:
1823: /**
1824: * Set if the page is non-cacheable.
1825: */
1826: public void setNoCache(boolean isNoCache) {
1827: _isNoCache = isNoCache;
1828: }
1829:
1830: /**
1831: * Returns true if the page is non-cacheable
1832: */
1833: public boolean isNoCache() {
1834: return _isNoCache;
1835: }
1836:
1837: /**
1838: * Set if the page is non-cacheable.
1839: */
1840: public void killCache() {
1841: _allowCache = false;
1842:
1843: // server/1b15
1844: // setNoCache(true);
1845: }
1846:
1847: /**
1848: * Fills the response for a cookie
1849: *
1850: * @param cb result buffer to contain the generated string
1851: * @param cookie the cookie
1852: * @param date the current date
1853: * @param version the cookies version
1854: */
1855: public boolean fillCookie(CharBuffer cb, Cookie cookie, long date,
1856: int version, boolean isCookie2) {
1857: // How to deal with quoted values? Old browsers can't deal with
1858: // the quotes.
1859:
1860: cb.clear();
1861: cb.append(cookie.getName());
1862: if (isCookie2) {
1863: cb.append("=\"");
1864: cb.append(cookie.getValue());
1865: cb.append("\"");
1866: } else {
1867: cb.append("=");
1868: cb.append(cookie.getValue());
1869: }
1870:
1871: String domain = cookie.getDomain();
1872: if (domain != null && !domain.equals("")) {
1873: if (isCookie2) {
1874: cb.append("; Domain=");
1875:
1876: cb.append('"');
1877: cb.append(domain);
1878: cb.append('"');
1879: } else {
1880: cb.append("; domain=");
1881: cb.append(domain);
1882: }
1883: }
1884:
1885: String path = cookie.getPath();
1886: if (path != null && !path.equals("")) {
1887: if (isCookie2) {
1888: cb.append("; Path=");
1889:
1890: cb.append('"');
1891: cb.append(path);
1892: cb.append('"');
1893: } else {
1894: // Caps from TCK test
1895: if (version > 0)
1896: cb.append("; Path=");
1897: else
1898: cb.append("; path=");
1899: cb.append(path);
1900: }
1901: }
1902:
1903: if (cookie.getSecure()) {
1904: if (version > 0)
1905: cb.append("; Secure");
1906: else
1907: cb.append("; secure");
1908: }
1909:
1910: int maxAge = cookie.getMaxAge();
1911: if (version > 0) {
1912: if (maxAge >= 0) {
1913: cb.append("; Max-Age=");
1914: cb.append(maxAge);
1915: }
1916:
1917: cb.append("; Version=");
1918: cb.append(version);
1919:
1920: if (cookie.getComment() != null) {
1921: cb.append("; Comment=\"");
1922: cb.append(cookie.getComment());
1923: cb.append("\"");
1924: }
1925:
1926: if (cookie instanceof CookieImpl) {
1927: CookieImpl extCookie = (CookieImpl) cookie;
1928: String port = extCookie.getPort();
1929:
1930: if (port != null && isCookie2) {
1931: cb.append("; Port=\"");
1932: cb.append(port);
1933: cb.append("\"");
1934: }
1935: }
1936: }
1937:
1938: if (isCookie2) {
1939: } else if (maxAge == 0) {
1940: cb.append("; expires=Thu, 01-Dec-1994 16:00:00 GMT");
1941: } else if (maxAge >= 0) {
1942: _calendar.setGMTTime(date + 1000L * (long) maxAge);
1943: cb.append("; expires=");
1944: cb.append(_calendar.format("%a, %d-%b-%Y %H:%M:%S GMT"));
1945: }
1946:
1947: WebApp app = _request.getWebApp();
1948: if (app.getCookieHttpOnly()) {
1949: cb.append("; HttpOnly");
1950: }
1951:
1952: return true;
1953: }
1954:
1955: protected ConnectionController getController() {
1956: if (_originalRequest instanceof AbstractHttpRequest) {
1957: AbstractHttpRequest request = (AbstractHttpRequest) _originalRequest;
1958: Connection conn = request.getConnection();
1959: return conn.getController();
1960: } else
1961: return null;
1962: }
1963:
1964: /**
1965: * Complete the request. Flushes the streams, completes caching
1966: * and writes the appropriate logs.
1967: */
1968: public void finish() throws IOException {
1969: finish(false);
1970: }
1971:
1972: /**
1973: * Complete the request. Flushes the streams, completes caching
1974: * and writes the appropriate logs.
1975: *
1976: * @param isClose true if the response should be flushed.
1977: */
1978: private void finish(boolean isClose) throws IOException {
1979: if (_isClosed)
1980: return;
1981:
1982: ConnectionController controller = null;
1983:
1984: try {
1985: if (_originalRequest instanceof AbstractHttpRequest) {
1986: AbstractHttpRequest request = (AbstractHttpRequest) _originalRequest;
1987:
1988: Connection conn = request.getConnection();
1989: controller = conn.getController();
1990:
1991: try {
1992: request.skip();
1993: } catch (BadRequestException e) {
1994: log.warning(e.toString());
1995: log.log(Level.FINE, e.toString(), e);
1996: } catch (Exception e) {
1997: log.log(Level.WARNING, e.toString(), e);
1998: }
1999: }
2000:
2001: if (_statusCode == SC_NOT_MODIFIED) {
2002: handleNotModified(_isTopCache);
2003: }
2004:
2005: if (controller != null && !controller.isClosed())
2006: isClose = false;
2007:
2008: // include() files finish too, but shouldn't force a flush, hence
2009: // flush is false
2010: // Never send flush?
2011: if (isClose)
2012: _responseStream.close();
2013: else if (_responseStream != _originalResponseStream)
2014: _responseStream.finish();
2015: else if (controller == null)
2016: _responseStream.finish();
2017: else
2018: _responseStream.flush();
2019:
2020: if (_responseStream != _originalResponseStream) {
2021: if (isClose)
2022: _originalResponseStream.close();
2023: else if (controller == null)
2024: _originalResponseStream.finish();
2025: }
2026:
2027: if (controller == null)
2028: _isClosed = true;
2029:
2030: if (_rawWrite == null) {
2031: }
2032: // server/0550
2033: // else if (isClose)
2034: // _rawWrite.close();
2035: else
2036: _rawWrite.flushBuffer();
2037:
2038: if (_cacheInvocation == null) {
2039: } else if (_newCacheEntry != null) {
2040: OutputStream cacheStream = _cacheStream;
2041: _cacheStream = null;
2042:
2043: Writer cacheWriter = _cacheWriter;
2044: _cacheWriter = null;
2045:
2046: if (cacheStream != null)
2047: cacheStream.close();
2048:
2049: if (cacheWriter != null)
2050: cacheWriter.close();
2051:
2052: if (_statusCode == 200 && _allowCache) {
2053: AbstractCacheFilterChain cache = _cacheInvocation;
2054: _cacheInvocation = null;
2055:
2056: AbstractCacheEntry cacheEntry = _newCacheEntry;
2057: _newCacheEntry = null;
2058:
2059: cache.finishCaching(cacheEntry);
2060: }
2061: }
2062: } catch (ClientDisconnectException e) {
2063: _request.killKeepalive();
2064:
2065: if (isIgnoreClientDisconnect())
2066: log.fine(e.toString());
2067: else
2068: throw e;
2069: } catch (IOException e) {
2070: _request.killKeepalive();
2071:
2072: throw e;
2073: } finally {
2074: if (controller == null)
2075: _isClosed = true;
2076:
2077: AbstractCacheFilterChain cache = _cacheInvocation;
2078: _cacheInvocation = null;
2079:
2080: AbstractCacheEntry cacheEntry = _newCacheEntry;
2081: _newCacheEntry = null;
2082:
2083: _cacheStream = null;
2084: _cacheWriter = null;
2085:
2086: if (cacheEntry != null)
2087: cache.killCaching(cacheEntry);
2088: }
2089: }
2090:
2091: public void killCaching() {
2092: AbstractCacheFilterChain cache = _cacheInvocation;
2093: _cacheInvocation = null;
2094:
2095: AbstractCacheEntry cacheEntry = _newCacheEntry;
2096: _newCacheEntry = null;
2097:
2098: if (cacheEntry != null)
2099: cache.killCaching(cacheEntry);
2100: }
2101:
2102: TempBuffer getBuffer() {
2103: return _tempBuffer;
2104: }
2105:
2106: protected final QDate getCalendar() {
2107: return _calendar;
2108: }
2109:
2110: protected void free() {
2111: _request = null;
2112: _originalRequest = null;
2113: _cacheInvocation = null;
2114: _newCacheEntry = null;
2115: _matchCacheEntry = null;
2116: _cacheStream = null;
2117: _cacheWriter = null;
2118: }
2119:
2120: static {
2121: _errors = new HashMap<String, String>();
2122: _errors.put("100", "Continue");
2123: _errors.put("101", "Switching Protocols");
2124: _errors.put("200", "OK");
2125: _errors.put("201", "Created");
2126: _errors.put("202", "Accepted");
2127: _errors.put("203", "Non-Authoritative Information");
2128: _errors.put("204", "No Content");
2129: _errors.put("205", "Reset Content");
2130: _errors.put("206", "Partial Content");
2131: _errors.put("300", "Multiple Choices");
2132: _errors.put("301", "Moved Permanently");
2133: _errors.put("302", "Found");
2134: _errors.put("303", "See Other");
2135: _errors.put("304", "Not Modified");
2136: _errors.put("305", "Use Proxy");
2137: _errors.put("307", "Temporary Redirect");
2138: _errors.put("400", "Bad Request");
2139: _errors.put("401", "Unauthorized");
2140: _errors.put("402", "Payment Required");
2141: _errors.put("403", "Forbidden");
2142: _errors.put("404", "Not Found");
2143: _errors.put("405", "Method Not Allowed");
2144: _errors.put("406", "Not Acceptable");
2145: _errors.put("407", "Proxy Authentication Required");
2146: _errors.put("408", "Request Timeout");
2147: _errors.put("409", "Conflict");
2148: _errors.put("410", "Gone");
2149: _errors.put("411", "Length Required");
2150: _errors.put("412", "Precondition Failed");
2151: _errors.put("413", "Request Entity Too Large");
2152: _errors.put("414", "Request-URI Too Long");
2153: _errors.put("415", "Unsupported Media Type");
2154: _errors.put("416", "Requested Range Not Satisfiable");
2155: _errors.put("417", "Expectation Failed");
2156: _errors.put("500", "Internal Server Error");
2157: _errors.put("501", "Not Implemented");
2158: _errors.put("502", "Bad Gateway");
2159: _errors.put("503", "Service Temporarily Unavailable");
2160: _errors.put("504", "Gateway Timeout");
2161: _errors.put("505", "Http Version Not Supported");
2162:
2163: _headerCodes = new CaseInsensitiveIntMap();
2164: _headerCodes.put("cache-control", HEADER_CACHE_CONTROL);
2165: _headerCodes.put("connection", HEADER_CONNECTION);
2166: _headerCodes.put("content-type", HEADER_CONTENT_TYPE);
2167: _headerCodes.put("content-length", HEADER_CONTENT_LENGTH);
2168: _headerCodes.put("date", HEADER_DATE);
2169: _headerCodes.put("server", HEADER_SERVER);
2170: }
2171: }
|