1 16 package org.mortbay.http; 17 18 import java.io.IOException ; 19 import java.io.InputStream ; 20 import java.io.OutputStream ; 21 import java.net.InetAddress ; 22 import java.net.Socket ; 23 import java.util.Enumeration ; 24 import java.util.List ; 25 26 import javax.net.ssl.SSLSocket; 27 28 import org.apache.commons.logging.Log; 29 import org.mortbay.log.LogFactory; 30 import org.mortbay.util.InetAddrPort; 31 import org.mortbay.util.LineInput; 32 import org.mortbay.util.LogSupport; 33 import org.mortbay.util.OutputObserver; 34 import org.mortbay.util.StringUtil; 35 36 37 38 53 public class HttpConnection 54 implements OutputObserver 55 { 56 private static Log log = LogFactory.getLog(HttpConnection.class); 57 58 59 private static ThreadLocal __threadConnection=new ThreadLocal (); 60 61 66 private static boolean __2068_Continues=Boolean.getBoolean("org.mortbay.http.HttpConnection.2068Continue"); 67 68 69 protected HttpRequest _request; 70 protected HttpResponse _response; 71 protected boolean _persistent; 72 protected boolean _keepAlive; 73 protected int _dotVersion; 74 75 private HttpListener _listener; 76 private HttpInputStream _inputStream; 77 private HttpOutputStream _outputStream; 78 private boolean _close; 79 private boolean _firstWrite; 80 private boolean _completing; 81 private Thread _handlingThread; 82 83 private InetAddress _remoteInetAddress; 84 private String _remoteAddr; 85 private String _remoteHost; 86 private HttpServer _httpServer; 87 private Object _connection; 88 private boolean _throttled ; 89 90 private boolean _statsOn; 91 private long _tmpTime; 92 private long _openTime; 93 private long _reqTime; 94 private int _requests; 95 private Object _object; 96 private HttpTunnel _tunnel ; 97 private boolean _resolveRemoteHost; 98 99 100 109 public HttpConnection(HttpListener listener, 110 InetAddress remoteAddr, 111 InputStream in, 112 OutputStream out, 113 Object connection) 114 { 115 if(log.isDebugEnabled())log.debug("new HttpConnection: "+connection); 116 _listener=listener; 117 _remoteInetAddress=remoteAddr; 118 int bufferSize=listener==null?4096:listener.getBufferSize(); 119 int reserveSize=listener==null?512:listener.getBufferReserve(); 120 _inputStream=new HttpInputStream(in,bufferSize); 121 _outputStream=new HttpOutputStream(out,bufferSize,reserveSize); 122 _outputStream.addObserver(this); 123 _firstWrite=false; 124 if (_listener!=null) 125 _httpServer=_listener.getHttpServer(); 126 _connection=connection; 127 128 _statsOn=_httpServer!=null && _httpServer.getStatsOn(); 129 if (_statsOn) 130 { 131 _openTime=System.currentTimeMillis(); 132 _httpServer.statsOpenConnection(); 133 } 134 _reqTime=0; 135 _requests=0; 136 137 _request = new HttpRequest(this); 138 _response = new HttpResponse(this); 139 140 _resolveRemoteHost=_listener!=null && _listener.getHttpServer()!=null && _listener.getHttpServer().getResolveRemoteHost(); 141 } 142 143 144 148 static HttpConnection getHttpConnection() 149 { 150 return (HttpConnection)__threadConnection.get(); 151 } 152 153 154 157 public InetAddress getRemoteInetAddress() 158 { 159 return _remoteInetAddress; 160 } 161 162 163 166 public String getRemoteAddr() 167 { 168 if (_remoteAddr==null) 169 { 170 if (_remoteInetAddress==null) 171 return "127.0.0.1"; 172 _remoteAddr=_remoteInetAddress.getHostAddress(); 173 } 174 return _remoteAddr; 175 } 176 177 178 181 public String getRemoteHost() 182 { 183 if (_remoteHost==null) 184 { 185 if (_resolveRemoteHost) 186 { 187 if (_remoteInetAddress==null) 188 return "localhost"; 189 _remoteHost=_remoteInetAddress.getHostName(); 190 } 191 else 192 { 193 if (_remoteInetAddress==null) 194 return "127.0.0.1"; 195 _remoteHost=getRemoteAddr(); 196 } 197 } 198 return _remoteHost; 199 } 200 201 202 205 public HttpInputStream getInputStream() 206 { 207 return _inputStream; 208 } 209 210 211 214 public HttpOutputStream getOutputStream() 215 { 216 return _outputStream; 217 } 218 219 220 225 public Object getConnection() 226 { 227 return _connection; 228 } 229 230 231 234 public HttpRequest getRequest() 235 { 236 return _request; 237 } 238 239 240 243 public HttpResponse getResponse() 244 { 245 return _response; 246 } 247 248 249 251 public void forceClose() 252 { 253 _persistent=false; 254 _close=true; 255 } 256 257 258 264 public void close() 265 throws IOException 266 { 267 try{ 268 _completing=true; 269 if (_connection instanceof Socket && !(_connection instanceof SSLSocket)) 270 ((Socket )_connection).shutdownOutput(); 271 _outputStream.close(); 272 _inputStream.close(); 273 } 274 finally 275 { 276 if (_handlingThread!=null && Thread.currentThread()!=_handlingThread) 277 _handlingThread.interrupt(); 278 } 279 } 280 281 282 285 public HttpListener getListener() 286 { 287 return _listener; 288 } 289 290 291 295 public HttpServer getHttpServer() 296 { 297 return _httpServer; 298 } 299 300 301 305 public String getDefaultScheme() 306 { 307 return _listener.getDefaultScheme(); 308 } 309 310 311 315 public String getServerName() 316 { 317 String host=_listener.getHost(); 318 if (InetAddrPort.__0_0_0_0.equals(host) && 319 _connection instanceof Socket ) 320 host = ((Socket )_connection).getLocalAddress().getHostName(); 321 322 return host; 323 } 324 325 326 329 public String getServerAddr() 330 { 331 if (_connection instanceof Socket ) 332 return ((Socket )_connection).getLocalAddress().getHostAddress(); 333 return _listener.getHost(); 334 } 335 336 337 341 public int getServerPort() 342 { 343 return _listener.getPort(); 344 } 345 346 347 350 public int getRemotePort() 351 { 352 if (_connection instanceof Socket ) 353 return ((Socket )_connection).getPort(); 354 return 0; 355 } 356 357 358 362 public boolean isThrottled() 363 { 364 return _throttled; 365 } 366 367 368 372 public void setThrottled(boolean throttled) 373 { 374 _throttled = throttled; 375 } 376 377 378 383 public Object getObject() 384 { 385 return _object; 386 } 387 388 389 394 public void setObject(Object o) 395 { 396 _object=o; 397 } 398 399 400 403 public HttpTunnel getHttpTunnel() 404 { 405 return _tunnel; 406 } 407 408 409 416 public void setHttpTunnel(HttpTunnel tunnel) 417 { 418 _tunnel = tunnel; 419 } 420 421 422 426 private void verifyHTTP_1_0() 427 { 428 int content_length= 430 _request.getIntField(HttpFields.__ContentLength); 431 if (content_length>=0) 432 _inputStream.setContentLength(content_length); 433 else if (content_length<0) 434 { 435 _inputStream.setContentLength(0); 442 } 443 444 if (!_keepAlive && HttpFields.__KeepAlive.equalsIgnoreCase(_request.getField(HttpFields.__ProxyConnection))) 446 _keepAlive=true; 447 448 _persistent=_keepAlive; 450 } 451 452 453 457 private void verifyHTTP_1_1() 458 throws HttpException, IOException 459 { 460 String host=_request.getField(HttpFields.__Host); 462 if (host==null) 463 throw new HttpException(HttpResponse.__400_Bad_Request); 464 465 String transfer_coding= 467 _request.getField(HttpFields.__TransferEncoding); 468 469 if (transfer_coding!=null && transfer_coding.length()>0) 470 { 471 if (transfer_coding.equalsIgnoreCase(HttpFields.__Chunked) || 476 StringUtil.endsWithIgnoreCase(transfer_coding,HttpFields.__Chunked)) 477 _inputStream.setChunking(); 478 else if (StringUtil.asciiToLowerCase(transfer_coding) 479 .indexOf(HttpFields.__Chunked)>=0) 480 throw new HttpException(HttpResponse.__400_Bad_Request); 481 } 482 483 int content_length=_request.getIntField(HttpFields.__ContentLength); 485 String content_type=_request.getField(HttpFields.__ContentType); 486 if (!_inputStream.isChunking()) 487 { 488 if (content_length>=0) 490 _inputStream.setContentLength(content_length); 491 else if (content_type==null || content_type.length()==0) 493 _inputStream.setContentLength(0); 494 else 496 { 497 _inputStream.setContentLength(0); 501 } 502 } 503 504 String expect=_request.getField(HttpFields.__Expect); 506 if (expect!=null && expect.length()>0) 507 { 508 if (StringUtil.asciiToLowerCase(expect).equals(HttpFields.__ExpectContinue)) 509 { 510 _inputStream.setExpectContinues(_outputStream.getOutputStream()); 511 } 512 else 513 throw new HttpException(HttpResponse.__417_Expectation_Failed); 514 } 515 else if (__2068_Continues && 516 _inputStream.available()<=0 && 517 (HttpRequest.__PUT.equals(_request.getMethod()) || 518 HttpRequest.__POST.equals(_request.getMethod()))) 519 { 520 OutputStream real_out=_outputStream.getOutputStream(); 522 real_out.write(HttpResponse.__Continue); 523 real_out.flush(); 524 } 525 526 _persistent=!_close; 528 } 529 530 531 540 public void outputNotify(OutputStream out, int action, Object ignoredData) 541 throws IOException 542 { 543 if (_response==null) 544 return; 545 546 switch(action) 547 { 548 case OutputObserver.__FIRST_WRITE: 549 if (!_firstWrite) 550 { 551 firstWrite(); 552 _firstWrite=true; 553 } 554 break; 555 556 case OutputObserver.__RESET_BUFFER: 557 resetBuffer(); 558 break; 559 560 case OutputObserver.__COMMITING: 561 commit(); 562 break; 563 564 case OutputObserver.__CLOSING: 565 566 if (_response!=null) 567 { 568 completing(); 569 if (!_response.isCommitted() && 570 _request.getState()==HttpMessage.__MSG_RECEIVED) 571 commit(); 572 } 573 break; 574 575 case OutputObserver.__CLOSED: 576 break; 577 } 578 } 579 580 581 585 protected void firstWrite() 586 throws IOException 587 { 588 if (_response.isCommitted()) 589 return; 590 591 if (HttpRequest.__HEAD.equals(_request.getMethod())) 593 _outputStream.nullOutput(); 594 595 int length=_response.getIntField(HttpFields.__ContentLength); 596 if (length>=0) 597 _outputStream.setContentLength(length); 598 } 599 600 603 private void resetBuffer() 604 { 605 } 606 607 608 609 void completing() 610 { 611 _completing=true; 612 } 613 614 615 protected void commit() 616 throws IOException 617 { 618 if (_response.isCommitted()) 619 return; 620 621 int status=_response.getStatus(); 622 int length=-1; 623 624 if (_inputStream.getExpectContinues()!=null) 626 { 627 _inputStream.setExpectContinues(null); 629 _inputStream.unsafeSetContentLength(0); 630 } 631 632 boolean has_close=HttpFields.__Close.equals(_response.getField(HttpFields.__Connection)); 634 if (!_persistent || _close || _listener!=null && (!_listener.isStarted()||_listener.isOutOfResources())) 635 { 636 _close=true; 637 if (!has_close) 638 _response.setField(HttpFields.__Connection,HttpFields.__Close); 639 has_close=true; 640 } 641 if (_close) 642 _persistent=false; 643 644 if (_persistent) 646 { 647 switch(_dotVersion) 648 { 649 case 1: 650 { 651 String transfer_coding=_response.getField(HttpFields.__TransferEncoding); 652 if (transfer_coding==null || transfer_coding.length()==0 || HttpFields.__Identity.equalsIgnoreCase(transfer_coding)) 653 { 654 if (status!=HttpResponse.__304_Not_Modified && 656 status!=HttpResponse.__204_No_Content && 657 _response.getField(HttpFields.__ContentLength)==null) 658 { 659 if (_completing) 660 { 661 length=_outputStream.getBytesWritten(); 662 _response.setContentLength(length); 663 } 664 else 665 { 666 _response.setField(HttpFields.__TransferEncoding,HttpFields.__Chunked); 668 _outputStream.setChunking(); 669 } 670 } 671 } 672 else 673 { 674 _response.removeField(HttpFields.__ContentLength); 676 _outputStream.setChunking(); 677 678 if (!HttpFields.__Chunked.equalsIgnoreCase(transfer_coding)) 679 { 680 List te = _request.getAcceptableTransferCodings(); 682 Enumeration enm = 683 _response.getFieldValues(HttpFields.__TransferEncoding, 684 HttpFields.__separators); 685 while (enm.hasMoreElements()) 686 { 687 String coding=(String )enm.nextElement(); 688 if (HttpFields.__Identity.equalsIgnoreCase(coding) || 689 HttpFields.__Chunked.equalsIgnoreCase(coding)) 690 continue; 691 if (te==null || !te.contains(coding)) 692 throw new HttpException(HttpResponse.__501_Not_Implemented,coding); 693 } 694 } 695 } 696 } 697 break; 698 699 case 0: 700 { 701 _response.removeField(HttpFields.__TransferEncoding); 703 if (_keepAlive) 704 { 705 if (status!=HttpResponse.__304_Not_Modified && 706 status!=HttpResponse.__204_No_Content && 707 _response.getField(HttpFields.__ContentLength)==null) 708 { 709 if (_completing) 710 { 711 length=_outputStream.getBytesWritten(); 712 _response.setContentLength(length); 713 _response.setField(HttpFields.__Connection,HttpFields.__KeepAlive); 714 } 715 else 716 { 717 _response.setField(HttpFields.__Connection,HttpFields.__Close); 718 has_close=_close=true; 719 _persistent=false; 720 } 721 } 722 else 723 _response.setField(HttpFields.__Connection,HttpFields.__KeepAlive); 724 } 725 else if (!has_close) 726 _response.setField(HttpFields.__Connection,HttpFields.__Close); 727 728 729 break; 730 } 731 default: 732 { 733 _close=true; 734 _persistent=false; 735 _keepAlive=false; 736 } 737 } 738 } 739 740 741 _request.setHandled(true); 743 744 _outputStream.writeHeader(_response); 745 _outputStream.flush(); 746 747 } 748 749 750 751 754 private void exception(Throwable e) 755 { 756 try 757 { 758 _persistent=false; 759 int error_code=HttpResponse.__500_Internal_Server_Error; 760 761 if (e instanceof HttpException) 762 { 763 error_code=((HttpException)e).getCode(); 764 765 if (_request==null) 766 log.warn(e.toString()); 767 else 768 log.warn(_request.getRequestLine()+" "+e.toString()); 769 log.debug(LogSupport.EXCEPTION,e); 770 } 771 else if (e instanceof EOFException) 772 { 773 LogSupport.ignore(log,e); 774 return; 775 } 776 else 777 { 778 _request.setAttribute("javax.servlet.error.exception_type",e.getClass()); 779 _request.setAttribute("javax.servlet.error.exception",e); 780 781 if (_request==null) 782 log.warn(LogSupport.EXCEPTION,e); 783 else 784 log.warn(_request.getRequestLine(),e); 785 } 786 787 if (_response != null && !_response.isCommitted()) 788 { 789 _response.reset(); 790 _response.removeField(HttpFields.__TransferEncoding); 791 _response.setField(HttpFields.__Connection,HttpFields.__Close); 792 _response.sendError(error_code); 793 } 794 } 795 catch(Exception ex) 796 { 797 LogSupport.ignore(log,ex); 798 } 799 } 800 801 802 803 815 protected HttpContext service(HttpRequest request, HttpResponse response) 816 throws HttpException, IOException 817 { 818 if (_httpServer==null) 819 throw new HttpException(HttpResponse.__503_Service_Unavailable); 820 return _httpServer.service(request,response); 821 } 822 823 824 832 public final void handle() 833 { 834 try 835 { 836 associateThread(); 837 while(_listener.isStarted() && handleNext()) 838 recycle(); 839 } 840 finally 841 { 842 disassociateThread(); 843 destroy(); 844 } 845 } 846 847 848 protected void associateThread() 849 { 850 __threadConnection.set(this); 851 _handlingThread=Thread.currentThread(); 852 } 853 854 855 protected void disassociateThread() 856 { 857 _handlingThread=null; 858 __threadConnection.set(null); 859 } 860 861 862 863 protected void readRequest() 864 throws IOException 865 { 866 _request.readHeader((LineInput)(_inputStream) 867 .getInputStream()); 868 } 869 870 871 885 public boolean handleNext() 886 { 887 if (_tunnel!=null) 889 { 890 if(log.isDebugEnabled())log.debug("Tunnel: "+_tunnel); 891 _outputStream.resetObservers(); 892 _tunnel.handle(_inputStream.getInputStream(), 893 _outputStream.getOutputStream()); 894 return false; 895 } 896 897 HttpContext context=null; 899 boolean stats=false; 900 try 901 { 902 _persistent=false; 905 _close=false; 906 _keepAlive=false; 907 _firstWrite=false; 908 _completing=false; 909 _dotVersion=0; 910 911 readRequest(); 913 if (_listener==null || !_listener.isStarted()) 914 { 915 _response.destroy(); 917 _response=null; 918 _persistent=false; 919 return false; 920 } 921 922 _listener.customizeRequest(this,_request); 923 if (_request.getState()!=HttpMessage.__MSG_RECEIVED) 924 throw new HttpException(HttpResponse.__400_Bad_Request); 925 926 statsRequestStart(); 928 stats=true; 929 930 _dotVersion=_request.getDotVersion(); 932 933 if (_dotVersion>1) 934 { 935 _dotVersion=1; 936 } 937 938 _response.setVersion(HttpMessage.__HTTP_1_1); 940 _response.setField(HttpFields.__Date,_request.getTimeStampStr()); 941 if (!Version.isParanoid()) 942 _response.setField(HttpFields.__Server,Version.getDetail()); 943 944 Enumeration connectionValues = 946 _request.getFieldValues(HttpFields.__Connection, 947 HttpFields.__separators); 948 if (connectionValues!=null) 949 { 950 while (connectionValues.hasMoreElements()) 951 { 952 String token=connectionValues.nextElement().toString(); 953 if (token.equalsIgnoreCase(HttpFields.__Close)) 955 { 956 _close=true; 957 _response.setField(HttpFields.__Connection, 958 HttpFields.__Close); 959 } 960 else if (token.equalsIgnoreCase(HttpFields.__KeepAlive) && 961 _dotVersion==0) 962 _keepAlive=true; 963 964 if (_dotVersion==0) 966 _request.forceRemoveField(token); 967 } 968 } 969 970 if (_dotVersion==1) 972 verifyHTTP_1_1(); 973 else if (_dotVersion==0) 974 verifyHTTP_1_0(); 975 else if (_dotVersion!=-1) 976 throw new HttpException(HttpResponse.__505_HTTP_Version_Not_Supported); 977 978 if(log.isDebugEnabled())log.debug("REQUEST from "+_listener+":\n"+_request); 979 980 if (!_request.isHandled() && _listener.getHttpHandler()!=null) 982 _listener.getHttpHandler().handle("",null, _request, _response); 983 984 if (!_request.isHandled()) 986 context=service(_request,_response); 987 } 988 catch(HttpException e) {exception(e);} 989 catch (IOException e) 990 { 991 if (_request.getState()!=HttpMessage.__MSG_RECEIVED) 992 { 993 if (log.isDebugEnabled()) 994 { 995 if (log.isTraceEnabled()) log.trace(LogSupport.EXCEPTION,e); 996 else if(log.isDebugEnabled())log.debug(e.toString()); 997 } 998 _response.destroy(); 999 _response=null; 1000 } 1001 else 1002 exception(e); 1003 } 1004 catch (Exception e) {exception(e);} 1005 catch (Error e) {exception(e);} 1006 finally 1007 { 1008 int bytes_written=0; 1009 int content_length = _response==null 1010 ?-1:_response.getIntField(HttpFields.__ContentLength); 1011 1012 if (_persistent) 1014 { 1015 boolean no_continue_sent=false; 1016 try{ 1017 if (_inputStream.getExpectContinues()!=null) 1018 { 1019 _inputStream.setExpectContinues(null); 1020 no_continue_sent=true; 1021 } 1022 else 1023 { 1024 int remaining = _inputStream.getContentLength(); 1025 if (remaining!=0) 1026 while(_inputStream.skip(4096)>0 || _inputStream.read()>=0); 1028 } 1029 } 1030 catch(IOException e) 1031 { 1032 if (_inputStream.getContentLength()>0) 1033 _inputStream.setContentLength(0); 1034 _persistent=false; 1035 LogSupport.ignore(log,e); 1036 exception(new HttpException(HttpResponse.__400_Bad_Request, 1037 "Missing Content")); 1038 } 1039 1040 if (!no_continue_sent && _inputStream.getContentLength()>0) 1042 { 1043 _inputStream.setContentLength(0); 1044 _persistent=false; 1045 exception (new HttpException(HttpResponse.__400_Bad_Request,"Missing Content")); 1046 } 1047 1048 try{ 1050 _outputStream.close(); 1051 bytes_written=_outputStream.getBytesWritten(); 1052 _outputStream.resetStream(); 1053 _outputStream.addObserver(this); 1054 _inputStream.resetStream(); 1055 } 1056 catch(IOException e) {exception(e);} 1057 } 1058 else if (_response!=null) { 1060 try{ 1062 if (_inputStream.getContentLength()>0) 1063 while(_inputStream.skip(4096)>0 || _inputStream.read()>=0); 1064 _inputStream.resetStream(); 1065 } 1066 catch(IOException e){LogSupport.ignore(log,e);} 1067 1068 try{ 1070 _outputStream.flush(); 1071 _response.commit(); 1072 bytes_written=_outputStream.getBytesWritten(); 1073 _outputStream.close(); 1074 _outputStream.resetStream(); 1075 } 1076 catch(IOException e) {exception(e);} 1077 } 1078 1079 if (_response!=null) 1081 { 1082 if(log.isDebugEnabled())log.debug("RESPONSE:\n"+_response); 1083 if (_persistent && content_length>=0 && bytes_written>0 && content_length!=bytes_written) 1084 { 1085 log.warn("Invalid length: Content-Length="+content_length+ 1086 " written="+bytes_written+ 1087 " for "+_request.getRequestURL()); 1088 _persistent=false; 1089 try{_outputStream.close();} 1090 catch(IOException e) {log.warn(LogSupport.EXCEPTION,e);} 1091 } 1092 } 1093 1094 if (stats) 1096 statsRequestEnd(); 1097 if (context!=null) 1098 context.log(_request,_response,bytes_written); 1099 } 1100 1101 return (_tunnel!=null) || _persistent; 1102 } 1103 1104 1105 protected void statsRequestStart() 1106 { 1107 if (_statsOn) 1108 { 1109 if (_reqTime>0) 1110 statsRequestEnd(); 1111 _requests++; 1112 _tmpTime=_request.getTimeStamp(); 1113 _reqTime=_tmpTime; 1114 _httpServer.statsGotRequest(); 1115 } 1116 } 1117 1118 1119 protected void statsRequestEnd() 1120 { 1121 if (_statsOn && _reqTime>0) 1122 { 1123 _httpServer.statsEndRequest(System.currentTimeMillis()-_reqTime, 1124 (_response!=null)); 1125 _reqTime=0; 1126 } 1127 } 1128 1129 1130 1133 protected void recycle() 1134 { 1135 _listener.persistConnection(this); 1136 if (_request!=null) 1137 _request.recycle(this); 1138 if (_response!=null) 1139 _response.recycle(this); 1140 } 1141 1142 1143 1146 protected void destroy() 1147 { 1148 try{close();} 1149 catch (IOException e){LogSupport.ignore(log,e);} 1150 catch (Exception e){log.warn(LogSupport.EXCEPTION,e);} 1151 1152 if (_request!=null) 1154 _request.destroy(); 1155 if (_response!=null) 1156 _response.destroy(); 1157 if (_inputStream!=null) 1158 _inputStream.destroy(); 1159 if (_outputStream!=null) 1160 _outputStream.destroy(); 1161 _inputStream=null; 1162 _outputStream=null; 1163 _request=null; 1164 _response=null; 1165 _handlingThread=null; 1166 1167 if (_statsOn) 1168 { 1169 _tmpTime=System.currentTimeMillis(); 1170 if (_reqTime>0) 1171 _httpServer.statsEndRequest(_tmpTime-_reqTime,false); 1172 _httpServer.statsCloseConnection(_tmpTime-_openTime,_requests); 1173 } 1174 } 1175 1176} 1177 | Popular Tags |