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