All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.coyote.Request Maven / Gradle / Ivy

There is a newer version: 11.0.1
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.coyote;

import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletConnection;

import org.apache.tomcat.util.buf.CharsetHolder;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.buf.UDecoder;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.http.Parameters;
import org.apache.tomcat.util.http.ServerCookies;
import org.apache.tomcat.util.http.parser.MediaType;
import org.apache.tomcat.util.net.ApplicationBufferHandler;
import org.apache.tomcat.util.res.StringManager;

/**
 * This is a low-level, efficient representation of a server request. Most fields are GC-free, expensive operations are
 * delayed until the user code needs the information. Processing is delegated to modules, using a hook mechanism. This
 * class is not intended for user code - it is used internally by tomcat for processing the request in the most
 * efficient way. Users ( servlets ) can access the information using a facade, which provides the high-level view of
 * the request. Tomcat defines a number of attributes:
 * 
    *
  • "org.apache.tomcat.request" - allows access to the low-level request object in trusted applications *
* * @author James Duncan Davidson [[email protected]] * @author James Todd [[email protected]] * @author Jason Hunter [[email protected]] * @author Harish Prabandham * @author Alex Cruikshank [[email protected]] * @author Hans Bergsten [[email protected]] * @author Costin Manolache * @author Remy Maucherat */ public final class Request { private static final StringManager sm = StringManager.getManager(Request.class); // Expected maximum typical number of cookies per request. private static final int INITIAL_COOKIE_SIZE = 4; /* * At 100,000 requests a second there are enough IDs here for ~3,000,000 years before it overflows (and then we have * another 3,000,000 years before it gets back to zero). * * Local testing shows that 5, 10, 50, 500 or 1000 threads can obtain 60,000,000+ IDs a second from a single * AtomicLong. That is about about 17ns per request. It does not appear that the introduction of this counter will * cause a bottleneck for request processing. */ private static final AtomicLong requestIdGenerator = new AtomicLong(0); // ----------------------------------------------------------- Constructors public Request() { parameters.setQuery(queryMB); parameters.setURLDecoder(urlDecoder); } // ----------------------------------------------------- Instance Variables private int serverPort = -1; private final MessageBytes serverNameMB = MessageBytes.newInstance(); private int remotePort; private int localPort; private final MessageBytes schemeMB = MessageBytes.newInstance(); private final MessageBytes methodMB = MessageBytes.newInstance(); private final MessageBytes uriMB = MessageBytes.newInstance(); private final MessageBytes decodedUriMB = MessageBytes.newInstance(); private final MessageBytes queryMB = MessageBytes.newInstance(); private final MessageBytes protoMB = MessageBytes.newInstance(); private volatile String requestId = Long.toString(requestIdGenerator.getAndIncrement()); // remote address/host private final MessageBytes remoteAddrMB = MessageBytes.newInstance(); private final MessageBytes peerAddrMB = MessageBytes.newInstance(); private final MessageBytes localNameMB = MessageBytes.newInstance(); private final MessageBytes remoteHostMB = MessageBytes.newInstance(); private final MessageBytes localAddrMB = MessageBytes.newInstance(); private final MimeHeaders headers = new MimeHeaders(); private final MimeHeaders trailerFields = new MimeHeaders(); /** * Path parameters */ private final Map pathParameters = new HashMap<>(); /** * Notes. */ private final Object notes[] = new Object[Constants.MAX_NOTES]; /** * Associated input buffer. */ private InputBuffer inputBuffer = null; /** * URL decoder. */ private final UDecoder urlDecoder = new UDecoder(); /** * HTTP specific fields. (remove them ?) */ private long contentLength = -1; private MessageBytes contentTypeMB = null; private CharsetHolder charsetHolder = CharsetHolder.EMPTY; /** * Is there an expectation ? */ private boolean expectation = false; private final ServerCookies serverCookies = new ServerCookies(INITIAL_COOKIE_SIZE); private final Parameters parameters = new Parameters(); private final MessageBytes remoteUser = MessageBytes.newInstance(); private boolean remoteUserNeedsAuthorization = false; private final MessageBytes authType = MessageBytes.newInstance(); private final HashMap attributes = new HashMap<>(); private Response response; private volatile ActionHook hook; private long bytesRead = 0; // Time of the request - useful to avoid repeated calls to System.currentTime private long startTimeNanos = -1; private long threadId = 0; private int available = 0; private final RequestInfo reqProcessorMX = new RequestInfo(this); private boolean sendfile = true; /** * Holds request body reading error exception. */ private Exception errorException = null; /* * State for non-blocking output is maintained here as it is the one point easily reachable from the * CoyoteInputStream and the CoyoteAdapter which both need access to state. */ volatile ReadListener listener; // Ensures listener is only fired after a call is isReady() private boolean fireListener = false; // Tracks read registration to prevent duplicate registrations private boolean registeredForRead = false; // Lock used to manage concurrent access to above flags private final Object nonBlockingStateLock = new Object(); public ReadListener getReadListener() { return listener; } public void setReadListener(ReadListener listener) { if (listener == null) { throw new NullPointerException(sm.getString("request.nullReadListener")); } if (getReadListener() != null) { throw new IllegalStateException(sm.getString("request.readListenerSet")); } // Note: This class is not used for HTTP upgrade so only need to test // for async AtomicBoolean result = new AtomicBoolean(false); action(ActionCode.ASYNC_IS_ASYNC, result); if (!result.get()) { throw new IllegalStateException(sm.getString("request.notAsync")); } this.listener = listener; // The container is responsible for the first call to // listener.onDataAvailable(). If isReady() returns true, the container // needs to call listener.onDataAvailable() from a new thread. If // isReady() returns false, the socket will be registered for read and // the container will call listener.onDataAvailable() once data arrives. // Must call isFinished() first as a call to isReady() if the request // has been finished will register the socket for read interest and that // is not required. if (!isFinished() && isReady()) { synchronized (nonBlockingStateLock) { // Ensure we don't get multiple read registrations registeredForRead = true; // Need to set the fireListener flag otherwise when the // container tries to trigger onDataAvailable, nothing will // happen fireListener = true; } action(ActionCode.DISPATCH_READ, null); if (!isRequestThread()) { // Not on a container thread so need to execute the dispatch action(ActionCode.DISPATCH_EXECUTE, null); } } } public boolean isReady() { // Assume read is not possible boolean ready = false; synchronized (nonBlockingStateLock) { if (registeredForRead) { fireListener = true; return false; } ready = checkRegisterForRead(); fireListener = !ready; } return ready; } private boolean checkRegisterForRead() { AtomicBoolean ready = new AtomicBoolean(false); synchronized (nonBlockingStateLock) { if (!registeredForRead) { action(ActionCode.NB_READ_INTEREST, ready); registeredForRead = !ready.get(); } } return ready.get(); } public void onDataAvailable() throws IOException { boolean fire = false; synchronized (nonBlockingStateLock) { registeredForRead = false; if (fireListener) { fireListener = false; fire = true; } } if (fire) { listener.onDataAvailable(); } } private final AtomicBoolean allDataReadEventSent = new AtomicBoolean(false); public boolean sendAllDataReadEvent() { return allDataReadEventSent.compareAndSet(false, true); } // ------------------------------------------------------------- Properties public MimeHeaders getMimeHeaders() { return headers; } public boolean isTrailerFieldsReady() { AtomicBoolean result = new AtomicBoolean(false); action(ActionCode.IS_TRAILER_FIELDS_READY, result); return result.get(); } public Map getTrailerFields() { return trailerFields.toMap(); } public MimeHeaders getMimeTrailerFields() { return trailerFields; } public UDecoder getURLDecoder() { return urlDecoder; } // -------------------- Request data -------------------- public MessageBytes scheme() { return schemeMB; } public MessageBytes method() { return methodMB; } public MessageBytes requestURI() { return uriMB; } public MessageBytes decodedURI() { return decodedUriMB; } public MessageBytes queryString() { return queryMB; } public MessageBytes protocol() { return protoMB; } /** * Get the "virtual host", derived from the Host: header associated with this request. * * @return The buffer holding the server name, if any. Use isNull() to check if there is no value set. */ public MessageBytes serverName() { return serverNameMB; } public int getServerPort() { return serverPort; } public void setServerPort(int serverPort) { this.serverPort = serverPort; } public MessageBytes remoteAddr() { return remoteAddrMB; } public MessageBytes peerAddr() { return peerAddrMB; } public MessageBytes remoteHost() { return remoteHostMB; } public MessageBytes localName() { return localNameMB; } public MessageBytes localAddr() { return localAddrMB; } public int getRemotePort() { return remotePort; } public void setRemotePort(int port) { this.remotePort = port; } public int getLocalPort() { return localPort; } public void setLocalPort(int port) { this.localPort = port; } // -------------------- encoding/type -------------------- /** * Get the character encoding used for this request. * * @return The value set via {@link #setCharset(Charset)} or if no call has been made to that method try to obtain * if from the content type. * * @deprecated Unused. This method will be removed in Tomcat 12. */ @Deprecated public String getCharacterEncoding() { if (charsetHolder.getName() == null) { charsetHolder = CharsetHolder.getInstance(getCharsetFromContentType(getContentType())); } return charsetHolder.getName(); } /** * Get the character encoding used for this request. * * @return The value set via {@link #setCharset(Charset)} or if no call has been made to that method try to obtain * if from the content type. * * @throws UnsupportedEncodingException If the user agent has specified an invalid character encoding * * @deprecated Unused. This method will be removed in Tomcat 12. */ @Deprecated public Charset getCharset() throws UnsupportedEncodingException { if (charsetHolder.getName() == null) { // Populates charsetHolder getCharacterEncoding(); } return charsetHolder.getValidatedCharset(); } /** * Unused. * * @param charset The Charset to use for the request * * @deprecated Unused. This method will be removed in Tomcat 12. */ @Deprecated public void setCharset(Charset charset) { charsetHolder = CharsetHolder.getInstance(charset); } public CharsetHolder getCharsetHolder() { if (charsetHolder.getName() == null) { charsetHolder = CharsetHolder.getInstance(getCharsetFromContentType(getContentType())); } return charsetHolder; } public void setCharsetHolder(CharsetHolder charsetHolder) { this.charsetHolder = charsetHolder; } public void setContentLength(long len) { this.contentLength = len; } public int getContentLength() { long length = getContentLengthLong(); if (length < Integer.MAX_VALUE) { return (int) length; } return -1; } public long getContentLengthLong() { if (contentLength > -1) { return contentLength; } MessageBytes clB = headers.getUniqueValue("content-length"); contentLength = (clB == null || clB.isNull()) ? -1 : clB.getLong(); return contentLength; } public String getContentType() { contentType(); if (contentTypeMB == null || contentTypeMB.isNull()) { return null; } return contentTypeMB.toStringType(); } public void setContentType(String type) { contentTypeMB.setString(type); } public MessageBytes contentType() { if (contentTypeMB == null) { contentTypeMB = headers.getValue("content-type"); } return contentTypeMB; } public void setContentType(MessageBytes mb) { contentTypeMB = mb; } public String getHeader(String name) { return headers.getHeader(name); } public void setExpectation(boolean expectation) { this.expectation = expectation; } public boolean hasExpectation() { return expectation; } // -------------------- Associated response -------------------- public Response getResponse() { return response; } public void setResponse(Response response) { this.response = response; response.setRequest(this); } protected void setHook(ActionHook hook) { this.hook = hook; } public void action(ActionCode actionCode, Object param) { if (hook != null) { if (param == null) { hook.action(actionCode, this); } else { hook.action(actionCode, param); } } } // -------------------- Cookies -------------------- public ServerCookies getCookies() { return serverCookies; } // -------------------- Parameters -------------------- public Parameters getParameters() { return parameters; } public void addPathParameter(String name, String value) { pathParameters.put(name, value); } public String getPathParameter(String name) { return pathParameters.get(name); } // -------------------- Other attributes -------------------- // We can use notes for most - need to discuss what is of general interest public void setAttribute(String name, Object o) { attributes.put(name, o); } public HashMap getAttributes() { return attributes; } public Object getAttribute(String name) { return attributes.get(name); } public MessageBytes getRemoteUser() { return remoteUser; } public boolean getRemoteUserNeedsAuthorization() { return remoteUserNeedsAuthorization; } public void setRemoteUserNeedsAuthorization(boolean remoteUserNeedsAuthorization) { this.remoteUserNeedsAuthorization = remoteUserNeedsAuthorization; } public MessageBytes getAuthType() { return authType; } public int getAvailable() { return available; } public void setAvailable(int available) { this.available = available; } public boolean getSendfile() { return sendfile; } public void setSendfile(boolean sendfile) { this.sendfile = sendfile; } public boolean isFinished() { AtomicBoolean result = new AtomicBoolean(false); action(ActionCode.REQUEST_BODY_FULLY_READ, result); return result.get(); } public boolean getSupportsRelativeRedirects() { if (protocol().equals("") || protocol().equals("HTTP/1.0")) { return false; } return true; } // -------------------- Input Buffer -------------------- public InputBuffer getInputBuffer() { return inputBuffer; } public void setInputBuffer(InputBuffer inputBuffer) { this.inputBuffer = inputBuffer; } /** * Read data from the input buffer and put it into ApplicationBufferHandler. The buffer is owned by the protocol * implementation - it will be reused on the next read. The Adapter must either process the data in place or copy it * to a separate buffer if it needs to hold it. In most cases this is done during byte->char conversions or via * InputStream. Unlike InputStream, this interface allows the app to process data in place, without copy. * * @param handler The destination to which to copy the data * * @return The number of bytes copied * * @throws IOException If an I/O error occurs during the copy */ public int doRead(ApplicationBufferHandler handler) throws IOException { if (getBytesRead() == 0 && !response.isCommitted()) { action(ActionCode.ACK, ContinueResponseTiming.ON_REQUEST_BODY_READ); } int n = inputBuffer.doRead(handler); if (n > 0) { bytesRead += n; } return n; } // -------------------- Error tracking -------------------- /** * Set the error Exception that occurred during the writing of the response processing. * * @param ex The exception that occurred */ public void setErrorException(Exception ex) { errorException = ex; } /** * Get the Exception that occurred during the writing of the response. * * @return The exception that occurred */ public Exception getErrorException() { return errorException; } public boolean isExceptionPresent() { return errorException != null; } // -------------------- debug -------------------- public String getRequestId() { return requestId; } public String getProtocolRequestId() { AtomicReference ref = new AtomicReference<>(); hook.action(ActionCode.PROTOCOL_REQUEST_ID, ref); return ref.get(); } public ServletConnection getServletConnection() { AtomicReference ref = new AtomicReference<>(); hook.action(ActionCode.SERVLET_CONNECTION, ref); return ref.get(); } @Override public String toString() { return "R( " + requestURI().toString() + ")"; } public long getStartTime() { return System.currentTimeMillis() - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTimeNanos); } public long getStartTimeNanos() { return startTimeNanos; } public void setStartTimeNanos(long startTimeNanos) { this.startTimeNanos = startTimeNanos; } public long getThreadId() { return threadId; } public void clearRequestThread() { threadId = 0; } @SuppressWarnings("deprecation") public void setRequestThread() { Thread t = Thread.currentThread(); threadId = t.getId(); getRequestProcessor().setWorkerThreadName(t.getName()); } @SuppressWarnings("deprecation") public boolean isRequestThread() { return Thread.currentThread().getId() == threadId; } // -------------------- Per-Request "notes" -------------------- /** * Used to store private data. Thread data could be used instead - but if you have the req, getting/setting a note * is just an array access, may be faster than ThreadLocal for very frequent operations. Example use: Catalina * CoyoteAdapter: ADAPTER_NOTES = 1 - stores the HttpServletRequest object ( req/res) To avoid conflicts, note in * the range 0 - 8 are reserved for the servlet container ( catalina connector, etc ), and values in 9 - 16 for * connector use. 17-31 range is not allocated or used. * * @param pos Index to use to store the note * @param value The value to store at that index */ public void setNote(int pos, Object value) { notes[pos] = value; } public Object getNote(int pos) { return notes[pos]; } // -------------------- Recycling -------------------- public void recycle() { bytesRead = 0; contentLength = -1; contentTypeMB = null; charsetHolder = CharsetHolder.EMPTY; expectation = false; headers.recycle(); trailerFields.recycle(); /* * Trailer fields are limited in size by bytes. The following call ensures that any request with a large number * of small trailer fields doesn't result in a long lasting, large array of headers inside the MimeHeader * instance. */ trailerFields.setLimit(MimeHeaders.DEFAULT_HEADER_SIZE); serverNameMB.recycle(); serverPort = -1; localAddrMB.recycle(); localNameMB.recycle(); localPort = -1; peerAddrMB.recycle(); remoteAddrMB.recycle(); remoteHostMB.recycle(); remotePort = -1; available = 0; sendfile = true; // There may be multiple calls to recycle but only the first should // trigger a change in the request ID until a new request has been // started. Use startTimeNanos to detect when a request has started so a // subsequent call to recycle() will trigger a change in the request ID. if (startTimeNanos != -1) { requestId = Long.toHexString(requestIdGenerator.getAndIncrement()); } serverCookies.recycle(); parameters.recycle(); pathParameters.clear(); uriMB.recycle(); decodedUriMB.recycle(); queryMB.recycle(); methodMB.recycle(); protoMB.recycle(); schemeMB.recycle(); remoteUser.recycle(); remoteUserNeedsAuthorization = false; authType.recycle(); attributes.clear(); errorException = null; listener = null; synchronized (nonBlockingStateLock) { fireListener = false; registeredForRead = false; } allDataReadEventSent.set(false); startTimeNanos = -1; threadId = 0; } // -------------------- Info -------------------- public void updateCounters() { reqProcessorMX.updateCounters(); } public RequestInfo getRequestProcessor() { return reqProcessorMX; } public long getBytesRead() { return bytesRead; } public boolean isProcessing() { return reqProcessorMX.getStage() == Constants.STAGE_SERVICE; } /** * Parse the character encoding from the specified content type header. If the content type is null, or there is no * explicit character encoding, null is returned. * * @param contentType a content type header */ private static String getCharsetFromContentType(String contentType) { if (contentType == null) { return null; } MediaType mediaType = null; try { mediaType = MediaType.parseMediaType(new StringReader(contentType)); } catch (IOException e) { // Ignore - null test below handles this } if (mediaType != null) { return mediaType.getCharset(); } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy