| /* |
| * 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.time.Instant; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| 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.Method; |
| 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: |
| * <ul> |
| * <li>"org.apache.tomcat.request" - allows access to the low-level request object in trusted applications |
| * </ul> |
| */ |
| 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 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); |
| |
| // public static final int NOTE_ADAPTER = 1; // Defined in CoyoteAdapter |
| /** |
| * Note key for bad request. |
| */ |
| public static final int NOTE_BAD_REQUEST = 2; |
| |
| |
| // ----------------------------------------------------------- Constructors |
| |
| /** |
| * Default constructor. |
| */ |
| 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 String method; |
| 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<String,String> 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 = null; |
| |
| /** |
| * 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<String,Object> 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 Instant startInstant = null; |
| 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(); |
| |
| /** |
| * Return the read listener. |
| * |
| * @return the read listener |
| */ |
| public ReadListener getReadListener() { |
| return listener; |
| } |
| |
| /** |
| * Set the read listener for non-blocking reads. |
| * |
| * @param listener the read 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); |
| } |
| } |
| } |
| |
| /** |
| * Check if the request is ready for reading. |
| * |
| * @return {@code true} if ready for reading |
| */ |
| public boolean isReady() { |
| // Assume read is not possible |
| boolean ready; |
| 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(); |
| } |
| |
| /** |
| * Called when data is available for reading. |
| * |
| * @throws IOException if an I/O error occurs |
| */ |
| 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); |
| |
| /** |
| * Send the all-data-read event. |
| * |
| * @return {@code true} if the event was sent |
| */ |
| public boolean sendAllDataReadEvent() { |
| return allDataReadEventSent.compareAndSet(false, true); |
| } |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| /** |
| * Return the MIME headers. |
| * |
| * @return the MIME headers |
| */ |
| public MimeHeaders getMimeHeaders() { |
| return headers; |
| } |
| |
| |
| /** |
| * Check if trailer fields are ready. |
| * |
| * @return {@code true} if trailer fields are ready |
| */ |
| public boolean isTrailerFieldsReady() { |
| AtomicBoolean result = new AtomicBoolean(false); |
| action(ActionCode.IS_TRAILER_FIELDS_READY, result); |
| return result.get(); |
| } |
| |
| |
| /** |
| * Return the trailer fields as a map. |
| * |
| * @return the trailer fields |
| */ |
| public Map<String,String> getTrailerFields() { |
| return trailerFields.toMap(); |
| } |
| |
| |
| /** |
| * Return the MIME trailer fields. |
| * |
| * @return the MIME trailer fields |
| */ |
| public MimeHeaders getMimeTrailerFields() { |
| return trailerFields; |
| } |
| |
| |
| /** |
| * Return the URL decoder. |
| * |
| * @return the URL decoder |
| */ |
| public UDecoder getURLDecoder() { |
| return urlDecoder; |
| } |
| |
| |
| // -------------------- Request data -------------------- |
| |
| /** |
| * Return the scheme (e.g. "http" or "https"). |
| * |
| * @return the scheme |
| */ |
| public MessageBytes scheme() { |
| return schemeMB; |
| } |
| |
| /** |
| * Set the HTTP method. |
| * |
| * @param method the HTTP method |
| */ |
| public void setMethod(String method) { |
| this.method = method; |
| } |
| |
| /** |
| * Set the HTTP method from a byte buffer. |
| * |
| * @param buf the byte buffer |
| * @param start the start offset |
| * @param len the length |
| */ |
| public void setMethod(byte[] buf, int start, int len) { |
| this.method = Method.bytesToString(buf, start, len); |
| } |
| |
| /** |
| * Return the HTTP method. |
| * |
| * @return the HTTP method |
| */ |
| public String getMethod() { |
| return method; |
| } |
| |
| /** |
| * Return the request URI. |
| * |
| * @return the request URI |
| */ |
| public MessageBytes requestURI() { |
| return uriMB; |
| } |
| |
| /** |
| * Return the decoded request URI. |
| * |
| * @return the decoded URI |
| */ |
| public MessageBytes decodedURI() { |
| return decodedUriMB; |
| } |
| |
| /** |
| * Return the query string. |
| * |
| * @return the query string |
| */ |
| public MessageBytes queryString() { |
| return queryMB; |
| } |
| |
| /** |
| * Return the protocol. |
| * |
| * @return the protocol |
| */ |
| 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; |
| } |
| |
| /** |
| * Return the server port. |
| * |
| * @return the server port |
| */ |
| public int getServerPort() { |
| return serverPort; |
| } |
| |
| /** |
| * Set the server port. |
| * |
| * @param serverPort the server port |
| */ |
| public void setServerPort(int serverPort) { |
| this.serverPort = serverPort; |
| } |
| |
| /** |
| * Return the remote address. |
| * |
| * @return the remote address |
| */ |
| public MessageBytes remoteAddr() { |
| return remoteAddrMB; |
| } |
| |
| /** |
| * Return the peer address. |
| * |
| * @return the peer address |
| */ |
| public MessageBytes peerAddr() { |
| return peerAddrMB; |
| } |
| |
| /** |
| * Return the remote host. |
| * |
| * @return the remote host |
| */ |
| public MessageBytes remoteHost() { |
| return remoteHostMB; |
| } |
| |
| /** |
| * Return the local name. |
| * |
| * @return the local name |
| */ |
| public MessageBytes localName() { |
| return localNameMB; |
| } |
| |
| /** |
| * Return the local address. |
| * |
| * @return the local address |
| */ |
| public MessageBytes localAddr() { |
| return localAddrMB; |
| } |
| |
| /** |
| * Return the remote port. |
| * |
| * @return the remote port |
| */ |
| public int getRemotePort() { |
| return remotePort; |
| } |
| |
| /** |
| * Set the remote port. |
| * |
| * @param port the remote port |
| */ |
| public void setRemotePort(int port) { |
| this.remotePort = port; |
| } |
| |
| /** |
| * Return the local port. |
| * |
| * @return the local port |
| */ |
| public int getLocalPort() { |
| return localPort; |
| } |
| |
| /** |
| * Set the local port. |
| * |
| * @param port the local port |
| */ |
| public void setLocalPort(int port) { |
| this.localPort = port; |
| } |
| |
| |
| // -------------------- encoding/type -------------------- |
| |
| /** |
| * Return the charset holder. |
| * |
| * @return the charset holder |
| */ |
| public CharsetHolder getCharsetHolder() { |
| if (charsetHolder == null) { |
| charsetHolder = CharsetHolder.getInstance(getCharsetFromContentType(getContentType())); |
| } |
| return charsetHolder; |
| } |
| |
| |
| /** |
| * Set the charset holder. |
| * |
| * @param charsetHolder the charset holder |
| */ |
| public void setCharsetHolder(CharsetHolder charsetHolder) { |
| if (charsetHolder == null || charsetHolder.getName() == null) { |
| this.charsetHolder = null; |
| } else { |
| this.charsetHolder = charsetHolder; |
| } |
| } |
| |
| |
| /** |
| * Set the content length. |
| * |
| * @param len the content length |
| */ |
| public void setContentLength(long len) { |
| this.contentLength = len; |
| } |
| |
| |
| /** |
| * Return the content length. |
| * |
| * @return the content length |
| */ |
| public int getContentLength() { |
| long length = getContentLengthLong(); |
| |
| if (length < Integer.MAX_VALUE) { |
| return (int) length; |
| } |
| return -1; |
| } |
| |
| /** |
| * Return the content length as a long. |
| * |
| * @return the content length |
| */ |
| public long getContentLengthLong() { |
| if (contentLength > -1) { |
| return contentLength; |
| } |
| |
| MessageBytes clB = headers.getUniqueValue("content-length"); |
| contentLength = (clB == null || clB.isNull()) ? -1 : clB.getLong(); |
| |
| return contentLength; |
| } |
| |
| /** |
| * Return the content type as a string. |
| * |
| * @return the content type |
| */ |
| public String getContentType() { |
| contentType(); |
| if (contentTypeMB == null || contentTypeMB.isNull()) { |
| return null; |
| } |
| return contentTypeMB.toStringType(); |
| } |
| |
| |
| /** |
| * Set the content type. |
| * |
| * @param type the content type |
| */ |
| public void setContentType(String type) { |
| contentTypeMB.setString(type); |
| } |
| |
| |
| /** |
| * Return the content type. |
| * |
| * @return the content type |
| */ |
| public MessageBytes contentType() { |
| if (contentTypeMB == null) { |
| contentTypeMB = headers.getValue("content-type"); |
| } |
| return contentTypeMB; |
| } |
| |
| |
| /** |
| * Set the content type. |
| * |
| * @param mb the content type |
| */ |
| public void setContentType(MessageBytes mb) { |
| contentTypeMB = mb; |
| } |
| |
| |
| /** |
| * Return the value of the specified header. |
| * |
| * @param name the header name |
| * @return the header value |
| */ |
| public String getHeader(String name) { |
| return headers.getHeader(name); |
| } |
| |
| |
| /** |
| * Set the expectation flag. |
| * |
| * @param expectation the expectation flag |
| */ |
| public void setExpectation(boolean expectation) { |
| this.expectation = expectation; |
| } |
| |
| |
| /** |
| * Check if there is an expectation. |
| * |
| * @return {@code true} if there is an expectation |
| */ |
| public boolean hasExpectation() { |
| return expectation; |
| } |
| |
| |
| // -------------------- Associated response -------------------- |
| |
| /** |
| * Return the associated response. |
| * |
| * @return the response |
| */ |
| public Response getResponse() { |
| return response; |
| } |
| |
| /** |
| * Set the associated response. |
| * |
| * @param response the response |
| */ |
| public void setResponse(Response response) { |
| this.response = response; |
| response.setRequest(this); |
| } |
| |
| void setHook(ActionHook hook) { |
| this.hook = hook; |
| } |
| |
| /** |
| * Perform an action on this request. |
| * |
| * @param actionCode the action code |
| * @param param the action parameter |
| */ |
| public void action(ActionCode actionCode, Object param) { |
| if (hook != null) { |
| hook.action(actionCode, Objects.requireNonNullElse(param, this)); |
| } |
| } |
| |
| |
| // -------------------- Cookies -------------------- |
| |
| /** |
| * Return the server cookies. |
| * |
| * @return the server cookies |
| */ |
| public ServerCookies getCookies() { |
| return serverCookies; |
| } |
| |
| |
| // -------------------- Parameters -------------------- |
| |
| /** |
| * Return the request parameters. |
| * |
| * @return the parameters |
| */ |
| public Parameters getParameters() { |
| return parameters; |
| } |
| |
| |
| /** |
| * Add a path parameter. |
| * |
| * @param name the parameter name |
| * @param value the parameter value |
| */ |
| public void addPathParameter(String name, String value) { |
| pathParameters.put(name, value); |
| } |
| |
| /** |
| * Return the value of the specified path parameter. |
| * |
| * @param name the parameter name |
| * @return the parameter 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 |
| |
| /** |
| * Set an attribute. |
| * |
| * @param name the attribute name |
| * @param o the attribute value |
| */ |
| public void setAttribute(String name, Object o) { |
| attributes.put(name, o); |
| } |
| |
| /** |
| * Return the request attributes map. |
| * |
| * @return the attributes map |
| */ |
| public HashMap<String,Object> getAttributes() { |
| return attributes; |
| } |
| |
| /** |
| * Return the value of the specified attribute. |
| * |
| * @param name the attribute name |
| * @return the attribute value |
| */ |
| public Object getAttribute(String name) { |
| return attributes.get(name); |
| } |
| |
| /** |
| * Return the remote user. |
| * |
| * @return the remote user |
| */ |
| public MessageBytes getRemoteUser() { |
| return remoteUser; |
| } |
| |
| /** |
| * Check if the remote user needs authorization. |
| * |
| * @return {@code true} if the remote user needs authorization |
| */ |
| public boolean getRemoteUserNeedsAuthorization() { |
| return remoteUserNeedsAuthorization; |
| } |
| |
| /** |
| * Set whether the remote user needs authorization. |
| * |
| * @param remoteUserNeedsAuthorization {@code true} if authorization is needed |
| */ |
| public void setRemoteUserNeedsAuthorization(boolean remoteUserNeedsAuthorization) { |
| this.remoteUserNeedsAuthorization = remoteUserNeedsAuthorization; |
| } |
| |
| /** |
| * Return the authentication type. |
| * |
| * @return the authentication type |
| */ |
| public MessageBytes getAuthType() { |
| return authType; |
| } |
| |
| /** |
| * Return the number of available bytes. |
| * |
| * @return the available bytes |
| */ |
| public int getAvailable() { |
| return available; |
| } |
| |
| /** |
| * Set the number of available bytes. |
| * |
| * @param available the available bytes |
| */ |
| public void setAvailable(int available) { |
| this.available = available; |
| } |
| |
| /** |
| * Check if sendfile is enabled. |
| * |
| * @return {@code true} if sendfile is enabled |
| */ |
| public boolean getSendfile() { |
| return sendfile; |
| } |
| |
| /** |
| * Set whether sendfile is enabled. |
| * |
| * @param sendfile {@code true} to enable sendfile |
| */ |
| public void setSendfile(boolean sendfile) { |
| this.sendfile = sendfile; |
| } |
| |
| /** |
| * Check if the request body has been fully read. |
| * |
| * @return {@code true} if finished |
| */ |
| public boolean isFinished() { |
| AtomicBoolean result = new AtomicBoolean(false); |
| action(ActionCode.REQUEST_BODY_FULLY_READ, result); |
| return result.get(); |
| } |
| |
| /** |
| * Check if relative redirects are supported. |
| * |
| * @return {@code true} if relative redirects are supported |
| */ |
| public boolean getSupportsRelativeRedirects() { |
| return !protocol().equals("") && !protocol().equals("HTTP/1.0"); |
| } |
| |
| |
| // -------------------- Input Buffer -------------------- |
| |
| /** |
| * Return the input buffer. |
| * |
| * @return the input buffer |
| */ |
| public InputBuffer getInputBuffer() { |
| return inputBuffer; |
| } |
| |
| |
| /** |
| * Set the input buffer. |
| * |
| * @param inputBuffer the input buffer |
| */ |
| 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; |
| } |
| |
| |
| /** |
| * Check if an error exception is present. |
| * |
| * @return {@code true} if an exception is present |
| */ |
| public boolean isExceptionPresent() { |
| return errorException != null; |
| } |
| |
| |
| // -------------------- debug -------------------- |
| |
| /** |
| * Return the request ID. |
| * |
| * @return the request ID |
| */ |
| public String getRequestId() { |
| return requestId; |
| } |
| |
| |
| /** |
| * Return the protocol request ID. |
| * |
| * @return the protocol request ID |
| */ |
| public String getProtocolRequestId() { |
| if (hook != null) { |
| AtomicReference<String> ref = new AtomicReference<>(); |
| hook.action(ActionCode.PROTOCOL_REQUEST_ID, ref); |
| return ref.get(); |
| } else { |
| return null; |
| } |
| } |
| |
| |
| /** |
| * Return the servlet connection. |
| * |
| * @return the servlet connection |
| */ |
| public ServletConnection getServletConnection() { |
| if (hook != null) { |
| AtomicReference<ServletConnection> ref = new AtomicReference<>(); |
| hook.action(ActionCode.SERVLET_CONNECTION, ref); |
| return ref.get(); |
| } else { |
| return null; |
| } |
| } |
| |
| |
| @Override |
| public String toString() { |
| return "R( " + requestURI().toString() + ")"; |
| } |
| |
| /** |
| * Return the request start time in milliseconds. |
| * |
| * @return the start time |
| */ |
| public long getStartTime() { |
| return System.currentTimeMillis() - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTimeNanos); |
| } |
| |
| /** |
| * Return the request start time in nanoseconds. |
| * |
| * @return the start time in nanoseconds |
| */ |
| public long getStartTimeNanos() { |
| return startTimeNanos; |
| } |
| |
| /** |
| * Mark the request start time. |
| */ |
| public void markStartTime() { |
| startTimeNanos = System.nanoTime(); |
| startInstant = Instant.now(); |
| } |
| |
| /** |
| * Return the request start instant. |
| * |
| * @return the start instant |
| */ |
| public Instant getStartInstant() { |
| return startInstant; |
| } |
| |
| /** |
| * Return the thread ID. |
| * |
| * @return the thread ID |
| */ |
| public long getThreadId() { |
| return threadId; |
| } |
| |
| /** |
| * Clear the request thread ID. |
| */ |
| public void clearRequestThread() { |
| threadId = 0; |
| } |
| |
| /** |
| * Set the request thread ID. |
| */ |
| @SuppressWarnings("deprecation") |
| public void setRequestThread() { |
| Thread t = Thread.currentThread(); |
| threadId = t.getId(); |
| getRequestProcessor().setWorkerThreadName(t.getName()); |
| } |
| |
| /** |
| * Check if the current thread is the request thread. |
| * |
| * @return {@code true} if the current thread is the request thread |
| */ |
| @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; |
| } |
| |
| |
| /** |
| * Return the note at the specified position. |
| * |
| * @param pos the position |
| * @return the note |
| */ |
| public Object getNote(int pos) { |
| return notes[pos]; |
| } |
| |
| |
| // -------------------- Recycling -------------------- |
| |
| |
| /** |
| * Recycle this request for reuse. |
| */ |
| public void recycle() { |
| bytesRead = 0; |
| |
| contentLength = -1; |
| contentTypeMB = null; |
| charsetHolder = null; |
| expectation = false; |
| headers.recycle(); |
| trailerFields.recycle(); |
| /* |
| * Trailer fields are limited in size by bytes. The following calls 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. The first call ensures the array is no larger than 8. The second call allows the next request to |
| * process more than 8 headers if it needs to. |
| */ |
| trailerFields.setLimit(MimeHeaders.DEFAULT_HEADER_SIZE); |
| trailerFields.setLimit(-1); |
| 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(); |
| method = null; |
| 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; |
| startInstant = null; |
| threadId = 0; |
| |
| if (hook instanceof NonPipeliningProcessor) { |
| /* |
| * No requirement to maintain state between requests so clear the hook (a.k.a. Processor) and the input |
| * buffer to aid GC. |
| */ |
| setHook(null); |
| setInputBuffer(null); |
| } |
| } |
| |
| // -------------------- Info -------------------- |
| /** |
| * Update request counters. |
| */ |
| public void updateCounters() { |
| reqProcessorMX.updateCounters(); |
| } |
| |
| /** |
| * Return the request processor info. |
| * |
| * @return the request processor info |
| */ |
| public RequestInfo getRequestProcessor() { |
| return reqProcessorMX; |
| } |
| |
| /** |
| * Return the number of bytes read. |
| * |
| * @return the bytes read |
| */ |
| public long getBytesRead() { |
| return bytesRead; |
| } |
| |
| /** |
| * Check if the request is currently being processed. |
| * |
| * @return {@code true} if processing |
| */ |
| 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, <code>null</code> 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 ioe) { |
| // Ignore - null test below handles this |
| } |
| if (mediaType != null) { |
| return mediaType.getCharset(); |
| } |
| |
| return null; |
| } |
| } |