001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.server.http;
031:
032: import com.caucho.server.cluster.Server;
033: import com.caucho.server.connection.AbstractHttpRequest;
034: import com.caucho.server.connection.AbstractHttpResponse;
035: import com.caucho.server.webapp.WebApp;
036: import com.caucho.util.Alarm;
037: import com.caucho.util.CharBuffer;
038: import com.caucho.vfs.WriteStream;
039:
040: import javax.servlet.http.Cookie;
041: import java.io.IOException;
042: import java.util.logging.Level;
043:
044: public class HttpResponse extends AbstractHttpResponse {
045: static final byte[] _http10ok = "HTTP/1.0 200 OK".getBytes();
046: static final byte[] _http11ok = "HTTP/1.1 200 OK".getBytes();
047: static final byte[] _contentLengthBytes = "\r\nContent-Length: "
048: .getBytes();
049: static final byte[] _contentTypeBytes = "\r\nContent-Type: "
050: .getBytes();
051: static final byte[] _textHtmlBytes = "\r\nContent-Type: text/html"
052: .getBytes();
053: static final byte[] _charsetBytes = "; charset=".getBytes();
054: static final byte[] _textHtmlLatin1Bytes = "\r\nContent-Type: text/html; charset=iso-8859-1"
055: .getBytes();
056:
057: static final byte[] _connectionCloseBytes = "\r\nConnection: close"
058: .getBytes();
059:
060: final byte[] _resinServerBytes;
061:
062: static final char[] _connectionCb = "Connection".toCharArray();
063: static final CharBuffer _closeCb = new CharBuffer("Close");
064:
065: private final HttpRequest _request;
066:
067: private final byte[] _dateBuffer = new byte[256];
068: private int _dateBufferLength;
069: private final CharBuffer _dateCharBuffer = new CharBuffer();
070: private long _lastDate;
071:
072: /**
073: * Creates a new HTTP-protocol response.
074: *
075: * @param request the matching request object.
076: */
077: HttpResponse(HttpRequest request) {
078: super (request);
079:
080: _request = request;
081:
082: Server server = (Server) request.getDispatchServer();
083:
084: _resinServerBytes = ("\r\nServer: " + server.getServerHeader())
085: .getBytes();
086: }
087:
088: /**
089: * Switch to raw socket mode.
090: */
091: public void switchToRaw() throws IOException {
092: clearBuffer();
093:
094: setStatus(101);
095:
096: finish(); // don't need to flush since it'll close anyway
097: }
098:
099: /**
100: * Switch to raw socket mode.
101: */
102: public WriteStream getRawOutput() throws IOException {
103: return _rawWrite;
104: }
105:
106: /**
107: * Return true for the top request.
108: */
109: public boolean isTop() {
110: if (!(_request instanceof AbstractHttpRequest))
111: return false;
112: else {
113: return ((AbstractHttpRequest) _request).isTop();
114: }
115: }
116:
117: /**
118: * Writes the 100 continue response.
119: */
120: protected void writeContinueInt(WriteStream os) throws IOException {
121: os.print("HTTP/1.1 100 Continue");
122:
123: if (!containsHeader("Server"))
124: os.write(_resinServerBytes, 0, _resinServerBytes.length);
125:
126: os.print("\r\nContent-Length: 0");
127:
128: long now = Alarm.getCurrentTime();
129: if (_lastDate + 1000 < now) {
130: fillDate(now);
131: }
132:
133: os.write(_dateBuffer, 0, _dateBufferLength);
134: os.flush();
135: }
136:
137: /**
138: * Implementation to write the HTTP headers. If the length is positive,
139: * it's a small request where the buffer contains the entire request,
140: * so the length is already known.
141: *
142: * @param os the output stream to write the headers to.
143: * @param length if non-negative, the length of the entire request.
144: *
145: * @return true if the data in the request should use chunked encoding.
146: */
147: protected boolean writeHeadersInt(WriteStream os, int length)
148: throws IOException {
149: boolean isChunked = false;
150:
151: int version = _request.getVersion();
152: boolean debug = log.isLoggable(Level.FINE);
153:
154: if (version < HttpRequest.HTTP_1_0) {
155: _request.killKeepalive();
156: return false;
157: }
158:
159: int statusCode = _statusCode;
160: if (statusCode == 200) {
161: if (version < HttpRequest.HTTP_1_1)
162: os.write(_http10ok, 0, _http10ok.length);
163: else
164: os.write(_http11ok, 0, _http11ok.length);
165: } else {
166: if (version < HttpRequest.HTTP_1_1)
167: os.print("HTTP/1.0 ");
168: else
169: os.print("HTTP/1.1 ");
170:
171: os.write((statusCode / 100) % 10 + '0');
172: os.write((statusCode / 10) % 10 + '0');
173: os.write(statusCode % 10 + '0');
174: os.write(' ');
175: os.print(_statusMessage);
176: }
177:
178: if (debug) {
179: log.fine(_request.dbgId() + "HTTP/1.1 " + _statusCode + " "
180: + _statusMessage);
181: }
182:
183: if (!containsHeader("Server"))
184: os.write(_resinServerBytes, 0, _resinServerBytes.length);
185:
186: if (statusCode >= 400) {
187: removeHeader("ETag");
188: removeHeader("Last-Modified");
189: }
190: // server/1b15
191: else if (_isNoCache) {
192: removeHeader("ETag");
193: removeHeader("Last-Modified");
194:
195: // even in case of 302, this may be needed for filters which
196: // automatically set cache headers
197: setHeader("Expires", "Thu, 01 Dec 1994 16:00:00 GMT");
198: os.print("\r\nCache-Control: no-cache");
199:
200: if (debug) {
201: log.fine(_request.dbgId() + ""
202: + "Expires: Thu, 01 Dec 1994 16:00:00 GMT");
203: }
204: } else if (!isPrivateCache()) {
205: } else if (HttpRequest.HTTP_1_1 <= version) {
206: // technically, this could be private="Set-Cookie,Set-Cookie2"
207: // but caches don't recognize it, so there's no real extra value
208: os.print("\r\nCache-Control: private");
209:
210: if (debug)
211: log.fine(_request.dbgId() + "Cache-Control: private");
212: } else if (!containsHeader("Cache-Control")) {
213: setHeader("Expires", "Thu, 01 Dec 1994 16:00:00 GMT");
214: os.print("\r\nCache-Control: no-cache");
215:
216: if (debug) {
217: log.fine(_request.dbgId() + ""
218: + "Expires: Thu, 01 Dec 1994 16:00:00 GMT");
219: }
220: }
221:
222: int size = _headerKeys.size();
223: for (int i = 0; i < size; i++) {
224: String key = (String) _headerKeys.get(i);
225: os.write('\r');
226: os.write('\n');
227: os.print(key);
228: os.write(':');
229: os.write(' ');
230: os.print((String) _headerValues.get(i));
231:
232: if (debug) {
233: log.fine(_request.dbgId() + "" + key + ": "
234: + _headerValues.get(i));
235: }
236: }
237:
238: long now = Alarm.getCurrentTime();
239: for (int i = 0; i < _cookiesOut.size(); i++) {
240: Cookie cookie = _cookiesOut.get(i);
241: int cookieVersion = cookie.getVersion();
242:
243: CharBuffer cb = _cb;
244: // XXX:
245: fillCookie(cb, cookie, now, cookieVersion, false);
246: os.print("\r\nSet-Cookie: ");
247: os.print(cb.getBuffer(), 0, cb.getLength());
248: if (cookieVersion > 0) {
249: fillCookie(cb, cookie, now, cookieVersion, true);
250: os.print("\r\nSet-Cookie2: ");
251: os.print(cb.getBuffer(), 0, cb.getLength());
252: }
253:
254: if (debug)
255: log.fine(_request.dbgId() + "Set-Cookie: " + cb);
256: }
257:
258: String contentType = _contentType;
259: if (contentType == null) {
260: } else if (!contentType.equals("text/html")) {
261: os.write(_contentTypeBytes, 0, _contentTypeBytes.length);
262: os.print(contentType);
263:
264: if (_charEncoding != null) {
265: os.write(_charsetBytes, 0, _charsetBytes.length);
266: os.print(_charEncoding);
267:
268: if (debug) {
269: log.fine(_request.dbgId() + "Content-Type: "
270: + contentType + "; charset="
271: + _charEncoding);
272: }
273: } else {
274: WebApp webApp = _request.getWebApp();
275: String charEncoding = (webApp != null ? webApp
276: .getCharacterEncoding() : null);
277:
278: if (charEncoding != null) {
279: os.write(_charsetBytes, 0, _charsetBytes.length);
280: os.print(charEncoding);
281:
282: if (debug) {
283: log.fine(_request.dbgId() + "Content-Type: "
284: + contentType + "; charset="
285: + _charEncoding);
286: }
287: } else {
288: if (debug) {
289: log.fine(_request.dbgId() + "Content-Type: "
290: + contentType);
291: }
292: }
293: }
294: } else if (_charEncoding != null) {
295: os.write(_textHtmlBytes, 0, _textHtmlBytes.length);
296: os.write(_charsetBytes, 0, _charsetBytes.length);
297: os.print(_charEncoding);
298:
299: if (debug) {
300: log.fine(_request.dbgId()
301: + "Content-Type: text/html; charset="
302: + _charEncoding);
303: }
304: } else {
305: WebApp webApp = _request.getWebApp();
306: String charEncoding = (webApp != null ? webApp
307: .getCharacterEncoding() : null);
308:
309: os.write(_textHtmlBytes, 0, _textHtmlBytes.length);
310: if (charEncoding != null) {
311: os.write(_charsetBytes, 0, _charsetBytes.length);
312: os.print(charEncoding);
313:
314: if (debug) {
315: log.fine(_request.dbgId()
316: + "Content-Type: text/html; charset="
317: + charEncoding);
318: }
319: } else {
320: if (debug) {
321: log.fine(_request.dbgId()
322: + "Content-Type: text/html");
323: }
324: }
325: }
326:
327: boolean hasContentLength = false;
328: if (_contentLength >= 0) {
329: os
330: .write(_contentLengthBytes, 0,
331: _contentLengthBytes.length);
332: os.print(_contentLength);
333: hasContentLength = true;
334:
335: if (debug)
336: log.fine(_request.dbgId() + "Content-Length: "
337: + _contentLength);
338: } else if (statusCode == SC_NOT_MODIFIED
339: || statusCode == SC_NO_CONTENT) {
340: hasContentLength = true;
341: os
342: .write(_contentLengthBytes, 0,
343: _contentLengthBytes.length);
344: os.print(0);
345:
346: if (debug)
347: log.fine(_request.dbgId() + "Content-Length: 0");
348: } else if (length >= 0) {
349: os
350: .write(_contentLengthBytes, 0,
351: _contentLengthBytes.length);
352: os.print(length);
353: hasContentLength = true;
354:
355: if (debug)
356: log
357: .fine(_request.dbgId() + "Content-Length: "
358: + length);
359: }
360:
361: if (version < HttpRequest.HTTP_1_1) {
362: _request.killKeepalive();
363: } else {
364: /* XXX: the request processing already processed this header
365: CharSegment conn = _request.getHeaderBuffer(_connectionCb,
366: _connectionCb.length);
367: if (conn != null && conn.equalsIgnoreCase(_closeCb)) {
368: _request.killKeepalive();
369: }
370: else
371: */
372:
373: if (!_request.allowKeepalive()) {
374: os.write(_connectionCloseBytes, 0,
375: _connectionCloseBytes.length);
376: _request.killKeepalive();
377:
378: if (debug)
379: log.fine(_request.dbgId() + "Connection: close");
380: }
381: }
382:
383: if (HttpRequest.HTTP_1_1 <= version && !hasContentLength
384: && !isHead()) {
385: os.print("\r\nTransfer-Encoding: chunked");
386: isChunked = true;
387:
388: if (debug)
389: log.fine(_request.dbgId()
390: + "Transfer-Encoding: chunked");
391: }
392:
393: if (_lastDate / 1000 != now / 1000) {
394: fillDate(now);
395: }
396:
397: if (isChunked)
398: os.write(_dateBuffer, 0, _dateBufferLength - 2);
399: else
400: os.write(_dateBuffer, 0, _dateBufferLength);
401:
402: return isChunked;
403: }
404:
405: private void fillDate(long now) {
406: if (_lastDate / 60000 == now / 60000) {
407: _lastDate = now;
408:
409: int sec = (int) (now / 1000 % 60);
410:
411: int s2 = '0' + (sec / 10);
412: int s1 = '0' + (sec % 10);
413:
414: _dateBuffer[31] = (byte) s2;
415: _dateBuffer[32] = (byte) s1;
416: return;
417: }
418:
419: _lastDate = now;
420: _calendar.setGMTTime(now);
421: _dateCharBuffer.clear();
422: _dateCharBuffer.append("\r\nDate: ");
423: _calendar.printDate(_dateCharBuffer);
424:
425: char[] cb = _dateCharBuffer.getBuffer();
426: int len = _dateCharBuffer.getLength();
427:
428: for (int i = len - 1; i >= 0; i--)
429: _dateBuffer[i] = (byte) cb[i];
430:
431: _dateBuffer[len] = (byte) '\r';
432: _dateBuffer[len + 1] = (byte) '\n';
433: _dateBuffer[len + 2] = (byte) '\r';
434: _dateBuffer[len + 3] = (byte) '\n';
435:
436: _dateBufferLength = len + 4;
437: }
438:
439: public String toString() {
440: return "HttpResponse" + _request.dbgId();
441: }
442: }
|