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.servlets;
031:
032: import com.caucho.config.types.Period;
033: import com.caucho.log.Log;
034: import com.caucho.server.webapp.Application;
035: import com.caucho.util.Alarm;
036: import com.caucho.util.CharBuffer;
037: import com.caucho.util.FreeList;
038: import com.caucho.util.L10N;
039: import com.caucho.util.QDate;
040: import com.caucho.vfs.Path;
041: import com.caucho.vfs.ReadStream;
042: import com.caucho.vfs.SocketStream;
043: import com.caucho.vfs.TempBuffer;
044: import com.caucho.vfs.WriteStream;
045:
046: import javax.servlet.GenericServlet;
047: import javax.servlet.ServletException;
048: import javax.servlet.ServletRequest;
049: import javax.servlet.ServletResponse;
050: import javax.servlet.http.HttpServletRequest;
051: import javax.servlet.http.HttpServletResponse;
052: import java.io.IOException;
053: import java.io.InputStream;
054: import java.io.OutputStream;
055: import java.net.InetAddress;
056: import java.net.Socket;
057: import java.util.ArrayList;
058: import java.util.Enumeration;
059: import java.util.logging.Level;
060: import java.util.logging.Logger;
061:
062: /**
063: * Load balancing.
064: *
065: * <pre>
066: * <servlet-mapping url-pattern='/remote/*'>
067: * <servlet-name>com.caucho.server.http.FastCGIServlet</servlet-name>
068: * <init-param server-address='localhost:8086'/>
069: * </servlet-mapping>
070: * </pre>
071: */
072: public class FastCGIServlet extends GenericServlet {
073: static final protected Logger log = Log.open(FastCGIServlet.class);
074: static final L10N L = new L10N(FastCGIServlet.class);
075:
076: private static final int FCGI_BEGIN_REQUEST = 1;
077: private static final int FCGI_ABORT_REQUEST = 2;
078: private static final int FCGI_END_REQUEST = 3;
079: private static final int FCGI_PARAMS = 4;
080: private static final int FCGI_STDIN = 5;
081: private static final int FCGI_STDOUT = 6;
082: private static final int FCGI_STDERR = 7;
083: private static final int FCGI_DATA = 8;
084: private static final int FCGI_GET_VALUES = 9;
085: private static final int FCGI_GET_VALUES_RESULT = 10;
086: private static final int FCGI_UNKNOWNE_TYPE = 11;
087:
088: private static final int FCGI_RESPONDER = 1;
089: private static final int FCGI_AUTHORIZER = 2;
090: private static final int FCGI_FILTER = 3;
091:
092: private static final int FCGI_VERSION = 1;
093:
094: private static final int FCGI_KEEP_CONN = 1;
095:
096: private static final int FCGI_REQUEST_COMPLETE = 0;
097: private static final int FCGI_CANT_MPX_CONN = 1;
098: private static final int FCGI_OVERLOADED = 2;
099: private static final int FCGI_UNKNOWN_ROLE = 3;
100:
101: private static final ArrayList<Integer> _fcgiServlets = new ArrayList<Integer>();
102:
103: private int _servletId;
104:
105: private FreeList<FastCGISocket> _freeSockets = new FreeList<FastCGISocket>(
106: 8);
107:
108: private String _hostAddress;
109: private InetAddress _hostAddr;
110: private int _hostPort;
111: protected QDate _calendar = new QDate();
112: private Application _app;
113: private long _readTimeout = 120000;
114:
115: private int _maxKeepaliveCount = 250;
116: private long _keepaliveTimeout = 15000;
117:
118: private int _idCount = 0;
119:
120: /**
121: * Sets the host address.
122: */
123: public void setServerAddress(String hostAddress)
124: throws ServletException {
125: _hostAddress = hostAddress;
126:
127: try {
128: int p = hostAddress.indexOf(':');
129: if (p > 0) {
130: _hostPort = new Integer(_hostAddress.substring(p + 1))
131: .intValue();
132: _hostAddr = InetAddress.getByName(_hostAddress
133: .substring(0, p));
134: }
135: } catch (Exception e) {
136: throw new ServletException(e);
137: }
138: }
139:
140: /**
141: * Sets the keepalive max.
142: */
143: public void setMaxKeepalive(int max) {
144: _maxKeepaliveCount = max;
145: }
146:
147: /**
148: * Sets the keepalive timeout.
149: */
150: public void setKeepaliveTimeout(Period period) {
151: _keepaliveTimeout = period.getPeriod();
152: }
153:
154: /**
155: * Sets the socket timeout.
156: */
157: public void setReadTimeout(Period timeout) {
158: _readTimeout = timeout.getPeriod();
159: }
160:
161: /**
162: * Initialize the servlet with the server's sruns.
163: */
164: public void init() throws ServletException {
165: int id = -1;
166:
167: for (int i = 0; i < 0x10000; i += 1024) {
168: if (!_fcgiServlets.contains(new Integer(i))) {
169: id = i;
170: break;
171: }
172: }
173:
174: if (id < 0)
175: throw new ServletException("Can't open FastCGI servlet");
176:
177: _fcgiServlets.add(new Integer(id));
178:
179: _servletId = id;
180:
181: _app = (Application) getServletContext();
182:
183: String serverAddress = getInitParameter("server-address");
184: if (serverAddress != null)
185: setServerAddress(serverAddress);
186:
187: if (_hostAddress == null)
188: throw new ServletException(
189: "FastCGIServlet needs valid server-address");
190: }
191:
192: /**
193: * Handle the request.
194: */
195: public void service(ServletRequest request, ServletResponse response)
196: throws ServletException, IOException {
197: HttpServletRequest req = (HttpServletRequest) request;
198: HttpServletResponse res = (HttpServletResponse) response;
199:
200: OutputStream out = res.getOutputStream();
201:
202: FastCGISocket fcgiSocket = null;
203:
204: do {
205: if (fcgiSocket != null)
206: fcgiSocket.close();
207:
208: fcgiSocket = _freeSockets.allocate();
209: } while (fcgiSocket != null && !fcgiSocket.isValid());
210:
211: if (fcgiSocket != null && fcgiSocket.isValid()) {
212: log.finer(fcgiSocket + ": reuse()");
213: } else {
214: if (fcgiSocket != null)
215: fcgiSocket.close();
216:
217: try {
218: Socket socket = new Socket(_hostAddr, _hostPort);
219:
220: if (_readTimeout > 0)
221: socket.setSoTimeout((int) _readTimeout);
222:
223: fcgiSocket = new FastCGISocket(nextId(), socket,
224: _maxKeepaliveCount);
225: } catch (IOException e) {
226: log.log(Level.FINE, e.toString(), e);
227:
228: throw new ServletException(
229: L
230: .l(
231: "Can't connect to FastCGI {0}:{1}. Check that the FastCGI service has started.",
232: _hostAddr, _hostPort));
233: }
234: }
235:
236: boolean isOkay = false;
237:
238: try {
239: fcgiSocket.setExpire(Alarm.getCurrentTime()
240: + _keepaliveTimeout);
241:
242: if (handleRequest(req, res, fcgiSocket, out, fcgiSocket
243: .allocateKeepalive())) {
244: if (_freeSockets.free(fcgiSocket)) {
245: log.finer(fcgiSocket + ": keepalive()");
246:
247: fcgiSocket = null;
248: }
249: }
250: } catch (Exception e) {
251: log.log(Level.WARNING, e.toString(), e);
252: } finally {
253: if (fcgiSocket != null)
254: fcgiSocket.close();
255: }
256: }
257:
258: private int nextId() {
259: synchronized (this ) {
260: int id = _idCount++;
261:
262: if (id <= 0 || 1024 < id) {
263: _idCount = 2;
264: id = 1;
265: }
266:
267: return id + _servletId;
268: }
269: }
270:
271: private boolean handleRequest(HttpServletRequest req,
272: HttpServletResponse res, FastCGISocket fcgiSocket,
273: OutputStream out, boolean keepalive)
274: throws ServletException, IOException {
275: ReadStream rs = fcgiSocket.getReadStream();
276: WriteStream ws = fcgiSocket.getWriteStream();
277:
278: writeHeader(fcgiSocket, ws, FCGI_BEGIN_REQUEST, 8);
279:
280: int role = FCGI_RESPONDER;
281:
282: ws.write(role >> 8);
283: ws.write(role);
284: ws.write(keepalive ? FCGI_KEEP_CONN : 0); // flags
285: for (int i = 0; i < 5; i++)
286: ws.write(0);
287:
288: setEnvironment(fcgiSocket, ws, req);
289:
290: InputStream in = req.getInputStream();
291: TempBuffer tempBuf = TempBuffer.allocate();
292: byte[] buf = tempBuf.getBuffer();
293: int len = buf.length;
294: int sublen;
295:
296: writeHeader(fcgiSocket, ws, FCGI_PARAMS, 0);
297:
298: boolean hasStdin = false;
299: while ((sublen = in.read(buf, 0, len)) > 0) {
300: hasStdin = true;
301: writeHeader(fcgiSocket, ws, FCGI_STDIN, sublen);
302: ws.write(buf, 0, sublen);
303: }
304:
305: TempBuffer.free(tempBuf);
306: tempBuf = null;
307:
308: if (hasStdin)
309: writeHeader(fcgiSocket, ws, FCGI_STDIN, 0);
310:
311: FastCGIInputStream is = new FastCGIInputStream(fcgiSocket);
312:
313: int ch = parseHeaders(res, is);
314:
315: if (ch >= 0)
316: out.write(ch);
317:
318: while ((ch = is.read()) >= 0)
319: out.write(ch);
320:
321: return !is.isDead() && keepalive;
322: }
323:
324: private void setEnvironment(FastCGISocket fcgi, WriteStream ws,
325: HttpServletRequest req) throws IOException {
326: addHeader(fcgi, ws, "REQUEST_URI", req.getRequestURI());
327: addHeader(fcgi, ws, "REQUEST_METHOD", req.getMethod());
328:
329: addHeader(fcgi, ws, "SERVER_SOFTWARE", "Resin/"
330: + com.caucho.Version.VERSION);
331:
332: addHeader(fcgi, ws, "SERVER_NAME", req.getServerName());
333: //addHeader(fcgi, ws, "SERVER_ADDR=" + req.getServerAddr());
334: addHeader(fcgi, ws, "SERVER_PORT", String.valueOf(req
335: .getServerPort()));
336:
337: addHeader(fcgi, ws, "REMOTE_ADDR", req.getRemoteAddr());
338: addHeader(fcgi, ws, "REMOTE_HOST", req.getRemoteAddr());
339: // addHeader(fcgi, ws, "REMOTE_PORT=" + req.getRemotePort());
340:
341: if (req.getRemoteUser() != null)
342: addHeader(fcgi, ws, "REMOTE_USER", req.getRemoteUser());
343: else
344: addHeader(fcgi, ws, "REMOTE_USER", "");
345: if (req.getAuthType() != null)
346: addHeader(fcgi, ws, "AUTH_TYPE", req.getAuthType());
347:
348: addHeader(fcgi, ws, "GATEWAY_INTERFACE", "CGI/1.1");
349: addHeader(fcgi, ws, "SERVER_PROTOCOL", req.getProtocol());
350: if (req.getQueryString() != null)
351: addHeader(fcgi, ws, "QUERY_STRING", req.getQueryString());
352: else
353: addHeader(fcgi, ws, "QUERY_STRING", "");
354:
355: String scriptPath = req.getServletPath();
356: String pathInfo = req.getPathInfo();
357:
358: Path appDir = _app.getAppDir();
359: String realPath = _app.getRealPath(scriptPath);
360:
361: if (!appDir.lookup(realPath).isFile() && pathInfo != null)
362: scriptPath = scriptPath + pathInfo;
363:
364: /*
365: * FastCGI (specifically quercus) uses the PATH_INFO and PATH_TRANSLATED
366: * for the script path.
367: */
368: log.finer("FCGI file: " + _app.getRealPath(scriptPath));
369:
370: addHeader(fcgi, ws, "PATH_INFO", req.getContextPath()
371: + scriptPath);
372: addHeader(fcgi, ws, "PATH_TRANSLATED", _app
373: .getRealPath(scriptPath));
374:
375: /* These are the values which would be sent to CGI.
376: addHeader(fcgi, ws, "SCRIPT_NAME", req.getContextPath() + scriptPath);
377: addHeader(fcgi, ws, "SCRIPT_FILENAME", app.getRealPath(scriptPath));
378:
379: if (pathInfo != null) {
380: addHeader(fcgi, ws, "PATH_INFO", pathInfo);
381: addHeader(fcgi, ws, "PATH_TRANSLATED", req.getRealPath(pathInfo));
382: }
383: else {
384: addHeader(fcgi, ws, "PATH_INFO", "");
385: addHeader(fcgi, ws, "PATH_TRANSLATED", "");
386: }
387: */
388:
389: int contentLength = req.getContentLength();
390: if (contentLength < 0)
391: addHeader(fcgi, ws, "CONTENT_LENGTH", "0");
392: else
393: addHeader(fcgi, ws, "CONTENT_LENGTH", String
394: .valueOf(contentLength));
395:
396: addHeader(fcgi, ws, "DOCUMENT_ROOT", _app.getContext("/")
397: .getRealPath("/"));
398:
399: CharBuffer cb = new CharBuffer();
400:
401: Enumeration e = req.getHeaderNames();
402: while (e.hasMoreElements()) {
403: String key = (String) e.nextElement();
404: String value = req.getHeader(key);
405:
406: if (key.equalsIgnoreCase("content-length"))
407: addHeader(fcgi, ws, "CONTENT_LENGTH", value);
408: else if (key.equalsIgnoreCase("content-type"))
409: addHeader(fcgi, ws, "CONTENT_TYPE", value);
410: else if (key.equalsIgnoreCase("if-modified-since")) {
411: } else if (key.equalsIgnoreCase("if-none-match")) {
412: } else if (key.equalsIgnoreCase("authorization")) {
413: } else if (key.equalsIgnoreCase("proxy-authorization")) {
414: } else
415: addHeader(fcgi, ws, convertHeader(cb, key), value);
416: }
417: }
418:
419: private CharBuffer convertHeader(CharBuffer cb, String key) {
420: cb.clear();
421:
422: cb.append("HTTP_");
423:
424: for (int i = 0; i < key.length(); i++) {
425: char ch = key.charAt(i);
426: if (ch == '-')
427: cb.append('_');
428: else if (ch >= 'a' && ch <= 'z')
429: cb.append((char) (ch + 'A' - 'a'));
430: else
431: cb.append(ch);
432: }
433:
434: return cb;
435: }
436:
437: private int parseHeaders(HttpServletResponse res, InputStream is)
438: throws IOException {
439: CharBuffer key = new CharBuffer();
440: CharBuffer value = new CharBuffer();
441:
442: int ch = is.read();
443:
444: if (ch < 0) {
445: log.fine("Can't contact FastCGI");
446: res.sendError(404);
447: return -1;
448: }
449:
450: while (ch >= 0) {
451: key.clear();
452: value.clear();
453:
454: for (; ch >= 0 && ch != ' ' && ch != '\r' && ch != '\n'
455: && ch != ':'; ch = is.read()) {
456: key.append((char) ch);
457: }
458:
459: for (; ch >= 0 && ch == ' ' || ch == ':'; ch = is.read()) {
460: }
461:
462: for (; ch >= 0 && ch != '\r' && ch != '\n'; ch = is.read()) {
463: value.append((char) ch);
464: }
465:
466: if (ch == '\r') {
467: ch = is.read();
468: if (ch == '\n')
469: ch = is.read();
470: }
471:
472: if (key.length() == 0)
473: return ch;
474:
475: if (log.isLoggable(Level.FINE))
476: log.fine("fastcgi:" + key + ": " + value);
477:
478: if (key.equalsIgnoreCase("status")) {
479: int status = 0;
480: int len = value.length();
481:
482: for (int i = 0; i < len; i++) {
483: char digit = value.charAt(i);
484:
485: if ('0' <= digit && digit <= '9')
486: status = 10 * status + digit - '0';
487: else
488: break;
489: }
490:
491: res.setStatus(status);
492: } else if (key.startsWith("http") || key.startsWith("HTTP")) {
493: } else if (key.equalsIgnoreCase("location")) {
494: res.sendRedirect(value.toString());
495: } else
496: res.addHeader(key.toString(), value.toString());
497: }
498:
499: return ch;
500: }
501:
502: private void addHeader(FastCGISocket fcgiSocket, WriteStream ws,
503: String key, String value) throws IOException {
504: int keyLen = key.length();
505: int valLen = value.length();
506:
507: int len = keyLen + valLen;
508:
509: if (keyLen < 0x80)
510: len += 1;
511: else
512: len += 4;
513:
514: if (valLen < 0x80)
515: len += 1;
516: else
517: len += 4;
518:
519: writeHeader(fcgiSocket, ws, FCGI_PARAMS, len);
520:
521: if (keyLen < 0x80)
522: ws.write(keyLen);
523: else {
524: ws.write(0x80 | (keyLen >> 24));
525: ws.write(keyLen >> 16);
526: ws.write(keyLen >> 8);
527: ws.write(keyLen);
528: }
529:
530: if (valLen < 0x80)
531: ws.write(valLen);
532: else {
533: ws.write(0x80 | (valLen >> 24));
534: ws.write(valLen >> 16);
535: ws.write(valLen >> 8);
536: ws.write(valLen);
537: }
538:
539: ws.print(key);
540: ws.print(value);
541: }
542:
543: private void addHeader(FastCGISocket fcgiSocket, WriteStream ws,
544: CharBuffer key, String value) throws IOException {
545: int keyLen = key.getLength();
546: int valLen = value.length();
547:
548: int len = keyLen + valLen;
549:
550: if (keyLen < 0x80)
551: len += 1;
552: else
553: len += 4;
554:
555: if (valLen < 0x80)
556: len += 1;
557: else
558: len += 4;
559:
560: writeHeader(fcgiSocket, ws, FCGI_PARAMS, len);
561:
562: if (keyLen < 0x80)
563: ws.write(keyLen);
564: else {
565: ws.write(0x80 | (keyLen >> 24));
566: ws.write(keyLen >> 16);
567: ws.write(keyLen >> 8);
568: ws.write(keyLen);
569: }
570:
571: if (valLen < 0x80)
572: ws.write(valLen);
573: else {
574: ws.write(0x80 | (valLen >> 24));
575: ws.write(valLen >> 16);
576: ws.write(valLen >> 8);
577: ws.write(valLen);
578: }
579:
580: ws.print(key.getBuffer(), 0, keyLen);
581: ws.print(value);
582: }
583:
584: private void writeHeader(FastCGISocket fcgiSocket, WriteStream ws,
585: int type, int length) throws IOException {
586: int id = 1;
587: int pad = 0;
588:
589: ws.write(FCGI_VERSION);
590: ws.write(type);
591: ws.write(id >> 8);
592: ws.write(id);
593: ws.write(length >> 8);
594: ws.write(length);
595: ws.write(pad);
596: ws.write(0);
597: }
598:
599: public void destroy() {
600: FastCGISocket socket;
601:
602: while ((socket = _freeSockets.allocate()) != null) {
603: try {
604: socket.close();
605: } catch (Throwable e) {
606: }
607: }
608:
609: _fcgiServlets.remove(new Integer(_servletId));
610: }
611:
612: static class FastCGIInputStream extends InputStream {
613: private FastCGISocket _fcgiSocket;
614:
615: private InputStream _is;
616: private int _chunkLength;
617: private int _padLength;
618: private boolean _isDead;
619:
620: public FastCGIInputStream() {
621: }
622:
623: public FastCGIInputStream(FastCGISocket fcgiSocket) {
624: init(fcgiSocket);
625: }
626:
627: public void init(FastCGISocket fcgiSocket) {
628: _fcgiSocket = fcgiSocket;
629:
630: _is = fcgiSocket.getReadStream();
631: _chunkLength = 0;
632: _isDead = false;
633: }
634:
635: public boolean isDead() {
636: return _isDead;
637: }
638:
639: public int read() throws IOException {
640: do {
641: if (_chunkLength > 0) {
642: _chunkLength--;
643: return _is.read();
644: }
645: } while (readNext());
646:
647: return -1;
648: }
649:
650: private boolean readNext() throws IOException {
651: if (_is == null)
652: return false;
653:
654: if (_padLength > 0) {
655: _is.skip(_padLength);
656: _padLength = 0;
657: }
658:
659: int version;
660:
661: while ((version = _is.read()) >= 0) {
662: int type = _is.read();
663: int id = (_is.read() << 8) + _is.read();
664: int length = (_is.read() << 8) + _is.read();
665: int padding = _is.read();
666: _is.read();
667:
668: switch (type) {
669: case FCGI_END_REQUEST: {
670: int appStatus = ((_is.read() << 24)
671: + (_is.read() << 16) + (_is.read() << 8) + (_is
672: .read()));
673: int pStatus = _is.read();
674:
675: if (log.isLoggable(Level.FINER)) {
676: log.finer(_fcgiSocket
677: + ": FCGI_END_REQUEST(appStatus:"
678: + appStatus + ", pStatus:" + pStatus
679: + ")");
680: }
681:
682: if (appStatus != 0)
683: _isDead = true;
684:
685: if (pStatus != FCGI_REQUEST_COMPLETE)
686: _isDead = true;
687:
688: _is.skip(3);
689: _is = null;
690: return false;
691: }
692:
693: case FCGI_STDOUT:
694: if (log.isLoggable(Level.FINER)) {
695: log
696: .finer(_fcgiSocket
697: + ": FCGI_STDOUT(length:"
698: + length + ", padding:"
699: + padding + ")");
700: }
701:
702: if (length == 0) {
703: if (padding > 0)
704: _is.skip(padding);
705:
706: break;
707: } else {
708: _chunkLength = length;
709: _padLength = padding;
710: return true;
711: }
712:
713: case FCGI_STDERR:
714: if (log.isLoggable(Level.FINER)) {
715: log
716: .finer(_fcgiSocket
717: + ": FCGI_STDERR(length:"
718: + length + ", padding:"
719: + padding + ")");
720: }
721:
722: byte[] buf = new byte[length];
723: _is.read(buf, 0, length);
724: log.warning(new String(buf, 0, length));
725:
726: if (padding > 0)
727: _is.skip(padding);
728: break;
729:
730: default:
731: log.warning(_fcgiSocket + ": Unknown Protocol("
732: + type + ")");
733:
734: _isDead = true;
735: _is.skip(length + padding);
736: break;
737: }
738: }
739:
740: _isDead = true;
741:
742: return false;
743: }
744: }
745:
746: static class FastCGISocket {
747: private int _id;
748: private int _keepaliveCount;
749: private long _expireTime;
750: private Socket _socket;
751: private SocketStream _socketStream;
752: private ReadStream _rs;
753: private WriteStream _ws;
754:
755: FastCGISocket(int id, Socket socket, int maxKeepaliveCount) {
756: _id = id;
757: _socket = socket;
758: _keepaliveCount = maxKeepaliveCount;
759:
760: _socketStream = new SocketStream(_socket);
761:
762: _ws = new WriteStream(_socketStream);
763: _ws.setDisableClose(true);
764:
765: _rs = new ReadStream(_socketStream, _ws);
766: _rs.setDisableClose(true);
767:
768: log.fine(this + ": open()");
769: }
770:
771: int getId() {
772: return _id;
773: }
774:
775: void setExpire(long expireTime) {
776: _expireTime = expireTime;
777: }
778:
779: ReadStream getReadStream() {
780: return _rs;
781: }
782:
783: WriteStream getWriteStream() {
784: return _ws;
785: }
786:
787: boolean isValid() {
788: return _socket != null
789: && Alarm.getCurrentTime() < _expireTime;
790: }
791:
792: boolean allocateKeepalive() {
793: if (!isValid())
794: return false;
795: else
796: return --_keepaliveCount > 0;
797: }
798:
799: boolean isKeepalive() {
800: return _keepaliveCount > 0;
801: }
802:
803: void close() {
804: try {
805: log.fine(this + ": close()");
806:
807: Socket socket = _socket;
808: _socket = null;
809:
810: _socketStream = null;
811:
812: if (socket != null)
813: socket.close();
814:
815: _ws.close();
816: _rs.close();
817: } catch (Throwable e) {
818: log.log(Level.FINER, e.toString(), e);
819: }
820: }
821:
822: public String toString() {
823: return "FastCGISocket[" + _id + "," + _socket + "]";
824: }
825: }
826: }
|