Mercurial Hosting > luan
view src/org/eclipse/jetty/server/Response.java @ 1022:3718afd99988
HttpHeaders uses StringCache
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Tue, 01 Nov 2016 01:04:46 -0600 |
parents | 6be43ef1eb96 |
children | 27f3dc761452 |
line wrap: on
line source
// // ======================================================================== // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.server; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.Enumeration; import java.util.Locale; import javax.servlet.RequestDispatcher; import javax.servlet.ServletOutputStream; import javax.servlet.SessionTrackingMode; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeaderValues; import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersions; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.BufferCache.CachedBuffer; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.ByteArrayISO8859Writer; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Response. * <p> * Implements {@link javax.servlet.http.HttpServletResponse} from the <code>javax.servlet.http</code> package. * </p> */ public final class Response implements HttpServletResponse { private static final Logger LOG = LoggerFactory.getLogger(Response.class); public static final int NONE=0, STREAM=1, WRITER=2; /** * If a header name starts with this string, the header (stripped of the prefix) * can be set during include using only {@link #setHeader(String, String)} or * {@link #addHeader(String, String)}. */ public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include."; /** * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie * will be set as HTTP ONLY. */ public final static String HTTP_ONLY_COMMENT="__HTTP_ONLY__"; private final AbstractHttpConnection _connection; private int _status = SC_OK; private String _reason; private Locale _locale; private String _mimeType; private CachedBuffer _cachedMimeType; private String _characterEncoding; private boolean _explicitEncoding; private String _contentType; private volatile int _outputState; private PrintWriter _writer; public Response(AbstractHttpConnection connection) { _connection = connection; } public void addCookie(HttpCookie cookie) { _connection._responseFields.addSetCookie(cookie); } @Override public void addCookie(Cookie cookie) { String comment=cookie.getComment(); boolean http_only=false; if (comment!=null) { int i=comment.indexOf(HTTP_ONLY_COMMENT); if (i>=0) { http_only=true; comment=comment.replace(HTTP_ONLY_COMMENT,"").trim(); if (comment.length()==0) comment=null; } } _connection._responseFields.addSetCookie(cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getPath(), cookie.getMaxAge(), comment, cookie.getSecure(), http_only || cookie.isHttpOnly(), cookie.getVersion()); } @Override public boolean containsHeader(String name) { return _connection._responseFields.containsKey(name); } @Override public String encodeURL(String url) { throw new UnsupportedOperationException(); } @Override public String encodeRedirectURL(String url) { throw new UnsupportedOperationException(); } @Override @Deprecated public String encodeUrl(String url) { throw new UnsupportedOperationException(); } @Override @Deprecated public String encodeRedirectUrl(String url) { throw new UnsupportedOperationException(); } @Override public void sendError(int code, String message) throws IOException { if (isCommitted()) LOG.warn("Committed before "+code+" "+message); resetBuffer(); _characterEncoding = null; setHeader(HttpHeaders.EXPIRES,null); setHeader(HttpHeaders.LAST_MODIFIED,null); setHeader(HttpHeaders.CACHE_CONTROL,null); setHeader(HttpHeaders.CONTENT_TYPE,null); setHeader(HttpHeaders.CONTENT_LENGTH,null); _outputState = NONE; setStatus(code,message); if (message==null) message=HttpStatus.getMessage(code); // If we are allowed to have a body if (code!=SC_NO_CONTENT && code!=SC_NOT_MODIFIED && code!=SC_PARTIAL_CONTENT && code>=SC_OK) { Request request = _connection.getRequest(); setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); setContentType(MimeTypes.TEXT_HTML_8859_1); ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048); if (message != null) { message= StringUtil.replace(message, "&", "&"); message= StringUtil.replace(message, "<", "<"); message= StringUtil.replace(message, ">", ">"); } String uri= request.getRequestURI(); if (uri!=null) { uri= StringUtil.replace(uri, "&", "&"); uri= StringUtil.replace(uri, "<", "<"); uri= StringUtil.replace(uri, ">", ">"); } writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n"); writer.write("<title>Error "); writer.write(Integer.toString(code)); writer.write(' '); if (message==null) message=HttpStatus.getMessage(code); writer.write(message); writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: "); writer.write(Integer.toString(code)); writer.write("</h2>\n<p>Problem accessing "); writer.write(uri); writer.write(". Reason:\n<pre> "); writer.write(message); writer.write("</pre>"); writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>"); for (int i= 0; i < 20; i++) writer.write("\n "); writer.write("\n</body>\n</html>\n"); writer.flush(); setContentLength(writer.size()); writer.writeTo(getOutputStream()); writer.destroy(); } else if (code!=SC_PARTIAL_CONTENT) { _connection._requestFields.remove(HttpHeaders.CONTENT_TYPE); _connection._requestFields.remove(HttpHeaders.CONTENT_LENGTH); _characterEncoding = null; _mimeType = null; _cachedMimeType = null; } complete(); } @Override public void sendError(int sc) throws IOException { sendError(sc,null); } @Override public void sendRedirect(String location) throws IOException { if (location==null) throw new IllegalArgumentException(); if (!URIUtil.hasScheme(location)) { StringBuilder buf = _connection.getRequest().getRootURL(); if (location.startsWith("/")) buf.append(location); else { String path=_connection.getRequest().getRequestURI(); String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path); location=URIUtil.addPaths(parent,location); if(location==null) throw new IllegalStateException("path cannot be above root"); if (!location.startsWith("/")) buf.append('/'); buf.append(location); } location=buf.toString(); HttpURI uri = new HttpURI(location); String path=uri.getDecodedPath(); String canonical=URIUtil.canonicalPath(path); if (canonical==null) throw new IllegalArgumentException(); if (!canonical.equals(path)) { buf = _connection.getRequest().getRootURL(); buf.append(URIUtil.encodePath(canonical)); String param=uri.getParam(); if (param!=null) { buf.append(';'); buf.append(param); } String query=uri.getQuery(); if (query!=null) { buf.append('?'); buf.append(query); } String fragment=uri.getFragment(); if (fragment!=null) { buf.append('#'); buf.append(fragment); } location=buf.toString(); } } resetBuffer(); setHeader(HttpHeaders.LOCATION,location); setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); complete(); } @Override public void setDateHeader(String name, long date) { _connection._responseFields.putDateField(name, date); } @Override public void addDateHeader(String name, long date) { _connection._responseFields.addDateField(name, date); } @Override public void setHeader(String name, String value) { if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) setContentType(value); else { _connection._responseFields.put(name, value); if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) { if (value==null) _connection._generator.setContentLength(-1); else _connection._generator.setContentLength(Long.parseLong(value)); } } } @Override public Collection<String> getHeaderNames() { final HttpFields fields=_connection._responseFields; return fields.getFieldNamesCollection(); } @Override public String getHeader(String name) { return _connection._responseFields.getStringField(name); } @Override public Collection<String> getHeaders(String name) { final HttpFields fields=_connection._responseFields; Collection<String> i = fields.getValuesCollection(name); if (i==null) return Collections.EMPTY_LIST; return i; } @Override public void addHeader(String name, String value) { if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name)) { setContentType(value); return; } _connection._responseFields.add(name, value); if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) _connection._generator.setContentLength(Long.parseLong(value)); } @Override public void setIntHeader(String name, int value) { _connection._responseFields.putLongField(name, value); if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) _connection._generator.setContentLength(value); } @Override public void addIntHeader(String name, int value) { _connection._responseFields.addLongField(name, value); if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name)) _connection._generator.setContentLength(value); } @Override public void setStatus(int sc) { setStatus(sc,null); } @Override public void setStatus(int sc, String sm) { if (sc<=0) throw new IllegalArgumentException(); _status = sc; _reason = sm; } @Override public String getCharacterEncoding() { if (_characterEncoding==null) _characterEncoding = StringUtil.__ISO_8859_1; return _characterEncoding; } @Override public String getContentType() { return _contentType; } @Override public ServletOutputStream getOutputStream() throws IOException { if (_outputState!=NONE && _outputState!=STREAM) throw new IllegalStateException("WRITER"); ServletOutputStream out = _connection.getOutputStream(); _outputState = STREAM; return out; } @Override public PrintWriter getWriter() throws IOException { if (_outputState!=NONE && _outputState!=WRITER) throw new IllegalStateException("STREAM"); /* if there is no writer yet */ if (_writer==null) { /* get encoding from Content-Type header */ String encoding = _characterEncoding; if (encoding==null) { /* implementation of educated defaults */ if(_cachedMimeType != null) encoding = MimeTypes.getCharsetFromContentType(_cachedMimeType); if (encoding==null) encoding = StringUtil.__ISO_8859_1; setCharacterEncoding(encoding); } /* construct Writer using correct encoding */ _writer = _connection.getPrintWriter(encoding); } _outputState=WRITER; return _writer; } @Override public void setCharacterEncoding(String encoding) { if (this._outputState==0 && !isCommitted()) { _explicitEncoding = true; if (encoding==null) { // Clear any encoding. if (_characterEncoding!=null) { _characterEncoding=null; if (_cachedMimeType!=null) _contentType = _cachedMimeType.toString(); else if (_mimeType!=null) _contentType = _mimeType; else _contentType = null; if (_contentType==null) _connection._responseFields.remove(HttpHeaders.CONTENT_TYPE); else _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else { // No, so just add this one to the mimetype _characterEncoding=encoding; if (_contentType!=null) { int i0=_contentType.indexOf(';'); if (i0<0) { _contentType=null; if(_cachedMimeType!=null) { CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); if (content_type!=null) { _contentType = content_type.toString(); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } if (_contentType==null) { _contentType = _mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else { int i1=_contentType.indexOf("charset=",i0); if (i1<0) { _contentType = _contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); } else { int i8=i1+8; int i2=_contentType.indexOf(" ",i8); if (i2<0) _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); else _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ")+_contentType.substring(i2); } _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } } } } @Override public void setContentLength(int len) { // Protect from setting after committed as default handling // of a servlet HEAD request ALWAYS sets _content length, even // if the getHandling committed the response! if (isCommitted()) return; _connection._generator.setContentLength(len); if (len>0) { _connection._responseFields.putLongField(HttpHeaders.CONTENT_LENGTH, len); if (_connection._generator.isAllContentWritten()) { if (_outputState==WRITER) _writer.close(); else if (_outputState==STREAM) { try { getOutputStream().close(); } catch(IOException e) { throw new RuntimeException(e); } } } } } @Override public void setContentType(String contentType) { if (isCommitted()) return; // Yes this method is horribly complex.... but there are lots of special cases and // as this method is called on every request, it is worth trying to save string creation. // if (contentType==null) { if (_locale==null) _characterEncoding = null; _mimeType = null; _cachedMimeType = null; _contentType=null; _connection._responseFields.remove(HttpHeaders.CONTENT_TYPE); } else { // Look for encoding in contentType int i0=contentType.indexOf(';'); if (i0>0) { // we have content type parameters // Extract params off mimetype _mimeType = contentType.substring(0,i0).trim(); _cachedMimeType = MimeTypes.CACHE.get(_mimeType); // Look for charset int i1=contentType.indexOf("charset=",i0+1); if (i1>=0) { _explicitEncoding = true; int i8=i1+8; int i2 = contentType.indexOf(' ',i8); if (_outputState==WRITER) { // strip the charset and ignore; if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' ')) { if (_cachedMimeType!=null) { CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); if (content_type!=null) { _contentType=content_type.toString(); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } else { _contentType = _mimeType+";charset="+_characterEncoding; _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else { _contentType = _mimeType+";charset="+_characterEncoding; _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else if (i2<0) { _contentType=contentType.substring(0,i1)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } else { _contentType=contentType.substring(0,i1)+contentType.substring(i2)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' ')) { // The params are just the char encoding _cachedMimeType = MimeTypes.CACHE.get(_mimeType); _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8)); if (_cachedMimeType!=null) { CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); if (content_type!=null) { _contentType=content_type.toString(); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } else { _contentType=contentType; _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else { _contentType=contentType; _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else if (i2>0) { _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8,i2)); _contentType=contentType; _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } else { _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8)); _contentType=contentType; _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else // No encoding in the params. { _cachedMimeType = null; _contentType=_characterEncoding==null?contentType:contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else // No params at all { _mimeType = contentType; _cachedMimeType = MimeTypes.CACHE.get(_mimeType); if (_characterEncoding!=null) { if (_cachedMimeType!=null) { CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding); if (content_type!=null) { _contentType=content_type.toString(); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } else { _contentType = _mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else { _contentType=contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } else if (_cachedMimeType!=null) { _contentType = _cachedMimeType.toString(); _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } else { _contentType=contentType; _connection._responseFields.put(HttpHeaders.CONTENT_TYPE,_contentType); } } } } @Override public void setBufferSize(int size) { throw new UnsupportedOperationException(); } @Override public int getBufferSize() { return _connection._generator.getContentBufferSize(); } @Override public void flushBuffer() throws IOException { _connection.flushResponse(); } @Override public void reset() { resetBuffer(); fwdReset(); _status = 200; _reason = null; HttpFields response_fields=_connection._responseFields; response_fields.clear(); String connection=_connection._requestFields.getStringField(HttpHeaders.CONNECTION); if (connection!=null) { String[] values = connection.split(","); for (int i=0;values!=null && i<values.length;i++) { int cb = HttpHeaderValues.CACHE.getOrdinal(values[0].trim()); if (cb != -1) { switch(cb) { case HttpHeaderValues.CLOSE_ORDINAL: response_fields.put(HttpHeaders.CONNECTION,HttpHeaderValues.CLOSE); break; case HttpHeaderValues.KEEP_ALIVE_ORDINAL: if (HttpVersions.HTTP_1_0.equalsIgnoreCase(_connection.getRequest().getProtocol())) response_fields.put(HttpHeaders.CONNECTION,HttpHeaderValues.KEEP_ALIVE); break; case HttpHeaderValues.TE_ORDINAL: response_fields.put(HttpHeaders.CONNECTION,HttpHeaderValues.TE); break; } } } } } private void fwdReset() { resetBuffer(); _writer=null; _outputState=NONE; } @Override public void resetBuffer() { if (isCommitted()) throw new IllegalStateException("Committed"); _connection._generator.resetBuffer(); } @Override public boolean isCommitted() { return _connection._generator.isCommitted(); } @Override public void setLocale(Locale locale) { if (locale == null || isCommitted()) return; _locale = locale; _connection._responseFields.put(HttpHeaders.CONTENT_LANGUAGE,locale.toString().replace('_','-')); } @Override public Locale getLocale() { if (_locale==null) return Locale.getDefault(); return _locale; } /* ------------------------------------------------------------ */ /** * @return The HTTP status code that has been set for this request. This will be <code>200<code> * ({@link HttpServletResponse#SC_OK}), unless explicitly set through one of the <code>setStatus</code> methods. */ public int getStatus() { return _status; } /* ------------------------------------------------------------ */ /** * @return The reason associated with the current {@link #getStatus() status}. This will be <code>null</code>, * unless one of the <code>setStatus</code> methods have been called. */ public String getReason() { return _reason; } public void complete() throws IOException { _connection.completeResponse(); } /* ------------------------------------------------------------- */ /** * @return the number of bytes actually written in response body */ public long getContentCount() { if (_connection==null) return -1; return _connection._generator.getContentWritten(); } public HttpFields getHttpFields() { return _connection._responseFields; } @Override public String toString() { return "HTTP/1.1 "+_status+" "+ (_reason==null?"":_reason) +System.getProperty("line.separator")+ _connection._responseFields.toString(); } }